vmspawn: discover firmware descriptors, opt-in secure boot support for coco (#42876)

This commit is contained in:
Lennart Poettering
2026-07-07 08:55:10 +02:00
committed by GitHub
5 changed files with 432 additions and 82 deletions
+193
View File
@@ -1,8 +1,17 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <stdlib.h>
#include "sd-json.h"
#include "alloc-util.h"
#include "fileio.h"
#include "path-util.h"
#include "rm-rf.h"
#include "set.h"
#include "string-util.h"
#include "tests.h"
#include "tmpfile-util.h"
#include "vmspawn-util.h"
#define _ESCAPE_QEMU_VALUE_CHECK(str, correct, varname) \
@@ -23,4 +32,188 @@ TEST(escape_qemu_value) {
ESCAPE_QEMU_VALUE_CHECK("", "");
}
typedef enum TestMapping {
MAPPING_FLASH_SPLIT, /* pflash executable + NVRAM template */
MAPPING_FLASH_STATELESS, /* read-only pflash, no NVRAM */
MAPPING_FLASH_COMBINED, /* read-write pflash with the variable store inside the executable */
MAPPING_MEMORY, /* mapped into memory, loaded via -bios */
MAPPING_KERNEL, /* loaded like a Linux kernel */
} TestMapping;
static void write_descriptor(
const char *dir,
const char *name,
const char *executable,
const char *format,
const char *interface_type,
const char *arch,
const char *machine,
TestMapping mapping,
char **features) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
_cleanup_free_ char *j = NULL, *p = NULL;
if (!arch)
ASSERT_OK(native_arch_as_qemu(&arch));
bool flash = IN_SET(mapping, MAPPING_FLASH_SPLIT, MAPPING_FLASH_STATELESS, MAPPING_FLASH_COMBINED);
ASSERT_OK(sd_json_buildo(&v,
SD_JSON_BUILD_PAIR_STRING("description", name),
SD_JSON_BUILD_PAIR_STRV("interface-types", STRV_MAKE(interface_type ?: "uefi")),
SD_JSON_BUILD_PAIR("mapping", SD_JSON_BUILD_OBJECT(
SD_JSON_BUILD_PAIR_STRING("device", flash ? "flash" : mapping == MAPPING_MEMORY ? "memory" : "kernel"),
SD_JSON_BUILD_PAIR_CONDITION(!flash, "filename", SD_JSON_BUILD_STRING(executable)),
SD_JSON_BUILD_PAIR_CONDITION(mapping == MAPPING_FLASH_STATELESS, "mode", SD_JSON_BUILD_STRING("stateless")),
SD_JSON_BUILD_PAIR_CONDITION(mapping == MAPPING_FLASH_COMBINED, "mode", SD_JSON_BUILD_STRING("combined")),
SD_JSON_BUILD_PAIR_CONDITION(mapping == MAPPING_FLASH_SPLIT, "nvram-template", SD_JSON_BUILD_OBJECT(
SD_JSON_BUILD_PAIR_STRING("filename", "/test/vars.fd"),
SD_JSON_BUILD_PAIR_STRING("format", "raw"))),
SD_JSON_BUILD_PAIR_CONDITION(flash, "executable", SD_JSON_BUILD_OBJECT(
SD_JSON_BUILD_PAIR_STRING("filename", executable),
SD_JSON_BUILD_PAIR_STRING("format", format))))),
SD_JSON_BUILD_PAIR("targets", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_OBJECT(
SD_JSON_BUILD_PAIR_STRING("architecture", arch),
SD_JSON_BUILD_PAIR_STRV("machines", STRV_MAKE(machine ?: QEMU_MACHINE_TYPE))))),
SD_JSON_BUILD_PAIR_STRV("features", features),
SD_JSON_BUILD_PAIR_EMPTY_ARRAY("tags")));
ASSERT_OK(sd_json_variant_format(v, /* flags= */ 0, &j));
ASSERT_NOT_NULL(p = path_join(dir, name));
ASSERT_OK(write_string_file(p, j, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755));
}
/* Searches with the given feature sets and flags, and asserts that the firmware with the expected
* executable path is selected, or -ENOENT if expect_path is NULL. Optionally returns the selected config
* for further assertions. */
static void check_find(char **include, char **exclude, FindOvmfConfigFlags flags, const char *expect_path, OvmfConfig **ret) {
_cleanup_set_free_ Set *inc = NULL, *exc = NULL;
_cleanup_(ovmf_config_freep) OvmfConfig *config = NULL;
int r;
ASSERT_OK(set_put_strdupv(&inc, include));
ASSERT_OK(set_put_strdupv(&exc, exclude));
r = find_ovmf_config(inc, exc, flags, &config, /* ret_firmware_json= */ NULL);
if (!expect_path)
ASSERT_ERROR(r, ENOENT);
else {
ASSERT_OK(r);
ASSERT_STREQ(config->path, expect_path);
}
if (ret)
*ret = TAKE_PTR(config);
}
TEST(find_ovmf_config) {
_cleanup_(rm_rf_physical_and_freep) char *tmp = NULL;
_cleanup_(ovmf_config_freep) OvmfConfig *config = NULL;
_cleanup_free_ char *dir = NULL;
if (native_arch_as_qemu(/* ret= */ NULL) < 0)
return (void) log_tests_skipped("native architecture not supported by qemu");
ASSERT_OK(mkdtemp_malloc("/tmp/test-vmspawn-firmware-XXXXXX", &tmp));
ASSERT_OK_ERRNO(setenv("XDG_CONFIG_HOME", tmp, /* overwrite= */ true));
ASSERT_NOT_NULL(dir = path_join(tmp, "qemu/firmware"));
/* All fixtures declare made-up features and every search below requires one of them, so
* descriptors installed on the host can never match and the test stays hermetic. */
/* Stateful vs. stateless selection, in both sort orders. */
write_descriptor(dir, "00-a-stateless.json", "/test/a-stateless.fd", "raw", NULL, NULL, NULL, MAPPING_FLASH_STATELESS, STRV_MAKE("vmspawn-test-a"));
write_descriptor(dir, "10-a-stateful.json", "/test/a-stateful.fd", "raw", NULL, NULL, NULL, MAPPING_FLASH_SPLIT, STRV_MAKE("vmspawn-test-a"));
write_descriptor(dir, "00-b-stateful.json", "/test/b-stateful.fd", "raw", NULL, NULL, NULL, MAPPING_FLASH_SPLIT, STRV_MAKE("vmspawn-test-b"));
write_descriptor(dir, "10-b-stateless.json", "/test/b-stateless.fd", "raw", NULL, NULL, NULL, MAPPING_FLASH_STATELESS, STRV_MAKE("vmspawn-test-b"));
/* By default only firmware with an NVRAM template is considered. */
check_find(STRV_MAKE("vmspawn-test-a"), /* exclude= */ NULL, /* flags= */ 0, "/test/a-stateful.fd", &config);
ASSERT_STREQ(config->vars, "/test/vars.fd");
ASSERT_STREQ(config->format, "raw");
ASSERT_FALSE(ovmf_config_is_stateless(config));
ASSERT_FALSE(ovmf_config_has_feature(config, "secure-boot"));
config = ovmf_config_free(config);
/* With FIND_OVMF_STATELESS only firmware in stateless flash mode is considered. */
check_find(STRV_MAKE("vmspawn-test-a"), /* exclude= */ NULL, FIND_OVMF_STATELESS, "/test/a-stateless.fd", &config);
ASSERT_NULL(config->vars);
ASSERT_TRUE(ovmf_config_is_stateless(config));
config = ovmf_config_free(config);
check_find(STRV_MAKE("vmspawn-test-b"), /* exclude= */ NULL, /* flags= */ 0, "/test/b-stateful.fd", /* ret= */ NULL);
check_find(STRV_MAKE("vmspawn-test-b"), /* exclude= */ NULL, FIND_OVMF_STATELESS, "/test/b-stateless.fd", /* ret= */ NULL);
/* FIND_OVMF_REQUIRE_RAW skips firmware in other formats, which is accepted otherwise. */
write_descriptor(dir, "00-c-qcow2.json", "/test/c.qcow2", "qcow2", NULL, NULL, NULL, MAPPING_FLASH_STATELESS, STRV_MAKE("vmspawn-test-c"));
write_descriptor(dir, "10-c-raw.json", "/test/c.fd", "raw", NULL, NULL, NULL, MAPPING_FLASH_STATELESS, STRV_MAKE("vmspawn-test-c"));
check_find(STRV_MAKE("vmspawn-test-c"), /* exclude= */ NULL, FIND_OVMF_STATELESS, "/test/c.qcow2", &config);
ASSERT_STREQ(config->format, "qcow2");
config = ovmf_config_free(config);
check_find(STRV_MAKE("vmspawn-test-c"), /* exclude= */ NULL, FIND_OVMF_STATELESS|FIND_OVMF_REQUIRE_RAW, "/test/c.fd", /* ret= */ NULL);
/* Memory-mapped firmware (loaded via -bios) is stateless and raw by definition. */
write_descriptor(dir, "00-m-memory.json", "/test/m-memory.fd", "raw", NULL, NULL, NULL, MAPPING_MEMORY, STRV_MAKE("vmspawn-test-m"));
check_find(STRV_MAKE("vmspawn-test-m"), /* exclude= */ NULL, /* flags= */ 0, /* expect_path= */ NULL, /* ret= */ NULL);
check_find(STRV_MAKE("vmspawn-test-m"), /* exclude= */ NULL, FIND_OVMF_STATELESS|FIND_OVMF_REQUIRE_RAW, "/test/m-memory.fd", &config);
ASSERT_NULL(config->vars);
ASSERT_NULL(config->format);
ASSERT_STREQ(ovmf_config_format(config), "raw");
ASSERT_TRUE(ovmf_config_is_stateless(config));
config = ovmf_config_free(config);
/* Combined-mode flash firmware carries a writable variable store inside the executable, it must not be treated as stateless. */
write_descriptor(dir, "00-f-combined.json", "/test/f-combined.fd", "raw", NULL, NULL, NULL, MAPPING_FLASH_COMBINED, STRV_MAKE("vmspawn-test-f"));
check_find(STRV_MAKE("vmspawn-test-f"), /* exclude= */ NULL, /* flags= */ 0, /* expect_path= */ NULL, /* ret= */ NULL);
check_find(STRV_MAKE("vmspawn-test-f"), /* exclude= */ NULL, FIND_OVMF_STATELESS, /* expect_path= */ NULL, /* ret= */ NULL);
/* Firmware that is not UEFI, uses an unsupported mapping device, or doesn't match the native
* architecture or machine type, is skipped. */
write_descriptor(dir, "00-d-bios.json", "/test/d-bios.fd", "raw", "bios", NULL, NULL, MAPPING_FLASH_SPLIT, STRV_MAKE("vmspawn-test-d"));
write_descriptor(dir, "05-d-kernel.json", "/test/d-kernel.fd", "raw", NULL, NULL, NULL, MAPPING_KERNEL, STRV_MAKE("vmspawn-test-d"));
write_descriptor(dir, "10-d-arch.json", "/test/d-arch.fd", "raw", NULL, "vmspawn-test-arch", NULL, MAPPING_FLASH_SPLIT, STRV_MAKE("vmspawn-test-d"));
write_descriptor(dir, "20-d-machine.json", "/test/d-machine.fd", "raw", NULL, NULL, "vmspawn-test-mach", MAPPING_FLASH_SPLIT, STRV_MAKE("vmspawn-test-d"));
write_descriptor(dir, "30-d-good.json", "/test/d-good.fd", "raw", NULL, NULL, NULL, MAPPING_FLASH_SPLIT, STRV_MAKE("vmspawn-test-d"));
check_find(STRV_MAKE("vmspawn-test-d"), /* exclude= */ NULL, /* flags= */ 0, "/test/d-good.fd", /* ret= */ NULL);
/* Feature include/exclude handling. */
write_descriptor(dir, "00-e-both.json", "/test/e-both.fd", "raw", NULL, NULL, NULL, MAPPING_FLASH_SPLIT, STRV_MAKE("vmspawn-test-e1", "vmspawn-test-e2", "secure-boot"));
write_descriptor(dir, "10-e-one.json", "/test/e-one.fd", "raw", NULL, NULL, NULL, MAPPING_FLASH_SPLIT, STRV_MAKE("vmspawn-test-e1"));
/* The first matching descriptor in sort order wins. */
check_find(STRV_MAKE("vmspawn-test-e1"), /* exclude= */ NULL, /* flags= */ 0, "/test/e-both.fd", &config);
ASSERT_TRUE(ovmf_config_has_feature(config, "secure-boot"));
config = ovmf_config_free(config);
/* Descriptors with an excluded feature are skipped. */
check_find(STRV_MAKE("vmspawn-test-e1"), STRV_MAKE("vmspawn-test-e2"), /* flags= */ 0, "/test/e-one.fd", &config);
ASSERT_TRUE(ovmf_config_has_feature(config, "vmspawn-test-e1"));
ASSERT_FALSE(ovmf_config_has_feature(config, "vmspawn-test-e2"));
config = ovmf_config_free(config);
/* Inclusion wins over exclusion. */
check_find(STRV_MAKE("vmspawn-test-e1", "vmspawn-test-e2"), STRV_MAKE("vmspawn-test-e2"), /* flags= */ 0, "/test/e-both.fd", &config);
ASSERT_TRUE(ovmf_config_has_feature(config, "vmspawn-test-e1"));
ASSERT_TRUE(ovmf_config_has_feature(config, "vmspawn-test-e2"));
config = ovmf_config_free(config);
/* All included features must be present. */
check_find(STRV_MAKE("vmspawn-test-e1", "vmspawn-test-nonexistent"), /* exclude= */ NULL, /* flags= */ 0, /* expect_path= */ NULL, /* ret= */ NULL);
/* The firmware description JSON is returned on request. */
_cleanup_set_free_ Set *inc = NULL;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
ASSERT_OK(set_put_strdup(&inc, "vmspawn-test-a"));
ASSERT_OK(find_ovmf_config(inc, /* features_exclude= */ NULL, /* flags= */ 0, &config, &json));
ASSERT_TRUE(sd_json_variant_is_object(json));
ASSERT_OK_ERRNO(unsetenv("XDG_CONFIG_HOME"));
}
DEFINE_TEST_MAIN(LOG_INFO);
+69 -14
View File
@@ -43,7 +43,7 @@ static const char* const architecture_to_qemu_table[_ARCHITECTURE_MAX] = {
[ARCHITECTURE_S390X] = "s390x",
};
static int native_arch_as_qemu(const char **ret) {
int native_arch_as_qemu(const char **ret) {
const char *s = architecture_to_qemu_table[native_architecture()];
if (!s)
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Architecture %s not supported by qemu", architecture_to_string(native_architecture()));
@@ -62,9 +62,36 @@ OvmfConfig* ovmf_config_free(OvmfConfig *config) {
free(config->format);
free(config->vars);
free(config->vars_format);
free(config->device);
free(config->mode);
strv_free(config->features);
return mfree(config);
}
bool ovmf_config_has_feature(const OvmfConfig *config, const char *feature) {
assert(config);
assert(feature);
return strv_contains(config->features, feature);
}
static bool firmware_is_stateless(const char *device, const char *mode) {
/* Memory-mapped firmware (loaded via -bios) carries no NVRAM. For flash, only mode "stateless"
* is stateless: an absent mode means "split" (executable plus NVRAM template), and "combined"
* images carry the variable store inside the (writable) executable. */
if (streq_ptr(device, "memory"))
return true;
return streq_ptr(device, "flash") && streq_ptr(mode, "stateless");
}
bool ovmf_config_is_stateless(const OvmfConfig *config) {
assert(config);
return firmware_is_stateless(config->device, config->mode);
}
DEFINE_STRING_TABLE_LOOKUP(network_stack, NetworkStack);
int qemu_check_kvm_support(void) {
@@ -140,6 +167,8 @@ typedef struct FirmwareData {
char *firmware_format;
char *vars;
char *vars_format;
char *device;
char *mode;
FirmwareTarget **targets;
size_t n_targets;
} FirmwareData;
@@ -164,12 +193,6 @@ static bool firmware_data_matches_machine(const FirmwareData *fwd, const char *a
return false;
}
static bool firmware_data_supports_sb(const FirmwareData *fwd) {
assert(fwd);
return strv_contains(fwd->features, "secure-boot");
}
static FirmwareData* firmware_data_free(FirmwareData *fwd) {
if (!fwd)
return NULL;
@@ -180,6 +203,8 @@ static FirmwareData* firmware_data_free(FirmwareData *fwd) {
free(fwd->firmware_format);
free(fwd->vars);
free(fwd->vars_format);
free(fwd->device);
free(fwd->mode);
firmware_target_free_many(fwd->targets, fwd->n_targets);
return mfree(fwd);
@@ -207,14 +232,30 @@ static int firmware_nvram_template(const char *name, sd_json_variant *v, sd_json
}
static int firmware_mapping(const char *name, sd_json_variant *v, sd_json_dispatch_flags_t flags, void *userdata) {
static const sd_json_dispatch_field table[] = {
{ "device", SD_JSON_VARIANT_STRING, NULL, 0, SD_JSON_MANDATORY },
{ "executable", SD_JSON_VARIANT_OBJECT, firmware_executable, 0, SD_JSON_MANDATORY },
{ "nvram-template", SD_JSON_VARIANT_OBJECT, firmware_nvram_template, 0, 0 },
static const sd_json_dispatch_field table_flash[] = {
{ "device", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(FirmwareData, device), SD_JSON_MANDATORY },
{ "mode", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(FirmwareData, mode), 0 },
{ "executable", SD_JSON_VARIANT_OBJECT, firmware_executable, 0, SD_JSON_MANDATORY },
{ "nvram-template", SD_JSON_VARIANT_OBJECT, firmware_nvram_template, 0, 0 },
{}
};
static const sd_json_dispatch_field table_memory[] = {
{ "device", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(FirmwareData, device), SD_JSON_MANDATORY },
{ "filename", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(FirmwareData, firmware), SD_JSON_MANDATORY },
{}
};
return sd_json_dispatch(v, table, flags, userdata);
sd_json_variant *d = sd_json_variant_by_key(v, "device");
if (!d || !sd_json_variant_is_string(d))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Firmware mapping lacks a device type.");
const char *device = sd_json_variant_string(d);
if (streq(device, "flash"))
return sd_json_dispatch(v, table_flash, flags, userdata);
if (streq(device, "memory"))
return sd_json_dispatch(v, table_memory, flags, userdata);
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported firmware mapping device type '%s'.", device);
}
static int dispatch_targets(const char *name, sd_json_variant *v, sd_json_dispatch_flags_t flags, void *userdata) {
@@ -406,7 +447,9 @@ static int ovmf_config_make(FirmwareData *fwd, OvmfConfig **ret) {
.format = TAKE_PTR(fwd->firmware_format),
.vars = TAKE_PTR(fwd->vars),
.vars_format = TAKE_PTR(fwd->vars_format),
.supports_sb = firmware_data_supports_sb(fwd),
.device = TAKE_PTR(fwd->device),
.mode = TAKE_PTR(fwd->mode),
.features = TAKE_PTR(fwd->features),
};
*ret = TAKE_PTR(config);
@@ -430,6 +473,7 @@ int load_ovmf_config(const char *path, OvmfConfig **ret) {
int find_ovmf_config(
Set *features_include,
Set *features_exclude,
FindOvmfConfigFlags flags,
OvmfConfig **ret,
sd_json_variant **ret_firmware_json) {
_cleanup_(ovmf_config_freep) OvmfConfig *config = NULL;
@@ -465,11 +509,22 @@ int find_ovmf_config(
continue;
}
if (!fwd->vars) {
if (FLAGS_SET(flags, FIND_OVMF_STATELESS)) {
if (!firmware_is_stateless(fwd->device, fwd->mode)) {
log_debug("Skipping %s, firmware is not stateless.", *file);
continue;
}
} else if (!fwd->vars) {
log_debug("Skipping %s, firmware does not have an NVRAM template.", *file);
continue;
}
/* Memory-mapped firmware carries no format field and is raw by definition. */
if (FLAGS_SET(flags, FIND_OVMF_REQUIRE_RAW) && !streq(fwd->firmware_format ?: "raw", "raw")) {
log_debug("Skipping %s, firmware image is not in raw format.", *file);
continue;
}
/* Check if any target matches our architecture and machine type. Machine
* patterns in firmware descriptions use globs like "pc-q35-*", so we do a
* substring check to see if our machine type (e.g. "q35") appears in any of
+16 -2
View File
@@ -99,7 +99,9 @@ typedef struct OvmfConfig {
char *format;
char *vars;
char *vars_format;
bool supports_sb;
char *device;
char *mode;
char **features;
} OvmfConfig;
static inline const char* ovmf_config_format(const OvmfConfig *c) {
@@ -110,6 +112,9 @@ static inline const char* ovmf_config_vars_format(const OvmfConfig *c) {
return ASSERT_PTR(c)->vars_format ?: "raw";
}
bool ovmf_config_is_stateless(const OvmfConfig *config);
bool ovmf_config_has_feature(const OvmfConfig *config, const char *feature);
OvmfConfig* ovmf_config_free(OvmfConfig *ovmf_config);
DEFINE_TRIVIAL_CLEANUP_FUNC(OvmfConfig*, ovmf_config_free);
@@ -134,7 +139,16 @@ int qemu_check_vsock_support(void);
int list_ovmf_config(char ***ret);
int list_ovmf_firmware_features(char ***ret);
int load_ovmf_config(const char *path, OvmfConfig **ret);
int find_ovmf_config(Set *features_include, Set *features_exclude, OvmfConfig **ret, sd_json_variant **ret_firmware_json);
typedef enum FindOvmfConfigFlags {
FIND_OVMF_STATELESS = 1 << 0, /* select stateless firmware (flash mode "stateless" or memory-mapped,
* bootable via -bios); default selects pflash firmware with an NVRAM
* template */
FIND_OVMF_REQUIRE_RAW = 1 << 1, /* skip non-raw firmware executables (-bios can't load qcow2) */
} FindOvmfConfigFlags;
int find_ovmf_config(Set *features_include, Set *features_exclude, FindOvmfConfigFlags flags, OvmfConfig **ret, sd_json_variant **ret_firmware_json);
int native_arch_as_qemu(const char **ret);
int find_qemu_binary(char **ret_qemu_binary);
int vsock_fix_child_cid(int vhost_device_fd, unsigned *machine_cid, const char *machine);
+113 -38
View File
@@ -79,6 +79,7 @@
#include "socket-util.h"
#include "stat-util.h"
#include "stdio-util.h"
#include "string-table.h"
#include "string-util.h"
#include "strv.h"
#include "swtpm-util.h"
@@ -2542,6 +2543,54 @@ static int prepare_device_info(const char *runtime_dir, MachineConfig *c) {
return assign_pcie_ports(c);
}
/* Maps a confidential computing mode to the firmware descriptor feature that firmware must declare
* to support it. Note that the feature names are defined by the QEMU firmware interop spec and
* deviate slightly from our own names for the same modes (cf. confidential_computing_to_string()):
* "sev-snp" vs. "amd-sev-snp", and "tdx" vs. "intel-tdx". */
static const char* const coco_firmware_feature_table[_COCO_MAX] = {
[COCO_AMD_SEV_SNP] = "amd-sev-snp",
[COCO_INTEL_TDX] = "intel-tdx",
};
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(coco_firmware_feature, ConfidentialComputing);
static int discover_ovmf_config(OvmfConfig **ret, sd_json_variant **ret_firmware_json) {
int r;
assert(ret);
const char *coco_feature = coco_firmware_feature_to_string(arg_confidential_computing);
if (coco_feature) {
r = set_put_strdup(&arg_firmware_features_include, coco_feature);
if (r < 0)
return log_oom();
/* CoCo firmware is stateless, so Secure Boot keys cannot be enrolled at runtime
* but must have baked-in, enrolled keys, which we avoid in other cases. */
if (set_contains(arg_firmware_features_include, "secure-boot")) {
r = set_put_strdup(&arg_firmware_features_include, "enrolled-keys");
if (r < 0)
return log_oom();
}
}
FindOvmfConfigFlags flags =
arg_confidential_computing != COCO_NO ? FIND_OVMF_STATELESS|FIND_OVMF_REQUIRE_RAW :
0;
r = find_ovmf_config(arg_firmware_features_include, arg_firmware_features_exclude, flags, ret, ret_firmware_json);
if (r == -ENOENT && coco_feature)
return log_error_errno(r, "No suitable firmware descriptor found for --coco=%s "
"(requires stateless firmware in raw format with the '%s' firmware feature). "
"Install a suitable firmware, select a firmware descriptor with --firmware=, or "
"opt in with --secure-boot=yes if the installed firmware enforces Secure Boot.",
confidential_computing_to_string(arg_confidential_computing), coco_feature);
if (r < 0)
return log_error_errno(r, "Failed to find OVMF config: %m");
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;
@@ -2608,19 +2657,53 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"--coco= requires KVM, but KVM is not available.");
if (arg_firmware_type == FIRMWARE_UEFI && arg_confidential_computing == COCO_NO) {
if (arg_firmware)
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 (r < 0)
return log_error_errno(r, "Failed to load firmware descriptor '%s': %m", arg_firmware);
} else {
r = discover_ovmf_config(&ovmf_config, /* ret_firmware_json= */ NULL);
if (r < 0)
return r;
}
if (set_contains(arg_firmware_features_include, "secure-boot") && !ovmf_config->supports_sb)
/* Flash mode "combined" places the variable store in the (writable) executable,
* which would have to be cloned for each guest. */
if (streq_ptr(ovmf_config->mode, "combined"))
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Firmware descriptor '%s' declares flash mode 'combined', which is not supported.",
arg_firmware ?: ovmf_config->path);
if (arg_confidential_computing != COCO_NO) {
const char *coco_feature = coco_firmware_feature_to_string(arg_confidential_computing);
if (!ovmf_config_has_feature(ovmf_config, coco_feature))
return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE),
"Firmware descriptor '%s' does not declare the '%s' feature, but "
"--coco=%s requires firmware built specifically for it.",
arg_firmware ?: ovmf_config->path, coco_feature,
confidential_computing_to_string(arg_confidential_computing));
if (!ovmf_config_is_stateless(ovmf_config))
return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE),
"Firmware descriptor '%s' does not describe stateless firmware, "
"but --coco=%s requires stateless firmware.",
arg_firmware ?: ovmf_config->path,
confidential_computing_to_string(arg_confidential_computing));
if (!streq(ovmf_config_format(ovmf_config), "raw"))
return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE),
"Firmware image '%s' is in %s format, "
"but --coco=%s requires a raw image.",
ovmf_config->path, ovmf_config_format(ovmf_config),
confidential_computing_to_string(arg_confidential_computing));
}
bool sb = ovmf_config_has_feature(ovmf_config, "secure-boot");
if (set_contains(arg_firmware_features_include, "secure-boot") && !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.", sb ? "with" : "without");
}
_cleanup_(machine_bind_user_context_freep) MachineBindUserContext *bind_user_context = NULL;
@@ -2683,8 +2766,9 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
return r;
}
if (ovmf_config && ARCHITECTURE_SUPPORTS_SMM) {
r = qemu_config_key(config_file, "smm", on_off(ovmf_config->supports_sb));
if (ovmf_config && ARCHITECTURE_SUPPORTS_SMM && arg_confidential_computing == COCO_NO) {
bool sb = ovmf_config_has_feature(ovmf_config, "secure-boot");
r = qemu_config_key(config_file, "smm", on_off(sb));
if (r < 0)
return r;
}
@@ -3126,9 +3210,11 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
}
}
/* Memory-mapped firmware asks for -bios loading by definition. Under
* confidential computing -bios is used even for (stateless) flash firmware. */
_cleanup_(unlink_and_freep) char *ovmf_vars = NULL;
if (arg_confidential_computing != COCO_NO) {
r = strv_extend_many(&cmdline, "-bios", arg_firmware);
if (ovmf_config && (arg_confidential_computing != COCO_NO || streq_ptr(ovmf_config->device, "memory"))) {
r = strv_extend_many(&cmdline, "-bios", ovmf_config->path);
if (r < 0)
return r;
} else {
@@ -4142,6 +4228,19 @@ static int verify_arguments(void) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--grow-image is not supported for qcow2 images, use 'qemu-img resize FILE SIZE'.");
if (arg_confidential_computing != COCO_NO) {
/* Confidential computing firmware is stateless, there is no NVRAM to instantiate from a
* template or to persist. */
if (arg_efi_nvram_template)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--efi-nvram-template= cannot be used with --coco=, "
"confidential computing firmware is stateless.");
if (arg_efi_nvram_state_mode == STATE_PATH)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"An explicit --efi-nvram-state= path cannot be used with --coco=, "
"confidential computing firmware is stateless. Use 'off' or 'auto'.");
}
if (arg_confidential_computing == COCO_AMD_SEV_SNP) {
if (native_architecture() != ARCHITECTURE_X86_64)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
@@ -4153,17 +4252,6 @@ static int verify_arguments(void) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--coco=sev-snp can't be used with %s firmware",
firmware_to_string(arg_firmware_type));
/* SNP can't use pflash + NVRAM split, so the firmware-descriptor
* machinery doesn't apply. Require an explicit raw .fd path and
* use it verbatim with -bios later. */
if (!arg_firmware)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--coco=sev-snp requires --firmware=PATH "
"pointing at a raw SNP-built OVMF .fd binary.");
log_debug("Using raw SNP firmware at %s (no NVRAM, no Secure Boot).", arg_firmware);
if (set_contains(arg_firmware_features_include, "secure-boot"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--coco=sev-snp cannot be combined with --secure-boot=yes.");
if (arg_tpm > 0)
log_warning("TPM can't be trusted by the confidential computing guest");
/* kernel-hashes=on only covers what QEMU itself loads via -kernel/-initrd/-append.
@@ -4187,19 +4275,6 @@ static int verify_arguments(void) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--coco=tdx can't be used with %s firmware",
firmware_to_string(arg_firmware_type));
/* TDX can't use pflash + NVRAM split, so the firmware-descriptor
* machinery doesn't apply. Require an explicit raw .fd path and
* use it verbatim with -bios later. */
if (!arg_firmware)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--coco=tdx requires --firmware=PATH "
"pointing at a raw TDX-built OVMF (TDVF) .fd binary.");
log_debug("Using raw TDX firmware at %s (no NVRAM, no Secure Boot).", arg_firmware);
/* Secure Boot state is baked into the supplied TDVF image and can't be enrolled at
* runtime (no writable NVRAM), so --secure-boot=yes would silently have no effect. */
if (set_contains(arg_firmware_features_include, "secure-boot"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--coco=tdx cannot be combined with --secure-boot=yes.");
if (arg_tpm > 0)
log_warning("TPM can't be trusted by the confidential computing guest");
}
@@ -4227,9 +4302,9 @@ static int run(int argc, char *argv[]) {
_cleanup_(ovmf_config_freep) OvmfConfig *ovmf_config = NULL;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
r = find_ovmf_config(arg_firmware_features_include, arg_firmware_features_exclude, &ovmf_config, &json);
r = discover_ovmf_config(&ovmf_config, &json);
if (r < 0)
return log_error_errno(r, "Failed to find OVMF config: %m");
return r;
r = sd_json_variant_dump(json, SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_COLOR_AUTO, stdout, /* prefix= */ NULL);
if (r < 0)