]> git.proxmox.com Git - mirror_edk2.git/blob - OvmfPkg/Library/VmgExitLib/VmTdExitVeHandler.c
OvmfPkg/VmgExitLig: HALT on #VE when access to private memory
[mirror_edk2.git] / OvmfPkg / Library / VmgExitLib / VmTdExitVeHandler.c
1 /** @file
2
3 Copyright (c) 2021, Intel Corporation. All rights reserved.<BR>
4
5 SPDX-License-Identifier: BSD-2-Clause-Patent
6
7 **/
8
9 #include <Library/BaseLib.h>
10 #include <Library/DebugLib.h>
11 #include "VmTdExitHandler.h"
12 #include <Library/VmgExitLib.h>
13 #include <Library/BaseMemoryLib.h>
14 #include <IndustryStandard/Tdx.h>
15 #include <IndustryStandard/InstructionParsing.h>
16
17 typedef union {
18 struct {
19 UINT32 Eax;
20 UINT32 Edx;
21 } Regs;
22 UINT64 Val;
23 } MSR_DATA;
24
25 typedef union {
26 UINT8 Val;
27 struct {
28 UINT8 B : 1;
29 UINT8 X : 1;
30 UINT8 R : 1;
31 UINT8 W : 1;
32 } Bits;
33 } REX;
34
35 typedef union {
36 UINT8 Val;
37 struct {
38 UINT8 Rm : 3;
39 UINT8 Reg : 3;
40 UINT8 Mod : 2;
41 } Bits;
42 } MODRM;
43
44 typedef struct {
45 UINT64 Regs[4];
46 } CPUID_DATA;
47
48 /**
49 Handle an CPUID event.
50
51 Use the TDVMCALL instruction to handle cpuid #ve
52
53 @param[in, out] Regs x64 processor context
54 @param[in] Veinfo VE Info
55
56 @retval 0 Event handled successfully
57 @return New exception value to propagate
58 **/
59 STATIC
60 UINT64
61 EFIAPI
62 CpuIdExit (
63 IN EFI_SYSTEM_CONTEXT_X64 *Regs,
64 IN TDCALL_VEINFO_RETURN_DATA *Veinfo
65 )
66 {
67 CPUID_DATA CpuIdData;
68 UINT64 Status;
69
70 Status = TdVmCallCpuid (Regs->Rax, Regs->Rcx, &CpuIdData);
71
72 if (Status == 0) {
73 Regs->Rax = CpuIdData.Regs[0];
74 Regs->Rbx = CpuIdData.Regs[1];
75 Regs->Rcx = CpuIdData.Regs[2];
76 Regs->Rdx = CpuIdData.Regs[3];
77 }
78
79 return Status;
80 }
81
82 /**
83 Handle an IO event.
84
85 Use the TDVMCALL instruction to handle either an IO read or an IO write.
86
87 @param[in, out] Regs x64 processor context
88 @param[in] Veinfo VE Info
89
90 @retval 0 Event handled successfully
91 @return New exception value to propagate
92 **/
93 STATIC
94 UINT64
95 EFIAPI
96 IoExit (
97 IN OUT EFI_SYSTEM_CONTEXT_X64 *Regs,
98 IN TDCALL_VEINFO_RETURN_DATA *Veinfo
99 )
100 {
101 BOOLEAN Write;
102 UINTN Size;
103 UINTN Port;
104 UINT64 Val;
105 UINT64 RepCnt;
106 UINT64 Status;
107
108 Val = 0;
109 Write = Veinfo->ExitQualification.Io.Direction ? FALSE : TRUE;
110 Size = Veinfo->ExitQualification.Io.Size + 1;
111 Port = Veinfo->ExitQualification.Io.Port;
112
113 if (Veinfo->ExitQualification.Io.String) {
114 //
115 // If REP is set, get rep-cnt from Rcx
116 //
117 RepCnt = Veinfo->ExitQualification.Io.Rep ? Regs->Rcx : 1;
118
119 while (RepCnt) {
120 Val = 0;
121 if (Write == TRUE) {
122 CopyMem (&Val, (VOID *)Regs->Rsi, Size);
123 Regs->Rsi += Size;
124 }
125
126 Status = TdVmCall (EXIT_REASON_IO_INSTRUCTION, Size, Write, Port, Val, (Write ? NULL : &Val));
127 if (Status != 0) {
128 break;
129 }
130
131 if (Write == FALSE) {
132 CopyMem ((VOID *)Regs->Rdi, &Val, Size);
133 Regs->Rdi += Size;
134 }
135
136 if (Veinfo->ExitQualification.Io.Rep) {
137 Regs->Rcx -= 1;
138 }
139
140 RepCnt -= 1;
141 }
142 } else {
143 if (Write == TRUE) {
144 CopyMem (&Val, (VOID *)&Regs->Rax, Size);
145 }
146
147 Status = TdVmCall (EXIT_REASON_IO_INSTRUCTION, Size, Write, Port, Val, (Write ? NULL : &Val));
148 if ((Status == 0) && (Write == FALSE)) {
149 CopyMem ((VOID *)&Regs->Rax, &Val, Size);
150 }
151 }
152
153 return Status;
154 }
155
156 /**
157 Handle an READ MSR event.
158
159 Use the TDVMCALL instruction to handle msr read
160
161 @param[in, out] Regs x64 processor context
162 @param[in] Veinfo VE Info
163
164 @retval 0 Event handled successfully
165 @return New exception value to propagate
166 **/
167 STATIC
168 UINT64
169 ReadMsrExit (
170 IN OUT EFI_SYSTEM_CONTEXT_X64 *Regs,
171 IN TDCALL_VEINFO_RETURN_DATA *Veinfo
172 )
173 {
174 MSR_DATA Data;
175 UINT64 Status;
176
177 Status = TdVmCall (EXIT_REASON_MSR_READ, Regs->Rcx, 0, 0, 0, &Data);
178 if (Status == 0) {
179 Regs->Rax = Data.Regs.Eax;
180 Regs->Rdx = Data.Regs.Edx;
181 }
182
183 return Status;
184 }
185
186 /**
187 Handle an WRITE MSR event.
188
189 Use the TDVMCALL instruction to handle msr write
190
191 @param[in, out] Regs x64 processor context
192 @param[in] Veinfo VE Info
193
194 @retval 0 Event handled successfully
195 @return New exception value to propagate
196 **/
197 STATIC
198 UINT64
199 WriteMsrExit (
200 IN OUT EFI_SYSTEM_CONTEXT_X64 *Regs,
201 IN TDCALL_VEINFO_RETURN_DATA *Veinfo
202 )
203 {
204 UINT64 Status;
205 MSR_DATA Data;
206
207 Data.Regs.Eax = (UINT32)Regs->Rax;
208 Data.Regs.Edx = (UINT32)Regs->Rdx;
209
210 Status = TdVmCall (EXIT_REASON_MSR_WRITE, Regs->Rcx, Data.Val, 0, 0, NULL);
211
212 return Status;
213 }
214
215 STATIC
216 VOID
217 EFIAPI
218 TdxDecodeInstruction (
219 IN UINT8 *Rip
220 )
221 {
222 UINTN i;
223
224 DEBUG ((DEBUG_INFO, "TDX: #TD[EPT] instruction (%p):", Rip));
225 for (i = 0; i < 15; i++) {
226 DEBUG ((DEBUG_INFO, "%02x:", Rip[i]));
227 }
228
229 DEBUG ((DEBUG_INFO, "\n"));
230 }
231
232 #define TDX_DECODER_BUG_ON(x) \
233 if ((x)) { \
234 TdxDecodeInstruction(Rip); \
235 TdVmCall(TDVMCALL_HALT, 0, 0, 0, 0, 0); \
236 }
237
238 STATIC
239 UINT64 *
240 EFIAPI
241 GetRegFromContext (
242 IN EFI_SYSTEM_CONTEXT_X64 *Regs,
243 IN UINTN RegIndex
244 )
245 {
246 switch (RegIndex) {
247 case 0: return &Regs->Rax;
248 break;
249 case 1: return &Regs->Rcx;
250 break;
251 case 2: return &Regs->Rdx;
252 break;
253 case 3: return &Regs->Rbx;
254 break;
255 case 4: return &Regs->Rsp;
256 break;
257 case 5: return &Regs->Rbp;
258 break;
259 case 6: return &Regs->Rsi;
260 break;
261 case 7: return &Regs->Rdi;
262 break;
263 case 8: return &Regs->R8;
264 break;
265 case 9: return &Regs->R9;
266 break;
267 case 10: return &Regs->R10;
268 break;
269 case 11: return &Regs->R11;
270 break;
271 case 12: return &Regs->R12;
272 break;
273 case 13: return &Regs->R13;
274 break;
275 case 14: return &Regs->R14;
276 break;
277 case 15: return &Regs->R15;
278 break;
279 }
280
281 return NULL;
282 }
283
284 /**
285 Handle an MMIO event.
286
287 Use the TDVMCALL instruction to handle either an mmio read or an mmio write.
288
289 @param[in, out] Regs x64 processor context
290 @param[in] Veinfo VE Info
291
292 @retval 0 Event handled successfully
293 @return New exception value to propagate
294 **/
295 STATIC
296 INTN
297 EFIAPI
298 MmioExit (
299 IN OUT EFI_SYSTEM_CONTEXT_X64 *Regs,
300 IN TDCALL_VEINFO_RETURN_DATA *Veinfo
301 )
302 {
303 UINT64 Status;
304 UINT32 MmioSize;
305 UINT32 RegSize;
306 UINT8 OpCode;
307 BOOLEAN SeenRex;
308 UINT64 *Reg;
309 UINT8 *Rip;
310 UINT64 Val;
311 UINT32 OpSize;
312 MODRM ModRm;
313 REX Rex;
314 TD_RETURN_DATA TdReturnData;
315 UINT8 Gpaw;
316 UINT64 TdSharedPageMask;
317
318 Rip = (UINT8 *)Regs->Rip;
319 Val = 0;
320 Rex.Val = 0;
321 SeenRex = FALSE;
322
323 Status = TdCall (TDCALL_TDINFO, 0, 0, 0, &TdReturnData);
324 if (Status == TDX_EXIT_REASON_SUCCESS) {
325 Gpaw = (UINT8)(TdReturnData.TdInfo.Gpaw & 0x3f);
326 TdSharedPageMask = 1ULL << (Gpaw - 1);
327 } else {
328 DEBUG ((DEBUG_ERROR, "TDCALL failed with status=%llx\n", Status));
329 return Status;
330 }
331
332 if ((Veinfo->GuestPA & TdSharedPageMask) == 0) {
333 DEBUG ((DEBUG_ERROR, "EPT-violation #VE on private memory is not allowed!"));
334 TdVmCall (TDVMCALL_HALT, 0, 0, 0, 0, 0);
335 CpuDeadLoop ();
336 }
337
338 //
339 // Default to 32bit transfer
340 //
341 OpSize = 4;
342
343 do {
344 OpCode = *Rip++;
345 if (OpCode == 0x66) {
346 OpSize = 2;
347 } else if ((OpCode == 0x64) || (OpCode == 0x65) || (OpCode == 0x67)) {
348 continue;
349 } else if ((OpCode >= 0x40) && (OpCode <= 0x4f)) {
350 SeenRex = TRUE;
351 Rex.Val = OpCode;
352 } else {
353 break;
354 }
355 } while (TRUE);
356
357 //
358 // We need to have at least 2 more bytes for this instruction
359 //
360 TDX_DECODER_BUG_ON (((UINT64)Rip - Regs->Rip) > 13);
361
362 OpCode = *Rip++;
363 //
364 // Two-byte opecode, get next byte
365 //
366 if (OpCode == 0x0F) {
367 OpCode = *Rip++;
368 }
369
370 switch (OpCode) {
371 case 0x88:
372 case 0x8A:
373 case 0xB6:
374 MmioSize = 1;
375 break;
376 case 0xB7:
377 MmioSize = 2;
378 break;
379 default:
380 MmioSize = Rex.Bits.W ? 8 : OpSize;
381 break;
382 }
383
384 /* Punt on AH/BH/CH/DH unless it shows up. */
385 ModRm.Val = *Rip++;
386 TDX_DECODER_BUG_ON (MmioSize == 1 && ModRm.Bits.Reg > 4 && !SeenRex && OpCode != 0xB6);
387 Reg = GetRegFromContext (Regs, ModRm.Bits.Reg | ((int)Rex.Bits.R << 3));
388 TDX_DECODER_BUG_ON (!Reg);
389
390 if (ModRm.Bits.Rm == 4) {
391 ++Rip; /* SIB byte */
392 }
393
394 if ((ModRm.Bits.Mod == 2) || ((ModRm.Bits.Mod == 0) && (ModRm.Bits.Rm == 5))) {
395 Rip += 4; /* DISP32 */
396 } else if (ModRm.Bits.Mod == 1) {
397 ++Rip; /* DISP8 */
398 }
399
400 switch (OpCode) {
401 case 0x88:
402 case 0x89:
403 CopyMem ((void *)&Val, Reg, MmioSize);
404 Status = TdVmCall (TDVMCALL_MMIO, MmioSize, 1, Veinfo->GuestPA, Val, 0);
405 break;
406 case 0xC7:
407 CopyMem ((void *)&Val, Rip, OpSize);
408 Status = TdVmCall (TDVMCALL_MMIO, MmioSize, 1, Veinfo->GuestPA, Val, 0);
409 Rip += OpSize;
410 default:
411 //
412 // 32-bit write registers are zero extended to the full register
413 // Hence 'MOVZX r[32/64], r/m16' is
414 // hardcoded to reg size 8, and the straight MOV case has a reg
415 // size of 8 in the 32-bit read case.
416 //
417 switch (OpCode) {
418 case 0xB6:
419 RegSize = Rex.Bits.W ? 8 : OpSize;
420 break;
421 case 0xB7:
422 RegSize = 8;
423 break;
424 default:
425 RegSize = MmioSize == 4 ? 8 : MmioSize;
426 break;
427 }
428
429 Status = TdVmCall (TDVMCALL_MMIO, MmioSize, 0, Veinfo->GuestPA, 0, &Val);
430 if (Status == 0) {
431 ZeroMem (Reg, RegSize);
432 CopyMem (Reg, (void *)&Val, MmioSize);
433 }
434 }
435
436 if (Status == 0) {
437 TDX_DECODER_BUG_ON (((UINT64)Rip - Regs->Rip) > 15);
438
439 //
440 // We change instruction length to reflect true size so handler can
441 // bump rip
442 //
443 Veinfo->ExitInstructionLength = (UINT32)((UINT64)Rip - Regs->Rip);
444 }
445
446 return Status;
447 }
448
449 /**
450 Handle a #VE exception.
451
452 Performs the necessary processing to handle a #VE exception.
453
454 @param[in, out] ExceptionType Pointer to an EFI_EXCEPTION_TYPE to be set
455 as value to use on error.
456 @param[in, out] SystemContext Pointer to EFI_SYSTEM_CONTEXT
457
458 @retval EFI_SUCCESS Exception handled
459 @retval EFI_UNSUPPORTED #VE not supported, (new) exception value to
460 propagate provided
461 @retval EFI_PROTOCOL_ERROR #VE handling failed, (new) exception value to
462 propagate provided
463
464 **/
465 EFI_STATUS
466 EFIAPI
467 VmTdExitHandleVe (
468 IN OUT EFI_EXCEPTION_TYPE *ExceptionType,
469 IN OUT EFI_SYSTEM_CONTEXT SystemContext
470 )
471 {
472 UINT64 Status;
473 TD_RETURN_DATA ReturnData;
474 EFI_SYSTEM_CONTEXT_X64 *Regs;
475
476 Regs = SystemContext.SystemContextX64;
477 Status = TdCall (TDCALL_TDGETVEINFO, 0, 0, 0, &ReturnData);
478 ASSERT (Status == 0);
479 if (Status != 0) {
480 DEBUG ((DEBUG_ERROR, "#VE happened. TDGETVEINFO failed with Status = 0x%llx\n", Status));
481 TdVmCall (TDVMCALL_HALT, 0, 0, 0, 0, 0);
482 }
483
484 switch (ReturnData.VeInfo.ExitReason) {
485 case EXIT_REASON_CPUID:
486 Status = CpuIdExit (Regs, &ReturnData.VeInfo);
487 DEBUG ((
488 DEBUG_VERBOSE,
489 "CPUID #VE happened, ExitReasion is %d, ExitQualification = 0x%x.\n",
490 ReturnData.VeInfo.ExitReason,
491 ReturnData.VeInfo.ExitQualification.Val
492 ));
493 break;
494
495 case EXIT_REASON_HLT:
496 Status = TdVmCall (EXIT_REASON_HLT, 0, 0, 0, 0, 0);
497 break;
498
499 case EXIT_REASON_IO_INSTRUCTION:
500 Status = IoExit (Regs, &ReturnData.VeInfo);
501 DEBUG ((
502 DEBUG_VERBOSE,
503 "IO_Instruction #VE happened, ExitReasion is %d, ExitQualification = 0x%x.\n",
504 ReturnData.VeInfo.ExitReason,
505 ReturnData.VeInfo.ExitQualification.Val
506 ));
507 break;
508
509 case EXIT_REASON_MSR_READ:
510 Status = ReadMsrExit (Regs, &ReturnData.VeInfo);
511 DEBUG ((
512 DEBUG_VERBOSE,
513 "RDMSR #VE happened, ExitReasion is %d, ExitQualification = 0x%x. Regs->Rcx=0x%llx, Status = 0x%llx\n",
514 ReturnData.VeInfo.ExitReason,
515 ReturnData.VeInfo.ExitQualification.Val,
516 Regs->Rcx,
517 Status
518 ));
519 break;
520
521 case EXIT_REASON_MSR_WRITE:
522 Status = WriteMsrExit (Regs, &ReturnData.VeInfo);
523 DEBUG ((
524 DEBUG_VERBOSE,
525 "WRMSR #VE happened, ExitReasion is %d, ExitQualification = 0x%x. Regs->Rcx=0x%llx, Status = 0x%llx\n",
526 ReturnData.VeInfo.ExitReason,
527 ReturnData.VeInfo.ExitQualification.Val,
528 Regs->Rcx,
529 Status
530 ));
531 break;
532
533 case EXIT_REASON_EPT_VIOLATION:
534 Status = MmioExit (Regs, &ReturnData.VeInfo);
535 DEBUG ((
536 DEBUG_VERBOSE,
537 "MMIO #VE happened, ExitReasion is %d, ExitQualification = 0x%x.\n",
538 ReturnData.VeInfo.ExitReason,
539 ReturnData.VeInfo.ExitQualification.Val
540 ));
541 break;
542
543 case EXIT_REASON_VMCALL:
544 case EXIT_REASON_MWAIT_INSTRUCTION:
545 case EXIT_REASON_MONITOR_INSTRUCTION:
546 case EXIT_REASON_WBINVD:
547 case EXIT_REASON_RDPMC:
548 /* Handle as nops. */
549 break;
550
551 default:
552 DEBUG ((
553 DEBUG_ERROR,
554 "Unsupported #VE happened, ExitReason is %d, ExitQualification = 0x%x.\n",
555 ReturnData.VeInfo.ExitReason,
556 ReturnData.VeInfo.ExitQualification.Val
557 ));
558
559 ASSERT (FALSE);
560 CpuDeadLoop ();
561 }
562
563 if (Status) {
564 DEBUG ((
565 DEBUG_ERROR,
566 "#VE Error (0x%llx) returned from host, ExitReason is %d, ExitQualification = 0x%x.\n",
567 Status,
568 ReturnData.VeInfo.ExitReason,
569 ReturnData.VeInfo.ExitQualification.Val
570 ));
571
572 TdVmCall (TDVMCALL_HALT, 0, 0, 0, 0, 0);
573 }
574
575 SystemContext.SystemContextX64->Rip += ReturnData.VeInfo.ExitInstructionLength;
576 return EFI_SUCCESS;
577 }