On Wed, 26 Feb 2020 at 23:12, Laszlo Ersek <ler...@redhat.com> wrote: > > 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.biesheu...@linaro.org> > Cc: Igor Mammedov <imamm...@redhat.com> > Cc: Jiewen Yao <jiewen....@intel.com> > Cc: Jordan Justen <jordan.l.jus...@intel.com> > Cc: Michael Kinney <michael.d.kin...@intel.com> > Cc: Philippe Mathieu-Daudé <phi...@redhat.com> > Ref: https://bugzilla.tianocore.org/show_bug.cgi?id=1512 > Signed-off-by: Laszlo Ersek <ler...@redhat.com>
Acked-by: Ard Biesheuvel <ard.biesheu...@linaro.org> > --- > > Notes: > v2: > > - implement the combined approach described here: > > > 111145fc-be3d-2a9a-a126-c14345a8a8a4@redhat.com">http://mid.mail-archive.com/111145fc-be3d-2a9a-a126-c14345a8a8a4@redhat.com > https://edk2.groups.io/g/devel/message/54754 > > by introducing "FIRST_SMI_HANDLER_CONTEXT.AboutToLeaveSmm". > > OvmfPkg/CpuHotplugSmm/CpuHotplugSmm.inf | 4 + > OvmfPkg/CpuHotplugSmm/FirstSmiHandlerContext.h | 47 ++++++ > OvmfPkg/CpuHotplugSmm/Smbase.h | 14 ++ > OvmfPkg/CpuHotplugSmm/FirstSmiHandler.nasm | 154 +++++++++++++++++++ > OvmfPkg/CpuHotplugSmm/Smbase.c | 157 ++++++++++++++++++++ > 5 files changed, 376 insertions(+) > > diff --git a/OvmfPkg/CpuHotplugSmm/CpuHotplugSmm.inf > b/OvmfPkg/CpuHotplugSmm/CpuHotplugSmm.inf > index bf4162299c7c..04322b0d7855 100644 > --- a/OvmfPkg/CpuHotplugSmm/CpuHotplugSmm.inf > +++ b/OvmfPkg/CpuHotplugSmm/CpuHotplugSmm.inf > @@ -5,56 +5,60 @@ > # > # SPDX-License-Identifier: BSD-2-Clause-Patent > ## > > [Defines] > INF_VERSION = 1.29 > PI_SPECIFICATION_VERSION = 0x00010046 # > PI-1.7.0 > BASE_NAME = CpuHotplugSmm > FILE_GUID = 84EEA114-C6BE-4445-8F90-51D97863E363 > MODULE_TYPE = DXE_SMM_DRIVER > ENTRY_POINT = CpuHotplugEntry > > # > # The following information is for reference only and not required by the > build > # tools. > # > # VALID_ARCHITECTURES = IA32 X64 > # > > [Sources] > ApicId.h > CpuHotplug.c > + FirstSmiHandler.nasm > + FirstSmiHandlerContext.h > PostSmmPen.nasm > QemuCpuhp.c > QemuCpuhp.h > Smbase.c > Smbase.h > > [Packages] > MdePkg/MdePkg.dec > OvmfPkg/OvmfPkg.dec > UefiCpuPkg/UefiCpuPkg.dec > > [LibraryClasses] > BaseLib > BaseMemoryLib > DebugLib > + LocalApicLib > MmServicesTableLib > PcdLib > SafeIntLib > + SynchronizationLib > UefiDriverEntryPoint > > [Protocols] > gEfiMmCpuIoProtocolGuid ## > CONSUMES > gEfiSmmCpuServiceProtocolGuid ## > CONSUMES > > [Pcd] > gUefiCpuPkgTokenSpaceGuid.PcdCpuHotPlugDataAddress ## > CONSUMES > gUefiOvmfPkgTokenSpaceGuid.PcdQ35SmramAtDefaultSmbase ## > CONSUMES > > [FeaturePcd] > gUefiOvmfPkgTokenSpaceGuid.PcdSmmSmramRequire ## > CONSUMES > > [Depex] > gEfiMmCpuIoProtocolGuid AND > gEfiSmmCpuServiceProtocolGuid > diff --git a/OvmfPkg/CpuHotplugSmm/FirstSmiHandlerContext.h > b/OvmfPkg/CpuHotplugSmm/FirstSmiHandlerContext.h > new file mode 100644 > index 000000000000..029de4cdea35 > --- /dev/null > +++ b/OvmfPkg/CpuHotplugSmm/FirstSmiHandlerContext.h > @@ -0,0 +1,47 @@ > +/** @file > + Define the FIRST_SMI_HANDLER_CONTEXT structure, which is an exchange area > + between the SMM Monarch and the hot-added CPU, for relocating the SMBASE of > + the hot-added CPU. > + > + Copyright (c) 2020, Red Hat, Inc. > + > + SPDX-License-Identifier: BSD-2-Clause-Patent > +**/ > + > +#ifndef FIRST_SMI_HANDLER_CONTEXT_H_ > +#define FIRST_SMI_HANDLER_CONTEXT_H_ > + > +// > +// The following structure is used to communicate between the SMM Monarch > +// (running the root MMI handler) and the hot-added CPU (handling its first > +// SMI). It is placed at SMM_DEFAULT_SMBASE, which is in SMRAM under QEMU's > +// "SMRAM at default SMBASE" feature. > +// > +#pragma pack (1) > +typedef struct { > + // > + // When ApicIdGate is MAX_UINT64, then no hot-added CPU may proceed with > + // SMBASE relocation. > + // > + // Otherwise, the hot-added CPU whose APIC ID equals ApicIdGate may proceed > + // with SMBASE relocation. > + // > + // This field is intentionally wider than APIC_ID (UINT32) because we need > a > + // "gate locked" value that is different from all possible APIC_IDs. > + // > + UINT64 ApicIdGate; > + // > + // The new SMBASE value for the hot-added CPU to set in the SMRAM Save > State > + // Map, before leaving SMM with the RSM instruction. > + // > + UINT32 NewSmbase; > + // > + // The hot-added CPU sets this field to 1 right before executing the RSM > + // instruction. This tells the SMM Monarch to proceed to polling the last > + // byte of the normal RAM reserved page (Post-SMM Pen). > + // > + UINT8 AboutToLeaveSmm; > +} FIRST_SMI_HANDLER_CONTEXT; > +#pragma pack () > + > +#endif // FIRST_SMI_HANDLER_CONTEXT_H_ > diff --git a/OvmfPkg/CpuHotplugSmm/Smbase.h b/OvmfPkg/CpuHotplugSmm/Smbase.h > index cb5aed98cdd3..e73730d19926 100644 > --- a/OvmfPkg/CpuHotplugSmm/Smbase.h > +++ b/OvmfPkg/CpuHotplugSmm/Smbase.h > @@ -1,32 +1,46 @@ > /** @file > SMBASE relocation for hot-plugged CPUs. > > Copyright (c) 2020, Red Hat, Inc. > > SPDX-License-Identifier: BSD-2-Clause-Patent > **/ > > #ifndef SMBASE_H_ > #define SMBASE_H_ > > #include <Uefi/UefiBaseType.h> // EFI_STATUS > #include <Uefi/UefiSpec.h> // EFI_BOOT_SERVICES > > +#include "ApicId.h" // APIC_ID > + > EFI_STATUS > SmbaseAllocatePostSmmPen ( > OUT UINT32 *PenAddress, > IN CONST EFI_BOOT_SERVICES *BootServices > ); > > VOID > SmbaseReinstallPostSmmPen ( > IN UINT32 PenAddress > ); > > VOID > SmbaseReleasePostSmmPen ( > IN UINT32 PenAddress, > IN CONST EFI_BOOT_SERVICES *BootServices > ); > > +VOID > +SmbaseInstallFirstSmiHandler ( > + VOID > + ); > + > +EFI_STATUS > +SmbaseRelocate ( > + IN APIC_ID ApicId, > + IN UINTN Smbase, > + IN UINT32 PenAddress > + ); > + > #endif // SMBASE_H_ > diff --git a/OvmfPkg/CpuHotplugSmm/FirstSmiHandler.nasm > b/OvmfPkg/CpuHotplugSmm/FirstSmiHandler.nasm > new file mode 100644 > index 000000000000..5399b5fa4387 > --- /dev/null > +++ b/OvmfPkg/CpuHotplugSmm/FirstSmiHandler.nasm > @@ -0,0 +1,154 @@ > +;------------------------------------------------------------------------------ > +; @file > +; Relocate the SMBASE on a hot-added CPU when it services its first SMI. > +; > +; Copyright (c) 2020, Red Hat, Inc. > +; > +; SPDX-License-Identifier: BSD-2-Clause-Patent > +; > +; The routine runs on the hot-added CPU in the following "big real mode", > +; 16-bit environment; per "SMI HANDLER EXECUTION ENVIRONMENT" in the Intel > SDM > +; (table "Processor Register Initialization in SMM"): > +; > +; - CS selector: 0x3000 (most significant 16 bits of SMM_DEFAULT_SMBASE). > +; > +; - CS limit: 0xFFFF_FFFF. > +; > +; - CS base: SMM_DEFAULT_SMBASE (0x3_0000). > +; > +; - IP: SMM_HANDLER_OFFSET (0x8000). > +; > +; - ES, SS, DS, FS, GS selectors: 0. > +; > +; - ES, SS, DS, FS, GS limits: 0xFFFF_FFFF. > +; > +; - ES, SS, DS, FS, GS bases: 0. > +; > +; - Operand-size and address-size override prefixes can be used to access > the > +; address space beyond 1MB. > +;------------------------------------------------------------------------------ > + > +SECTION .data > +BITS 16 > + > +; > +; Bring in SMM_DEFAULT_SMBASE from > +; "MdePkg/Include/Register/Intel/SmramSaveStateMap.h". > +; > +SMM_DEFAULT_SMBASE: equ 0x3_0000 > + > +; > +; Field offsets in FIRST_SMI_HANDLER_CONTEXT, which resides at > +; SMM_DEFAULT_SMBASE. > +; > +ApicIdGate: equ 0 ; UINT64 > +NewSmbase: equ 8 ; UINT32 > +AboutToLeaveSmm: equ 12 ; UINT8 > + > +; > +; SMRAM Save State Map field offsets, per the AMD (not Intel) layout that > QEMU > +; implements. Relative to SMM_DEFAULT_SMBASE. > +; > +SaveStateRevId: equ 0xFEFC ; UINT32 > +SaveStateSmbase: equ 0xFEF8 ; UINT32 > +SaveStateSmbase64: equ 0xFF00 ; UINT32 > + > +; > +; CPUID constants, from "MdePkg/Include/Register/Intel/Cpuid.h". > +; > +CPUID_SIGNATURE: equ 0x00 > +CPUID_EXTENDED_TOPOLOGY: equ 0x0B > +CPUID_VERSION_INFO: equ 0x01 > + > +GLOBAL ASM_PFX (mFirstSmiHandler) ; UINT8[] > +GLOBAL ASM_PFX (mFirstSmiHandlerSize) ; UINT16 > + > +ASM_PFX (mFirstSmiHandler): > + ; > + ; Get our own APIC ID first, so we can contend for ApicIdGate. > + ; > + ; This basically reimplements GetInitialApicId() from > + ; "UefiCpuPkg/Library/BaseXApicLib/BaseXApicLib.c". > + ; > + mov eax, CPUID_SIGNATURE > + cpuid > + cmp eax, CPUID_EXTENDED_TOPOLOGY > + jb GetApicIdFromVersionInfo > + > + mov eax, CPUID_EXTENDED_TOPOLOGY > + mov ecx, 0 > + cpuid > + test ebx, 0xFFFF > + jz GetApicIdFromVersionInfo > + > + ; > + ; EDX has the APIC ID, save it to ESI. > + ; > + mov esi, edx > + jmp KnockOnGate > + > +GetApicIdFromVersionInfo: > + mov eax, CPUID_VERSION_INFO > + cpuid > + shr ebx, 24 > + ; > + ; EBX has the APIC ID, save it to ESI. > + ; > + mov esi, ebx > + > +KnockOnGate: > + ; > + ; See if ApicIdGate shows our own APIC ID. If so, swap it to MAX_UINT64 > + ; (close the gate), and advance. Otherwise, keep knocking. > + ; > + ; InterlockedCompareExchange64(): > + ; - Value := &FIRST_SMI_HANDLER_CONTEXT.ApicIdGate > + ; - CompareValue (EDX:EAX) := APIC ID (from ESI) > + ; - ExchangeValue (ECX:EBX) := MAX_UINT64 > + ; > + mov edx, 0 > + mov eax, esi > + mov ecx, 0xFFFF_FFFF > + mov ebx, 0xFFFF_FFFF > + lock cmpxchg8b [ds : dword (SMM_DEFAULT_SMBASE + ApicIdGate)] > + jz ApicIdMatch > + pause > + jmp KnockOnGate > + > +ApicIdMatch: > + ; > + ; Update the SMBASE field in the SMRAM Save State Map. > + ; > + ; First, calculate the address of the SMBASE field, based on the SMM > Revision > + ; ID; store the result in EBX. > + ; > + mov eax, dword [ds : dword (SMM_DEFAULT_SMBASE + SaveStateRevId)] > + test eax, 0xFFFF > + jz LegacySaveStateMap > + > + mov ebx, SMM_DEFAULT_SMBASE + SaveStateSmbase64 > + jmp UpdateSmbase > + > +LegacySaveStateMap: > + mov ebx, SMM_DEFAULT_SMBASE + SaveStateSmbase > + > +UpdateSmbase: > + ; > + ; Load the new SMBASE value into EAX. > + ; > + mov eax, dword [ds : dword (SMM_DEFAULT_SMBASE + NewSmbase)] > + ; > + ; Save it to the SMBASE field whose address we calculated in EBX. > + ; > + mov dword [ds : dword ebx], eax > + ; > + ; Set AboutToLeaveSmm. > + ; > + mov byte [ds : dword (SMM_DEFAULT_SMBASE + AboutToLeaveSmm)], 1 > + ; > + ; We're done; leave SMM and continue to the pen. > + ; > + rsm > + > +ASM_PFX (mFirstSmiHandlerSize): > + dw $ - ASM_PFX (mFirstSmiHandler) > diff --git a/OvmfPkg/CpuHotplugSmm/Smbase.c b/OvmfPkg/CpuHotplugSmm/Smbase.c > index ea21153d9145..170571221d84 100644 > --- a/OvmfPkg/CpuHotplugSmm/Smbase.c > +++ b/OvmfPkg/CpuHotplugSmm/Smbase.c > @@ -1,38 +1,46 @@ > /** @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. > **/ > @@ -89,22 +97,171 @@ SmbaseReinstallPostSmmPen ( > } > > /** > 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. > + // > + // 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; > +} > -- > 2.19.1.3.g30247aa5d201 > > -=-=-=-=-=-=-=-=-=-=-=- Groups.io Links: You receive all messages sent to this group. View/Reply Online (#55205): https://edk2.groups.io/g/devel/message/55205 Mute This Topic: https://groups.io/mt/71575188/21656 Group Owner: devel+ow...@edk2.groups.io Unsubscribe: https://edk2.groups.io/g/devel/unsub [arch...@mail-archive.com] -=-=-=-=-=-=-=-=-=-=-=-