Tom Lendacky 1b0db1ec87 UefiCpuPkg, OvmfPkg: Disable interrupts when using the GHCB
BZ: https://bugzilla.tianocore.org/show_bug.cgi?id=3008

The QemuFlashPtrWrite() flash services runtime uses the GHCB and VmgExit()
directly to perform the flash write when running as an SEV-ES guest. If an
interrupt arrives between VmgInit() and VmgExit(), the Dr7 read in the
interrupt handler will generate a #VC, which can overwrite information in
the GHCB that QemuFlashPtrWrite() has set. This has been seen with the
timer interrupt firing and the CpuExceptionHandlerLib library code,
UefiCpuPkg/Library/CpuExceptionHandlerLib/X64/
  Xcode5ExceptionHandlerAsm.nasm and
  ExceptionHandlerAsm.nasm
reading the Dr7 register while QemuFlashPtrWrite() is using the GHCB. In
general, it is necessary to protect the GHCB whenever it is used, not just
in QemuFlashPtrWrite().

Disable interrupts around the usage of the GHCB by modifying the VmgInit()
and VmgDone() interfaces:
- VmgInit() will take an extra parameter that is a pointer to a BOOLEAN
  that will hold the interrupt state at the time of invocation. VmgInit()
  will get and save this interrupt state before updating the GHCB.
- VmgDone() will take an extra parameter that is used to indicate whether
  interrupts are to be (re)enabled. Before exiting, VmgDone() will enable
  interrupts if that is requested.

Fixes: 437eb3f7a8db7681afe0e6064d3a8edb12abb766
Cc: Eric Dong <eric.dong@intel.com>
Cc: Ray Ni <ray.ni@intel.com>
Cc: Laszlo Ersek <lersek@redhat.com>
Cc: Rahul Kumar <rahul1.kumar@intel.com>
Cc: Jordan Justen <jordan.l.justen@intel.com>
Cc: Ard Biesheuvel <ard.biesheuvel@arm.com>
Cc: Tom Lendacky <thomas.lendacky@amd.com>
Cc: Brijesh Singh <brijesh.singh@amd.com>
Acked-by: Eric Dong <eric.dong@intel.com>
Reviewed-by: Laszlo Ersek <lersek@redhat.com>
Signed-off-by: Tom Lendacky <thomas.lendacky@amd.com>
Message-Id: <c326a4fd78253f784b42eb317589176cf7d8592a.1604685192.git.thomas.lendacky@amd.com>
2020-11-10 19:07:55 +00:00

236 lines
6.1 KiB
C

/** @file
VMGEXIT Support Library.
Copyright (C) 2020, Advanced Micro Devices, Inc. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include <Base.h>
#include <Uefi.h>
#include <Library/BaseMemoryLib.h>
#include <Library/VmgExitLib.h>
#include <Register/Amd/Msr.h>
/**
Check for VMGEXIT error
Check if the hypervisor has returned an error after completion of the VMGEXIT
by examining the SwExitInfo1 field of the GHCB.
@param[in] Ghcb A pointer to the GHCB
@retval 0 VMGEXIT succeeded.
@return Exception number to be propagated, VMGEXIT processing
did not succeed.
**/
STATIC
UINT64
VmgExitErrorCheck (
IN GHCB *Ghcb
)
{
GHCB_EVENT_INJECTION Event;
GHCB_EXIT_INFO ExitInfo;
UINT64 Status;
ExitInfo.Uint64 = Ghcb->SaveArea.SwExitInfo1;
ASSERT ((ExitInfo.Elements.Lower32Bits == 0) ||
(ExitInfo.Elements.Lower32Bits == 1));
Status = 0;
if (ExitInfo.Elements.Lower32Bits == 0) {
return Status;
}
if (ExitInfo.Elements.Lower32Bits == 1) {
ASSERT (Ghcb->SaveArea.SwExitInfo2 != 0);
//
// Check that the return event is valid
//
Event.Uint64 = Ghcb->SaveArea.SwExitInfo2;
if (Event.Elements.Valid &&
Event.Elements.Type == GHCB_EVENT_INJECTION_TYPE_EXCEPTION) {
switch (Event.Elements.Vector) {
case GP_EXCEPTION:
case UD_EXCEPTION:
//
// Use returned event as return code
//
Status = Event.Uint64;
}
}
}
if (Status == 0) {
GHCB_EVENT_INJECTION GpEvent;
GpEvent.Uint64 = 0;
GpEvent.Elements.Vector = GP_EXCEPTION;
GpEvent.Elements.Type = GHCB_EVENT_INJECTION_TYPE_EXCEPTION;
GpEvent.Elements.Valid = 1;
Status = GpEvent.Uint64;
}
return Status;
}
/**
Perform VMGEXIT.
Sets the necessary fields of the GHCB, invokes the VMGEXIT instruction and
then handles the return actions.
@param[in, out] Ghcb A pointer to the GHCB
@param[in] ExitCode VMGEXIT code to be assigned to the SwExitCode
field of the GHCB.
@param[in] ExitInfo1 VMGEXIT information to be assigned to the
SwExitInfo1 field of the GHCB.
@param[in] ExitInfo2 VMGEXIT information to be assigned to the
SwExitInfo2 field of the GHCB.
@retval 0 VMGEXIT succeeded.
@return Exception number to be propagated, VMGEXIT
processing did not succeed.
**/
UINT64
EFIAPI
VmgExit (
IN OUT GHCB *Ghcb,
IN UINT64 ExitCode,
IN UINT64 ExitInfo1,
IN UINT64 ExitInfo2
)
{
Ghcb->SaveArea.SwExitCode = ExitCode;
Ghcb->SaveArea.SwExitInfo1 = ExitInfo1;
Ghcb->SaveArea.SwExitInfo2 = ExitInfo2;
VmgSetOffsetValid (Ghcb, GhcbSwExitCode);
VmgSetOffsetValid (Ghcb, GhcbSwExitInfo1);
VmgSetOffsetValid (Ghcb, GhcbSwExitInfo2);
//
// Guest memory is used for the guest-hypervisor communication, so fence
// the invocation of the VMGEXIT instruction to ensure GHCB accesses are
// synchronized properly.
//
MemoryFence ();
AsmVmgExit ();
MemoryFence ();
return VmgExitErrorCheck (Ghcb);
}
/**
Perform pre-VMGEXIT initialization/preparation.
Performs the necessary steps in preparation for invoking VMGEXIT. Must be
called before setting any fields within the GHCB.
@param[in, out] Ghcb A pointer to the GHCB
@param[in, out] InterruptState A pointer to hold the current interrupt
state, used for restoring in VmgDone ()
**/
VOID
EFIAPI
VmgInit (
IN OUT GHCB *Ghcb,
IN OUT BOOLEAN *InterruptState
)
{
//
// Be sure that an interrupt can't cause a #VC while the GHCB is
// being used.
//
*InterruptState = GetInterruptState ();
if (*InterruptState) {
DisableInterrupts ();
}
SetMem (&Ghcb->SaveArea, sizeof (Ghcb->SaveArea), 0);
}
/**
Perform post-VMGEXIT cleanup.
Performs the necessary steps to cleanup after invoking VMGEXIT. Must be
called after obtaining needed fields within the GHCB.
@param[in, out] Ghcb A pointer to the GHCB
@param[in] InterruptState An indicator to conditionally (re)enable
interrupts
**/
VOID
EFIAPI
VmgDone (
IN OUT GHCB *Ghcb,
IN BOOLEAN InterruptState
)
{
if (InterruptState) {
EnableInterrupts ();
}
}
/**
Marks a field at the specified offset as valid in the GHCB.
The ValidBitmap area represents the areas of the GHCB that have been marked
valid. Set the bit in ValidBitmap for the input offset.
@param[in, out] Ghcb Pointer to the Guest-Hypervisor Communication Block
@param[in] Offset Qword offset in the GHCB to mark valid
**/
VOID
EFIAPI
VmgSetOffsetValid (
IN OUT GHCB *Ghcb,
IN GHCB_REGISTER Offset
)
{
UINT32 OffsetIndex;
UINT32 OffsetBit;
OffsetIndex = Offset / 8;
OffsetBit = Offset % 8;
Ghcb->SaveArea.ValidBitmap[OffsetIndex] |= (1 << OffsetBit);
}
/**
Checks if a specified offset is valid in the GHCB.
The ValidBitmap area represents the areas of the GHCB that have been marked
valid. Return whether the bit in the ValidBitmap is set for the input offset.
@param[in] Ghcb A pointer to the GHCB
@param[in] Offset Qword offset in the GHCB to mark valid
@retval TRUE Offset is marked valid in the GHCB
@retval FALSE Offset is not marked valid in the GHCB
**/
BOOLEAN
EFIAPI
VmgIsOffsetValid (
IN GHCB *Ghcb,
IN GHCB_REGISTER Offset
)
{
UINT32 OffsetIndex;
UINT32 OffsetBit;
OffsetIndex = Offset / 8;
OffsetBit = Offset % 8;
return ((Ghcb->SaveArea.ValidBitmap[OffsetIndex] & (1 << OffsetBit)) != 0);
}