OvmfPkg: Harden #VC instruction emulation somewhat (CVE-2024-25742)

Ensure that when a #VC exception happens, the instruction at the
instruction pointer matches the instruction that is expected given the
error code. This is to mitigate the ahoi WeSee attack [1] that could
allow hypervisors to breach integrity and confidentiality of the
firmware by maliciously injecting interrupts. This change is a
translated version of a linux patch e3ef461af35a ("x86/sev: Harden #VC
instruction emulation somewhat")

[1] https://ahoi-attacks.github.io/wesee/

Cc: Borislav Petkov (AMD) <bp@alien8.de>
Cc: Tom Lendacky <thomas.lendacky@amd.com>
Signed-off-by: Adam Dunlap <acdunlap@google.com>
Reviewed-by: Tom Lendacky <thomas.lendacky@amd.com>
Reviewed-by: Gerd Hoffmann <kraxel@redhat.com>
This commit is contained in:
Adam Dunlap 2024-04-19 11:21:46 -07:00 committed by mergify[bot]
parent 86c8d69146
commit e3fa6986ae
1 changed files with 173 additions and 11 deletions

View File

@ -533,8 +533,6 @@ MwaitExit (
IN CC_INSTRUCTION_DATA *InstructionData
)
{
CcDecodeModRm (Regs, InstructionData);
Ghcb->SaveArea.Rax = Regs->Rax;
CcExitVmgSetOffsetValid (Ghcb, GhcbRax);
Ghcb->SaveArea.Rcx = Regs->Rcx;
@ -565,8 +563,6 @@ MonitorExit (
IN CC_INSTRUCTION_DATA *InstructionData
)
{
CcDecodeModRm (Regs, InstructionData);
Ghcb->SaveArea.Rax = Regs->Rax; // Identity mapped, so VA = PA
CcExitVmgSetOffsetValid (Ghcb, GhcbRax);
Ghcb->SaveArea.Rcx = Regs->Rcx;
@ -671,8 +667,6 @@ VmmCallExit (
{
UINT64 Status;
CcDecodeModRm (Regs, InstructionData);
Ghcb->SaveArea.Rax = Regs->Rax;
CcExitVmgSetOffsetValid (Ghcb, GhcbRax);
Ghcb->SaveArea.Cpl = (UINT8)(Regs->Cs & 0x3);
@ -1628,8 +1622,6 @@ Dr7WriteExit (
Ext = &InstructionData->Ext;
SevEsData = (SEV_ES_PER_CPU_DATA *)(Ghcb + 1);
CcDecodeModRm (Regs, InstructionData);
//
// MOV DRn always treats MOD == 3 no matter how encoded
//
@ -1680,8 +1672,6 @@ Dr7ReadExit (
Ext = &InstructionData->Ext;
SevEsData = (SEV_ES_PER_CPU_DATA *)(Ghcb + 1);
CcDecodeModRm (Regs, InstructionData);
//
// MOV DRn always treats MOD == 3 no matter how encoded
//
@ -1696,6 +1686,170 @@ Dr7ReadExit (
return 0;
}
/**
Check that the opcode matches the exit code for a #VC.
Each exit code should only be raised while executing certain instructions.
Verify that rIP points to a correct instruction based on the exit code to
protect against maliciously injected interrupts via the hypervisor. If it does
not, report an unsupported event to the hypervisor.
Decodes the ModRm byte into InstructionData if necessary.
@param[in, out] Ghcb Pointer to the Guest-Hypervisor Communication
Block
@param[in, out] Regs x64 processor context
@param[in, out] InstructionData Instruction parsing context
@param[in] ExitCode Exit code given by #VC.
@retval 0 No problems detected.
@return New exception value to propagate
**/
STATIC
UINT64
VcCheckOpcodeBytes (
IN OUT GHCB *Ghcb,
IN OUT EFI_SYSTEM_CONTEXT_X64 *Regs,
IN OUT CC_INSTRUCTION_DATA *InstructionData,
IN UINT64 ExitCode
)
{
UINT8 OpCode;
//
// Expected opcodes are either 1 or 2 bytes. If they are 2 bytes, they always
// start with TWO_BYTE_OPCODE_ESCAPE (0x0f), so skip over that.
//
OpCode = *(InstructionData->OpCodes);
if (OpCode == TWO_BYTE_OPCODE_ESCAPE) {
OpCode = *(InstructionData->OpCodes + 1);
}
switch (ExitCode) {
case SVM_EXIT_IOIO_PROT:
case SVM_EXIT_NPF:
/* handled separately */
return 0;
case SVM_EXIT_CPUID:
if (OpCode == 0xa2) {
return 0;
}
break;
case SVM_EXIT_INVD:
if (OpCode == 0x08) {
return 0;
}
break;
case SVM_EXIT_MONITOR:
CcDecodeModRm (Regs, InstructionData);
if ((OpCode == 0x01) &&
( (InstructionData->ModRm.Uint8 == 0xc8) /* MONITOR */
|| (InstructionData->ModRm.Uint8 == 0xfa))) /* MONITORX */
{
return 0;
}
break;
case SVM_EXIT_MWAIT:
CcDecodeModRm (Regs, InstructionData);
if ((OpCode == 0x01) &&
( (InstructionData->ModRm.Uint8 == 0xc9) /* MWAIT */
|| (InstructionData->ModRm.Uint8 == 0xfb))) /* MWAITX */
{
return 0;
}
break;
case SVM_EXIT_MSR:
/* RDMSR */
if ((OpCode == 0x32) ||
/* WRMSR */
(OpCode == 0x30))
{
return 0;
}
break;
case SVM_EXIT_RDPMC:
if (OpCode == 0x33) {
return 0;
}
break;
case SVM_EXIT_RDTSC:
if (OpCode == 0x31) {
return 0;
}
break;
case SVM_EXIT_RDTSCP:
CcDecodeModRm (Regs, InstructionData);
if ((OpCode == 0x01) && (InstructionData->ModRm.Uint8 == 0xf9)) {
return 0;
}
break;
case SVM_EXIT_DR7_READ:
CcDecodeModRm (Regs, InstructionData);
if ((OpCode == 0x21) &&
(InstructionData->Ext.ModRm.Reg == 7))
{
return 0;
}
break;
case SVM_EXIT_VMMCALL:
CcDecodeModRm (Regs, InstructionData);
if ((OpCode == 0x01) && (InstructionData->ModRm.Uint8 == 0xd9)) {
return 0;
}
break;
case SVM_EXIT_DR7_WRITE:
CcDecodeModRm (Regs, InstructionData);
if ((OpCode == 0x23) &&
(InstructionData->Ext.ModRm.Reg == 7))
{
return 0;
}
break;
case SVM_EXIT_WBINVD:
if (OpCode == 0x9) {
return 0;
}
break;
default:
break;
}
return UnsupportedExit (Ghcb, Regs, InstructionData);
}
/**
Handle a #VC exception.
@ -1798,7 +1952,15 @@ InternalVmgExitHandleVc (
CcInitInstructionData (&InstructionData, Ghcb, Regs);
Status = NaeExit (Ghcb, Regs, &InstructionData);
Status = VcCheckOpcodeBytes (Ghcb, Regs, &InstructionData, ExitCode);
//
// If the opcode does not match the exit code, do not process the exception
//
if (Status == 0) {
Status = NaeExit (Ghcb, Regs, &InstructionData);
}
if (Status == 0) {
Regs->Rip += CcInstructionLength (&InstructionData);
} else {