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>
---
Notes:
v2:
- implement the combined approach described here:
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 (#54955): https://edk2.groups.io/g/devel/message/54955
Mute This Topic: https://groups.io/mt/71575188/1787277
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/unsub [importer@patchew.org]
-=-=-=-=-=-=-=-=-=-=-=-
On Wed, 26 Feb 2020 at 23:12, Laszlo Ersek <lersek@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.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>
Acked-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
> ---
>
> Notes:
> v2:
>
> - implement the combined approach described here:
>
> 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/1787277
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/unsub [importer@patchew.org]
-=-=-=-=-=-=-=-=-=-=-=-
© 2016 - 2026 Red Hat, Inc.