]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
vmspawn: verify selected fw matches requirements
authorPaul Meyer <katexochen0@gmail.com>
Thu, 2 Jul 2026 16:04:36 +0000 (18:04 +0200)
committerPaul Meyer <katexochen0@gmail.com>
Mon, 6 Jul 2026 09:04:11 +0000 (11:04 +0200)
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 <katexochen0@gmail.com>
src/vmspawn/test-vmspawn-util.c
src/vmspawn/vmspawn-util.c
src/vmspawn/vmspawn-util.h
src/vmspawn/vmspawn.c

index d5108a73d7d7f058811ddfea1b6aed9a5097ceb1..21d22e9161a2462d4e7880514ffd5a8713887203 100644 (file)
@@ -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);
index 5b0ced8e01e0414887fff30eeaa20c9de6c1d23a..03fef0f1b8b9cf3f596785e378a31bc7c959932c 100644 (file)
@@ -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);
index cc7cd147aa67773bbcdeefacb2e1be17c4fd6f14..8d1911663ca1c534bd709e378f4fb33d5784669e 100644 (file)
@@ -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);
index e5fb02a2348253ee6aaf4d7d28fea62c33d72ade..2121fd29d0b04d9ca6250687efd1e3970c422d65 100644 (file)
@@ -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;
         }