OvmfPkg/CpuHotplugSmm: introduce First SMI Handler for hot-added CPUs

Implement the First SMI Handler for hot-added CPUs, in NASM.

Add the interfacing C-language function that the SMM Monarch calls. This
function launches and coordinates SMBASE relocation for a hot-added CPU.

Cc: Ard Biesheuvel <ard.biesheuvel@linaro.org>
Cc: Igor Mammedov <imammedo@redhat.com>
Cc: Jiewen Yao <jiewen.yao@intel.com>
Cc: Jordan Justen <jordan.l.justen@intel.com>
Cc: Michael Kinney <michael.d.kinney@intel.com>
Cc: Philippe Mathieu-Daudé <philmd@redhat.com>
Ref: https://bugzilla.tianocore.org/show_bug.cgi?id=1512
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
Message-Id: <20200226221156.29589-13-lersek@redhat.com>
Acked-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
Tested-by: Boris Ostrovsky <boris.ostrovsky@oracle.com>
This commit is contained in:
Laszlo Ersek
2020-02-26 23:11:52 +01:00
committed by mergify[bot]
parent 63c89da242
commit 51a6fb4118
5 changed files with 376 additions and 0 deletions

View File

@@ -7,13 +7,21 @@
**/
#include <Base.h> // BASE_1MB
#include <Library/BaseLib.h> // CpuPause()
#include <Library/BaseMemoryLib.h> // CopyMem()
#include <Library/DebugLib.h> // DEBUG()
#include <Library/LocalApicLib.h> // SendInitSipiSipi()
#include <Library/SynchronizationLib.h> // InterlockedCompareExchange64()
#include <Register/Intel/SmramSaveStateMap.h> // SMM_DEFAULT_SMBASE
#include "FirstSmiHandlerContext.h" // FIRST_SMI_HANDLER_CONTEXT
#include "Smbase.h"
extern CONST UINT8 mPostSmmPen[];
extern CONST UINT16 mPostSmmPenSize;
extern CONST UINT8 mFirstSmiHandler[];
extern CONST UINT16 mFirstSmiHandlerSize;
/**
Allocate a non-SMRAM reserved memory page for the Post-SMM Pen for hot-added
@@ -108,3 +116,152 @@ SmbaseReleasePostSmmPen (
{
BootServices->FreePages (PenAddress, 1);
}
/**
Place the handler routine for the first SMIs of hot-added CPUs at
(SMM_DEFAULT_SMBASE + SMM_HANDLER_OFFSET).
Note that this effects an "SMRAM to SMRAM" copy.
Additionally, shut the APIC ID gate in FIRST_SMI_HANDLER_CONTEXT.
This function may only be called from the entry point function of the driver,
and only after PcdQ35SmramAtDefaultSmbase has been determined to be TRUE.
**/
VOID
SmbaseInstallFirstSmiHandler (
VOID
)
{
FIRST_SMI_HANDLER_CONTEXT *Context;
CopyMem ((VOID *)(UINTN)(SMM_DEFAULT_SMBASE + SMM_HANDLER_OFFSET),
mFirstSmiHandler, mFirstSmiHandlerSize);
Context = (VOID *)(UINTN)SMM_DEFAULT_SMBASE;
Context->ApicIdGate = MAX_UINT64;
}
/**
Relocate the SMBASE on a hot-added CPU. Then pen the hot-added CPU in the
normal RAM reserved memory page, set up earlier with
SmbaseAllocatePostSmmPen() and SmbaseReinstallPostSmmPen().
The SMM Monarch is supposed to call this function from the root MMI handler.
The SMM Monarch is responsible for calling SmbaseInstallFirstSmiHandler(),
SmbaseAllocatePostSmmPen(), and SmbaseReinstallPostSmmPen() before calling
this function.
If the OS maliciously boots the hot-added CPU ahead of letting the ACPI CPU
hotplug event handler broadcast the CPU hotplug MMI, then the hot-added CPU
returns to the OS rather than to the pen, upon RSM. In that case, this
function will hang forever (unless the OS happens to signal back through the
last byte of the pen page).
@param[in] ApicId The APIC ID of the hot-added CPU whose SMBASE should
be relocated.
@param[in] Smbase The new SMBASE address. The root MMI handler is
responsible for passing in a free ("unoccupied")
SMBASE address that was pre-configured by
PiSmmCpuDxeSmm in CPU_HOT_PLUG_DATA.
@param[in] PenAddress The address of the Post-SMM Pen for hot-added CPUs, as
returned by SmbaseAllocatePostSmmPen(), and installed
by SmbaseReinstallPostSmmPen().
@retval EFI_SUCCESS The SMBASE of the hot-added CPU with APIC ID
ApicId has been relocated to Smbase. The
hot-added CPU has reported back about leaving
SMM.
@retval EFI_PROTOCOL_ERROR Synchronization bug encountered around
FIRST_SMI_HANDLER_CONTEXT.ApicIdGate.
@retval EFI_INVALID_PARAMETER Smbase does not fit in 32 bits. No relocation
has been attempted.
**/
EFI_STATUS
SmbaseRelocate (
IN APIC_ID ApicId,
IN UINTN Smbase,
IN UINT32 PenAddress
)
{
EFI_STATUS Status;
volatile UINT8 *SmmVacated;
volatile FIRST_SMI_HANDLER_CONTEXT *Context;
UINT64 ExchangeResult;
if (Smbase > MAX_UINT32) {
Status = EFI_INVALID_PARAMETER;
DEBUG ((DEBUG_ERROR, "%a: ApicId=" FMT_APIC_ID " Smbase=0x%Lx: %r\n",
__FUNCTION__, ApicId, (UINT64)Smbase, Status));
return Status;
}
SmmVacated = (UINT8 *)(UINTN)PenAddress + (EFI_PAGE_SIZE - 1);
Context = (VOID *)(UINTN)SMM_DEFAULT_SMBASE;
//
// Clear AboutToLeaveSmm, so we notice when the hot-added CPU is just about
// to reach RSM, and we can proceed to polling the last byte of the reserved
// page (which could be attacked by the OS).
//
Context->AboutToLeaveSmm = 0;
//
// Clear the last byte of the reserved page, so we notice when the hot-added
// CPU checks back in from the pen.
//
*SmmVacated = 0;
//
// Boot the hot-added CPU.
//
// If the OS is benign, and so the hot-added CPU is still in RESET state,
// then the broadcast SMI is still pending for it; it will now launch
// directly into SMM.
//
// If the OS is malicious, the hot-added CPU has been booted already, and so
// it is already spinning on the APIC ID gate. In that case, the
// INIT-SIPI-SIPI below will be ignored.
//
SendInitSipiSipi (ApicId, PenAddress);
//
// Expose the desired new SMBASE value to the hot-added CPU.
//
Context->NewSmbase = (UINT32)Smbase;
//
// Un-gate SMBASE relocation for the hot-added CPU whose APIC ID is ApicId.
//
ExchangeResult = InterlockedCompareExchange64 (&Context->ApicIdGate,
MAX_UINT64, ApicId);
if (ExchangeResult != MAX_UINT64) {
Status = EFI_PROTOCOL_ERROR;
DEBUG ((DEBUG_ERROR, "%a: ApicId=" FMT_APIC_ID " ApicIdGate=0x%Lx: %r\n",
__FUNCTION__, ApicId, ExchangeResult, Status));
return Status;
}
//
// Wait until the hot-added CPU is just about to execute RSM.
//
while (Context->AboutToLeaveSmm == 0) {
CpuPause ();
}
//
// Now wait until the hot-added CPU reports back from the pen (or the OS
// attacks the last byte of the reserved page).
//
while (*SmmVacated == 0) {
CpuPause ();
}
Status = EFI_SUCCESS;
return Status;
}