From d76ce200a61591069f47710e8b50383776968946 Mon Sep 17 00:00:00 2001 From: Paul Meyer Date: Wed, 17 Jun 2026 16:13:35 +0200 Subject: [PATCH 1/4] confidential-virt: treat an unreadable SEV MSR as confidential msr() returned 0 on failure, indistinguishable from a real MSR value of 0. With /dev/cpu/0/msr unavailable (e.g. the msr module not loaded in the initrd), detect_sev() read 0 and reported a genuine SEV/-ES/-SNP guest as CONFIDENTIAL_VIRTUALIZATION_NONE. That inverts the firmware-credential trust gate: import_credentials_*() skip fw_cfg/SMBIOS credentials only when detect_confidential_virtualization() is > 0 ("don't trust firmware in confidential VMs"). A false NONE makes a confidential guest trust and import credentials injected by the untrusted hypervisor. msr() now returns a negative errno, and detect_sev() assumes plain SEV when the MSR is unreadable but CPUID already advertised SEV under a hypervisor, so the gate still trips. The conservative branch only fires when CPUID already advertised SEV, i.e. for a guest the hypervisor marked SEV-capable. QEMU gates that CPUID leaf on the SEV launch object and does not expose it to ordinary guests even under -cpu host, so it does not misfire for non-confidential guests. Were a hypervisor to expose the bit anyway the outcome is fail-safe (we only decline to trust firmware-supplied data); nothing in-tree branches on the specific SEV tier. Signed-off-by: Paul Meyer --- src/basic/confidential-virt.c | 58 +++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/src/basic/confidential-virt.c b/src/basic/confidential-virt.c index 29d598a598..3f8cb2a3cd 100644 --- a/src/basic/confidential-virt.c +++ b/src/basic/confidential-virt.c @@ -43,37 +43,32 @@ static uint32_t cpuid_leaf(uint32_t eax, char ret_sig[static 13], bool swapped) #define MSR_DEVICE "/dev/cpu/0/msr" -static uint64_t msr(uint64_t index) { - uint64_t ret; - ssize_t rv; +static int msr(uint64_t index, uint64_t *ret) { _cleanup_close_ int fd = -EBADF; + uint64_t v; + ssize_t n; + + assert(ret); fd = open(MSR_DEVICE, O_RDONLY|O_CLOEXEC); - if (fd < 0) { - log_debug_errno(errno, - "Cannot open MSR device %s (index %" PRIu64 "), ignoring: %m", - MSR_DEVICE, - index); - return 0; - } + if (fd < 0) + return log_debug_errno(errno, + "Cannot open MSR device %s (index %" PRIu64 "): %m", + MSR_DEVICE, index); - rv = pread(fd, &ret, sizeof(ret), index); - if (rv < 0) { - log_debug_errno(errno, - "Cannot read MSR device %s (index %" PRIu64 "), ignoring: %m", - MSR_DEVICE, - index); - return 0; - } else if (rv != sizeof(ret)) { - log_debug("Short read %zd bytes from MSR device %s (index %" PRIu64 "), ignoring", - rv, - MSR_DEVICE, - index); - return 0; - } + n = pread(fd, &v, sizeof(v), index); + if (n < 0) + return log_debug_errno(errno, + "Cannot read MSR device %s (index %" PRIu64 "): %m", + MSR_DEVICE, index); + if (n != sizeof(v)) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Short read %zd bytes from MSR device %s (index %" PRIu64 ")", + n, MSR_DEVICE, index); - log_debug("MSR %" PRIu64 " result %" PRIu64 "", index, ret); - return ret; + log_debug("MSR %" PRIu64 " result %" PRIu64, index, v); + *ret = v; + return 0; } static bool detect_hyperv_cvm(uint32_t isoltype) { @@ -110,6 +105,7 @@ static bool detect_hyperv_cvm(uint32_t isoltype) { static ConfidentialVirtualization detect_sev(void) { uint32_t eax, ebx, ecx, edx; uint64_t msrval; + int r; eax = CPUID_GET_HIGHEST_FUNCTION; ebx = ecx = edx = 0; @@ -140,7 +136,15 @@ static ConfidentialVirtualization detect_sev(void) { return CONFIDENTIAL_VIRTUALIZATION_NONE; } - msrval = msr(MSR_AMD64_SEV); + r = msr(MSR_AMD64_SEV, &msrval); + if (r < 0) { + /* The CPU advertises SEV support and we're running under a hypervisor, but we couldn't read + * the SEV MSR to determine the exact mode (e.g. /dev/cpu/0/msr is unavailable because the + * msr module isn't loaded). Assume plain SEV. Misreporting a genuine confidential guest as + * non-confidential would wrongly make us trust hypervisor-provided data such as firmware credentials. */ + log_debug_errno(r, "Failed to read SEV MSR, assuming SEV: %m"); + return CONFIDENTIAL_VIRTUALIZATION_SEV; + } /* Test reverse order, since the SEV-SNP bit implies * the SEV-ES bit, which implies the SEV bit */ From fb1bdb9eff2d1026646240951957c310dc2f8b6c Mon Sep 17 00:00:00 2001 From: Paul Meyer Date: Thu, 18 Jun 2026 07:44:09 +0200 Subject: [PATCH 2/4] confidential-virt: read the TDX CPUID leaf unconditionally detect_tdx() guarded the read of the TDX enumeration leaf (0x21, a standard leaf) with CPUID_GET_HIGHEST_FUNCTION (0x80000000), which returns the highest *extended* function. eax is therefore always >= 0x80000000, so the "eax < 0x21" guard never held and the leaf was read unconditionally anyway. Drop the guard rather than re-gate it on the basic max function (leaf 0), and read 0x21 directly, relying on the IntelTDX signature compare. This matches the kernel, which reads the leaf unconditionally on purpose: an out-of-range CPUID leaf returns the highest basic leaf's data (no fault, per the Intel SDM), and 0x21 is a synthetic TDX leaf whose presence need not be reflected in the reported max basic function, so gating the read on it risks missing a genuine TDX guest. With no guard the Hyper-V isolation fallback (Azure TDX guests have 0x21 blocked) also stays reachable. Ref: Linux 59bd54a84d15 ("x86/tdx: Detect running as a TDX guest in early boot"), arch/x86/coco/tdx/tdx.c:1119 (tdx_early_init()). Signed-off-by: Paul Meyer --- src/basic/confidential-virt.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/basic/confidential-virt.c b/src/basic/confidential-virt.c index 3f8cb2a3cd..412fceb2ab 100644 --- a/src/basic/confidential-virt.c +++ b/src/basic/confidential-virt.c @@ -159,17 +159,10 @@ static ConfidentialVirtualization detect_sev(void) { } static ConfidentialVirtualization detect_tdx(void) { - uint32_t eax, ebx, ecx, edx; char sig[13] = {}; - eax = CPUID_GET_HIGHEST_FUNCTION; - ebx = ecx = edx = 0; - - cpuid(&eax, &ebx, &ecx, &edx); - - if (eax < CPUID_INTEL_TDX_ENUMERATION) - return CONFIDENTIAL_VIRTUALIZATION_NONE; - + /* Querying an unsupported CPUID leaf is harmless (it returns the highest basic leaf's data rather + * than faulting), so reading this leaf and matching the IntelTDX signature is sufficient. */ cpuid_leaf(CPUID_INTEL_TDX_ENUMERATION, sig, true); if (memcmp(sig, CPUID_SIG_INTEL_TDX, sizeof(sig)) == 0) From 7c433f39454d19e7913f14669c5883d8b41e716d Mon Sep 17 00:00:00 2001 From: Paul Meyer Date: Tue, 23 Jun 2026 12:34:12 +0200 Subject: [PATCH 3/4] boot: read the TDX CPUID leaf unconditionally vmm.c carries the confidential-VM detection used by sd-boot/sd-stub. Its detect_tdx() had the same dead guard as the userspace copy: it gated the 0x21 read on CPUID_GET_HIGHEST_FUNCTION (0x80000000, the extended max function), which is always >= 0x80000000, so the guard never held. Mirror the userspace fix: read leaf 0x21 directly and rely on the IntelTDX signature, matching the kernel. An out-of-range CPUID leaf returns the highest basic leaf's data (no fault), and 0x21 is a synthetic TDX leaf whose presence need not be reflected in the max basic function, so it must not be gated on it. Ref: Linux 59bd54a84d15 ("x86/tdx: Detect running as a TDX guest in early boot"), arch/x86/coco/tdx/tdx.c:1119 (tdx_early_init()). Signed-off-by: Paul Meyer --- src/boot/vmm.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/boot/vmm.c b/src/boot/vmm.c index 902f61b41c..e571a3990d 100644 --- a/src/boot/vmm.c +++ b/src/boot/vmm.c @@ -225,14 +225,10 @@ static bool detect_sev(void) { } static bool detect_tdx(void) { - uint32_t eax, ebx, ecx, edx; char sig[13] = {}; - __cpuid(CPUID_GET_HIGHEST_FUNCTION, eax, ebx, ecx, edx); - - if (eax < CPUID_INTEL_TDX_ENUMERATION) - return false; - + /* Querying an unsupported CPUID leaf is harmless (it returns the highest basic leaf's data rather + * than faulting), so reading this leaf and matching the IntelTDX signature is sufficient. */ cpuid_leaf(CPUID_INTEL_TDX_ENUMERATION, sig, true); if (memcmp(sig, CPUID_SIG_INTEL_TDX, sizeof(sig)) == 0) From 9d2673924b68d4cb0fa812e065474cc47608d422 Mon Sep 17 00:00:00 2001 From: Paul Meyer Date: Tue, 23 Jun 2026 12:43:00 +0200 Subject: [PATCH 4/4] confidential-virt: fix comment regarding vmm.c location Signed-off-by: Paul Meyer --- src/fundamental/confidential-virt.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fundamental/confidential-virt.h b/src/fundamental/confidential-virt.h index d78ad3650c..fce172e6f3 100644 --- a/src/fundamental/confidential-virt.h +++ b/src/fundamental/confidential-virt.h @@ -4,7 +4,7 @@ #include /* Keep CVM detection logic in this file at feature parity with - * that in src/efi/boot/vmm.c */ + * that in src/boot/vmm.c */ #define CPUID_PROCESSOR_INFO_AND_FEATURE_BITS UINT32_C(0x1)