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