123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317 |
- /** @file
- SMBASE relocation for hot-plugged CPUs.
- Copyright (c) 2020, Red Hat, Inc.
- SPDX-License-Identifier: BSD-2-Clause-Patent
- **/
- #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
- CPUs.
- This function may only be called from the entry point function of the driver.
- @param[out] PenAddress The address of the allocated (normal RAM) reserved
- page.
- @param[in] BootServices Pointer to the UEFI boot services table. Used for
- allocating the normal RAM (not SMRAM) reserved page.
- @retval EFI_SUCCESS Allocation successful.
- @retval EFI_BAD_BUFFER_SIZE The Post-SMM Pen template is not smaller than
- EFI_PAGE_SIZE.
- @return Error codes propagated from underlying services.
- DEBUG_ERROR messages have been logged. No
- resources have been allocated.
- **/
- EFI_STATUS
- SmbaseAllocatePostSmmPen (
- OUT UINT32 *PenAddress,
- IN CONST EFI_BOOT_SERVICES *BootServices
- )
- {
- EFI_STATUS Status;
- EFI_PHYSICAL_ADDRESS Address;
- //
- // The pen code must fit in one page, and the last byte must remain free for
- // signaling the SMM Monarch.
- //
- if (mPostSmmPenSize >= EFI_PAGE_SIZE) {
- Status = EFI_BAD_BUFFER_SIZE;
- DEBUG ((
- DEBUG_ERROR,
- "%a: mPostSmmPenSize=%u: %r\n",
- __FUNCTION__,
- mPostSmmPenSize,
- Status
- ));
- return Status;
- }
- Address = BASE_1MB - 1;
- Status = BootServices->AllocatePages (
- AllocateMaxAddress,
- EfiReservedMemoryType,
- 1,
- &Address
- );
- if (EFI_ERROR (Status)) {
- DEBUG ((DEBUG_ERROR, "%a: AllocatePages(): %r\n", __FUNCTION__, Status));
- return Status;
- }
- DEBUG ((DEBUG_INFO, "%a: Post-SMM Pen at 0x%Lx\n", __FUNCTION__, Address));
- *PenAddress = (UINT32)Address;
- return EFI_SUCCESS;
- }
- /**
- Copy the Post-SMM Pen template code into the reserved page allocated with
- SmbaseAllocatePostSmmPen().
- Note that this effects an "SMRAM to normal RAM" copy.
- The SMM Monarch is supposed to call this function from the root MMI handler.
- @param[in] PenAddress The allocation address returned by
- SmbaseAllocatePostSmmPen().
- **/
- VOID
- SmbaseReinstallPostSmmPen (
- IN UINT32 PenAddress
- )
- {
- CopyMem ((VOID *)(UINTN)PenAddress, mPostSmmPen, mPostSmmPenSize);
- }
- /**
- Release the reserved page allocated with SmbaseAllocatePostSmmPen().
- This function may only be called from the entry point function of the driver,
- on the error path.
- @param[in] PenAddress The allocation address returned by
- SmbaseAllocatePostSmmPen().
- @param[in] BootServices Pointer to the UEFI boot services table. Used for
- releasing the normal RAM (not SMRAM) reserved page.
- **/
- VOID
- SmbaseReleasePostSmmPen (
- IN UINT32 PenAddress,
- IN CONST EFI_BOOT_SERVICES *BootServices
- )
- {
- 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.
- //
- // There are 2*2 cases to consider:
- //
- // (1) The CPU was hot-added before the SMI was broadcast.
- //
- // (1.1) The OS is benign.
- //
- // The hot-added CPU is in RESET state, with the broadcast SMI pending
- // for it. The directed SMI below will be ignored (it's idempotent),
- // and the INIT-SIPI-SIPI will launch the CPU directly into SMM.
- //
- // (1.2) The OS is malicious.
- //
- // The hot-added CPU has been booted, by the OS. Thus, the hot-added
- // CPU is spinning on the APIC ID gate. In that case, both the SMI and
- // the INIT-SIPI-SIPI below will be ignored.
- //
- // (2) The CPU was hot-added after the SMI was broadcast.
- //
- // (2.1) The OS is benign.
- //
- // The hot-added CPU is in RESET state, with no SMI pending for it. The
- // directed SMI will latch the SMI for the CPU. Then the INIT-SIPI-SIPI
- // will launch the CPU into SMM.
- //
- // (2.2) The OS is malicious.
- //
- // The hot-added CPU is executing OS code. The directed SMI will pull
- // the hot-added CPU into SMM, where it will start spinning on the APIC
- // ID gate. The INIT-SIPI-SIPI will be ignored.
- //
- SendSmiIpi (ApicId);
- 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;
- }
|