vmspawn: Support direct kernel boot without UEFI firmware
When --linux= specifies a non-PE kernel image, automatically disable UEFI firmware loading (as if --firmware= was passed). If --firmware= is explicitly set to a path in this case, fail with an error. Booting a UKI with --firmware= is also rejected since UKIs require UEFI. --firmware= (empty string) can also be used explicitly to disable firmware loading for PE kernels. Other changes: - Extract OVMF pflash drive setup into cmdline_add_ovmf() - Extract kernel image type detection into determine_kernel() - Add smbios_supported() helper to centralize the SMBIOS availability check (always available on x86, elsewhere requires firmware) - Gate SMM, OVMF drives, SMBIOS11 and credential SMBIOS paths on firmware/SMBIOS being available - Beef up the credential logic to fall back to fw_cfg and kernel command line in case SMBIOS is not available
This commit is contained in:
committed by
Daan De Meyer
parent
c0b3b855b7
commit
bb93fdaaf3
+18
-6
@@ -330,12 +330,19 @@
|
||||
<varlistentry>
|
||||
<term><option>--firmware=<replaceable>PATH</replaceable></option></term>
|
||||
|
||||
<listitem><para>Takes an absolute path, or a relative path beginning with
|
||||
<filename>./</filename>. Specifies a JSON firmware definition file, which allows selecting the
|
||||
firmware to boot in the VM. If not specified, a suitable firmware is automatically discovered. If the
|
||||
special string <literal>list</literal> is specified lists all discovered firmwares. If the special
|
||||
string <literal>describe</literal> is specified, the firmware that would be selected (taking
|
||||
<option>--firmware-features=</option> into account) is printed and the program exits.</para>
|
||||
<listitem><para>Selects which firmware to use in the VM. Takes one of <literal>auto</literal>,
|
||||
<literal>uefi</literal>, <literal>bios</literal>, <literal>none</literal>, an absolute path, or a
|
||||
relative path beginning with <filename>./</filename>. Defaults to <literal>auto</literal>, which
|
||||
selects UEFI firmware unless <option>--linux=</option> specifies a non-PE kernel image, in which
|
||||
case <literal>none</literal> is selected. <literal>uefi</literal> loads OVMF firmware (use a path
|
||||
to a JSON firmware definition file to select a specific one). <literal>bios</literal> skips OVMF
|
||||
loading and lets QEMU use its built-in BIOS (e.g. SeaBIOS on x86). <literal>none</literal> disables
|
||||
firmware loading entirely and requires <option>--linux=</option> to be specified for direct kernel
|
||||
boot. Booting a UKI requires <literal>uefi</literal>. If the special string <literal>list</literal>
|
||||
is specified, all discovered firmware definition files are listed. If the special string
|
||||
<literal>describe</literal> is specified, the UEFI firmware that would be selected (taking
|
||||
<option>--firmware-features=</option> into account) is printed and the program exits. If an empty
|
||||
string is specified, the option is reset to its default.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
|
||||
</varlistentry>
|
||||
@@ -761,6 +768,11 @@
|
||||
embed a <constant>NUL</constant> byte). Note that the invoking shell might already apply unescaping
|
||||
once, hence this might require double escaping!</para>
|
||||
|
||||
<para>Credentials are preferably passed to the VM via SMBIOS Type 11 strings or QEMU fw_cfg files.
|
||||
If neither mechanism is available, credentials are passed on the kernel command line using
|
||||
<varname>systemd.set_credential_binary=</varname> which is not a confidential channel. Do not use
|
||||
this for passing secrets to the VM in that case.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
|
||||
@@ -44,3 +44,11 @@ static const char *const console_transport_table[_CONSOLE_TRANSPORT_MAX] = {
|
||||
};
|
||||
|
||||
DEFINE_STRING_TABLE_LOOKUP(console_transport, ConsoleTransport);
|
||||
|
||||
static const char *const firmware_table[_FIRMWARE_MAX] = {
|
||||
[FIRMWARE_UEFI] = "uefi",
|
||||
[FIRMWARE_BIOS] = "bios",
|
||||
[FIRMWARE_NONE] = "none",
|
||||
};
|
||||
|
||||
DEFINE_STRING_TABLE_LOOKUP(firmware, Firmware);
|
||||
|
||||
@@ -49,6 +49,14 @@ typedef enum ConsoleTransport {
|
||||
_CONSOLE_TRANSPORT_INVALID = -EINVAL,
|
||||
} ConsoleTransport;
|
||||
|
||||
typedef enum Firmware {
|
||||
FIRMWARE_UEFI, /* load OVMF firmware */
|
||||
FIRMWARE_BIOS, /* don't load OVMF, let qemu use its built-in BIOS (e.g. SeaBIOS on x86) */
|
||||
FIRMWARE_NONE, /* no firmware at all, requires --linux= for direct kernel boot */
|
||||
_FIRMWARE_MAX,
|
||||
_FIRMWARE_INVALID = -EINVAL,
|
||||
} Firmware;
|
||||
|
||||
typedef enum SettingsMask {
|
||||
SETTING_START_MODE = UINT64_C(1) << 0,
|
||||
SETTING_MACHINE_ID = UINT64_C(1) << 6,
|
||||
@@ -62,4 +70,5 @@ typedef enum SettingsMask {
|
||||
DECLARE_STRING_TABLE_LOOKUP(console_mode, ConsoleMode);
|
||||
DECLARE_STRING_TABLE_LOOKUP(console_transport, ConsoleTransport);
|
||||
DECLARE_STRING_TABLE_LOOKUP(disk_type, DiskType);
|
||||
DECLARE_STRING_TABLE_LOOKUP(firmware, Firmware);
|
||||
DECLARE_STRING_TABLE_LOOKUP(image_format, ImageFormat);
|
||||
|
||||
@@ -39,6 +39,26 @@
|
||||
# define ARCHITECTURE_SUPPORTS_CXL 0
|
||||
#endif
|
||||
|
||||
#if defined(__x86_64__) || defined(__i386__) || defined(__arm__) || defined(__aarch64__) || defined(__riscv) || defined(__loongarch64)
|
||||
# define ARCHITECTURE_SUPPORTS_FW_CFG 1
|
||||
#else
|
||||
# define ARCHITECTURE_SUPPORTS_FW_CFG 0
|
||||
#endif
|
||||
|
||||
/* QEMU's fw_cfg file path buffer is FW_CFG_MAX_FILE_PATH (56) bytes including NUL */
|
||||
#define QEMU_FW_CFG_MAX_KEY_LEN 55
|
||||
|
||||
/* These match the kernel's COMMAND_LINE_SIZE for each architecture */
|
||||
#if defined(__loongarch64)
|
||||
# define KERNEL_CMDLINE_SIZE 4096
|
||||
#elif defined(__x86_64__) || defined(__i386__) || defined(__aarch64__)
|
||||
# define KERNEL_CMDLINE_SIZE 2048
|
||||
#elif defined(__arm__) || defined(__riscv)
|
||||
# define KERNEL_CMDLINE_SIZE 1024
|
||||
#else
|
||||
# define KERNEL_CMDLINE_SIZE 512
|
||||
#endif
|
||||
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
# define QEMU_MACHINE_TYPE "q35"
|
||||
#elif defined(__arm__) || defined(__aarch64__) || defined(__riscv) || defined(__loongarch64) || defined(__m68k__)
|
||||
|
||||
+357
-196
@@ -138,6 +138,7 @@ static int arg_vsock = -1;
|
||||
static unsigned arg_vsock_cid = VMADDR_CID_ANY;
|
||||
static int arg_tpm = -1;
|
||||
static char *arg_linux = NULL;
|
||||
static KernelImageType arg_linux_image_type = _KERNEL_IMAGE_TYPE_INVALID;
|
||||
static char **arg_initrds = NULL;
|
||||
static ConsoleMode arg_console_mode = CONSOLE_INTERACTIVE;
|
||||
static ConsoleTransport arg_console_transport = CONSOLE_TRANSPORT_VIRTIO;
|
||||
@@ -146,6 +147,7 @@ static MachineCredentialContext arg_credentials = {};
|
||||
static uid_t arg_uid_shift = UID_INVALID, arg_uid_range = 0x10000U;
|
||||
static RuntimeMountContext arg_runtime_mounts = {};
|
||||
static char *arg_firmware = NULL;
|
||||
static Firmware arg_firmware_type = _FIRMWARE_INVALID;
|
||||
static bool arg_firmware_describe = false;
|
||||
static Set *arg_firmware_features_include = NULL;
|
||||
static Set *arg_firmware_features_exclude = NULL;
|
||||
@@ -542,8 +544,15 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
break;
|
||||
}
|
||||
|
||||
OPTION_LONG("firmware", "PATH|list|describe",
|
||||
"Select firmware definition file (or list/describe available)"):
|
||||
OPTION_LONG("firmware", "auto|uefi|bios|none|PATH|list|describe",
|
||||
"Select firmware to use, or a firmware definition file (or list/describe available)"): {
|
||||
if (isempty(arg) || streq(arg, "auto")) {
|
||||
arg_firmware = mfree(arg_firmware);
|
||||
arg_firmware_type = _FIRMWARE_INVALID;
|
||||
arg_firmware_describe = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (streq(arg, "list")) {
|
||||
_cleanup_strv_free_ char **l = NULL;
|
||||
|
||||
@@ -563,19 +572,33 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
/* Handled after argument parsing so that --firmware-features= is
|
||||
* taken into account. */
|
||||
arg_firmware = mfree(arg_firmware);
|
||||
/* We only look for UEFI firmware when "describe" is specified. */
|
||||
arg_firmware_type = FIRMWARE_UEFI;
|
||||
arg_firmware_describe = true;
|
||||
break;
|
||||
}
|
||||
|
||||
arg_firmware_describe = false;
|
||||
Firmware f = firmware_from_string(arg);
|
||||
if (f >= 0) {
|
||||
arg_firmware = mfree(arg_firmware);
|
||||
arg_firmware_type = f;
|
||||
arg_firmware_describe = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isempty(arg) && !path_is_absolute(arg) && !startswith(arg, "./"))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Absolute path or path starting with './' required.");
|
||||
if (!path_is_absolute(arg) && !startswith(arg, "./"))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Expected one of 'auto', 'uefi', 'bios', 'none', 'list', 'describe', or an absolute path or path starting with './', got: %s",
|
||||
arg);
|
||||
|
||||
r = parse_path_argument(arg, /* suppress_root= */ false, &arg_firmware);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
arg_firmware_type = FIRMWARE_UEFI;
|
||||
arg_firmware_describe = false;
|
||||
break;
|
||||
}
|
||||
|
||||
OPTION_LONG("firmware-features", "FEATURE,...|list",
|
||||
"Require/exclude specific firmware features"): {
|
||||
@@ -1265,16 +1288,16 @@ static int on_child_exit(sd_event_source *s, const siginfo_t *si, void *userdata
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmdline_add_vsock(char ***cmdline, int vsock_fd) {
|
||||
int r;
|
||||
static bool smbios_supported(void) {
|
||||
/* SMBIOS is always available on x86 (via SeaBIOS fallback), but on
|
||||
* other architectures it requires UEFI firmware to be loaded. */
|
||||
return ARCHITECTURE_SUPPORTS_SMBIOS &&
|
||||
(IN_SET(native_architecture(), ARCHITECTURE_X86, ARCHITECTURE_X86_64) || arg_firmware_type == FIRMWARE_UEFI);
|
||||
}
|
||||
|
||||
assert(cmdline);
|
||||
static int add_vsock_credential(int vsock_fd) {
|
||||
assert(vsock_fd >= 0);
|
||||
|
||||
r = strv_extend(cmdline, "-smbios");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
union sockaddr_union addr;
|
||||
socklen_t addr_len = sizeof addr.vm;
|
||||
if (getsockname(vsock_fd, &addr.sa, &addr_len) < 0)
|
||||
@@ -1283,54 +1306,57 @@ static int cmdline_add_vsock(char ***cmdline, int vsock_fd) {
|
||||
assert(addr_len >= sizeof addr.vm);
|
||||
assert(addr.vm.svm_family == AF_VSOCK);
|
||||
|
||||
r = strv_extendf(cmdline, "type=11,value=io.systemd.credential:vmm.notify_socket=vsock-stream:%u:%u", (unsigned) VMADDR_CID_HOST, addr.vm.svm_port);
|
||||
if (r < 0)
|
||||
return r;
|
||||
_cleanup_free_ char *value = NULL;
|
||||
if (asprintf(&value, "vsock-stream:%u:%u", (unsigned) VMADDR_CID_HOST, addr.vm.svm_port) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
return machine_credential_add(&arg_credentials, "vmm.notify_socket", value, SIZE_MAX);
|
||||
}
|
||||
|
||||
static int cmdline_add_kernel_cmdline(char ***cmdline, const char *kernel, const char *smbios_dir) {
|
||||
static int cmdline_add_kernel_cmdline(char ***cmdline, int smbios_dir_fd, const char *smbios_dir) {
|
||||
int r;
|
||||
|
||||
assert(cmdline);
|
||||
assert(smbios_dir_fd >= 0);
|
||||
assert(smbios_dir);
|
||||
|
||||
if (strv_isempty(arg_kernel_cmdline_extra))
|
||||
return 0;
|
||||
|
||||
KernelImageType type = _KERNEL_IMAGE_TYPE_INVALID;
|
||||
if (kernel) {
|
||||
r = inspect_kernel(AT_FDCWD, kernel, &type);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to determine '%s' kernel image type: %m", kernel);
|
||||
}
|
||||
|
||||
_cleanup_free_ char *kcl = strv_join(arg_kernel_cmdline_extra, " ");
|
||||
if (!kcl)
|
||||
return log_oom();
|
||||
|
||||
if (kernel && type != KERNEL_IMAGE_TYPE_UKI) {
|
||||
size_t kcl_len = strlen(kcl);
|
||||
if (kcl_len >= KERNEL_CMDLINE_SIZE)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(E2BIG),
|
||||
"Kernel command line length (%zu) exceeds the kernel's COMMAND_LINE_SIZE (%d).",
|
||||
kcl_len, KERNEL_CMDLINE_SIZE);
|
||||
|
||||
if (arg_linux_image_type >= 0 && arg_linux_image_type != KERNEL_IMAGE_TYPE_UKI) {
|
||||
if (strv_extend_many(cmdline, "-append", kcl) < 0)
|
||||
return log_oom();
|
||||
} else {
|
||||
if (!ARCHITECTURE_SUPPORTS_SMBIOS) {
|
||||
if (!smbios_supported()) {
|
||||
log_warning("Cannot append extra args to kernel cmdline, native architecture doesn't support SMBIOS, ignoring.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
FOREACH_STRING(id, "io.systemd.stub.kernel-cmdline-extra", "io.systemd.boot.kernel-cmdline-extra") {
|
||||
_cleanup_free_ char *content = strjoin(id, "=", kcl);
|
||||
if (!content)
|
||||
return log_oom();
|
||||
|
||||
r = write_string_file_at(
|
||||
smbios_dir_fd, id, content,
|
||||
WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE|WRITE_STRING_FILE_MODE_0600);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write smbios kernel command line to file: %m");
|
||||
|
||||
_cleanup_free_ char *p = path_join(smbios_dir, id);
|
||||
if (!p)
|
||||
return log_oom();
|
||||
|
||||
r = write_string_filef(
|
||||
p,
|
||||
WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE|WRITE_STRING_FILE_MODE_0600,
|
||||
"%s=%s", id, kcl);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write smbios kernel command line to file: %m");
|
||||
|
||||
if (strv_extend(cmdline, "-smbios") < 0)
|
||||
return log_oom();
|
||||
|
||||
@@ -1342,15 +1368,112 @@ static int cmdline_add_kernel_cmdline(char ***cmdline, const char *kernel, const
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmdline_add_smbios11(char ***cmdline, const char* smbios_dir) {
|
||||
static int cmdline_add_credentials(char ***cmdline, int smbios_dir_fd, const char *smbios_dir) {
|
||||
int r;
|
||||
|
||||
assert(cmdline);
|
||||
assert(smbios_dir_fd >= 0);
|
||||
assert(smbios_dir);
|
||||
|
||||
FOREACH_ARRAY(cred, arg_credentials.credentials, arg_credentials.n_credentials) {
|
||||
_cleanup_free_ char *cred_data_b64 = NULL;
|
||||
ssize_t n;
|
||||
|
||||
n = base64mem(cred->data, cred->size, &cred_data_b64);
|
||||
if (n < 0)
|
||||
return log_oom();
|
||||
|
||||
if (smbios_supported()) {
|
||||
_cleanup_free_ char *content = NULL;
|
||||
if (asprintf(&content, "io.systemd.credential.binary:%s=%s", cred->id, cred_data_b64) < 0)
|
||||
return log_oom();
|
||||
|
||||
r = write_string_file_at(
|
||||
smbios_dir_fd, cred->id, content,
|
||||
WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE|WRITE_STRING_FILE_MODE_0600);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write smbios credential file: %m");
|
||||
|
||||
_cleanup_free_ char *p = path_join(smbios_dir, cred->id);
|
||||
if (!p)
|
||||
return log_oom();
|
||||
|
||||
if (strv_extend(cmdline, "-smbios") < 0)
|
||||
return log_oom();
|
||||
|
||||
if (strv_extend_joined(cmdline, "type=11,path=", p) < 0)
|
||||
return log_oom();
|
||||
|
||||
} else if (ARCHITECTURE_SUPPORTS_FW_CFG) {
|
||||
/* fw_cfg keys are limited to 55 characters */
|
||||
_cleanup_free_ char *key = strjoin("opt/io.systemd.credentials/", cred->id);
|
||||
if (!key)
|
||||
return log_oom();
|
||||
|
||||
if (strlen(key) <= QEMU_FW_CFG_MAX_KEY_LEN) {
|
||||
r = write_data_file_atomic_at(
|
||||
smbios_dir_fd, cred->id,
|
||||
&IOVEC_MAKE(cred->data, cred->size),
|
||||
WRITE_DATA_FILE_MODE_0400);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write fw_cfg credential file: %m");
|
||||
|
||||
_cleanup_free_ char *p = path_join(smbios_dir, cred->id);
|
||||
if (!p)
|
||||
return log_oom();
|
||||
|
||||
if (strv_extend(cmdline, "-fw_cfg") < 0)
|
||||
return log_oom();
|
||||
|
||||
if (strv_extendf(cmdline, "name=%s,file=%s", key, p) < 0)
|
||||
return log_oom();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Fall through to kernel command line if key is too long */
|
||||
log_notice("fw_cfg key '%s' exceeds %d character limit, passing credential via kernel command line. "
|
||||
"Note that this will make literal credentials readable to unprivileged userspace.",
|
||||
key, QEMU_FW_CFG_MAX_KEY_LEN);
|
||||
|
||||
if (arg_linux_image_type < 0)
|
||||
return log_error_errno(
|
||||
SYNTHETIC_ERRNO(E2BIG),
|
||||
"Cannot pass credential '%s' to VM, fw_cfg key exceeds %d character limit and no kernel for direct boot specified.",
|
||||
cred->id,
|
||||
QEMU_FW_CFG_MAX_KEY_LEN);
|
||||
|
||||
if (strv_extendf(&arg_kernel_cmdline_extra,
|
||||
"systemd.set_credential_binary=%s:%s", cred->id, cred_data_b64) < 0)
|
||||
return log_oom();
|
||||
|
||||
} else if (arg_linux_image_type >= 0) {
|
||||
log_notice("Both SMBIOS and fw_cfg are not supported, passing credential via kernel command line. "
|
||||
"Note that this will make literal credentials readable to unprivileged userspace.");
|
||||
if (strv_extendf(&arg_kernel_cmdline_extra,
|
||||
"systemd.set_credential_binary=%s:%s", cred->id, cred_data_b64) < 0)
|
||||
return log_oom();
|
||||
} else
|
||||
return log_error_errno(
|
||||
SYNTHETIC_ERRNO(EOPNOTSUPP),
|
||||
"Cannot pass credential '%s' to VM, native architecture doesn't support SMBIOS or fw_cfg and no kernel for direct boot specified.",
|
||||
cred->id);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmdline_add_smbios11(char ***cmdline, int smbios_dir_fd, const char *smbios_dir) {
|
||||
int r;
|
||||
|
||||
assert(cmdline);
|
||||
assert(smbios_dir_fd >= 0);
|
||||
assert(smbios_dir);
|
||||
|
||||
if (strv_isempty(arg_smbios11))
|
||||
return 0;
|
||||
|
||||
if (!ARCHITECTURE_SUPPORTS_SMBIOS) {
|
||||
if (!smbios_supported()) {
|
||||
log_warning("Cannot issue SMBIOS Type #11 strings, native architecture doesn't support SMBIOS, ignoring.");
|
||||
return 0;
|
||||
}
|
||||
@@ -1362,8 +1485,13 @@ static int cmdline_add_smbios11(char ***cmdline, const char* smbios_dir) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = write_string_file(
|
||||
p, *i,
|
||||
_cleanup_free_ char *fn = NULL;
|
||||
r = path_extract_filename(p, &fn);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = write_string_file_at(
|
||||
smbios_dir_fd, fn, *i,
|
||||
WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE|WRITE_STRING_FILE_MODE_0600);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write smbios data to smbios file %s: %m", p);
|
||||
@@ -2057,9 +2185,117 @@ static int disk_serial(const char *filename, size_t max_len, char **ret) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmdline_add_ovmf(FILE *config_file, const OvmfConfig *ovmf_config, char **ret_ovmf_vars) {
|
||||
int r;
|
||||
|
||||
assert(config_file);
|
||||
assert(ret_ovmf_vars);
|
||||
|
||||
if (!ovmf_config) {
|
||||
*ret_ovmf_vars = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = qemu_config_section(config_file, "drive", "ovmf-code",
|
||||
"if", "pflash",
|
||||
"format", ovmf_config_format(ovmf_config),
|
||||
"readonly", "on",
|
||||
"file", ovmf_config->path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!ovmf_config->vars && !arg_efi_nvram_template) {
|
||||
*ret_ovmf_vars = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (arg_efi_nvram_state_mode == STATE_AUTO && !arg_ephemeral) {
|
||||
assert(!arg_efi_nvram_state_path);
|
||||
|
||||
r = make_sidecar_path(".efinvramstate", &arg_efi_nvram_state_path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
log_debug("Storing EFI NVRAM state persistently under '%s'.", arg_efi_nvram_state_path);
|
||||
}
|
||||
|
||||
const char *vars_source = arg_efi_nvram_template ?: ovmf_config->vars;
|
||||
_cleanup_close_ int target_fd = -EBADF;
|
||||
_cleanup_(unlink_and_freep) char *destroy_path = NULL;
|
||||
bool newly_created;
|
||||
const char *state;
|
||||
if (arg_efi_nvram_state_path) {
|
||||
_cleanup_free_ char *d = strdup(arg_efi_nvram_state_path);
|
||||
if (!d)
|
||||
return log_oom();
|
||||
|
||||
target_fd = openat_report_new(AT_FDCWD, arg_efi_nvram_state_path, O_WRONLY|O_CREAT|O_CLOEXEC, 0600, &newly_created);
|
||||
if (target_fd < 0)
|
||||
return log_error_errno(target_fd, "Failed to open file for OVMF vars at %s: %m", arg_efi_nvram_state_path);
|
||||
|
||||
if (newly_created)
|
||||
destroy_path = TAKE_PTR(d);
|
||||
|
||||
r = fd_verify_regular(target_fd);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Not a regular file for OVMF variables at %s: %m", arg_efi_nvram_state_path);
|
||||
|
||||
state = arg_efi_nvram_state_path;
|
||||
} else {
|
||||
_cleanup_free_ char *t = NULL;
|
||||
r = tempfn_random_child(/* p= */ NULL, "vmspawn-", &t);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create temporary filename: %m");
|
||||
|
||||
target_fd = open(t, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0600);
|
||||
if (target_fd < 0)
|
||||
return log_error_errno(errno, "Failed to create regular file for OVMF vars at %s: %m", t);
|
||||
|
||||
newly_created = true;
|
||||
state = *ret_ovmf_vars = TAKE_PTR(t);
|
||||
}
|
||||
|
||||
if (newly_created) {
|
||||
_cleanup_close_ int source_fd = open(vars_source, O_RDONLY|O_CLOEXEC);
|
||||
if (source_fd < 0)
|
||||
return log_error_errno(errno, "Failed to open OVMF vars file %s: %m", vars_source);
|
||||
|
||||
r = copy_bytes(source_fd, target_fd, UINT64_MAX, COPY_REFLINK);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to copy bytes from %s to %s: %m", vars_source, state);
|
||||
|
||||
/* This isn't always available so don't raise an error if it fails */
|
||||
(void) copy_times(source_fd, target_fd, 0);
|
||||
}
|
||||
|
||||
destroy_path = mfree(destroy_path); /* disarm auto-destroy */
|
||||
|
||||
/* Mark the UEFI variable store pflash as requiring SMM access. This
|
||||
* prevents the guest OS from writing to pflash directly, ensuring all
|
||||
* variable updates go through the firmware's validation checks. Without
|
||||
* this, secure boot keys could be overwritten by the OS. */
|
||||
if (ARCHITECTURE_SUPPORTS_SMM) {
|
||||
r = qemu_config_section(config_file, "global", /* id= */ NULL,
|
||||
"driver", "cfi.pflash01",
|
||||
"property", "secure",
|
||||
"value", "on");
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = qemu_config_section(config_file, "drive", "ovmf-vars",
|
||||
"file", state,
|
||||
"if", "pflash",
|
||||
"format", ovmf_config_format(ovmf_config));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
_cleanup_(ovmf_config_freep) OvmfConfig *ovmf_config = NULL;
|
||||
_cleanup_free_ char *qemu_binary = NULL, *mem = NULL, *kernel = NULL;
|
||||
_cleanup_free_ char *qemu_binary = NULL, *mem = NULL;
|
||||
_cleanup_(rm_rf_physical_and_freep) char *ssh_private_key_path = NULL, *ssh_public_key_path = NULL;
|
||||
_cleanup_(rm_rf_subvolume_and_freep) char *snapshot_directory = NULL;
|
||||
_cleanup_(release_lock_file) LockFile tree_global_lock = LOCK_FILE_INIT, tree_local_lock = LOCK_FILE_INIT;
|
||||
@@ -2115,18 +2351,20 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
use_kvm = r;
|
||||
}
|
||||
|
||||
if (arg_firmware)
|
||||
r = load_ovmf_config(arg_firmware, &ovmf_config);
|
||||
else
|
||||
r = find_ovmf_config(arg_firmware_features_include, arg_firmware_features_exclude, &ovmf_config, /* ret_firmware_json= */ NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to find OVMF config: %m");
|
||||
if (arg_firmware_type == FIRMWARE_UEFI) {
|
||||
if (arg_firmware)
|
||||
r = load_ovmf_config(arg_firmware, &ovmf_config);
|
||||
else
|
||||
r = find_ovmf_config(arg_firmware_features_include, arg_firmware_features_exclude, &ovmf_config, /* ret_firmware_json= */ NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to find OVMF config: %m");
|
||||
|
||||
if (set_contains(arg_firmware_features_include, "secure-boot") && !ovmf_config->supports_sb)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE),
|
||||
"Secure Boot requested, but selected OVMF firmware doesn't support it.");
|
||||
if (set_contains(arg_firmware_features_include, "secure-boot") && !ovmf_config->supports_sb)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE),
|
||||
"Secure Boot requested, but selected OVMF firmware doesn't support it.");
|
||||
|
||||
log_debug("Using OVMF firmware %s Secure Boot support.", ovmf_config->supports_sb ? "with" : "without");
|
||||
log_debug("Using OVMF firmware %s Secure Boot support.", ovmf_config->supports_sb ? "with" : "without");
|
||||
}
|
||||
|
||||
_cleanup_(machine_bind_user_context_freep) MachineBindUserContext *bind_user_context = NULL;
|
||||
r = machine_bind_user_prepare(
|
||||
@@ -2144,19 +2382,6 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (arg_linux) {
|
||||
kernel = strdup(arg_linux);
|
||||
if (!kernel)
|
||||
return log_oom();
|
||||
} else if (arg_directory) {
|
||||
/* a kernel is required for directory type images so attempt to locate a UKI under /boot and /efi */
|
||||
r = discover_boot_entry(arg_directory, &kernel, &arg_initrds);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to locate UKI in directory type image, please specify one with --linux=.");
|
||||
|
||||
log_debug("Discovered UKI image at %s", kernel);
|
||||
}
|
||||
|
||||
r = find_qemu_binary(&qemu_binary);
|
||||
if (r == -EOPNOTSUPP)
|
||||
return log_error_errno(r, "Native architecture is not supported by qemu.");
|
||||
@@ -2207,7 +2432,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (ARCHITECTURE_SUPPORTS_SMM) {
|
||||
if (ovmf_config && ARCHITECTURE_SUPPORTS_SMM) {
|
||||
r = qemu_config_key(config_file, "smm", on_off(ovmf_config->supports_sb));
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -2399,7 +2624,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
return r;
|
||||
}
|
||||
|
||||
bool use_vsock = arg_vsock > 0 && ARCHITECTURE_SUPPORTS_SMBIOS;
|
||||
bool use_vsock = arg_vsock > 0;
|
||||
if (arg_vsock < 0) {
|
||||
r = qemu_check_vsock_support();
|
||||
if (r < 0)
|
||||
@@ -2595,106 +2820,19 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
}
|
||||
}
|
||||
|
||||
r = qemu_config_section(config_file, "drive", "ovmf-code",
|
||||
"if", "pflash",
|
||||
"format", ovmf_config_format(ovmf_config),
|
||||
"readonly", "on",
|
||||
"file", ovmf_config->path);
|
||||
_cleanup_(unlink_and_freep) char *ovmf_vars = NULL;
|
||||
r = cmdline_add_ovmf(config_file, ovmf_config, &ovmf_vars);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (arg_efi_nvram_state_mode == STATE_AUTO && !arg_ephemeral) {
|
||||
assert(!arg_efi_nvram_state_path);
|
||||
|
||||
r = make_sidecar_path(".efinvramstate", &arg_efi_nvram_state_path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
log_debug("Storing EFI NVRAM state persistently under '%s'.", arg_efi_nvram_state_path);
|
||||
}
|
||||
|
||||
_cleanup_(unlink_and_freep) char *ovmf_vars = NULL;
|
||||
if (ovmf_config->vars || arg_efi_nvram_template) {
|
||||
const char *vars_source = arg_efi_nvram_template ?: ovmf_config->vars;
|
||||
_cleanup_close_ int target_fd = -EBADF;
|
||||
_cleanup_(unlink_and_freep) char *destroy_path = NULL;
|
||||
bool newly_created;
|
||||
const char *state;
|
||||
if (arg_efi_nvram_state_path) {
|
||||
_cleanup_free_ char *d = strdup(arg_efi_nvram_state_path);
|
||||
if (!d)
|
||||
return log_oom();
|
||||
|
||||
target_fd = openat_report_new(AT_FDCWD, arg_efi_nvram_state_path, O_WRONLY|O_CREAT|O_CLOEXEC, 0600, &newly_created);
|
||||
if (target_fd < 0)
|
||||
return log_error_errno(target_fd, "Failed to open file for OVMF vars at %s: %m", arg_efi_nvram_state_path);
|
||||
|
||||
if (newly_created)
|
||||
destroy_path = TAKE_PTR(d);
|
||||
|
||||
r = fd_verify_regular(target_fd);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Not a regular file for OVMF variables at %s: %m", arg_efi_nvram_state_path);
|
||||
|
||||
state = arg_efi_nvram_state_path;
|
||||
} else {
|
||||
_cleanup_free_ char *t = NULL;
|
||||
r = tempfn_random_child(/* p= */ NULL, "vmspawn-", &t);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create temporary filename: %m");
|
||||
|
||||
target_fd = open(t, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0600);
|
||||
if (target_fd < 0)
|
||||
return log_error_errno(errno, "Failed to create regular file for OVMF vars at %s: %m", t);
|
||||
|
||||
newly_created = true;
|
||||
state = ovmf_vars = TAKE_PTR(t);
|
||||
}
|
||||
|
||||
if (newly_created) {
|
||||
_cleanup_close_ int source_fd = open(vars_source, O_RDONLY|O_CLOEXEC);
|
||||
if (source_fd < 0)
|
||||
return log_error_errno(errno, "Failed to open OVMF vars file %s: %m", vars_source);
|
||||
|
||||
r = copy_bytes(source_fd, target_fd, UINT64_MAX, COPY_REFLINK);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to copy bytes from %s to %s: %m", vars_source, state);
|
||||
|
||||
/* This isn't always available so don't raise an error if it fails */
|
||||
(void) copy_times(source_fd, target_fd, 0);
|
||||
}
|
||||
|
||||
destroy_path = mfree(destroy_path); /* disarm auto-destroy */
|
||||
|
||||
/* Mark the UEFI variable store pflash as requiring SMM access. This
|
||||
* prevents the guest OS from writing to pflash directly, ensuring all
|
||||
* variable updates go through the firmware's validation checks. Without
|
||||
* this, secure boot keys could be overwritten by the OS. */
|
||||
if (ARCHITECTURE_SUPPORTS_SMM) {
|
||||
r = qemu_config_section(config_file, "global", /* id= */ NULL,
|
||||
"driver", "cfi.pflash01",
|
||||
"property", "secure",
|
||||
"value", "on");
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = qemu_config_section(config_file, "drive", "ovmf-vars",
|
||||
"file", state,
|
||||
"if", "pflash",
|
||||
"format", ovmf_config_format(ovmf_config));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (kernel) {
|
||||
r = strv_extend_many(&cmdline, "-kernel", kernel);
|
||||
if (arg_linux) {
|
||||
r = strv_extend_many(&cmdline, "-kernel", arg_linux);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
/* We can't rely on gpt-auto-generator when direct kernel booting so synthesize a root=
|
||||
* kernel argument instead. */
|
||||
if (arg_image) {
|
||||
if (arg_linux_image_type != KERNEL_IMAGE_TYPE_UKI && arg_image) {
|
||||
r = kernel_cmdline_maybe_append_root();
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -3074,15 +3212,11 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
}
|
||||
|
||||
_cleanup_(rm_rf_physical_and_freep) char *smbios_dir = NULL;
|
||||
r = mkdtemp_malloc("/var/tmp/vmspawn-smbios-XXXXXX", &smbios_dir);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create temporary directory: %m");
|
||||
_cleanup_close_ int smbios_dir_fd = mkdtemp_open("/var/tmp/vmspawn-smbios-XXXXXX", /* flags= */ 0, &smbios_dir);
|
||||
if (smbios_dir_fd < 0)
|
||||
return log_error_errno(smbios_dir_fd, "Failed to create temporary directory: %m");
|
||||
|
||||
r = cmdline_add_kernel_cmdline(&cmdline, kernel, smbios_dir);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = cmdline_add_smbios11(&cmdline, smbios_dir);
|
||||
r = cmdline_add_smbios11(&cmdline, smbios_dir_fd, smbios_dir);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -3284,47 +3418,24 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
return log_error_errno(r, "Failed to set credential systemd.unit-dropin.sshd-vsock@.service: %m");
|
||||
}
|
||||
|
||||
if (ARCHITECTURE_SUPPORTS_SMBIOS)
|
||||
FOREACH_ARRAY(cred, arg_credentials.credentials, arg_credentials.n_credentials) {
|
||||
_cleanup_free_ char *p = NULL, *cred_data_b64 = NULL;
|
||||
ssize_t n;
|
||||
|
||||
n = base64mem(cred->data, cred->size, &cred_data_b64);
|
||||
if (n < 0)
|
||||
return log_oom();
|
||||
|
||||
p = path_join(smbios_dir, cred->id);
|
||||
if (!p)
|
||||
return log_oom();
|
||||
|
||||
r = write_string_filef(
|
||||
p,
|
||||
WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE|WRITE_STRING_FILE_MODE_0600,
|
||||
"io.systemd.credential.binary:%s=%s", cred->id, cred_data_b64);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write smbios credential file %s: %m", p);
|
||||
|
||||
r = strv_extend(&cmdline, "-smbios");
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
r = strv_extend_joined(&cmdline, "type=11,path=", p);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
if (use_vsock) {
|
||||
notify_sock_fd = open_vsock();
|
||||
if (notify_sock_fd < 0)
|
||||
return log_error_errno(notify_sock_fd, "Failed to open VSOCK: %m");
|
||||
|
||||
r = cmdline_add_vsock(&cmdline, notify_sock_fd);
|
||||
if (r == -ENOMEM)
|
||||
return log_oom();
|
||||
r = add_vsock_credential(notify_sock_fd);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to call getsockname on VSOCK: %m");
|
||||
return log_error_errno(r, "Failed to add VSOCK credential: %m");
|
||||
}
|
||||
|
||||
r = cmdline_add_credentials(&cmdline, smbios_dir_fd, smbios_dir);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = cmdline_add_kernel_cmdline(&cmdline, smbios_dir_fd, smbios_dir);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Finalize the config file and add -readconfig to the cmdline */
|
||||
r = fflush_and_check(config_file);
|
||||
if (r < 0)
|
||||
@@ -3651,10 +3762,56 @@ static int determine_names(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int determine_kernel(void) {
|
||||
int r;
|
||||
|
||||
if (!arg_linux && arg_directory) {
|
||||
/* A kernel is required for directory type images so attempt to find one under /boot and /efi */
|
||||
r = discover_boot_entry(arg_directory, &arg_linux, &arg_initrds);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to locate UKI in directory type image, please specify one with --linux=.");
|
||||
|
||||
log_debug("Discovered UKI image at %s", arg_linux);
|
||||
}
|
||||
|
||||
if (!arg_linux) {
|
||||
if (arg_firmware_type == _FIRMWARE_INVALID)
|
||||
arg_firmware_type = FIRMWARE_UEFI;
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = inspect_kernel(AT_FDCWD, arg_linux, &arg_linux_image_type);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to determine '%s' kernel image type: %m", arg_linux);
|
||||
|
||||
if (arg_linux_image_type == KERNEL_IMAGE_TYPE_UNKNOWN) {
|
||||
if (arg_firmware_type == FIRMWARE_UEFI)
|
||||
return log_error_errno(
|
||||
SYNTHETIC_ERRNO(EINVAL),
|
||||
"Kernel image '%s' is not a PE binary, --firmware=uefi (or a firmware path) is not supported.",
|
||||
arg_linux);
|
||||
if (arg_firmware_type == _FIRMWARE_INVALID)
|
||||
arg_firmware_type = FIRMWARE_NONE;
|
||||
}
|
||||
|
||||
if (arg_firmware_type == _FIRMWARE_INVALID)
|
||||
arg_firmware_type = FIRMWARE_UEFI;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int verify_arguments(void) {
|
||||
if (!strv_isempty(arg_initrds) && !arg_linux)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --initrd= cannot be used without --linux=.");
|
||||
|
||||
if (arg_firmware_type != FIRMWARE_UEFI && arg_linux_image_type == KERNEL_IMAGE_TYPE_UKI)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Booting a UKI requires --firmware=uefi.");
|
||||
|
||||
if (arg_firmware_type == FIRMWARE_NONE && !arg_linux)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"--firmware=none requires --linux= to be specified.");
|
||||
|
||||
if (arg_image_disk_type == DISK_TYPE_VIRTIO_SCSI_CDROM) {
|
||||
if (arg_ephemeral)
|
||||
log_warning("--ephemeral has no effect with --image-disk-type=scsi-cd (CD-ROMs are read-only).");
|
||||
@@ -3702,6 +3859,10 @@ static int run(int argc, char *argv[]) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = determine_kernel();
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = verify_arguments();
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
Reference in New Issue
Block a user