vmspawn: discover firmware descriptors, opt-in secure boot support for coco (#42876)
This commit is contained in:
+41
-28
@@ -269,7 +269,8 @@
|
||||
<listitem><para>Takes an absolute path, or a relative path beginning with
|
||||
<filename>./</filename>. Specifies the path to an EFI NVRAM template file to copy and use as the
|
||||
initial EFI variable NVRAM state. If not specified, the default NVRAM template from the firmware
|
||||
definition is copied and used.</para>
|
||||
definition is copied and used. Cannot be used together with <option>--coco=</option>, as
|
||||
confidential computing firmware is stateless.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
|
||||
</varlistentry>
|
||||
@@ -283,7 +284,8 @@
|
||||
automatically derived from the VM image path or directory path, with the
|
||||
<literal>.efinvramstate</literal> suffix appended. If set to the special string
|
||||
<literal>off</literal> the EFI variable NVRAM state is only maintained transiently and flushed out
|
||||
when the VM shuts down. Defaults to <literal>auto</literal>.</para>
|
||||
when the VM shuts down. Defaults to <literal>auto</literal>. An explicit path cannot be used
|
||||
together with <option>--coco=</option>, as confidential computing firmware is stateless.</para>
|
||||
|
||||
<para>If <option>--ephemeral</option> is specified, <literal>auto</literal> behaves like
|
||||
<literal>off</literal>.</para>
|
||||
@@ -300,6 +302,13 @@
|
||||
<option>--firmware-features=~secure-boot</option>. Setting this to <literal>auto</literal>
|
||||
removes <literal>secure-boot</literal> from both the included and excluded feature lists.</para>
|
||||
|
||||
<para>With <option>--coco=</option>, setting this to <literal>yes</literal> additionally
|
||||
requires the <literal>enrolled-keys</literal> firmware feature, overriding its default
|
||||
exclusion: confidential computing firmware is stateless, so keys cannot be enrolled at runtime
|
||||
and Secure Boot is only operative with keys baked into the image at build time, which in turn
|
||||
makes the firmware refuse to boot unsigned images from the very first boot. The default
|
||||
exclusion of <literal>enrolled-keys</literal> hence keeps unsigned images bootable.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
@@ -317,8 +326,8 @@
|
||||
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>
|
||||
<option>--firmware-features=</option> and <option>--coco=</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>
|
||||
@@ -350,31 +359,35 @@
|
||||
<literal>no</literal>.</para>
|
||||
|
||||
<para><literal>sev-snp</literal> enables AMD SEV-SNP. This requires KVM on an x86_64 host with
|
||||
SNP-capable hardware and firmware. <option>--firmware=</option> must point to a raw SNP-built
|
||||
OVMF <filename>.fd</filename> image; the standard pflash + NVRAM split is not supported under
|
||||
SNP, so the firmware is loaded via QEMU's <option>-bios</option> and Secure Boot is
|
||||
unavailable. Direct kernel boot via <option>--linux=</option> is required so that the kernel,
|
||||
initrd and command line are hashed into the launch measurement
|
||||
(<literal>kernel-hashes=on</literal>); booting the kernel off the disk image via the firmware
|
||||
would leave it outside the measurement. Credentials passed via <option>--set-credential=</option>
|
||||
or <option>--load-credential=</option> are bundled into a cpio archive appended to the initrd
|
||||
(mirroring what <command>systemd-stub</command> does for ESP credentials), so they enter the
|
||||
launch measurement via <literal>kernel-hashes=on</literal>; the SMBIOS and fw_cfg channels
|
||||
normally used to deliver credentials are not used because they are unmeasured and would be
|
||||
discarded by PID1 in confidential guests. This channel is measured but not confidential with
|
||||
respect to the host or VMM: the initrd (and thus the credentials it carries) is supplied to QEMU
|
||||
as plaintext and only its hash enters the launch measurement, which guarantees integrity but does
|
||||
not keep the credentials secret from the host. This requires the guest to run a sufficiently
|
||||
recent version of systemd (supporting <filename>/.extra/system_credentials/</filename>). A vTPM,
|
||||
if attached via <option>--tpm=</option>, must be treated as untrusted by the guest.</para>
|
||||
SNP-capable hardware and firmware. A suitable SNP-built OVMF firmware is picked automatically
|
||||
from the installed QEMU firmware descriptors, by requiring the <literal>amd-sev-snp</literal>
|
||||
firmware feature; use <option>--firmware=</option> with a path to a firmware descriptor file to
|
||||
select a specific one. Secure Boot is effectively off by default (see
|
||||
<option>--secure-boot=</option>). Direct kernel boot via
|
||||
<option>--linux=</option> is required so that the kernel, initrd and command line are hashed
|
||||
into the launch measurement (<literal>kernel-hashes=on</literal>); booting the kernel off the
|
||||
disk image via the firmware would leave it outside the measurement. Credentials passed via
|
||||
<option>--set-credential=</option> or <option>--load-credential=</option> are bundled into a
|
||||
cpio archive appended to the initrd (mirroring what <command>systemd-stub</command> does for
|
||||
ESP credentials), so they enter the launch measurement via <literal>kernel-hashes=on</literal>;
|
||||
the SMBIOS and fw_cfg channels normally used to deliver credentials are not used because they are
|
||||
unmeasured and would be discarded by PID1 in confidential guests. This channel is measured but
|
||||
not confidential with respect to the host or VMM: the initrd (and thus the credentials it
|
||||
carries) is supplied to QEMU as plaintext and only its hash enters the launch measurement,
|
||||
which guarantees integrity but does not keep the credentials secret from the host. This requires
|
||||
the guest to run a sufficiently recent version of systemd (supporting
|
||||
<filename>/.extra/system_credentials/</filename>). A vTPM, if attached via
|
||||
<option>--tpm=</option>, must be treated as untrusted by the guest.</para>
|
||||
|
||||
<para><literal>tdx</literal> enables Intel TDX. This requires KVM on an x86_64 host with
|
||||
TDX-capable hardware and a TDX-enabled host kernel. As with <literal>sev-snp</literal>,
|
||||
<option>--firmware=</option> must point to a raw TDX-built OVMF (TDVF) <filename>.fd</filename>
|
||||
image, which is loaded via QEMU's <option>-bios</option> (pflash + NVRAM split is not
|
||||
supported), and the CPU model is fixed to <literal>host</literal>. Firmware is measured into
|
||||
MRTD when the TD is built. Secure Boot cannot be enrolled at runtime (there is no writable
|
||||
NVRAM); its state is fixed by the supplied TDVF image and is part of the measured firmware.
|
||||
TDX-capable hardware and a TDX-enabled host kernel. As with <literal>sev-snp</literal>, a
|
||||
TDX-built OVMF (TDVF) firmware is picked automatically from the installed QEMU firmware
|
||||
descriptors, by requiring the <literal>intel-tdx</literal> firmware feature; use
|
||||
<option>--firmware=</option> with a path to a firmware descriptor file to select a specific
|
||||
one. The CPU model is fixed to <literal>host</literal>. Firmware is measured into MRTD when the
|
||||
TD is built. Secure Boot cannot be enrolled at runtime (there is no writable NVRAM); its state
|
||||
is fixed by the selected TDVF image and is part of the measured firmware (see
|
||||
<option>--secure-boot=</option>).
|
||||
When booting a UKI, the whole UKI PE is measured into RTMR 1, and the loaded sections are
|
||||
measured individually by <command>systemd-stub</command> into RTMR 2. For direct linux boot,
|
||||
firmware measures the kernel PE into RTMR 1, and the Linux EFI stub measures initrd and
|
||||
@@ -384,7 +397,7 @@
|
||||
to the host or VMM, since the host assembles the SMBIOS table. A vTPM, if attached via
|
||||
<option>--tpm=</option>, must be treated as untrusted by the guest. To obtain TD Quotes for
|
||||
remote attestation, the guest is wired to the host's local TDX Quote Generation Service
|
||||
automatically: the unix socket up <filename>/run/tdx-qgs/qgs.socket</filename> is used if it
|
||||
automatically: the unix socket <filename>/run/tdx-qgs/qgs.socket</filename> is used if it
|
||||
exists, otherwise vsock port 4050 on the host (cid 2). If the QGS is listening on neither
|
||||
channel, the guest's quote requests will fail.
|
||||
<xi:include href="version-info.xml" xpointer="v262"/></para>
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user