[Sources]\r
ApicId.h\r
CpuHotplug.c\r
+ FirstSmiHandler.nasm\r
+ FirstSmiHandlerContext.h\r
PostSmmPen.nasm\r
QemuCpuhp.c\r
QemuCpuhp.h\r
BaseLib\r
BaseMemoryLib\r
DebugLib\r
+ LocalApicLib\r
MmServicesTableLib\r
PcdLib\r
SafeIntLib\r
+ SynchronizationLib\r
UefiDriverEntryPoint\r
\r
[Protocols]\r
--- /dev/null
+;------------------------------------------------------------------------------\r
+; @file\r
+; Relocate the SMBASE on a hot-added CPU when it services its first SMI.\r
+;\r
+; Copyright (c) 2020, Red Hat, Inc.\r
+;\r
+; SPDX-License-Identifier: BSD-2-Clause-Patent\r
+;\r
+; The routine runs on the hot-added CPU in the following "big real mode",\r
+; 16-bit environment; per "SMI HANDLER EXECUTION ENVIRONMENT" in the Intel SDM\r
+; (table "Processor Register Initialization in SMM"):\r
+;\r
+; - CS selector: 0x3000 (most significant 16 bits of SMM_DEFAULT_SMBASE).\r
+;\r
+; - CS limit: 0xFFFF_FFFF.\r
+;\r
+; - CS base: SMM_DEFAULT_SMBASE (0x3_0000).\r
+;\r
+; - IP: SMM_HANDLER_OFFSET (0x8000).\r
+;\r
+; - ES, SS, DS, FS, GS selectors: 0.\r
+;\r
+; - ES, SS, DS, FS, GS limits: 0xFFFF_FFFF.\r
+;\r
+; - ES, SS, DS, FS, GS bases: 0.\r
+;\r
+; - Operand-size and address-size override prefixes can be used to access the\r
+; address space beyond 1MB.\r
+;------------------------------------------------------------------------------\r
+\r
+SECTION .data\r
+BITS 16\r
+\r
+;\r
+; Bring in SMM_DEFAULT_SMBASE from\r
+; "MdePkg/Include/Register/Intel/SmramSaveStateMap.h".\r
+;\r
+SMM_DEFAULT_SMBASE: equ 0x3_0000\r
+\r
+;\r
+; Field offsets in FIRST_SMI_HANDLER_CONTEXT, which resides at\r
+; SMM_DEFAULT_SMBASE.\r
+;\r
+ApicIdGate: equ 0 ; UINT64\r
+NewSmbase: equ 8 ; UINT32\r
+AboutToLeaveSmm: equ 12 ; UINT8\r
+\r
+;\r
+; SMRAM Save State Map field offsets, per the AMD (not Intel) layout that QEMU\r
+; implements. Relative to SMM_DEFAULT_SMBASE.\r
+;\r
+SaveStateRevId: equ 0xFEFC ; UINT32\r
+SaveStateSmbase: equ 0xFEF8 ; UINT32\r
+SaveStateSmbase64: equ 0xFF00 ; UINT32\r
+\r
+;\r
+; CPUID constants, from "MdePkg/Include/Register/Intel/Cpuid.h".\r
+;\r
+CPUID_SIGNATURE: equ 0x00\r
+CPUID_EXTENDED_TOPOLOGY: equ 0x0B\r
+CPUID_VERSION_INFO: equ 0x01\r
+\r
+GLOBAL ASM_PFX (mFirstSmiHandler) ; UINT8[]\r
+GLOBAL ASM_PFX (mFirstSmiHandlerSize) ; UINT16\r
+\r
+ASM_PFX (mFirstSmiHandler):\r
+ ;\r
+ ; Get our own APIC ID first, so we can contend for ApicIdGate.\r
+ ;\r
+ ; This basically reimplements GetInitialApicId() from\r
+ ; "UefiCpuPkg/Library/BaseXApicLib/BaseXApicLib.c".\r
+ ;\r
+ mov eax, CPUID_SIGNATURE\r
+ cpuid\r
+ cmp eax, CPUID_EXTENDED_TOPOLOGY\r
+ jb GetApicIdFromVersionInfo\r
+\r
+ mov eax, CPUID_EXTENDED_TOPOLOGY\r
+ mov ecx, 0\r
+ cpuid\r
+ test ebx, 0xFFFF\r
+ jz GetApicIdFromVersionInfo\r
+\r
+ ;\r
+ ; EDX has the APIC ID, save it to ESI.\r
+ ;\r
+ mov esi, edx\r
+ jmp KnockOnGate\r
+\r
+GetApicIdFromVersionInfo:\r
+ mov eax, CPUID_VERSION_INFO\r
+ cpuid\r
+ shr ebx, 24\r
+ ;\r
+ ; EBX has the APIC ID, save it to ESI.\r
+ ;\r
+ mov esi, ebx\r
+\r
+KnockOnGate:\r
+ ;\r
+ ; See if ApicIdGate shows our own APIC ID. If so, swap it to MAX_UINT64\r
+ ; (close the gate), and advance. Otherwise, keep knocking.\r
+ ;\r
+ ; InterlockedCompareExchange64():\r
+ ; - Value := &FIRST_SMI_HANDLER_CONTEXT.ApicIdGate\r
+ ; - CompareValue (EDX:EAX) := APIC ID (from ESI)\r
+ ; - ExchangeValue (ECX:EBX) := MAX_UINT64\r
+ ;\r
+ mov edx, 0\r
+ mov eax, esi\r
+ mov ecx, 0xFFFF_FFFF\r
+ mov ebx, 0xFFFF_FFFF\r
+ lock cmpxchg8b [ds : dword (SMM_DEFAULT_SMBASE + ApicIdGate)]\r
+ jz ApicIdMatch\r
+ pause\r
+ jmp KnockOnGate\r
+\r
+ApicIdMatch:\r
+ ;\r
+ ; Update the SMBASE field in the SMRAM Save State Map.\r
+ ;\r
+ ; First, calculate the address of the SMBASE field, based on the SMM Revision\r
+ ; ID; store the result in EBX.\r
+ ;\r
+ mov eax, dword [ds : dword (SMM_DEFAULT_SMBASE + SaveStateRevId)]\r
+ test eax, 0xFFFF\r
+ jz LegacySaveStateMap\r
+\r
+ mov ebx, SMM_DEFAULT_SMBASE + SaveStateSmbase64\r
+ jmp UpdateSmbase\r
+\r
+LegacySaveStateMap:\r
+ mov ebx, SMM_DEFAULT_SMBASE + SaveStateSmbase\r
+\r
+UpdateSmbase:\r
+ ;\r
+ ; Load the new SMBASE value into EAX.\r
+ ;\r
+ mov eax, dword [ds : dword (SMM_DEFAULT_SMBASE + NewSmbase)]\r
+ ;\r
+ ; Save it to the SMBASE field whose address we calculated in EBX.\r
+ ;\r
+ mov dword [ds : dword ebx], eax\r
+ ;\r
+ ; Set AboutToLeaveSmm.\r
+ ;\r
+ mov byte [ds : dword (SMM_DEFAULT_SMBASE + AboutToLeaveSmm)], 1\r
+ ;\r
+ ; We're done; leave SMM and continue to the pen.\r
+ ;\r
+ rsm\r
+\r
+ASM_PFX (mFirstSmiHandlerSize):\r
+ dw $ - ASM_PFX (mFirstSmiHandler)\r
--- /dev/null
+/** @file\r
+ Define the FIRST_SMI_HANDLER_CONTEXT structure, which is an exchange area\r
+ between the SMM Monarch and the hot-added CPU, for relocating the SMBASE of\r
+ the hot-added CPU.\r
+\r
+ Copyright (c) 2020, Red Hat, Inc.\r
+\r
+ SPDX-License-Identifier: BSD-2-Clause-Patent\r
+**/\r
+\r
+#ifndef FIRST_SMI_HANDLER_CONTEXT_H_\r
+#define FIRST_SMI_HANDLER_CONTEXT_H_\r
+\r
+//\r
+// The following structure is used to communicate between the SMM Monarch\r
+// (running the root MMI handler) and the hot-added CPU (handling its first\r
+// SMI). It is placed at SMM_DEFAULT_SMBASE, which is in SMRAM under QEMU's\r
+// "SMRAM at default SMBASE" feature.\r
+//\r
+#pragma pack (1)\r
+typedef struct {\r
+ //\r
+ // When ApicIdGate is MAX_UINT64, then no hot-added CPU may proceed with\r
+ // SMBASE relocation.\r
+ //\r
+ // Otherwise, the hot-added CPU whose APIC ID equals ApicIdGate may proceed\r
+ // with SMBASE relocation.\r
+ //\r
+ // This field is intentionally wider than APIC_ID (UINT32) because we need a\r
+ // "gate locked" value that is different from all possible APIC_IDs.\r
+ //\r
+ UINT64 ApicIdGate;\r
+ //\r
+ // The new SMBASE value for the hot-added CPU to set in the SMRAM Save State\r
+ // Map, before leaving SMM with the RSM instruction.\r
+ //\r
+ UINT32 NewSmbase;\r
+ //\r
+ // The hot-added CPU sets this field to 1 right before executing the RSM\r
+ // instruction. This tells the SMM Monarch to proceed to polling the last\r
+ // byte of the normal RAM reserved page (Post-SMM Pen).\r
+ //\r
+ UINT8 AboutToLeaveSmm;\r
+} FIRST_SMI_HANDLER_CONTEXT;\r
+#pragma pack ()\r
+\r
+#endif // FIRST_SMI_HANDLER_CONTEXT_H_\r
**/\r
\r
#include <Base.h> // BASE_1MB\r
+#include <Library/BaseLib.h> // CpuPause()\r
#include <Library/BaseMemoryLib.h> // CopyMem()\r
#include <Library/DebugLib.h> // DEBUG()\r
+#include <Library/LocalApicLib.h> // SendInitSipiSipi()\r
+#include <Library/SynchronizationLib.h> // InterlockedCompareExchange64()\r
+#include <Register/Intel/SmramSaveStateMap.h> // SMM_DEFAULT_SMBASE\r
+\r
+#include "FirstSmiHandlerContext.h" // FIRST_SMI_HANDLER_CONTEXT\r
\r
#include "Smbase.h"\r
\r
extern CONST UINT8 mPostSmmPen[];\r
extern CONST UINT16 mPostSmmPenSize;\r
+extern CONST UINT8 mFirstSmiHandler[];\r
+extern CONST UINT16 mFirstSmiHandlerSize;\r
\r
/**\r
Allocate a non-SMRAM reserved memory page for the Post-SMM Pen for hot-added\r
{\r
BootServices->FreePages (PenAddress, 1);\r
}\r
+\r
+/**\r
+ Place the handler routine for the first SMIs of hot-added CPUs at\r
+ (SMM_DEFAULT_SMBASE + SMM_HANDLER_OFFSET).\r
+\r
+ Note that this effects an "SMRAM to SMRAM" copy.\r
+\r
+ Additionally, shut the APIC ID gate in FIRST_SMI_HANDLER_CONTEXT.\r
+\r
+ This function may only be called from the entry point function of the driver,\r
+ and only after PcdQ35SmramAtDefaultSmbase has been determined to be TRUE.\r
+**/\r
+VOID\r
+SmbaseInstallFirstSmiHandler (\r
+ VOID\r
+ )\r
+{\r
+ FIRST_SMI_HANDLER_CONTEXT *Context;\r
+\r
+ CopyMem ((VOID *)(UINTN)(SMM_DEFAULT_SMBASE + SMM_HANDLER_OFFSET),\r
+ mFirstSmiHandler, mFirstSmiHandlerSize);\r
+\r
+ Context = (VOID *)(UINTN)SMM_DEFAULT_SMBASE;\r
+ Context->ApicIdGate = MAX_UINT64;\r
+}\r
+\r
+/**\r
+ Relocate the SMBASE on a hot-added CPU. Then pen the hot-added CPU in the\r
+ normal RAM reserved memory page, set up earlier with\r
+ SmbaseAllocatePostSmmPen() and SmbaseReinstallPostSmmPen().\r
+\r
+ The SMM Monarch is supposed to call this function from the root MMI handler.\r
+\r
+ The SMM Monarch is responsible for calling SmbaseInstallFirstSmiHandler(),\r
+ SmbaseAllocatePostSmmPen(), and SmbaseReinstallPostSmmPen() before calling\r
+ this function.\r
+\r
+ If the OS maliciously boots the hot-added CPU ahead of letting the ACPI CPU\r
+ hotplug event handler broadcast the CPU hotplug MMI, then the hot-added CPU\r
+ returns to the OS rather than to the pen, upon RSM. In that case, this\r
+ function will hang forever (unless the OS happens to signal back through the\r
+ last byte of the pen page).\r
+\r
+ @param[in] ApicId The APIC ID of the hot-added CPU whose SMBASE should\r
+ be relocated.\r
+\r
+ @param[in] Smbase The new SMBASE address. The root MMI handler is\r
+ responsible for passing in a free ("unoccupied")\r
+ SMBASE address that was pre-configured by\r
+ PiSmmCpuDxeSmm in CPU_HOT_PLUG_DATA.\r
+\r
+ @param[in] PenAddress The address of the Post-SMM Pen for hot-added CPUs, as\r
+ returned by SmbaseAllocatePostSmmPen(), and installed\r
+ by SmbaseReinstallPostSmmPen().\r
+\r
+ @retval EFI_SUCCESS The SMBASE of the hot-added CPU with APIC ID\r
+ ApicId has been relocated to Smbase. The\r
+ hot-added CPU has reported back about leaving\r
+ SMM.\r
+\r
+ @retval EFI_PROTOCOL_ERROR Synchronization bug encountered around\r
+ FIRST_SMI_HANDLER_CONTEXT.ApicIdGate.\r
+\r
+ @retval EFI_INVALID_PARAMETER Smbase does not fit in 32 bits. No relocation\r
+ has been attempted.\r
+**/\r
+EFI_STATUS\r
+SmbaseRelocate (\r
+ IN APIC_ID ApicId,\r
+ IN UINTN Smbase,\r
+ IN UINT32 PenAddress\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ volatile UINT8 *SmmVacated;\r
+ volatile FIRST_SMI_HANDLER_CONTEXT *Context;\r
+ UINT64 ExchangeResult;\r
+\r
+ if (Smbase > MAX_UINT32) {\r
+ Status = EFI_INVALID_PARAMETER;\r
+ DEBUG ((DEBUG_ERROR, "%a: ApicId=" FMT_APIC_ID " Smbase=0x%Lx: %r\n",\r
+ __FUNCTION__, ApicId, (UINT64)Smbase, Status));\r
+ return Status;\r
+ }\r
+\r
+ SmmVacated = (UINT8 *)(UINTN)PenAddress + (EFI_PAGE_SIZE - 1);\r
+ Context = (VOID *)(UINTN)SMM_DEFAULT_SMBASE;\r
+\r
+ //\r
+ // Clear AboutToLeaveSmm, so we notice when the hot-added CPU is just about\r
+ // to reach RSM, and we can proceed to polling the last byte of the reserved\r
+ // page (which could be attacked by the OS).\r
+ //\r
+ Context->AboutToLeaveSmm = 0;\r
+\r
+ //\r
+ // Clear the last byte of the reserved page, so we notice when the hot-added\r
+ // CPU checks back in from the pen.\r
+ //\r
+ *SmmVacated = 0;\r
+\r
+ //\r
+ // Boot the hot-added CPU.\r
+ //\r
+ // If the OS is benign, and so the hot-added CPU is still in RESET state,\r
+ // then the broadcast SMI is still pending for it; it will now launch\r
+ // directly into SMM.\r
+ //\r
+ // If the OS is malicious, the hot-added CPU has been booted already, and so\r
+ // it is already spinning on the APIC ID gate. In that case, the\r
+ // INIT-SIPI-SIPI below will be ignored.\r
+ //\r
+ SendInitSipiSipi (ApicId, PenAddress);\r
+\r
+ //\r
+ // Expose the desired new SMBASE value to the hot-added CPU.\r
+ //\r
+ Context->NewSmbase = (UINT32)Smbase;\r
+\r
+ //\r
+ // Un-gate SMBASE relocation for the hot-added CPU whose APIC ID is ApicId.\r
+ //\r
+ ExchangeResult = InterlockedCompareExchange64 (&Context->ApicIdGate,\r
+ MAX_UINT64, ApicId);\r
+ if (ExchangeResult != MAX_UINT64) {\r
+ Status = EFI_PROTOCOL_ERROR;\r
+ DEBUG ((DEBUG_ERROR, "%a: ApicId=" FMT_APIC_ID " ApicIdGate=0x%Lx: %r\n",\r
+ __FUNCTION__, ApicId, ExchangeResult, Status));\r
+ return Status;\r
+ }\r
+\r
+ //\r
+ // Wait until the hot-added CPU is just about to execute RSM.\r
+ //\r
+ while (Context->AboutToLeaveSmm == 0) {\r
+ CpuPause ();\r
+ }\r
+\r
+ //\r
+ // Now wait until the hot-added CPU reports back from the pen (or the OS\r
+ // attacks the last byte of the reserved page).\r
+ //\r
+ while (*SmmVacated == 0) {\r
+ CpuPause ();\r
+ }\r
+\r
+ Status = EFI_SUCCESS;\r
+ return Status;\r
+}\r
#include <Uefi/UefiBaseType.h> // EFI_STATUS\r
#include <Uefi/UefiSpec.h> // EFI_BOOT_SERVICES\r
\r
+#include "ApicId.h" // APIC_ID\r
+\r
EFI_STATUS\r
SmbaseAllocatePostSmmPen (\r
OUT UINT32 *PenAddress,\r
IN CONST EFI_BOOT_SERVICES *BootServices\r
);\r
\r
+VOID\r
+SmbaseInstallFirstSmiHandler (\r
+ VOID\r
+ );\r
+\r
+EFI_STATUS\r
+SmbaseRelocate (\r
+ IN APIC_ID ApicId,\r
+ IN UINTN Smbase,\r
+ IN UINT32 PenAddress\r
+ );\r
+\r
#endif // SMBASE_H_\r