2020-02-26 23:11:51 +01:00
|
|
|
/** @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
|
2020-02-26 23:11:52 +01:00
|
|
|
#include <Library/BaseLib.h> // CpuPause()
|
2020-02-26 23:11:51 +01:00
|
|
|
#include <Library/BaseMemoryLib.h> // CopyMem()
|
|
|
|
#include <Library/DebugLib.h> // DEBUG()
|
2020-02-26 23:11:52 +01:00
|
|
|
#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
|
2020-02-26 23:11:51 +01:00
|
|
|
|
|
|
|
#include "Smbase.h"
|
|
|
|
|
|
|
|
extern CONST UINT8 mPostSmmPen[];
|
|
|
|
extern CONST UINT16 mPostSmmPenSize;
|
2020-02-26 23:11:52 +01:00
|
|
|
extern CONST UINT8 mFirstSmiHandler[];
|
|
|
|
extern CONST UINT16 mFirstSmiHandlerSize;
|
2020-02-26 23:11:51 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
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",
|
2023-04-06 21:49:41 +02:00
|
|
|
__func__,
|
2020-02-26 23:11:51 +01:00
|
|
|
mPostSmmPenSize,
|
|
|
|
Status
|
|
|
|
));
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
|
|
|
|
Address = BASE_1MB - 1;
|
|
|
|
Status = BootServices->AllocatePages (
|
|
|
|
AllocateMaxAddress,
|
|
|
|
EfiReservedMemoryType,
|
|
|
|
1,
|
|
|
|
&Address
|
|
|
|
);
|
|
|
|
if (EFI_ERROR (Status)) {
|
2023-04-06 21:49:41 +02:00
|
|
|
DEBUG ((DEBUG_ERROR, "%a: AllocatePages(): %r\n", __func__, Status));
|
2020-02-26 23:11:51 +01:00
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
|
2023-04-06 21:49:41 +02:00
|
|
|
DEBUG ((DEBUG_INFO, "%a: Post-SMM Pen at 0x%Lx\n", __func__, Address));
|
2020-02-26 23:11:51 +01:00
|
|
|
*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);
|
|
|
|
}
|
2020-02-26 23:11:52 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
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",
|
2023-04-06 21:49:41 +02:00
|
|
|
__func__,
|
2020-02-26 23:11:52 +01:00
|
|
|
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.
|
|
|
|
//
|
OvmfPkg/CpuHotplugSmm: fix CPU hotplug race just after SMI broadcast
The "virsh setvcpus" (plural) command may hot-plug several VCPUs in quick
succession -- it means a series of "device_add" QEMU monitor commands,
back-to-back.
If a "device_add" occurs *just after* ACPI raises the broadcast SMI, then:
- the CPU_FOREACH() loop in QEMU's ich9_apm_ctrl_changed() cannot make the
SMI pending for the new CPU -- at that time, the new CPU doesn't even
exist yet,
- OVMF will find the new CPU however (in the CPU hotplug register block),
in QemuCpuhpCollectApicIds().
As a result, when the firmware sends an INIT-SIPI-SIPI to the new CPU in
SmbaseRelocate(), expecting it to boot into SMM (due to the pending SMI),
the new CPU instead boots straight into the post-RSM (normal mode) "pen",
skipping its initial SMI handler.
The CPU halts nicely in the pen, but its SMBASE is never relocated, and
the SMRAM message exchange with the BSP falls apart -- the BSP gets stuck
in the following loop:
//
// Wait until the hot-added CPU is just about to execute RSM.
//
while (Context->AboutToLeaveSmm == 0) {
CpuPause ();
}
because the new CPU's initial SMI handler never sets the flag to nonzero.
Fix this by sending a directed SMI to the new CPU just before sending it
the INIT-SIPI-SIPI. The various scenarios are documented in the code --
the cases affected by the patch are documented under point (2).
Note that this is not considered a security patch, as for a malicious
guest OS, the issue is not exploitable -- the symptom is a hang on the
BSP, in the above-noted loop in SmbaseRelocate(). Instead, the patch fixes
behavior for a benign guest OS.
Cc: Ard Biesheuvel <ard.biesheuvel@arm.com>
Cc: Igor Mammedov <imammedo@redhat.com>
Cc: Jordan Justen <jordan.l.justen@intel.com>
Cc: Philippe Mathieu-Daudé <philmd@redhat.com>
Fixes: 51a6fb41181529e4b50ea13377425bda6bb69ba6
Ref: https://bugzilla.tianocore.org/show_bug.cgi?id=2929
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
Message-Id: <20200826222129.25798-3-lersek@redhat.com>
Reviewed-by: Ard Biesheuvel <ard.biesheuvel@arm.com>
2020-08-27 00:21:29 +02:00
|
|
|
// There are 2*2 cases to consider:
|
2020-02-26 23:11:52 +01:00
|
|
|
//
|
OvmfPkg/CpuHotplugSmm: fix CPU hotplug race just after SMI broadcast
The "virsh setvcpus" (plural) command may hot-plug several VCPUs in quick
succession -- it means a series of "device_add" QEMU monitor commands,
back-to-back.
If a "device_add" occurs *just after* ACPI raises the broadcast SMI, then:
- the CPU_FOREACH() loop in QEMU's ich9_apm_ctrl_changed() cannot make the
SMI pending for the new CPU -- at that time, the new CPU doesn't even
exist yet,
- OVMF will find the new CPU however (in the CPU hotplug register block),
in QemuCpuhpCollectApicIds().
As a result, when the firmware sends an INIT-SIPI-SIPI to the new CPU in
SmbaseRelocate(), expecting it to boot into SMM (due to the pending SMI),
the new CPU instead boots straight into the post-RSM (normal mode) "pen",
skipping its initial SMI handler.
The CPU halts nicely in the pen, but its SMBASE is never relocated, and
the SMRAM message exchange with the BSP falls apart -- the BSP gets stuck
in the following loop:
//
// Wait until the hot-added CPU is just about to execute RSM.
//
while (Context->AboutToLeaveSmm == 0) {
CpuPause ();
}
because the new CPU's initial SMI handler never sets the flag to nonzero.
Fix this by sending a directed SMI to the new CPU just before sending it
the INIT-SIPI-SIPI. The various scenarios are documented in the code --
the cases affected by the patch are documented under point (2).
Note that this is not considered a security patch, as for a malicious
guest OS, the issue is not exploitable -- the symptom is a hang on the
BSP, in the above-noted loop in SmbaseRelocate(). Instead, the patch fixes
behavior for a benign guest OS.
Cc: Ard Biesheuvel <ard.biesheuvel@arm.com>
Cc: Igor Mammedov <imammedo@redhat.com>
Cc: Jordan Justen <jordan.l.justen@intel.com>
Cc: Philippe Mathieu-Daudé <philmd@redhat.com>
Fixes: 51a6fb41181529e4b50ea13377425bda6bb69ba6
Ref: https://bugzilla.tianocore.org/show_bug.cgi?id=2929
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
Message-Id: <20200826222129.25798-3-lersek@redhat.com>
Reviewed-by: Ard Biesheuvel <ard.biesheuvel@arm.com>
2020-08-27 00:21:29 +02:00
|
|
|
// (1) The CPU was hot-added before the SMI was broadcast.
|
2020-02-26 23:11:52 +01:00
|
|
|
//
|
OvmfPkg/CpuHotplugSmm: fix CPU hotplug race just after SMI broadcast
The "virsh setvcpus" (plural) command may hot-plug several VCPUs in quick
succession -- it means a series of "device_add" QEMU monitor commands,
back-to-back.
If a "device_add" occurs *just after* ACPI raises the broadcast SMI, then:
- the CPU_FOREACH() loop in QEMU's ich9_apm_ctrl_changed() cannot make the
SMI pending for the new CPU -- at that time, the new CPU doesn't even
exist yet,
- OVMF will find the new CPU however (in the CPU hotplug register block),
in QemuCpuhpCollectApicIds().
As a result, when the firmware sends an INIT-SIPI-SIPI to the new CPU in
SmbaseRelocate(), expecting it to boot into SMM (due to the pending SMI),
the new CPU instead boots straight into the post-RSM (normal mode) "pen",
skipping its initial SMI handler.
The CPU halts nicely in the pen, but its SMBASE is never relocated, and
the SMRAM message exchange with the BSP falls apart -- the BSP gets stuck
in the following loop:
//
// Wait until the hot-added CPU is just about to execute RSM.
//
while (Context->AboutToLeaveSmm == 0) {
CpuPause ();
}
because the new CPU's initial SMI handler never sets the flag to nonzero.
Fix this by sending a directed SMI to the new CPU just before sending it
the INIT-SIPI-SIPI. The various scenarios are documented in the code --
the cases affected by the patch are documented under point (2).
Note that this is not considered a security patch, as for a malicious
guest OS, the issue is not exploitable -- the symptom is a hang on the
BSP, in the above-noted loop in SmbaseRelocate(). Instead, the patch fixes
behavior for a benign guest OS.
Cc: Ard Biesheuvel <ard.biesheuvel@arm.com>
Cc: Igor Mammedov <imammedo@redhat.com>
Cc: Jordan Justen <jordan.l.justen@intel.com>
Cc: Philippe Mathieu-Daudé <philmd@redhat.com>
Fixes: 51a6fb41181529e4b50ea13377425bda6bb69ba6
Ref: https://bugzilla.tianocore.org/show_bug.cgi?id=2929
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
Message-Id: <20200826222129.25798-3-lersek@redhat.com>
Reviewed-by: Ard Biesheuvel <ard.biesheuvel@arm.com>
2020-08-27 00:21:29 +02:00
|
|
|
// (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);
|
2020-02-26 23:11:52 +01:00
|
|
|
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",
|
2023-04-06 21:49:41 +02:00
|
|
|
__func__,
|
2020-02-26 23:11:52 +01:00
|
|
|
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;
|
|
|
|
}
|