CVE-2025-36600
An attacker could exploit this vulnerability to elevate privileges from ring 0 to ring -2 and execute arbitrary code in System Management Mode, an environment more privileged than and completely isolated from the operating system (OS). Running arbitrary code in SMM also bypasses SMM-based SPI flash protections against modification, which can help an attacker to install a firmware backdoor/implant. Such malicious code in the firmware could persist through operating system reinstallations. In addition, this vulnerability could potentially be used by malicious actors to bypass security mechanisms provided by UEFI firmware, such as Secure Boot and some types of memory isolation for hypervisors.
This vulnerability was detected by the Deep Vulnerability Analysis (DVA) component from Binarly Platform
Let's consider the module 4e6c9eacce454b937a69d9022a2693e9f237cac8ee6354d197141df58503f43e
.
The pseudocode of the vulnerable function is shown below:
EFI_STATUS SwSmiHandler(
EFI_HANDLE DispatchHandle,
const void *Context,
EFI_SMM_SW_CONTEXT *CommBuffer,
UINTN *CommBufferSize)
{
EFI_STATUS Status;
SMI_UC_DWORD_REGS *Regs;
UINT8 FuncIndex;
SMI_UC_DWORD_REGS *Buffer;
Buffer = 0;
if ( !gInit )
{
gInit = 1;
gInitError = InitMicrocodeVariables() == 0;
}
Status = gEfiSmmCpuProtocol->ReadSaveState(
gEfiSmmCpuProtocol,
4,
EFI_SMM_SAVE_STATE_REGISTER_RSI,
CommBuffer->SwSmiCpuIndex,
&Buffer);
if ( Status == EFI_SUCCESS )
{
// validation takes place, but the validation function
// is not sufficient
Status = ValidateBuffer(Buffer, 44);
if ( Status == EFI_SUCCESS )
{
if ( LOWORD(Regs->EAX) == 0xD042 )
{
FuncIndex = Regs->EBX; // SMRAM write
Regs->EFLAGS &= ~1; // SMRAM write
LOWORD(Buffer->EAX) = 0;
if ( gInitError || FuncIndex >= 4 )
{
Buffer->EFLAGS |= 1; // SMRAM write
LOWORD(Buffer->EAX) = 0x8600; // SMRAM write
}
else
{
(gMicrocodeUpdateAPI[FuncIndex])(Buffer);
}
}
return EFI_SUCCESS;
}
}
return Status;
}
As we can see from the pseudocode, Buffer
is an attacker-controlled pointer (obtained via EFI_SMM_SAVE_STATE_REGISTER_RSI)
. Buffer
has a size of 44 (0x2C)
and is validated with the ValidateBuffer
function:
EFI_STATUS ValidateBuffer(UINT64 Buffer, UINTN Size)
{
if ( Buffer >= gSmramRegionStart && Buffer < gSmramRegionEnd
|| Buffer + Size >= gSmramRegionStart && Buffer + Size < gSmramRegionEnd )
{
return EFI_ACCESS_DENIED;
}
else
{
return EFI_SUCCESS;
}
}
Where gSmramRegionStart
and gSmramRegionEnd
variables initialized in the following function:
EFI_STATUS Main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS NUMPAD "+" TO EXPAND]
SmramMap = 0;
SmramMapSize = 0;
if ( !InitMicrocodeVariables() )
return EFI_UNSUPPORTED;
gSmst->SmmLocateProtocol(&EFI_SMM_SW_DISPATCH2_PROTOCOL_GUID, 0, &EfiSmmSwDispatch2Protocol);
gSmst->SmmLocateProtocol(&EFI_SMM_CPU_PROTOCOL_GUID, 0, &gEfiSmmCpuProtocol);
RegisterContext.SwSmiInputValue = 0x44;
EfiSmmSwDispatch2Protocol->Register(EfiSmmSwDispatch2Protocol, SwSmiHandler, &RegisterContext, DispatchHandle);
gBS->LocateProtocol(&EFI_SMM_ACCESS2_PROTOCOL_GUID, 0, &gEfiSmmAccess2Protocol);
if ( gEfiSmmAccess2Protocol->GetCapabilities(gEfiSmmAccess2Protocol, &SmramMapSize, 0) == EFI_BUFFER_TOO_SMALL )
{
SmramMap = SmmAllocatePool(EfiRuntimeServicesData, SmramMapSize);
(gEfiSmmAccess2Protocol->GetCapabilities)(gEfiSmmAccess2Protocol, &SmramMapSize, SmramMap);
}
Current = SmramMap;
if ( SmramMap < (SmramMap + SmramMapSize) )
{
while ( Current->CpuStart <= 0x100000 )
{
if ( ++Current >= (SmramMap + SmramMapSize) )
goto _Free;
}
CpuStart = Current->CpuStart;
PhysicalSize = Current->PhysicalSize;
// start address and end address for one SMRAM region
gSmramRegionStart = CpuStart;
gSmramRegionEnd = PhysicalSize + CpuStart;
}
_Free:
Free(SmramMap);
return 0;
}
Given the functionality implemented in the ValidateBuffer
function, it will only validate Buffer
against one SMRAM region whereas it is possible to have multiple SMRAM regions, for example, on our test system:
0x0000000048000000 - 0x000000004BFFFFFF (Size = 0x04000000)
0x0000000048000000 - 0x0000000048001000
This leaves the attacker with the ability to specify a Buffer
that passes the check but is still in SMRAM.
Buffer
has the following structure:
00000000 struct SMI_UC_DWORD_REGS
00000000 {
00000000 UINT32 EAX;
00000004 UINT32 EBX;
00000008 UINT32 ECX;
0000000C UINT32 EDX;
00000010 UINT32 ESI;
00000014 UINT32 EDI;
00000018 UINT32 EFLAGS;
0000001C UINT16 ES;
0000001E UINT16 CS;
00000020 UINT16 SS;
00000022 UINT16 DS;
00000024 UINT16 FS;
00000026 UINT16 GS;
00000028 UINT32 EBP;
0000002C };
If LOWORD(Buffer->EAX)
is equal to 0xD042
, writes will occur in SwSmiHandler
function and in functions from gMicrocodeUpdateAPI
table:
.data:0000000000006518 gMicrocodeUpdateAPI dq offset PresenceTest
.data:0000000000006518 ; DATA XREF: SwSmiHandler+8B↑o
.data:0000000000006518 ; SwSmiHandler+92↑r
.data:0000000000006520 dq offset WriteUpdateData
.data:0000000000006528 dq offset UpdateControl
.data:0000000000006530 dq offset ReadUpdateData
UINT64 PresenceTest(SMI_UC_DWORD_REGS *Regs)
{
UINT64 NumMcodeBlks;
// Regs is attacker-controlled
NumMcodeBlks = gNumMcodeBlks;
qmemcpy(&Regs->EBX, "ETNIPEPL", 8); // SMRAM write
LOWORD(Regs->ESI) = NumMcodeBlks; // SMRAM write
Regs->EDX = 1; // SMRAM write
return NumMcodeBlks;
}
void UpdateControl(SMI_UC_DWORD_REGS *Regs)
{
// Regs is attacker-controlled
LOBYTE(Regs->EBX) = 1; // SMRAM write
}
In the WriteUpdateData
and ReadUpdateData
functions, nested pointers are validated with the same unsafe ValidateBuffer
function.
The updated firmware version uses the secure gAmiSmmBufferValidationProtocol->ValidateMemoryBuffer()
function instead of the insecure ValidateBuffer()
function:
Status = gEfiSmmCpuProtocol->ReadSaveState(
gEfiSmmCpuProtocol,
4,
EFI_SMM_SAVE_STATE_REGISTER_RSI,
CommBuffer->SwSmiCpuIndex,
&Buffer);
if ( Status == EFI_SUCCESS )
{
if ( gAmiSmmBufferValidationProtocol )
{
Status = (gAmiSmmBufferValidationProtocol->ValidateMemoryBuffer)(Buffer, 44);
if ( Status == EFI_SUCCESS )
This vulnerability is subject to a 90 day disclosure period. After 90 days or when a patch has been made generally available (whichever comes first) the advisory will be publicly disclosed.
BINARLY REsearch team