CVE-2025-4426
An attacker can exploit this vulnerability to elevate privileges from ring 0 to ring -2, and read SMRAM content (that can help to execute arbitrary code in System Management Mode - an evironment more privileged than operating system (OS) and completely isolated from it). 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.
Let's consider the module 87cafb102629124735c2e506956606875378e8c4d7b32ed8abc95e79fafd4657
.
This module contains custom logic to register SMI handlers (callbacks) using EFI_L05_SMM_SW_SMI_INTERFACE_PROTOCOL
:
EFI_STATUS RegisterCallbackFunctions()
{
UINTN Offset;
EFI_STATUS Status;
EFI_L05_SMM_SW_SMI_INTERFACE_PROTOCOL *EfiL05SmmSwSmiInterfaceProtocol;
Offset = 0;
EfiL05SmmSwSmiInterfaceProtocol = 0;
Status0 = gSmst->SmmLocateProtocol(&EFI_SMM_CPU_PROTOCOL_GUID, 0, &gEfiSmmCpuProtocol);
if ( EFI_SUCCESS(Status) )
{
Status0 = gSmst->SmmLocateProtocol(&EFI_SMM_VARIABLE_PROTOCOL_GUID, 0, &gEfiSmmVariableProtocol);
if ( EFI_SUCCESS(Status) )
{
Status0 = gSmst->SmmLocateProtocol(
&EFI_L05_SMM_SW_SMI_INTERFACE_PROTOCOL_GUID,
0,
&EfiL05SmmSwSmiInterfaceProtocol);
if ( EFI_SUCCESS(Status) )
{
do
{
Status = EfiL05SmmSwSmiInterfaceProtocol->RegisterCallbackFunction(
EfiL05SmmSwSmiInterfaceProtocol,
0x20,
FeatureCallbackType,
*(&gCallbacksTable.Function + Offset));
if ( Status == EFI_OUT_OF_RESOURCES )
break;
Offset += 16;
}
while ( Offset < 0x30 );
CalculateCrc32Table();
return Status;
}
}
}
return Status0;
}
After executing this function, all handlers from gCallbacksTable
will be registered in the following loop (with SwSmiNum = 0x20
):
do
{
Status = EfiL05SmmSwSmiInterfaceProtocol->RegisterCallbackFunction(
EfiL05SmmSwSmiInterfaceProtocol,
0x20,
FeatureCallbackType,
*(&gCallbacksTable.Function + Offset));
if ( Status == EFI_OUT_OF_RESOURCES )
break;
Offset += 16;
}
while ( Offset < 0x30 );
gCallbacksTable
contains 3 SMI handlers:
.data:00000000000030C0 ; CALLBACK_ITEM gCallbacksTable
.data:00000000000030C0 gCallbacksTable CALLBACK_ITEM <4, offset Callback04>
.data:00000000000030C0 ; DATA XREF: RegisterCallbackFunctions+83↑o
.data:00000000000030D0 CALLBACK_ITEM <0Fh, offset Callback0F>
.data:00000000000030E0 CALLBACK_ITEM <0B8h, offset CallbackB8>
The pseudocode of Callback04
function is shown below:
MACRO_EFI Callback04(UINTN CpuIndex)
{
PARAM_BUFFER *Param;
UINTN Index;
PARAM_BUFFER_VARIABLE *Var;
CHAR16 *Name;
UINT32 NameSize;
UINT8 *VariableData;
UINT32 RdiReg;
UINT32 RsiReg;
CHAR16 VariableName[128];
UINT32 RaxReg;
UINT32 RbxReg;
UINT32 RcxReg;
RaxReg = 0;
RbxReg = 0;
RcxReg = 0;
RdiReg = 0;
RsiReg = 0;
gEfiSmmCpuProtocol->ReadSaveState(gEfiSmmCpuProtocol, 4, EFI_SMM_SAVE_STATE_REGISTER_RAX, CpuIndex, &RaxReg);
gEfiSmmCpuProtocol->ReadSaveState(gEfiSmmCpuProtocol, 4, EFI_SMM_SAVE_STATE_REGISTER_RBX, CpuIndex, &RbxReg);
gEfiSmmCpuProtocol->ReadSaveState(gEfiSmmCpuProtocol, 4, EFI_SMM_SAVE_STATE_REGISTER_RCX, CpuIndex, &RcxReg);
gEfiSmmCpuProtocol->ReadSaveState(gEfiSmmCpuProtocol, 4, EFI_SMM_SAVE_STATE_REGISTER_RDI, CpuIndex, &RdiReg);
gEfiSmmCpuProtocol->ReadSaveState(gEfiSmmCpuProtocol, 4, EFI_SMM_SAVE_STATE_REGISTER_RSI, CpuIndex, &RsiReg);
if ( RaxReg != 0x534D0420 )
return EFI_UNSUPPORTED;
if ( RbxReg != 0x1323190D || RcxReg != 0xD121F1E )
{
RaxReg = 0x80000000;
gEfiSmmCpuProtocol->WriteSaveState(gEfiSmmCpuProtocol, 4, EFI_SMM_SAVE_STATE_REGISTER_RAX, CpuIndex, &RaxReg);
return EFI_UNSUPPORTED;
}
Param = RdiReg;
Index = 0;
// Attacker-controlled pointer
Var = (RdiReg + 0x38);
if ( Param->Count )
{
do
{
ZeroMem(VariableName, 200);
Name = &Var->VariableName;
NameSize = Var->VariableNameSize;
if ( NameSize )
{
if ( VariableName != Name )
{
// 1. VariableNameSize is not validated -> overflow of VariableName stack buffer
CopyMem(VariableName, Name, Var->VariableNameSize);
NameSize = Var->VariableNameSize;
}
}
VariableData = &Var->VariableName + NameSize;
// 2. SmmSetVariable() is used with attacker-controlled arguments
// 3. VariableData can point in SMRAM -> SMRAM disclosure
gEfiSmmVariableProtocol->SmmSetVariable(
VariableName,
&Var->VariableGuid,
VARIABLE_ATTRIBUTE_NV_BS_RT,
Var->VariableDataSize,
VariableData);
++Index;
Var = &VariableData[Var->VariableDataSize];
}
while ( Index < Param->Count );
}
RaxReg = 0;
gEfiSmmCpuProtocol->WriteSaveState(gEfiSmmCpuProtocol, 4, EFI_SMM_SAVE_STATE_REGISTER_RAX, CpuIndex, &RaxReg);
return 0;
}
The RdiReg
value (obtained from the RDI
register using gEfiSmmCpuProtocol->ReadSaveState
) is controlled by the attacker.
The buffer pointed to by Var = (RdiReg + 0x38)
has the following structure:
00000000 struct PARAM_BUFFER_VARIABLE
00000000 {
00000000 UINT32 VariableAttributes;
00000004 UINT32 VariableNameSize;
00000008 UINT32 VariableDataSize;
0000000C EFI_GUID VariableGuid;
0000001C UINT32 VariableName;
00000020 };
The function performs the following operation to set NVRAM variable with controllable parameters:
VariableData = &Var->VariableName + NameSize;
// 2. SmmSetVariable() is used with attacker-controlled arguments
// 3. VariableData can point in SMRAM -> SMRAM disclosure
gEfiSmmVariableProtocol->SmmSetVariable(
VariableName,
&Var->VariableGuid,
VARIABLE_ATTRIBUTE_NV_BS_RT,
Var->VariableDataSize,
VariableData);
++Index;
The VariableData
pointer is controlled by the attacker and can point to SMRAM or just before SMRAM, allowing an attacker to write SMRAM contents (up to 64 KiB) to an NVRAM variable with a chosen name and GUID.
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