79df79b44e
Resolves: RHEL-50103,RHEL-50651
396 lines
13 KiB
Diff
396 lines
13 KiB
Diff
From 945dd3348084b1cc71721e9ed35041ef37689e49 Mon Sep 17 00:00:00 2001
|
||
From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= <berrange@redhat.com>
|
||
Date: Fri, 9 Jun 2023 15:37:18 +0100
|
||
Subject: [PATCH] add APIs for detecting confidential virtualization
|
||
MIME-Version: 1.0
|
||
Content-Type: text/plain; charset=UTF-8
|
||
Content-Transfer-Encoding: 8bit
|
||
|
||
This code uses various CPUID checks to be able to identify
|
||
|
||
* AMD SEV
|
||
* AMD SEV-ES
|
||
* AMD SEV-SNP
|
||
* Intel TDX
|
||
|
||
On HyperV/Azure, it has special checks for detecting SEV-SNP
|
||
since the normal CPUID is blocked.
|
||
|
||
Related: https://github.com/systemd/systemd/issues/27604
|
||
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
|
||
(cherry picked from commit a577a61625b979f0198e1ed9a527ba48fd78be13)
|
||
|
||
Related: RHEL-50651
|
||
---
|
||
src/basic/confidential-virt.c | 293 ++++++++++++++++++++++++++++++++++
|
||
src/basic/confidential-virt.h | 25 +++
|
||
src/basic/meson.build | 2 +
|
||
src/test/test-tables.c | 2 +
|
||
4 files changed, 322 insertions(+)
|
||
create mode 100644 src/basic/confidential-virt.c
|
||
create mode 100644 src/basic/confidential-virt.h
|
||
|
||
diff --git a/src/basic/confidential-virt.c b/src/basic/confidential-virt.c
|
||
new file mode 100644
|
||
index 0000000000..3d4e9eac33
|
||
--- /dev/null
|
||
+++ b/src/basic/confidential-virt.c
|
||
@@ -0,0 +1,293 @@
|
||
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||
+
|
||
+#if defined(__i386__) || defined(__x86_64__)
|
||
+#include <cpuid.h>
|
||
+#endif
|
||
+#include <errno.h>
|
||
+#include <fcntl.h>
|
||
+#include <stdint.h>
|
||
+#include <stdlib.h>
|
||
+#include <unistd.h>
|
||
+
|
||
+#include "confidential-virt.h"
|
||
+#include "fd-util.h"
|
||
+#include "missing_threads.h"
|
||
+#include "string-table.h"
|
||
+#include "utf8.h"
|
||
+
|
||
+#define CPUID_PROCESSOR_INFO_AND_FEATURE_BITS UINT32_C(0x1)
|
||
+
|
||
+/*
|
||
+ * AMD64 Architecture Programmer’s Manual Volume 3:
|
||
+ * General-Purpose and System Instructions.
|
||
+ * Chapter: E4.1 - Maximum Extended Function Number and Vendor String
|
||
+ * https://www.amd.com/system/files/TechDocs/24594.pdf
|
||
+ */
|
||
+#define CPUID_GET_HIGHEST_FUNCTION UINT32_C(0x80000000)
|
||
+
|
||
+/*
|
||
+ * AMD64 Architecture Programmer’s Manual Volume 3:
|
||
+ * General-Purpose and System Instructions.
|
||
+ * Chapter: E4.17 - Encrypted Memory Capabilities
|
||
+ * https://www.amd.com/system/files/TechDocs/24594.pdf
|
||
+ */
|
||
+#define CPUID_AMD_GET_ENCRYPTED_MEMORY_CAPABILITIES UINT32_C(0x8000001f)
|
||
+
|
||
+/*
|
||
+ * AMD64 Architecture Programmer’s Manual Volume 3:
|
||
+ * General-Purpose and System Instructions.
|
||
+ * Chapter: 15.34.10 - SEV_STATUS MSR
|
||
+ * https://www.amd.com/system/files/TechDocs/24593.pdf
|
||
+ */
|
||
+#define MSR_AMD64_SEV UINT32_C(0xc0010131)
|
||
+
|
||
+/*
|
||
+ * Intel® TDX Module v1.5 Base Architecture Specification
|
||
+ * Chapter: 11.2
|
||
+ * https://www.intel.com/content/www/us/en/content-details/733575/intel-tdx-module-v1-5-base-architecture-specification.html
|
||
+ */
|
||
+
|
||
+#define CPUID_INTEL_TDX_ENUMERATION UINT32_C(0x21)
|
||
+
|
||
+/* Requirements for Implementing the Microsoft Hypervisor Interface
|
||
+ * https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/tlfs/tlfs
|
||
+ */
|
||
+#define CPUID_HYPERV_VENDOR_AND_MAX_FUNCTIONS UINT32_C(0x40000000)
|
||
+
|
||
+#define CPUID_HYPERV_FEATURES UINT32_C(0x40000003)
|
||
+
|
||
+#define CPUID_HYPERV_ISOLATION_CONFIG UINT32_C(0x4000000C)
|
||
+
|
||
+#define CPUID_HYPERV_MIN UINT32_C(0x40000005)
|
||
+#define CPUID_HYPERV_MAX UINT32_C(0x4000ffff)
|
||
+
|
||
+#define CPUID_SIG_AMD "AuthenticAMD"
|
||
+#define CPUID_SIG_INTEL "GenuineIntel"
|
||
+#define CPUID_SIG_INTEL_TDX "IntelTDX "
|
||
+#define CPUID_SIG_HYPERV "Microsoft Hv"
|
||
+
|
||
+/* ecx bit 31: set => hyperpvisor, unset => bare metal */
|
||
+#define CPUID_FEATURE_HYPERVISOR (UINT32_C(1) << 31)
|
||
+
|
||
+/* Linux include/asm-generic/hyperv-tlfs.h */
|
||
+#define CPUID_HYPERV_CPU_MANAGEMENT (UINT32_C(1) << 12) /* root partition */
|
||
+#define CPUID_HYPERV_ISOLATION (UINT32_C(1) << 22) /* confidential VM partition */
|
||
+
|
||
+#define CPUID_HYPERV_ISOLATION_TYPE_MASK UINT32_C(0xf)
|
||
+#define CPUID_HYPERV_ISOLATION_TYPE_SNP 2
|
||
+
|
||
+#define EAX_SEV (UINT32_C(1) << 1)
|
||
+#define MSR_SEV (UINT64_C(1) << 0)
|
||
+#define MSR_SEV_ES (UINT64_C(1) << 1)
|
||
+#define MSR_SEV_SNP (UINT64_C(1) << 2)
|
||
+
|
||
+#if defined(__x86_64__)
|
||
+
|
||
+static void cpuid(uint32_t *eax, uint32_t *ebx, uint32_t *ecx, uint32_t *edx) {
|
||
+ log_debug("CPUID func %" PRIx32 " %" PRIx32, *eax, *ecx);
|
||
+ __cpuid_count(*eax, *ecx, *eax, *ebx, *ecx, *edx);
|
||
+ log_debug("CPUID result %" PRIx32 " %" PRIx32 " %" PRIx32 " %" PRIx32, *eax, *ebx, *ecx, *edx);
|
||
+}
|
||
+
|
||
+static uint32_t cpuid_leaf(uint32_t eax, char ret_sig[static 13], bool swapped) {
|
||
+ /* zero-init as some queries explicitly require subleaf == 0 */
|
||
+ uint32_t sig[3] = {};
|
||
+
|
||
+ if (swapped)
|
||
+ cpuid(&eax, &sig[0], &sig[2], &sig[1]);
|
||
+ else
|
||
+ cpuid(&eax, &sig[0], &sig[1], &sig[2]);
|
||
+ memcpy(ret_sig, sig, sizeof(sig));
|
||
+ ret_sig[12] = 0; /* \0-terminate the string to make string comparison possible */
|
||
+
|
||
+ /* In some CI tests ret_sig doesn't contain valid UTF8 and prints garbage to the console */
|
||
+ log_debug("CPUID sig '%s'", strna(utf8_is_valid(ret_sig)));
|
||
+
|
||
+ return eax;
|
||
+}
|
||
+
|
||
+#define MSR_DEVICE "/dev/cpu/0/msr"
|
||
+
|
||
+static uint64_t msr(uint64_t index) {
|
||
+ uint64_t ret;
|
||
+ ssize_t rv;
|
||
+ _cleanup_close_ int fd = -EBADF;
|
||
+
|
||
+ 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;
|
||
+ }
|
||
+
|
||
+ 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 %ld bytes from MSR device %s (index %" PRIu64 "), ignoring",
|
||
+ rv,
|
||
+ MSR_DEVICE,
|
||
+ index);
|
||
+ return 0;
|
||
+ }
|
||
+
|
||
+ log_debug("MSR %" PRIu64 " result %" PRIu64 "", index, ret);
|
||
+ return ret;
|
||
+}
|
||
+
|
||
+static bool detect_hyperv_sev(void) {
|
||
+ uint32_t eax, ebx, ecx, edx, feat;
|
||
+ char sig[13] = {};
|
||
+
|
||
+ feat = cpuid_leaf(CPUID_HYPERV_VENDOR_AND_MAX_FUNCTIONS, sig, false);
|
||
+
|
||
+ if (feat < CPUID_HYPERV_MIN || feat > CPUID_HYPERV_MAX)
|
||
+ return false;
|
||
+
|
||
+ if (memcmp(sig, CPUID_SIG_HYPERV, sizeof(sig)) != 0)
|
||
+ return false;
|
||
+
|
||
+ log_debug("CPUID is on hyperv");
|
||
+ eax = CPUID_HYPERV_FEATURES;
|
||
+ ebx = ecx = edx = 0;
|
||
+
|
||
+ cpuid(&eax, &ebx, &ecx, &edx);
|
||
+
|
||
+ if (ebx & CPUID_HYPERV_ISOLATION && !(ebx & CPUID_HYPERV_CPU_MANAGEMENT)) {
|
||
+
|
||
+ eax = CPUID_HYPERV_ISOLATION_CONFIG;
|
||
+ ebx = ecx = edx = 0;
|
||
+ cpuid(&eax, &ebx, &ecx, &edx);
|
||
+
|
||
+ if ((ebx & CPUID_HYPERV_ISOLATION_TYPE_MASK) == CPUID_HYPERV_ISOLATION_TYPE_SNP)
|
||
+ return true;
|
||
+ }
|
||
+
|
||
+ return false;
|
||
+}
|
||
+
|
||
+static ConfidentialVirtualization detect_sev(void) {
|
||
+ uint32_t eax, ebx, ecx, edx;
|
||
+ uint64_t msrval;
|
||
+
|
||
+ eax = CPUID_GET_HIGHEST_FUNCTION;
|
||
+ ebx = ecx = edx = 0;
|
||
+
|
||
+ cpuid(&eax, &ebx, &ecx, &edx);
|
||
+
|
||
+ if (eax < CPUID_AMD_GET_ENCRYPTED_MEMORY_CAPABILITIES)
|
||
+ return CONFIDENTIAL_VIRTUALIZATION_NONE;
|
||
+
|
||
+ eax = CPUID_AMD_GET_ENCRYPTED_MEMORY_CAPABILITIES;
|
||
+ ebx = ecx = edx = 0;
|
||
+
|
||
+ cpuid(&eax, &ebx, &ecx, &edx);
|
||
+
|
||
+ /* bit 1 == CPU supports SEV feature
|
||
+ *
|
||
+ * Note, Azure blocks this CPUID leaf from its SEV-SNP
|
||
+ * guests, so we must fallback to trying some HyperV
|
||
+ * specific CPUID checks.
|
||
+ */
|
||
+ if (!(eax & EAX_SEV)) {
|
||
+ log_debug("No sev in CPUID, trying hyperv CPUID");
|
||
+
|
||
+ if (detect_hyperv_sev())
|
||
+ return CONFIDENTIAL_VIRTUALIZATION_SEV_SNP;
|
||
+
|
||
+ log_debug("No hyperv CPUID");
|
||
+ return CONFIDENTIAL_VIRTUALIZATION_NONE;
|
||
+ }
|
||
+
|
||
+ msrval = msr(MSR_AMD64_SEV);
|
||
+
|
||
+ /* Test reverse order, since the SEV-SNP bit implies
|
||
+ * the SEV-ES bit, which implies the SEV bit */
|
||
+ if (msrval & MSR_SEV_SNP)
|
||
+ return CONFIDENTIAL_VIRTUALIZATION_SEV_SNP;
|
||
+ if (msrval & MSR_SEV_ES)
|
||
+ return CONFIDENTIAL_VIRTUALIZATION_SEV_ES;
|
||
+ if (msrval & MSR_SEV)
|
||
+ return CONFIDENTIAL_VIRTUALIZATION_SEV;
|
||
+
|
||
+ return CONFIDENTIAL_VIRTUALIZATION_NONE;
|
||
+}
|
||
+
|
||
+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;
|
||
+
|
||
+ cpuid_leaf(CPUID_INTEL_TDX_ENUMERATION, sig, true);
|
||
+
|
||
+ if (memcmp(sig, CPUID_SIG_INTEL_TDX, sizeof(sig)) == 0)
|
||
+ return CONFIDENTIAL_VIRTUALIZATION_TDX;
|
||
+
|
||
+ return CONFIDENTIAL_VIRTUALIZATION_NONE;
|
||
+}
|
||
+
|
||
+static bool detect_hypervisor(void) {
|
||
+ uint32_t eax, ebx, ecx, edx;
|
||
+ bool is_hv;
|
||
+
|
||
+ eax = CPUID_PROCESSOR_INFO_AND_FEATURE_BITS;
|
||
+ ebx = ecx = edx = 0;
|
||
+
|
||
+ cpuid(&eax, &ebx, &ecx, &edx);
|
||
+
|
||
+ is_hv = ecx & CPUID_FEATURE_HYPERVISOR;
|
||
+
|
||
+ log_debug("CPUID is hypervisor: %s", yes_no(is_hv));
|
||
+ return is_hv;
|
||
+}
|
||
+
|
||
+ConfidentialVirtualization detect_confidential_virtualization(void) {
|
||
+ static thread_local ConfidentialVirtualization cached_found = _CONFIDENTIAL_VIRTUALIZATION_INVALID;
|
||
+ char sig[13] = {};
|
||
+ ConfidentialVirtualization cv = CONFIDENTIAL_VIRTUALIZATION_NONE;
|
||
+
|
||
+ if (cached_found >= 0)
|
||
+ return cached_found;
|
||
+
|
||
+ /* Skip everything on bare metal */
|
||
+ if (detect_hypervisor()) {
|
||
+ cpuid_leaf(0, sig, true);
|
||
+
|
||
+ if (memcmp(sig, CPUID_SIG_AMD, sizeof(sig)) == 0)
|
||
+ cv = detect_sev();
|
||
+ else if (memcmp(sig, CPUID_SIG_INTEL, sizeof(sig)) == 0)
|
||
+ cv = detect_tdx();
|
||
+ }
|
||
+
|
||
+ cached_found = cv;
|
||
+ return cv;
|
||
+}
|
||
+#else /* ! x86_64 */
|
||
+ConfidentialVirtualization detect_confidential_virtualization(void) {
|
||
+ log_debug("No confidential virtualization detection on this architecture");
|
||
+ return CONFIDENTIAL_VIRTUALIZATION_NONE;
|
||
+}
|
||
+#endif /* ! x86_64 */
|
||
+
|
||
+static const char *const confidential_virtualization_table[_CONFIDENTIAL_VIRTUALIZATION_MAX] = {
|
||
+ [CONFIDENTIAL_VIRTUALIZATION_NONE] = "none",
|
||
+ [CONFIDENTIAL_VIRTUALIZATION_SEV] = "sev",
|
||
+ [CONFIDENTIAL_VIRTUALIZATION_SEV_ES] = "sev-es",
|
||
+ [CONFIDENTIAL_VIRTUALIZATION_SEV_SNP] = "sev-snp",
|
||
+ [CONFIDENTIAL_VIRTUALIZATION_TDX] = "tdx",
|
||
+};
|
||
+
|
||
+DEFINE_STRING_TABLE_LOOKUP(confidential_virtualization, ConfidentialVirtualization);
|
||
diff --git a/src/basic/confidential-virt.h b/src/basic/confidential-virt.h
|
||
new file mode 100644
|
||
index 0000000000..c02f3b2321
|
||
--- /dev/null
|
||
+++ b/src/basic/confidential-virt.h
|
||
@@ -0,0 +1,25 @@
|
||
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||
+#pragma once
|
||
+
|
||
+#include <stdbool.h>
|
||
+
|
||
+#include "errno-list.h"
|
||
+#include "macro.h"
|
||
+
|
||
+typedef enum ConfidentialVirtualization {
|
||
+ CONFIDENTIAL_VIRTUALIZATION_NONE = 0,
|
||
+
|
||
+ CONFIDENTIAL_VIRTUALIZATION_SEV,
|
||
+ CONFIDENTIAL_VIRTUALIZATION_SEV_ES,
|
||
+ CONFIDENTIAL_VIRTUALIZATION_SEV_SNP,
|
||
+ CONFIDENTIAL_VIRTUALIZATION_TDX,
|
||
+
|
||
+ _CONFIDENTIAL_VIRTUALIZATION_MAX,
|
||
+ _CONFIDENTIAL_VIRTUALIZATION_INVALID = -EINVAL,
|
||
+ _CONFIDENTIAL_VIRTUALIZATION_ERRNO_MAX = -ERRNO_MAX, /* ensure full range of errno fits into this enum */
|
||
+} ConfidentialVirtualization;
|
||
+
|
||
+ConfidentialVirtualization detect_confidential_virtualization(void);
|
||
+
|
||
+const char *confidential_virtualization_to_string(ConfidentialVirtualization v) _const_;
|
||
+ConfidentialVirtualization confidential_virtualization_from_string(const char *s) _pure_;
|
||
diff --git a/src/basic/meson.build b/src/basic/meson.build
|
||
index c0f0b07418..11053a5ecd 100644
|
||
--- a/src/basic/meson.build
|
||
+++ b/src/basic/meson.build
|
||
@@ -31,6 +31,8 @@ basic_sources = files(
|
||
'chattr-util.h',
|
||
'conf-files.c',
|
||
'conf-files.h',
|
||
+ 'confidential-virt.c',
|
||
+ 'confidential-virt.h',
|
||
'def.h',
|
||
'devnum-util.c',
|
||
'devnum-util.h',
|
||
diff --git a/src/test/test-tables.c b/src/test/test-tables.c
|
||
index 30ca1871cb..d47d3d75cc 100644
|
||
--- a/src/test/test-tables.c
|
||
+++ b/src/test/test-tables.c
|
||
@@ -6,6 +6,7 @@
|
||
#include "cgroup-util.h"
|
||
#include "compress.h"
|
||
#include "condition.h"
|
||
+#include "confidential-virt.h"
|
||
#include "device-private.h"
|
||
#include "device.h"
|
||
#include "discover-image.h"
|
||
@@ -50,6 +51,7 @@ int main(int argc, char **argv) {
|
||
test_table(collect_mode, COLLECT_MODE);
|
||
test_table(condition_result, CONDITION_RESULT);
|
||
test_table(condition_type, CONDITION_TYPE);
|
||
+ test_table(confidential_virtualization, CONFIDENTIAL_VIRTUALIZATION);
|
||
test_table(device_action, SD_DEVICE_ACTION);
|
||
test_table(device_state, DEVICE_STATE);
|
||
test_table(dns_over_tls_mode, DNS_OVER_TLS_MODE);
|