From: Paul Meyer Date: Thu, 2 Jul 2026 16:04:36 +0000 (+0200) Subject: vmspawn: verify selected fw matches requirements X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=2f95c17a2f3f3e564b3bfca31e7f2c035417cc00;p=thirdparty%2Fsystemd.git vmspawn: verify selected fw matches requirements Carry the firmware descriptor's feature list in OvmfConfig, and verify that firmware selected via an explicit --firmware= descriptor declares the amd-sev-snp/intel-tdx feature, is stateless, and is in raw format, the same constraints automatic discovery already imposes. This replaces the cached supports_sb boolean with a generic feature lookup. Signed-off-by: Paul Meyer --- diff --git a/src/vmspawn/test-vmspawn-util.c b/src/vmspawn/test-vmspawn-util.c index d5108a73d7d..21d22e9161a 100644 --- a/src/vmspawn/test-vmspawn-util.c +++ b/src/vmspawn/test-vmspawn-util.c @@ -133,7 +133,7 @@ TEST(find_ovmf_config) { ASSERT_STREQ(config->vars, "/test/vars.fd"); ASSERT_STREQ(config->format, "raw"); ASSERT_FALSE(ovmf_config_is_stateless(config)); - ASSERT_FALSE(config->supports_sb); + 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. */ @@ -188,14 +188,20 @@ TEST(find_ovmf_config) { /* 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(config->supports_sb); + 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", /* ret= */ NULL); + 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", /* ret= */ NULL); + 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); diff --git a/src/vmspawn/vmspawn-util.c b/src/vmspawn/vmspawn-util.c index 5b0ced8e01e..03fef0f1b8b 100644 --- a/src/vmspawn/vmspawn-util.c +++ b/src/vmspawn/vmspawn-util.c @@ -64,9 +64,17 @@ OvmfConfig* ovmf_config_free(OvmfConfig *config) { 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" @@ -185,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; @@ -447,7 +449,7 @@ static int ovmf_config_make(FirmwareData *fwd, OvmfConfig **ret) { .vars_format = TAKE_PTR(fwd->vars_format), .device = TAKE_PTR(fwd->device), .mode = TAKE_PTR(fwd->mode), - .supports_sb = firmware_data_supports_sb(fwd), + .features = TAKE_PTR(fwd->features), }; *ret = TAKE_PTR(config); diff --git a/src/vmspawn/vmspawn-util.h b/src/vmspawn/vmspawn-util.h index cc7cd147aa6..8d1911663ca 100644 --- a/src/vmspawn/vmspawn-util.h +++ b/src/vmspawn/vmspawn-util.h @@ -101,7 +101,7 @@ typedef struct OvmfConfig { char *vars_format; char *device; char *mode; - bool supports_sb; + char **features; } OvmfConfig; static inline const char* ovmf_config_format(const OvmfConfig *c) { @@ -111,7 +111,9 @@ static inline const char* ovmf_config_format(const OvmfConfig *c) { 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); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index e5fb02a2348..2121fd29d0b 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -2669,11 +2669,42 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { 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; @@ -2742,7 +2773,8 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { } if (ovmf_config && ARCHITECTURE_SUPPORTS_SMM && arg_confidential_computing == COCO_NO) { - r = qemu_config_key(config_file, "smm", on_off(ovmf_config->supports_sb)); + 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; }