1e7c50bb03586ffd88f6ef9a5a4945fb6dce6e86
[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
74 #define CPUID_SIG_AMD       "AuthenticAMD"
75 #define CPUID_SIG_INTEL     "GenuineIntel"
76 #define CPUID_SIG_INTEL_TDX "IntelTDX    "
77
78 /* ecx bit 31: set => hyperpvisor, unset => bare metal */
79 #define CPUID_FEATURE_HYPERVISOR (1 << 31)
80
81 /*
82  * This TPM NV data format is not explicitly documented anywhere,
83  * but the header definition is present in code at:
84  *
85  * https://github.com/kinvolk/azure-cvm-tooling/blob/main/az-snp-vtpm/src/hcl.rs
86  */
87 #define TPM_AZURE_HCLA_REPORT_INDEX 0x01400001
88
89 struct TPMAzureHCLAHeader {
90   uint32_t signature;
91   uint32_t version;
92   uint32_t report_len;
93   uint32_t report_type;
94   uint32_t unknown[4];
95 };
96
97 /* The bytes for "HCLA" */
98 #define TPM_AZURE_HCLA_SIGNATURE 0x414C4348
99 #define TPM_AZURE_HCLA_VERSION 0x1
100 #define TPM_AZURE_HCLA_REPORT_TYPE_SNP 0x2
101
102 #if defined(__x86_64__)
103
104 #ifdef HAVE_TPM2_TSS
105 static char *
106 tpm_nvread(uint32_t nvindex, size_t *retlen)
107 {
108   TSS2_RC rc;
109   ESYS_CONTEXT *ctx = NULL;
110   ESYS_TR primary = ESYS_TR_NONE;
111   ESYS_TR session = ESYS_TR_NONE;
112   ESYS_TR nvobj = ESYS_TR_NONE;
113   TPM2B_NV_PUBLIC *pubData = NULL;
114   TPMT_SYM_DEF sym = {
115     .algorithm = TPM2_ALG_AES,
116     .keyBits = { .aes = 128 },
117     .mode = { .aes = TPM2_ALG_CFB }
118   };
119   char *ret;
120   size_t retwant;
121
122   rc = Esys_Initialize(&ctx, NULL, NULL);
123   if (rc != TSS2_RC_SUCCESS)
124     return NULL;
125
126   rc = Esys_Startup(ctx, TPM2_SU_CLEAR);
127   debug("tpm startup %d\n", rc);
128   if (rc != TSS2_RC_SUCCESS)
129     goto error;
130
131   rc = Esys_StartAuthSession(ctx, ESYS_TR_NONE, ESYS_TR_NONE,
132                              ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE,
133                              NULL, 0,
134                              &sym, TPM2_ALG_SHA256, &session);
135   debug("tpm auth session %d\n", rc);
136   if (rc != TSS2_RC_SUCCESS)
137     goto error;
138
139   rc = Esys_TR_FromTPMPublic(ctx, nvindex, ESYS_TR_NONE,
140                              ESYS_TR_NONE, ESYS_TR_NONE, &nvobj);
141   debug("tpm from public %d\n", rc);
142   if (rc != TSS2_RC_SUCCESS)
143     goto error;
144
145   rc = Esys_NV_ReadPublic(ctx, nvobj, ESYS_TR_NONE,
146                           ESYS_TR_NONE, ESYS_TR_NONE,
147                           &pubData, NULL);
148   debug("tpm read public %d\n", rc);
149   if (rc != TPM2_RC_SUCCESS)
150     goto error;
151
152   retwant = pubData->nvPublic.dataSize;
153   free(pubData);
154   *retlen = 0;
155   ret = malloc(retwant);
156   assert(ret);
157   while (*retlen < retwant) {
158     size_t want = retwant - *retlen;
159     TPM2B_MAX_NV_BUFFER *data = NULL;
160     if (want > 1024)
161       want = 1024;
162     rc = Esys_NV_Read(ctx,  ESYS_TR_RH_OWNER, nvobj, session, ESYS_TR_NONE, ESYS_TR_NONE,
163                       want, *retlen, &data);
164     debug("tpm nv read %d\n", rc);
165     if (rc != TPM2_RC_SUCCESS) {
166       free(ret);
167       goto error;
168     }
169
170     memcpy(ret + *retlen, data->buffer, data->size);
171     *retlen += data->size;
172     free(data);
173   }
174
175   return ret;
176
177  error:
178   if (nvobj != ESYS_TR_NONE)
179     Esys_FlushContext(ctx, nvobj);
180   if (session != ESYS_TR_NONE)
181     Esys_FlushContext(ctx, session);
182   if (primary != ESYS_TR_NONE)
183     Esys_FlushContext(ctx, primary);
184   Esys_Finalize(&ctx);
185   *retlen = 0;
186   return NULL;
187 }
188 #else /* ! HAVE_TPM2_TSS */
189 static char *
190 tpm_nvread(uint32_t nvindex, size_t *retlen)
191 {
192   return NULL;
193 }
194 #endif /* ! HAVE_TPM2_TSS */
195
196 /* Copied from the Linux kernel definition in
197  * arch/x86/include/asm/processor.h
198  */
199 static inline void
200 cpuid (uint32_t *eax, uint32_t *ebx, uint32_t *ecx, uint32_t *edx)
201 {
202   debug("CPUID func %x %x\n", *eax, *ecx);
203   asm volatile ("cpuid"
204                 : "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx)
205                 : "0" (*eax), "2" (*ecx)
206                 : "memory");
207   debug("CPUID result %x %x %x %x\n", *eax, *ebx, *ecx, *edx);
208 }
209
210
211 static uint32_t
212 cpuid_leaf (uint32_t eax, char *sig, bool swapped)
213 {
214   uint32_t *sig32 = (uint32_t *) sig;
215
216   if (swapped)
217     cpuid (&eax, &sig32[0], &sig32[2], &sig32[1]);
218   else
219     cpuid (&eax, &sig32[0], &sig32[1], &sig32[2]);
220   sig[12] = 0; /* \0-terminate the string to make string comparison possible */
221   debug("CPUID sig %s\n", sig);
222   return eax;
223 }
224
225 #define MSR_DEVICE "/dev/cpu/0/msr"
226
227 static uint64_t
228 msr (off_t index)
229 {
230   uint64_t ret;
231   int fd = open (MSR_DEVICE, O_RDONLY);
232   if (fd < 0) {
233     debug ("Cannot open MSR device %s", MSR_DEVICE);
234     return 0;
235   }
236
237   if (pread (fd, &ret, sizeof(ret), index) != sizeof(ret))
238     ret = 0;
239
240   close (fd);
241
242   debug ("MSR %llx result %llx\n", (unsigned long long)index,
243          (unsigned long long)ret);
244   return ret;
245 }
246
247 bool
248 cpu_sig_amd_azure (void)
249 {
250   size_t datalen = 0;
251   char *data = tpm_nvread(TPM_AZURE_HCLA_REPORT_INDEX, &datalen);
252   struct TPMAzureHCLAHeader *header = (struct TPMAzureHCLAHeader *)data;
253   bool ret;
254
255   if (!data)
256     return false;
257
258   if (datalen < sizeof(struct TPMAzureHCLAHeader)) {
259     debug ("TPM data len is too small to be an Azure HCLA report");
260     return false;
261   }
262
263   debug ("Azure TPM HCLA report header sig %x ver %x type %x\n",
264          header->signature, header->version, header->report_type);
265
266   ret = (header->signature == TPM_AZURE_HCLA_SIGNATURE &&
267          header->version == TPM_AZURE_HCLA_VERSION &&
268          header->report_type == TPM_AZURE_HCLA_REPORT_TYPE_SNP);
269   debug ("Azure TPM HCLA report present ? %d\n", ret);
270
271   free(data);
272   return ret;
273 }
274
275 static void
276 cpu_sig_amd (void)
277 {
278   uint32_t eax, ebx, ecx, edx;
279   uint64_t msrval;
280
281   eax = CPUID_GET_HIGHEST_FUNCTION;
282   ebx = ecx = edx = 0;
283
284   cpuid (&eax, &ebx, &ecx, &edx);
285
286   if (eax < CPUID_AMD_GET_ENCRYPTED_MEMORY_CAPABILITIES)
287     return;
288
289   eax = CPUID_AMD_GET_ENCRYPTED_MEMORY_CAPABILITIES;
290   ebx = ecx = edx = 0;
291
292   cpuid (&eax, &ebx, &ecx, &edx);
293
294   /* bit 1 == CPU supports SEV feature
295    *
296    * Note, Azure blocks this CPUID leaf from its SEV-SNP
297    * guests, so we must fallback to probing the TPM which
298    * exposes a SEV-SNP attestation report as evidence.
299    */
300   if (!(eax & (1 << 1))) {
301     debug ("No sev in CPUID, try azure TPM NV\n");
302
303     if (cpu_sig_amd_azure()) {
304       puts ("amd-sev-snp");
305       puts ("azure-hcl");
306     } else {
307       debug("No azure TPM NV\n");
308     }
309     return;
310   }
311
312   msrval = msr (MSR_AMD64_SEV);
313
314   /* Test reverse order, since the SEV-SNP bit implies
315    * the SEV-ES bit, which implies the SEV bit */
316   if (msrval & (1 << 2)) {
317     puts ("amd-sev-snp");
318   } else if (msrval & (1 << 1)) {
319     puts ("amd-sev-es");
320   } else if (msrval & (1 << 0)) {
321     puts ("amd-sev");
322   }
323 }
324
325 static void
326 cpu_sig_intel (void)
327 {
328   uint32_t eax, ebx, ecx, edx;
329   char sig[13];
330
331   eax = CPUID_GET_HIGHEST_FUNCTION;
332   ebx = ecx = edx = 0;
333
334   cpuid (&eax, &ebx, &ecx, &edx);
335   debug ("CPUID max function: %x %x %x %x\n", eax, ebx, ecx,edx);
336
337   if (eax < CPUID_INTEL_TDX_ENUMERATION)
338     return;
339
340   memset (sig, 0, sizeof sig);
341   cpuid_leaf (CPUID_INTEL_TDX_ENUMERATION, sig, true);
342
343   if (memcmp (sig, CPUID_SIG_INTEL_TDX, sizeof(sig)) == 0)
344     puts ("intel-tdx");
345 }
346
347 static bool
348 cpu_is_hv (void)
349 {
350   uint32_t eax, ebx, ecx, edx;
351   bool is_hv;
352
353   eax = CPUID_PROCESSOR_INFO_AND_FEATURE_BITS;
354   ebx = ecx = edx = 0;
355
356   cpuid(&eax, &ebx, &ecx, &edx);
357
358   is_hv = ecx & CPUID_FEATURE_HYPERVISOR;
359
360   debug ("CPUID is hypervisor: %s\n", is_hv ? "yes" : "no");
361   return is_hv;
362 }
363
364 static void
365 cpu_sig (void)
366 {
367   char sig[13];
368
369   /* Skip everything on bare metal */
370   if (!cpu_is_hv ())
371     return;
372
373   memset (sig, 0, sizeof sig);
374   cpuid_leaf (0, sig, true);
375
376   if (memcmp (sig, CPUID_SIG_AMD, sizeof(sig)) == 0)
377     cpu_sig_amd ();
378   else if (memcmp (sig, CPUID_SIG_INTEL, sizeof(sig)) == 0)
379     cpu_sig_intel ();
380 }
381
382 #else /* !x86_64 */
383
384 static void
385 cpu_sig (void)
386 {
387   /* nothing for other architectures */
388 }
389
390 #endif
391
392 int
393 main(int argc, char **argv)
394 {
395   int c;
396
397   while (true) {
398     int option_index = 0;
399     static struct option long_options[] = {
400       {"debug", no_argument, 0, 'd' },
401       {"version", no_argument, 0, 'v' },
402       {"help", no_argument, 0, 'h'},
403       {0, 0, 0, 0 }
404     };
405
406     c = getopt_long(argc, argv, "dvh",
407                     long_options, &option_index);
408     if (c == -1)
409       break;
410
411     switch (c) {
412     case 'd':
413       dodebug = true;
414       break;
415     case 'v':
416       fprintf(stdout, "%s\n", PACKAGE_VERSION);
417       exit(EXIT_SUCCESS);
418       break;
419     case 'h':
420     default: /* '?' */
421       fprintf(c == 'h' ? stdout : stderr,
422               "Usage: %s [--debug|-d] [--help|-h] [--version|-v]\n",
423               argv[0]);
424       exit(c == 'h' ? EXIT_SUCCESS : EXIT_FAILURE);
425     }
426   }
427
428   if (!dodebug)
429     setenv("TSS2_LOG", "all+none", 1);
430
431   cpu_sig ();
432
433   exit(EXIT_SUCCESS);
434 }