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: 437eb3f7a8
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>
		
	
		
			
				
	
	
		
			236 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			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);
 | 
						|
}
 |