]>
Commit | Line | Data |
---|---|---|
a577a616 DB |
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | ||
3 | #if defined(__i386__) || defined(__x86_64__) | |
4 | #include <cpuid.h> | |
5 | #endif | |
6 | #include <errno.h> | |
7 | #include <fcntl.h> | |
a577a616 DB |
8 | #include <stdlib.h> |
9 | #include <unistd.h> | |
10 | ||
129b9e3f | 11 | #include "confidential-virt-fundamental.h" |
a577a616 DB |
12 | #include "confidential-virt.h" |
13 | #include "fd-util.h" | |
14 | #include "missing_threads.h" | |
15 | #include "string-table.h" | |
16 | #include "utf8.h" | |
17 | ||
a577a616 DB |
18 | |
19 | #if defined(__x86_64__) | |
20 | ||
21 | static void cpuid(uint32_t *eax, uint32_t *ebx, uint32_t *ecx, uint32_t *edx) { | |
22 | log_debug("CPUID func %" PRIx32 " %" PRIx32, *eax, *ecx); | |
23 | __cpuid_count(*eax, *ecx, *eax, *ebx, *ecx, *edx); | |
24 | log_debug("CPUID result %" PRIx32 " %" PRIx32 " %" PRIx32 " %" PRIx32, *eax, *ebx, *ecx, *edx); | |
25 | } | |
26 | ||
27 | static uint32_t cpuid_leaf(uint32_t eax, char ret_sig[static 13], bool swapped) { | |
28 | /* zero-init as some queries explicitly require subleaf == 0 */ | |
29 | uint32_t sig[3] = {}; | |
30 | ||
31 | if (swapped) | |
32 | cpuid(&eax, &sig[0], &sig[2], &sig[1]); | |
33 | else | |
34 | cpuid(&eax, &sig[0], &sig[1], &sig[2]); | |
35 | memcpy(ret_sig, sig, sizeof(sig)); | |
36 | ret_sig[12] = 0; /* \0-terminate the string to make string comparison possible */ | |
37 | ||
38 | /* In some CI tests ret_sig doesn't contain valid UTF8 and prints garbage to the console */ | |
39 | log_debug("CPUID sig '%s'", strna(utf8_is_valid(ret_sig))); | |
40 | ||
41 | return eax; | |
42 | } | |
43 | ||
44 | #define MSR_DEVICE "/dev/cpu/0/msr" | |
45 | ||
46 | static uint64_t msr(uint64_t index) { | |
47 | uint64_t ret; | |
48 | ssize_t rv; | |
49 | _cleanup_close_ int fd = -EBADF; | |
50 | ||
51 | fd = open(MSR_DEVICE, O_RDONLY|O_CLOEXEC); | |
52 | if (fd < 0) { | |
53 | log_debug_errno(errno, | |
54 | "Cannot open MSR device %s (index %" PRIu64 "), ignoring: %m", | |
55 | MSR_DEVICE, | |
56 | index); | |
57 | return 0; | |
58 | } | |
59 | ||
60 | rv = pread(fd, &ret, sizeof(ret), index); | |
61 | if (rv < 0) { | |
62 | log_debug_errno(errno, | |
63 | "Cannot read MSR device %s (index %" PRIu64 "), ignoring: %m", | |
64 | MSR_DEVICE, | |
65 | index); | |
66 | return 0; | |
67 | } else if (rv != sizeof(ret)) { | |
f692c4a6 | 68 | log_debug("Short read %zd bytes from MSR device %s (index %" PRIu64 "), ignoring", |
a577a616 DB |
69 | rv, |
70 | MSR_DEVICE, | |
71 | index); | |
72 | return 0; | |
73 | } | |
74 | ||
75 | log_debug("MSR %" PRIu64 " result %" PRIu64 "", index, ret); | |
76 | return ret; | |
77 | } | |
78 | ||
79 | static bool detect_hyperv_sev(void) { | |
80 | uint32_t eax, ebx, ecx, edx, feat; | |
81 | char sig[13] = {}; | |
82 | ||
83 | feat = cpuid_leaf(CPUID_HYPERV_VENDOR_AND_MAX_FUNCTIONS, sig, false); | |
84 | ||
85 | if (feat < CPUID_HYPERV_MIN || feat > CPUID_HYPERV_MAX) | |
86 | return false; | |
87 | ||
88 | if (memcmp(sig, CPUID_SIG_HYPERV, sizeof(sig)) != 0) | |
89 | return false; | |
90 | ||
91 | log_debug("CPUID is on hyperv"); | |
92 | eax = CPUID_HYPERV_FEATURES; | |
93 | ebx = ecx = edx = 0; | |
94 | ||
95 | cpuid(&eax, &ebx, &ecx, &edx); | |
96 | ||
97 | if (ebx & CPUID_HYPERV_ISOLATION && !(ebx & CPUID_HYPERV_CPU_MANAGEMENT)) { | |
98 | ||
99 | eax = CPUID_HYPERV_ISOLATION_CONFIG; | |
100 | ebx = ecx = edx = 0; | |
101 | cpuid(&eax, &ebx, &ecx, &edx); | |
102 | ||
103 | if ((ebx & CPUID_HYPERV_ISOLATION_TYPE_MASK) == CPUID_HYPERV_ISOLATION_TYPE_SNP) | |
104 | return true; | |
105 | } | |
106 | ||
107 | return false; | |
108 | } | |
109 | ||
110 | static ConfidentialVirtualization detect_sev(void) { | |
111 | uint32_t eax, ebx, ecx, edx; | |
112 | uint64_t msrval; | |
113 | ||
114 | eax = CPUID_GET_HIGHEST_FUNCTION; | |
115 | ebx = ecx = edx = 0; | |
116 | ||
117 | cpuid(&eax, &ebx, &ecx, &edx); | |
118 | ||
119 | if (eax < CPUID_AMD_GET_ENCRYPTED_MEMORY_CAPABILITIES) | |
120 | return CONFIDENTIAL_VIRTUALIZATION_NONE; | |
121 | ||
122 | eax = CPUID_AMD_GET_ENCRYPTED_MEMORY_CAPABILITIES; | |
123 | ebx = ecx = edx = 0; | |
124 | ||
125 | cpuid(&eax, &ebx, &ecx, &edx); | |
126 | ||
127 | /* bit 1 == CPU supports SEV feature | |
128 | * | |
129 | * Note, Azure blocks this CPUID leaf from its SEV-SNP | |
130 | * guests, so we must fallback to trying some HyperV | |
131 | * specific CPUID checks. | |
132 | */ | |
133 | if (!(eax & EAX_SEV)) { | |
134 | log_debug("No sev in CPUID, trying hyperv CPUID"); | |
135 | ||
136 | if (detect_hyperv_sev()) | |
137 | return CONFIDENTIAL_VIRTUALIZATION_SEV_SNP; | |
138 | ||
139 | log_debug("No hyperv CPUID"); | |
140 | return CONFIDENTIAL_VIRTUALIZATION_NONE; | |
141 | } | |
142 | ||
143 | msrval = msr(MSR_AMD64_SEV); | |
144 | ||
145 | /* Test reverse order, since the SEV-SNP bit implies | |
146 | * the SEV-ES bit, which implies the SEV bit */ | |
147 | if (msrval & MSR_SEV_SNP) | |
148 | return CONFIDENTIAL_VIRTUALIZATION_SEV_SNP; | |
149 | if (msrval & MSR_SEV_ES) | |
150 | return CONFIDENTIAL_VIRTUALIZATION_SEV_ES; | |
151 | if (msrval & MSR_SEV) | |
152 | return CONFIDENTIAL_VIRTUALIZATION_SEV; | |
153 | ||
154 | return CONFIDENTIAL_VIRTUALIZATION_NONE; | |
155 | } | |
156 | ||
157 | static ConfidentialVirtualization detect_tdx(void) { | |
158 | uint32_t eax, ebx, ecx, edx; | |
159 | char sig[13] = {}; | |
160 | ||
161 | eax = CPUID_GET_HIGHEST_FUNCTION; | |
162 | ebx = ecx = edx = 0; | |
163 | ||
164 | cpuid(&eax, &ebx, &ecx, &edx); | |
165 | ||
166 | if (eax < CPUID_INTEL_TDX_ENUMERATION) | |
167 | return CONFIDENTIAL_VIRTUALIZATION_NONE; | |
168 | ||
169 | cpuid_leaf(CPUID_INTEL_TDX_ENUMERATION, sig, true); | |
170 | ||
171 | if (memcmp(sig, CPUID_SIG_INTEL_TDX, sizeof(sig)) == 0) | |
172 | return CONFIDENTIAL_VIRTUALIZATION_TDX; | |
173 | ||
174 | return CONFIDENTIAL_VIRTUALIZATION_NONE; | |
175 | } | |
176 | ||
177 | static bool detect_hypervisor(void) { | |
178 | uint32_t eax, ebx, ecx, edx; | |
179 | bool is_hv; | |
180 | ||
181 | eax = CPUID_PROCESSOR_INFO_AND_FEATURE_BITS; | |
182 | ebx = ecx = edx = 0; | |
183 | ||
184 | cpuid(&eax, &ebx, &ecx, &edx); | |
185 | ||
186 | is_hv = ecx & CPUID_FEATURE_HYPERVISOR; | |
187 | ||
188 | log_debug("CPUID is hypervisor: %s", yes_no(is_hv)); | |
189 | return is_hv; | |
190 | } | |
191 | ||
192 | ConfidentialVirtualization detect_confidential_virtualization(void) { | |
193 | static thread_local ConfidentialVirtualization cached_found = _CONFIDENTIAL_VIRTUALIZATION_INVALID; | |
194 | char sig[13] = {}; | |
195 | ConfidentialVirtualization cv = CONFIDENTIAL_VIRTUALIZATION_NONE; | |
196 | ||
197 | if (cached_found >= 0) | |
198 | return cached_found; | |
199 | ||
200 | /* Skip everything on bare metal */ | |
201 | if (detect_hypervisor()) { | |
202 | cpuid_leaf(0, sig, true); | |
203 | ||
204 | if (memcmp(sig, CPUID_SIG_AMD, sizeof(sig)) == 0) | |
205 | cv = detect_sev(); | |
206 | else if (memcmp(sig, CPUID_SIG_INTEL, sizeof(sig)) == 0) | |
207 | cv = detect_tdx(); | |
208 | } | |
209 | ||
210 | cached_found = cv; | |
211 | return cv; | |
212 | } | |
213 | #else /* ! x86_64 */ | |
214 | ConfidentialVirtualization detect_confidential_virtualization(void) { | |
215 | log_debug("No confidential virtualization detection on this architecture"); | |
216 | return CONFIDENTIAL_VIRTUALIZATION_NONE; | |
217 | } | |
218 | #endif /* ! x86_64 */ | |
219 | ||
220 | static const char *const confidential_virtualization_table[_CONFIDENTIAL_VIRTUALIZATION_MAX] = { | |
221 | [CONFIDENTIAL_VIRTUALIZATION_NONE] = "none", | |
222 | [CONFIDENTIAL_VIRTUALIZATION_SEV] = "sev", | |
223 | [CONFIDENTIAL_VIRTUALIZATION_SEV_ES] = "sev-es", | |
224 | [CONFIDENTIAL_VIRTUALIZATION_SEV_SNP] = "sev-snp", | |
225 | [CONFIDENTIAL_VIRTUALIZATION_TDX] = "tdx", | |
226 | }; | |
227 | ||
228 | DEFINE_STRING_TABLE_LOOKUP(confidential_virtualization, ConfidentialVirtualization); |