virt-what-cvm: probe for SNP/HCL on HyperV/Azure via CPUID
[virt-what.git] / virt-what-cvm.c
1 /* virt-what-cvm-helper: Are we running inside confidential VM
2  * Copyright (C) 2023 Red Hat Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include "config.h"
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <stdint.h>
24 #include <string.h>
25 #include <stdbool.h>
26 #include <fcntl.h>
27 #include <unistd.h>
28 #include <getopt.h>
29 #ifdef HAVE_TPM2_TSS
30 #include <tss2/tss2_esys.h>
31 #include <assert.h>
32 #endif
33
34 static bool dodebug = false;
35
36 #define debug(...) do { if (dodebug) fprintf(stderr, __VA_ARGS__); } while(0)
37
38
39 #define CPUID_PROCESSOR_INFO_AND_FEATURE_BITS 0x1
40
41 /*
42  * AMD64 Architecture Programmer’s Manual Volume 3:
43  * General-Purpose and System Instructions.
44  * Chapter: E4.1 - Maximum Extended Function Number and Vendor String
45  *  https://www.amd.com/system/files/TechDocs/24594.pdf
46  */
47 #define CPUID_GET_HIGHEST_FUNCTION 0x80000000
48
49 /*
50  * AMD64 Architecture Programmer’s Manual Volume 3:
51  * General-Purpose and System Instructions.
52  * Chapter: E4.17 - Encrypted Memory Capabilities
53  *  https://www.amd.com/system/files/TechDocs/24594.pdf
54  */
55 #define CPUID_AMD_GET_ENCRYPTED_MEMORY_CAPABILITIES 0x8000001f
56
57 /*
58  * AMD64 Architecture Programmer’s Manual Volume 3:
59  * General-Purpose and System Instructions.
60  * Chapter: 15.34.10 - SEV_STATUS MSR
61  * https://www.amd.com/system/files/TechDocs/24593.pdf
62  */
63 #define MSR_AMD64_SEV 0xc0010131
64
65 /*
66  * Intel® TDX Module v1.5 Base Architecture Specification
67  * Chapter: 11.2
68  * https://www.intel.com/content/www/us/en/content-details/733575/intel-tdx-module-v1-5-base-architecture-specification.html
69  */
70
71 #define CPUID_INTEL_TDX_ENUMERATION 0x21
72
73 /* Requirements for Implementing the Microsoft Hypervisor Interface
74  * https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/tlfs/tlfs
75  */
76 #define CPUID_HYPERV_VENDOR_AND_MAX_FUNCTIONS 0x40000000
77
78 #define CPUID_HYPERV_FEATURES 0x40000003
79
80 #define CPUID_HYPERV_ISOLATION_CONFIG 0x4000000C
81
82 #define CPUID_HYPERV_MIN 0x40000005
83 #define CPUID_HYPERV_MAX 0x4000ffff
84
85 #define CPUID_SIG_AMD       "AuthenticAMD"
86 #define CPUID_SIG_INTEL     "GenuineIntel"
87 #define CPUID_SIG_INTEL_TDX "IntelTDX    "
88 #define CPUID_SIG_HYPERV    "Microsoft Hv"
89
90 /* ecx bit 31: set => hyperpvisor, unset => bare metal */
91 #define CPUID_FEATURE_HYPERVISOR (1 << 31)
92
93 /* Linux include/asm-generic/hyperv-tlfs.h */
94 #define CPUID_HYPERV_CPU_MANAGEMENT (1 << 12) /* root partition */
95 #define CPUID_HYPERV_ISOLATION      (1 << 22) /* confidential VM partition */
96
97 #define CPUID_HYPERV_ISOLATION_TYPE_MASK 0xf
98 #define CPUID_HYPERV_ISOLATION_TYPE_SNP 2
99
100 /*
101  * This TPM NV data format is not explicitly documented anywhere,
102  * but the header definition is present in code at:
103  *
104  * https://github.com/kinvolk/azure-cvm-tooling/blob/main/az-snp-vtpm/src/hcl.rs
105  */
106 #define TPM_AZURE_HCLA_REPORT_INDEX 0x01400001
107
108 struct TPMAzureHCLAHeader {
109   uint32_t signature;
110   uint32_t version;
111   uint32_t report_len;
112   uint32_t report_type;
113   uint32_t unknown[4];
114 };
115
116 /* The bytes for "HCLA" */
117 #define TPM_AZURE_HCLA_SIGNATURE 0x414C4348
118 #define TPM_AZURE_HCLA_VERSION 0x1
119 #define TPM_AZURE_HCLA_REPORT_TYPE_SNP 0x2
120
121 #if defined(__x86_64__)
122
123 #ifdef HAVE_TPM2_TSS
124 static char *
125 tpm_nvread(uint32_t nvindex, size_t *retlen)
126 {
127   TSS2_RC rc;
128   ESYS_CONTEXT *ctx = NULL;
129   ESYS_TR primary = ESYS_TR_NONE;
130   ESYS_TR session = ESYS_TR_NONE;
131   ESYS_TR nvobj = ESYS_TR_NONE;
132   TPM2B_NV_PUBLIC *pubData = NULL;
133   TPMT_SYM_DEF sym = {
134     .algorithm = TPM2_ALG_AES,
135     .keyBits = { .aes = 128 },
136     .mode = { .aes = TPM2_ALG_CFB }
137   };
138   char *ret;
139   size_t retwant;
140
141   rc = Esys_Initialize(&ctx, NULL, NULL);
142   if (rc != TSS2_RC_SUCCESS)
143     return NULL;
144
145   rc = Esys_Startup(ctx, TPM2_SU_CLEAR);
146   debug("tpm startup %d\n", rc);
147   if (rc != TSS2_RC_SUCCESS)
148     goto error;
149
150   rc = Esys_StartAuthSession(ctx, ESYS_TR_NONE, ESYS_TR_NONE,
151                              ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE,
152                              NULL, 0,
153                              &sym, TPM2_ALG_SHA256, &session);
154   debug("tpm auth session %d\n", rc);
155   if (rc != TSS2_RC_SUCCESS)
156     goto error;
157
158   rc = Esys_TR_FromTPMPublic(ctx, nvindex, ESYS_TR_NONE,
159                              ESYS_TR_NONE, ESYS_TR_NONE, &nvobj);
160   debug("tpm from public %d\n", rc);
161   if (rc != TSS2_RC_SUCCESS)
162     goto error;
163
164   rc = Esys_NV_ReadPublic(ctx, nvobj, ESYS_TR_NONE,
165                           ESYS_TR_NONE, ESYS_TR_NONE,
166                           &pubData, NULL);
167   debug("tpm read public %d\n", rc);
168   if (rc != TPM2_RC_SUCCESS)
169     goto error;
170
171   retwant = pubData->nvPublic.dataSize;
172   free(pubData);
173   *retlen = 0;
174   ret = malloc(retwant);
175   assert(ret);
176   while (*retlen < retwant) {
177     size_t want = retwant - *retlen;
178     TPM2B_MAX_NV_BUFFER *data = NULL;
179     if (want > 1024)
180       want = 1024;
181     rc = Esys_NV_Read(ctx,  ESYS_TR_RH_OWNER, nvobj, session, ESYS_TR_NONE, ESYS_TR_NONE,
182                       want, *retlen, &data);
183     debug("tpm nv read %d\n", rc);
184     if (rc != TPM2_RC_SUCCESS) {
185       free(ret);
186       goto error;
187     }
188
189     memcpy(ret + *retlen, data->buffer, data->size);
190     *retlen += data->size;
191     free(data);
192   }
193
194   return ret;
195
196  error:
197   if (nvobj != ESYS_TR_NONE)
198     Esys_FlushContext(ctx, nvobj);
199   if (session != ESYS_TR_NONE)
200     Esys_FlushContext(ctx, session);
201   if (primary != ESYS_TR_NONE)
202     Esys_FlushContext(ctx, primary);
203   Esys_Finalize(&ctx);
204   *retlen = 0;
205   return NULL;
206 }
207 #else /* ! HAVE_TPM2_TSS */
208 static char *
209 tpm_nvread(uint32_t nvindex, size_t *retlen)
210 {
211   return NULL;
212 }
213 #endif /* ! HAVE_TPM2_TSS */
214
215 /* Copied from the Linux kernel definition in
216  * arch/x86/include/asm/processor.h
217  */
218 static inline void
219 cpuid (uint32_t *eax, uint32_t *ebx, uint32_t *ecx, uint32_t *edx)
220 {
221   debug("CPUID func %x %x\n", *eax, *ecx);
222   asm volatile ("cpuid"
223                 : "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx)
224                 : "0" (*eax), "2" (*ecx)
225                 : "memory");
226   debug("CPUID result %x %x %x %x\n", *eax, *ebx, *ecx, *edx);
227 }
228
229
230 static uint32_t
231 cpuid_leaf (uint32_t eax, char *sig, bool swapped)
232 {
233   uint32_t *sig32 = (uint32_t *) sig;
234
235   if (swapped)
236     cpuid (&eax, &sig32[0], &sig32[2], &sig32[1]);
237   else
238     cpuid (&eax, &sig32[0], &sig32[1], &sig32[2]);
239   sig[12] = 0; /* \0-terminate the string to make string comparison possible */
240   debug("CPUID sig %s\n", sig);
241   return eax;
242 }
243
244 #define MSR_DEVICE "/dev/cpu/0/msr"
245
246 static uint64_t
247 msr (off_t index)
248 {
249   uint64_t ret;
250   int fd = open (MSR_DEVICE, O_RDONLY);
251   if (fd < 0) {
252     debug ("Cannot open MSR device %s", MSR_DEVICE);
253     return 0;
254   }
255
256   if (pread (fd, &ret, sizeof(ret), index) != sizeof(ret))
257     ret = 0;
258
259   close (fd);
260
261   debug ("MSR %llx result %llx\n", (unsigned long long)index,
262          (unsigned long long)ret);
263   return ret;
264 }
265
266 bool
267 cpu_sig_amd_azure (void)
268 {
269   size_t datalen = 0;
270   char *data = tpm_nvread(TPM_AZURE_HCLA_REPORT_INDEX, &datalen);
271   struct TPMAzureHCLAHeader *header = (struct TPMAzureHCLAHeader *)data;
272   bool ret;
273
274   if (!data)
275     return false;
276
277   if (datalen < sizeof(struct TPMAzureHCLAHeader)) {
278     debug ("TPM data len is too small to be an Azure HCLA report");
279     return false;
280   }
281
282   debug ("Azure TPM HCLA report header sig %x ver %x type %x\n",
283          header->signature, header->version, header->report_type);
284
285   ret = (header->signature == TPM_AZURE_HCLA_SIGNATURE &&
286          header->version == TPM_AZURE_HCLA_VERSION &&
287          header->report_type == TPM_AZURE_HCLA_REPORT_TYPE_SNP);
288   debug ("Azure TPM HCLA report present ? %d\n", ret);
289
290   free(data);
291   return ret;
292 }
293
294 static bool
295 cpu_sig_amd_hyperv (void)
296 {
297   uint32_t eax, ebx, ecx, edx;
298   char sig[13];
299   uint32_t feat;
300
301   feat = cpuid_leaf (CPUID_HYPERV_VENDOR_AND_MAX_FUNCTIONS, sig, false);
302
303   if (feat < CPUID_HYPERV_MIN ||
304       feat > CPUID_HYPERV_MAX)
305     return false;
306
307   if (memcmp (sig, CPUID_SIG_HYPERV, sizeof(sig)) != 0)
308     return false;
309
310   debug ("CPUID is on hyperv\n");
311   eax = CPUID_HYPERV_FEATURES;
312   ebx = ecx = edx = 0;
313
314   cpuid(&eax, &ebx, &ecx, &edx);
315
316   if (ebx & CPUID_HYPERV_ISOLATION &&
317       !(ebx & CPUID_HYPERV_CPU_MANAGEMENT)) {
318
319     eax = CPUID_HYPERV_ISOLATION_CONFIG;
320     ebx = ecx = edx = 0;
321     cpuid(&eax, &ebx, &ecx, &edx);
322
323     if ((ebx & CPUID_HYPERV_ISOLATION_TYPE_MASK) ==
324         CPUID_HYPERV_ISOLATION_TYPE_SNP) {
325       return true;
326     }
327   }
328
329   return false;
330 }
331
332 static void
333 cpu_sig_amd (void)
334 {
335   uint32_t eax, ebx, ecx, edx;
336   uint64_t msrval;
337
338   eax = CPUID_GET_HIGHEST_FUNCTION;
339   ebx = ecx = edx = 0;
340
341   cpuid (&eax, &ebx, &ecx, &edx);
342
343   if (eax < CPUID_AMD_GET_ENCRYPTED_MEMORY_CAPABILITIES)
344     return;
345
346   eax = CPUID_AMD_GET_ENCRYPTED_MEMORY_CAPABILITIES;
347   ebx = ecx = edx = 0;
348
349   cpuid (&eax, &ebx, &ecx, &edx);
350
351   /* bit 1 == CPU supports SEV feature
352    *
353    * Note, Azure blocks this CPUID leaf from its SEV-SNP
354    * guests, so we must fallback to probing the TPM which
355    * exposes a SEV-SNP attestation report as evidence.
356    */
357   if (!(eax & (1 << 1))) {
358     debug ("No sev in CPUID, try hyperv CPUID/azure TPM NV\n");
359
360     if (cpu_sig_amd_hyperv () ||
361         cpu_sig_amd_azure()) {
362       puts ("amd-sev-snp");
363       puts ("azure-hcl");
364     } else {
365       debug("No azure TPM NV\n");
366     }
367     return;
368   }
369
370   msrval = msr (MSR_AMD64_SEV);
371
372   /* Test reverse order, since the SEV-SNP bit implies
373    * the SEV-ES bit, which implies the SEV bit */
374   if (msrval & (1 << 2)) {
375     puts ("amd-sev-snp");
376   } else if (msrval & (1 << 1)) {
377     puts ("amd-sev-es");
378   } else if (msrval & (1 << 0)) {
379     puts ("amd-sev");
380   }
381 }
382
383 static void
384 cpu_sig_intel (void)
385 {
386   uint32_t eax, ebx, ecx, edx;
387   char sig[13];
388
389   eax = CPUID_GET_HIGHEST_FUNCTION;
390   ebx = ecx = edx = 0;
391
392   cpuid (&eax, &ebx, &ecx, &edx);
393   debug ("CPUID max function: %x %x %x %x\n", eax, ebx, ecx,edx);
394
395   if (eax < CPUID_INTEL_TDX_ENUMERATION)
396     return;
397
398   memset (sig, 0, sizeof sig);
399   cpuid_leaf (CPUID_INTEL_TDX_ENUMERATION, sig, true);
400
401   if (memcmp (sig, CPUID_SIG_INTEL_TDX, sizeof(sig)) == 0)
402     puts ("intel-tdx");
403 }
404
405 static bool
406 cpu_is_hv (void)
407 {
408   uint32_t eax, ebx, ecx, edx;
409   bool is_hv;
410
411   eax = CPUID_PROCESSOR_INFO_AND_FEATURE_BITS;
412   ebx = ecx = edx = 0;
413
414   cpuid(&eax, &ebx, &ecx, &edx);
415
416   is_hv = ecx & CPUID_FEATURE_HYPERVISOR;
417
418   debug ("CPUID is hypervisor: %s\n", is_hv ? "yes" : "no");
419   return is_hv;
420 }
421
422 static void
423 cpu_sig (void)
424 {
425   char sig[13];
426
427   /* Skip everything on bare metal */
428   if (!cpu_is_hv ())
429     return;
430
431   memset (sig, 0, sizeof sig);
432   cpuid_leaf (0, sig, true);
433
434   if (memcmp (sig, CPUID_SIG_AMD, sizeof(sig)) == 0)
435     cpu_sig_amd ();
436   else if (memcmp (sig, CPUID_SIG_INTEL, sizeof(sig)) == 0)
437     cpu_sig_intel ();
438 }
439
440 #else /* !x86_64 */
441
442 static void
443 cpu_sig (void)
444 {
445   /* nothing for other architectures */
446 }
447
448 #endif
449
450 int
451 main(int argc, char **argv)
452 {
453   int c;
454
455   while (true) {
456     int option_index = 0;
457     static struct option long_options[] = {
458       {"debug", no_argument, 0, 'd' },
459       {"version", no_argument, 0, 'v' },
460       {"help", no_argument, 0, 'h'},
461       {0, 0, 0, 0 }
462     };
463
464     c = getopt_long(argc, argv, "dvh",
465                     long_options, &option_index);
466     if (c == -1)
467       break;
468
469     switch (c) {
470     case 'd':
471       dodebug = true;
472       break;
473     case 'v':
474       fprintf(stdout, "%s\n", PACKAGE_VERSION);
475       exit(EXIT_SUCCESS);
476       break;
477     case 'h':
478     default: /* '?' */
479       fprintf(c == 'h' ? stdout : stderr,
480               "Usage: %s [--debug|-d] [--help|-h] [--version|-v]\n",
481               argv[0]);
482       exit(c == 'h' ? EXIT_SUCCESS : EXIT_FAILURE);
483     }
484   }
485
486   if (!dodebug)
487     setenv("TSS2_LOG", "all+none", 1);
488
489   cpu_sig ();
490
491   exit(EXIT_SUCCESS);
492 }