vmspawn: add Intel TDX confidential VM support (#42835)
This commit is contained in:
+23
-1
@@ -346,7 +346,8 @@
|
||||
its current form) in a future version of systemd.</para>
|
||||
|
||||
<para>Configures whether to run the guest as a confidential VM. Takes one of
|
||||
<literal>no</literal> or <literal>sev-snp</literal>. Defaults to <literal>no</literal>.</para>
|
||||
<literal>no</literal>, <literal>sev-snp</literal> or <literal>tdx</literal>. Defaults to
|
||||
<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
|
||||
@@ -367,6 +368,27 @@
|
||||
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.
|
||||
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
|
||||
command line into RTMR 2. Credentials passed via <option>--set-credential=</option> or
|
||||
<option>--load-credential=</option> are delivered through SMBIOS Type 11 OEM strings, which the
|
||||
firmware (TDVF) measures into RTMR 0. This channel is measured but not confidential with respect
|
||||
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
|
||||
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>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
|
||||
@@ -622,7 +622,14 @@ static int import_credentials_smbios(ImportCredentialsContext *c) {
|
||||
if (detect_container() > 0) /* don't access /sys/ in a container */
|
||||
return 0;
|
||||
|
||||
if (detect_confidential_virtualization() > 0) /* don't trust firmware if confidential VMs */
|
||||
/* SMBIOS OEM strings are host-controlled, so by default we don't trust them in a confidential VM.
|
||||
* Exception: under Intel TDX the SMBIOS table (including Type 11) is measured into RTMR0 by the
|
||||
* firmware (TDVF's SmbiosMeasurementDxe) on direct boot, and additionally by systemd-stub on UKI
|
||||
* boot, so a remote verifier can detect host tampering with these credentials. As with the kernel
|
||||
* command line (also host-influenced but measured), we can't and needn't locally verify the
|
||||
* measurement actually happened; the verifier anchors the firmware's behaviour via MRTD. */
|
||||
ConfidentialVirtualization cv = detect_confidential_virtualization();
|
||||
if (cv > 0 && cv != CONFIDENTIAL_VIRTUALIZATION_TDX)
|
||||
return 0;
|
||||
|
||||
for (unsigned i = 0;; i++) {
|
||||
|
||||
@@ -142,6 +142,14 @@ static int tsm_report_fill(
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to read 'provider' attribute: %m");
|
||||
|
||||
/* A zero-length outblob is never a valid attestation report, e.g. under Intel TDX the kernel
|
||||
* yields an empty quote when the GetQuote round-trip to the host's Quote Generation Service
|
||||
* produced nothing. */
|
||||
if (!iovec_is_set(&report->outblob))
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENODATA),
|
||||
"TSM provider '%s' returned an empty 'outblob'; no attestation report available.",
|
||||
report->provider);
|
||||
|
||||
r = read_full_file_at(entry_fd, "auxblob",
|
||||
(char**) &report->auxblob.iov_base, &report->auxblob.iov_len);
|
||||
if (r < 0 && r != -ENOENT) /* auxblob is optional */
|
||||
|
||||
@@ -40,6 +40,7 @@ DEFINE_STRING_TABLE_LOOKUP(firmware, Firmware);
|
||||
static const char *const confidential_computing_table[_COCO_MAX] = {
|
||||
[COCO_NO] = "no",
|
||||
[COCO_AMD_SEV_SNP] = "sev-snp",
|
||||
[COCO_INTEL_TDX] = "tdx",
|
||||
};
|
||||
|
||||
DEFINE_STRING_TABLE_LOOKUP(confidential_computing, ConfidentialComputing);
|
||||
|
||||
@@ -45,6 +45,7 @@ typedef enum Firmware {
|
||||
typedef enum ConfidentialComputing {
|
||||
COCO_NO,
|
||||
COCO_AMD_SEV_SNP,
|
||||
COCO_INTEL_TDX,
|
||||
_COCO_MAX,
|
||||
_COCO_INVALID = -EINVAL,
|
||||
} ConfidentialComputing;
|
||||
|
||||
+87
-11
@@ -105,6 +105,13 @@
|
||||
#define DISK_SERIAL_MAX_LEN_NVME 20
|
||||
#define DISK_SERIAL_MAX_LEN_VIRTIO_BLK 20
|
||||
|
||||
/* Well-known endpoints for the host's TDX Quote Generation Service (qgsd), auto-discovered so the
|
||||
* guest can obtain TD Quotes. The Intel reference qgsd listens on a unix socket; the common
|
||||
* deployment listens on vsock port 4050 (cid 2 = host). */
|
||||
#define TDX_QGS_UNIX_SOCKET_PATH "/run/tdx-qgs/qgs.socket"
|
||||
#define TDX_QGS_VSOCK_CID "2"
|
||||
#define TDX_QGS_VSOCK_PORT "4050"
|
||||
|
||||
/* First and one-past-last pcie.0 device-numbers used for multifunction-packed
|
||||
* pcie-root-ports. Sits above the auto-assigned virtio devices (0x01-0x03) and
|
||||
* below 0x1f, which q35 reserves for ICH9 LPC at 0x1f.0 (single-function). */
|
||||
@@ -634,7 +641,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
break;
|
||||
}
|
||||
|
||||
OPTION_LONG("coco", "no|sev-snp", "Run the guest as a confidential VM"): {
|
||||
OPTION_LONG("coco", "no|sev-snp|tdx", "Run the guest as a confidential VM"): {
|
||||
ConfidentialComputing cc = confidential_computing_from_string(opts.arg);
|
||||
if (cc < 0)
|
||||
return log_error_errno(cc, "Unknown --coco= value: %s", opts.arg);
|
||||
@@ -2607,11 +2614,11 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
use_kvm = r;
|
||||
}
|
||||
|
||||
if (arg_confidential_computing == COCO_AMD_SEV_SNP && !use_kvm)
|
||||
if (arg_confidential_computing != COCO_NO && !use_kvm)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
|
||||
"--coco=sev-snp requires KVM, but KVM is not available.");
|
||||
"--coco= requires KVM, but KVM is not available.");
|
||||
|
||||
if (arg_firmware_type == FIRMWARE_UEFI && arg_confidential_computing != COCO_AMD_SEV_SNP) {
|
||||
if (arg_firmware_type == FIRMWARE_UEFI && arg_confidential_computing == COCO_NO) {
|
||||
if (arg_firmware)
|
||||
r = load_ovmf_config(arg_firmware, &ovmf_config);
|
||||
else
|
||||
@@ -2685,7 +2692,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (arg_confidential_computing == COCO_AMD_SEV_SNP) {
|
||||
if (arg_confidential_computing != COCO_NO) {
|
||||
r = qemu_config_key(config_file, "kernel-irqchip", "split");
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -2719,6 +2726,10 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
r = qemu_config_key(config_file, "confidential-guest-support", "snp0");
|
||||
if (r < 0)
|
||||
return r;
|
||||
} else if (arg_confidential_computing == COCO_INTEL_TDX) {
|
||||
r = qemu_config_key(config_file, "confidential-guest-support", "tdx0");
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = qemu_config_section(config_file, "smp-opts", /* id= */ NULL,
|
||||
@@ -2927,6 +2938,41 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
"kernel-hashes", "on");
|
||||
if (r < 0)
|
||||
return r;
|
||||
} else if (arg_confidential_computing == COCO_INTEL_TDX) {
|
||||
r = qemu_config_section(config_file, "object", "tdx0",
|
||||
"qom-type", "tdx-guest");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* The guest needs a connection to the TDX Quote Generation Service (QGS) running on
|
||||
* the host to obtain TD Quotes (e.g. via the systemd-report-sign-tsm). There are two
|
||||
* well-known interfaces for QGS: a well-known unix socket, or vsock port 4050. QEMU
|
||||
* only connects on a GetQuote request, and pointing at an absent QGS is harmless,
|
||||
* the request just fails and the guest gets no quote. */
|
||||
r = is_socket(TDX_QGS_UNIX_SOCKET_PATH);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to check for QGS socket '%s', falling back to vsock: %m", TDX_QGS_UNIX_SOCKET_PATH);
|
||||
if (r > 0) {
|
||||
r = qemu_config_key(config_file, "quote-generation-socket.type", "unix");
|
||||
if (r < 0)
|
||||
return r;
|
||||
r = qemu_config_key(config_file, "quote-generation-socket.path", TDX_QGS_UNIX_SOCKET_PATH);
|
||||
if (r < 0)
|
||||
return r;
|
||||
log_debug("Using TDX Quote Generation Service at unix socket %s.", TDX_QGS_UNIX_SOCKET_PATH);
|
||||
} else {
|
||||
r = qemu_config_key(config_file, "quote-generation-socket.type", "vsock");
|
||||
if (r < 0)
|
||||
return r;
|
||||
r = qemu_config_key(config_file, "quote-generation-socket.cid", TDX_QGS_VSOCK_CID);
|
||||
if (r < 0)
|
||||
return r;
|
||||
r = qemu_config_key(config_file, "quote-generation-socket.port", TDX_QGS_VSOCK_PORT);
|
||||
if (r < 0)
|
||||
return r;
|
||||
log_debug("No QGS unix socket found, pointing TDX quote generation at vsock %s:%s.",
|
||||
TDX_QGS_VSOCK_CID, TDX_QGS_VSOCK_PORT);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned child_cid = arg_vsock_cid;
|
||||
@@ -2948,11 +2994,13 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
|
||||
/* -cpu stays on cmdline since not all flags are supported in config. SNP needs a stable,
|
||||
* named CPU model so the launch measurement is reproducible across hosts; EPYC-v4 is the
|
||||
* baseline that covers all SNP-capable processors (Milan and later). */
|
||||
* baseline that covers all SNP-capable processors (Milan and later). TDX requires host
|
||||
* CPU model. */
|
||||
const char *cpu_model =
|
||||
#ifdef __x86_64__
|
||||
arg_confidential_computing == COCO_AMD_SEV_SNP ? "EPYC-v4"
|
||||
: "max,hv_relaxed,hv-vapic,hv-time";
|
||||
arg_confidential_computing == COCO_AMD_SEV_SNP ? "EPYC-v4" :
|
||||
arg_confidential_computing == COCO_INTEL_TDX ? "host" :
|
||||
"max,hv_relaxed,hv-vapic,hv-time";
|
||||
#else
|
||||
"max";
|
||||
#endif
|
||||
@@ -3531,7 +3579,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
* confidential VMs. Instead, package credentials into a cpio archive appended to the initrd
|
||||
* (mirroring what systemd-stub does for ESP credentials) so they enter the launch measurement
|
||||
* via QEMU's "kernel-hashes=on". The new initrd path requires a guest PID1 that knows about
|
||||
* /.extra/system_credentials/, so we keep this scoped to SNP for now. Non-CoCo guests
|
||||
* /.extra/system_credentials/, so we keep this scoped to SNP for now. Non-SNP guests
|
||||
* continue to use the SMBIOS path below, which works with older systemd versions too.
|
||||
* Must run after all credential-mutating calls above so the cpio captures the complete set. */
|
||||
bool use_initrd_cpio = arg_confidential_computing == COCO_AMD_SEV_SNP &&
|
||||
@@ -4128,7 +4176,7 @@ static int verify_arguments(void) {
|
||||
"--coco=sev-snp requires KVM, remove --kvm=no.");
|
||||
if (arg_firmware_type != FIRMWARE_UEFI)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"--coco can't be used with %s firmware",
|
||||
"--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
|
||||
@@ -4140,7 +4188,7 @@ static int verify_arguments(void) {
|
||||
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),
|
||||
"--secure-boot=yes cannot be combined with --coco.");
|
||||
"--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.
|
||||
@@ -4153,6 +4201,34 @@ static int verify_arguments(void) {
|
||||
"so kernel, initrd and cmdline are covered by the launch measurement.");
|
||||
}
|
||||
|
||||
if (arg_confidential_computing == COCO_INTEL_TDX) {
|
||||
if (native_architecture() != ARCHITECTURE_X86_64)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
|
||||
"--coco=tdx is only supported on x86_64.");
|
||||
if (arg_kvm == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"--coco=tdx requires KVM, remove --kvm=no.");
|
||||
if (arg_firmware_type != FIRMWARE_UEFI)
|
||||
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");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user