OvmfPkg/CpuHotplugSmm: introduce Post-SMM Pen for hot-added CPUs

Once a hot-added CPU finishes the SMBASE relocation, we need to pen it in
a HLT loop. Add the NASM implementation (with just a handful of
instructions, but much documentation), and some C language helper
functions.

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-12-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:51 +01:00 committed by mergify[bot]
parent 17cb8ddba3
commit 63c89da242
4 changed files with 297 additions and 0 deletions

View File

@ -24,8 +24,11 @@
[Sources]
ApicId.h
CpuHotplug.c
PostSmmPen.nasm
QemuCpuhp.c
QemuCpuhp.h
Smbase.c
Smbase.h
[Packages]
MdePkg/MdePkg.dec
@ -34,6 +37,7 @@
[LibraryClasses]
BaseLib
BaseMemoryLib
DebugLib
MmServicesTableLib
PcdLib

View File

@ -0,0 +1,151 @@
;------------------------------------------------------------------------------
; @file
; Pen any hot-added CPU in a 16-bit, real mode HLT loop, after it leaves SMM by
; executing the RSM instruction.
;
; Copyright (c) 2020, Red Hat, Inc.
;
; SPDX-License-Identifier: BSD-2-Clause-Patent
;
; The routine implemented here is stored into normal RAM, under 1MB, at the
; beginning of a page that is allocated as EfiReservedMemoryType. On any
; hot-added CPU, it is executed after *at least* the first RSM (i.e., after
; SMBASE relocation).
;
; The first execution of this code occurs as follows:
;
; - The hot-added CPU is in RESET state.
;
; - The ACPI CPU hotplug event handler triggers a broadcast SMI, from the OS.
;
; - Existent CPUs (BSP and APs) enter SMM.
;
; - The hot-added CPU remains in RESET state, but an SMI is pending for it now.
; (See "SYSTEM MANAGEMENT INTERRUPT (SMI)" in the Intel SDM.)
;
; - In SMM, pre-existent CPUs that are not elected SMM Monarch, keep themselves
; busy with their wait loops.
;
; - From the root MMI handler, the SMM Monarch:
;
; - places this routine in the reserved page,
;
; - clears the "about to leave SMM" byte in SMRAM,
;
; - clears the last byte of the reserved page,
;
; - sends an INIT-SIPI-SIPI sequence to the hot-added CPU,
;
; - un-gates the default SMI handler by APIC ID.
;
; - The startup vector in the SIPI that is sent by the SMM Monarch points to
; this code; i.e., to the reserved page. (Example: 0x9_F000.)
;
; - The SMM Monarch starts polling the "about to leave SMM" byte in SMRAM.
;
; - The hot-added CPU boots, and immediately enters SMM due to the pending SMI.
; It starts executing the default SMI handler.
;
; - Importantly, the SMRAM Save State Map captures the following information,
; when the hot-added CPU enters SMM:
;
; - CS selector: assumes the 16 most significant bits of the 20-bit (i.e.,
; below 1MB) startup vector from the SIPI. (Example: 0x9F00.)
;
; - CS attributes: Accessed, Readable, User (S=1), CodeSegment (bit#11),
; Present.
;
; - CS limit: 0xFFFF.
;
; - CS base: the CS selector value shifted left by 4 bits. That is, the CS
; base equals the SIPI startup vector. (Example: 0x9_F000.)
;
; - IP: the least significant 4 bits from the SIPI startup vector. Because
; the routine is page-aligned, these bits are zero (hence IP is zero).
;
; - ES, SS, DS, FS, GS selectors: 0.
;
; - ES, SS, DS, FS, GS attributes: same as the CS attributes, minus
; CodeSegment (bit#11).
;
; - ES, SS, DS, FS, GS limits: 0xFFFF.
;
; - ES, SS, DS, FS, GS bases: 0.
;
; - The hot-added CPU sets its new SMBASE value in the SMRAM Save State Map.
;
; - The hot-added CPU sets the "about to leave SMM" byte in SMRAM, then
; executes the RSM instruction immediately after, leaving SMM.
;
; - The SMM Monarch notices that the "about to leave SMM" byte in SMRAM has
; been set, and starts polling the last byte in the reserved page.
;
; - The hot-added CPU jumps ("returns") to the code below (in the reserved
; page), according to the register state listed in the SMRAM Save State Map.
;
; - The hot-added CPU sets the last byte of the reserved page, then halts
; itself.
;
; - The SMM Monarch notices that the hot-added CPU is done with SMBASE
; relocation.
;
; Note that, if the OS is malicious and sends INIT-SIPI-SIPI to the hot-added
; CPU before allowing the ACPI CPU hotplug event handler to trigger a broadcast
; SMI, then said broadcast SMI will yank the hot-added CPU directly into SMM,
; without becoming pending for it (as the hot-added CPU is no longer in RESET
; state). This is OK, because:
;
; - The default SMI handler copes with this, as it is gated by APIC ID. The
; hot-added CPU won't start the actual SMBASE relocation until the SMM
; Monarch lets it.
;
; - The INIT-SIPI-SIPI sequence that the SMM Monarch sends to the hot-added CPU
; will be ignored in this sate (it won't even be latched). See "SMI HANDLER
; EXECUTION ENVIRONMENT" in the Intel SDM: "INIT operations are inhibited
; when the processor enters SMM".
;
; - When the hot-added CPU (e.g., CPU#1) executes the RSM (having relocated
; SMBASE), it returns to the OS. The OS can use CPU#1 to attack the last byte
; of the reserved page, while another CPU (e.g., CPU#2) is relocating SMBASE,
; in order to trick the SMM Monarch (e.g., CPU#0) to open the APIC ID gate
; for yet another CPU (e.g., CPU#3). However, the SMM Monarch won't look at
; the last byte of the reserved page, until CPU#2 sets the "about to leave
; SMM" byte in SMRAM. This leaves a very small window (just one instruction's
; worth before the RSM) for CPU#3 to "catch up" with CPU#2, and overwrite
; CPU#2's SMBASE with its own.
;
; In other words, we do not / need not prevent a malicious OS from booting the
; hot-added CPU early; instead we provide benign OSes with a pen for hot-added
; CPUs.
;------------------------------------------------------------------------------
SECTION .data
BITS 16
GLOBAL ASM_PFX (mPostSmmPen) ; UINT8[]
GLOBAL ASM_PFX (mPostSmmPenSize) ; UINT16
ASM_PFX (mPostSmmPen):
;
; Point DS at the same reserved page.
;
mov ax, cs
mov ds, ax
;
; Inform the SMM Monarch that we're done with SMBASE relocation, by setting
; the last byte in the reserved page.
;
mov byte [ds : word 0xFFF], 1
;
; Halt now, until we get woken by another SMI, or (more likely) the OS
; reboots us with another INIT-SIPI-SIPI.
;
HltLoop:
cli
hlt
jmp HltLoop
ASM_PFX (mPostSmmPenSize):
dw $ - ASM_PFX (mPostSmmPen)

View File

@ -0,0 +1,110 @@
/** @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/BaseMemoryLib.h> // CopyMem()
#include <Library/DebugLib.h> // DEBUG()
#include "Smbase.h"
extern CONST UINT8 mPostSmmPen[];
extern CONST UINT16 mPostSmmPenSize;
/**
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);
}

View File

@ -0,0 +1,32 @@
/** @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
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
);
#endif // SMBASE_H_