Multiple SMM memory corruption vulnerabilities in SMM module on Lenovo device (SMRAM write).
BINARLY REsearch team has discovered multiple memory corruption vulnerabilities in Lenovo device firmware that could allow a potential attacker to write fixed or predictable data to an attacker-controlled address.
Image preview
Potential Impact
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.
Image preview
Vulnerability Information
- BINARLY internal vulnerability identifier: BRLY-DVA-2025-015
- Lenovo PSIRT assigned CVE identifier: CVE-2025-4423
- Lenovo advisory: LEN-201013
- Insyde advisory: INSYDE-SA-2025007
- CVSS v3.1: 8.2 High AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H
Image preview
Affected firmware
| Device name | Unpacked firmware SHA256 | Firmware version | OEM | IBV | Module name | Module GUID | Module SHA256 | Module kind |
|---|---|---|---|---|---|---|---|---|
ideacentre-aio-3-24arr9 | 39214500da92c1f37fabc733127c3ac46ea0302eb5ad97a18e562fc3f17111e1 | O6KKT12A (2025-01-16) | Lenovo | Insyde | SetupAutomationSmm | dc08d13d-ec1a-4458-b37a-c50118d7ac48 | 87cafb102629124735c2e506956606875378e8c4d7b32ed8abc95e79fafd4657 | SmmModule |
ideacentre-aio-3-24irh9 | 54f3ed6c1c43ccd228c7965c483e8a476d271b7b850fe39a628343443da4229f | O6AKT1DA/1.0.0.29 (2024-08-09) | Lenovo | Insyde | SetupAutomationSmm | dc08d13d-ec1a-4458-b37a-c50118d7ac48 | 890616a5a64cb65044a9420a3b674d9e2a1bfa9e5aae1082e48ce6e8c9fa6348 | SmmModule |
yoga-aio-9-32irh8 | 6e1c212345471b08ca6aa41b1f1996e924191d6b88d5190c51bf82452f194858 | O62KT24A (2024-08-08) | Lenovo | Insyde | SetupAutomationSmm | dc08d13d-ec1a-4458-b37a-c50118d7ac48 | e31fb0dfec46d7e0dcef3de2488859da00c7e2439d2f91eb0938f94bd5052199 | SmmModule |
Image preview
Vulnerability description
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 Callback0F function is shown below:
MACRO_EFI Callback0F(UINTN CpuIndex)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS NUMPAD "+" TO EXPAND]
RaxReg = 0;
Offset = 0;
RbxReg = 0;
RcxReg = 0;
RdiReg = 0;
RsiReg = 0;
Buffer = 0;
Size = 0;
EfiPcdProtocol = LocateEfiPcdProtocol();
PtrFromPcd = (EfiPcdProtocol->GetPtr)(&L05_SERVICES_TOKEN_SPACE_GUID, 0x1205201D);
ZeroMem(StackBuffer, 0x18);
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 != 0x534D0F20 )
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;
Data = (RdiReg + 0x28);
*RdiReg = 'USR$'; // SMRAM write
Var = &Param->Var;
Param->Value4 = 3; // SMRAM write
Param->Count = 0; // SMRAM write
Param->Value14 = 0x200; // SMRAM write
SetMem(Data, 0x10, 0xFF); // SMRAM write
while ( StackBuffer != PtrFromPcd && CompareMem(StackBuffer, PtrFromPcd, 0x10) )
{
NameFromPcd = (PtrFromPcd + 16);
Size = 0;
Buffer = 0;
if ( gEfiSmmVariableProtocol->SmmGetVariable(PtrFromPcd + 8, PtrFromPcd, 0, &Size, 0) == EFI_BUFFER_TOO_SMALL )
gSmst->SmmAllocatePool(EfiRuntimeServicesData, Size, &Buffer);
if ( Buffer )
{
if ( (gEfiSmmVariableProtocol->SmmGetVariable(PtrFromPcd + 8, PtrFromPcd, 0, &Size, Buffer) & 0x8000000000000000) == EFI_SUCCESS )
{
Var->VariableAttributes = 7; // SMRAM write
VarNameFromPcd = PtrFromPcd + 16;
VarLen = 0;
if ( *NameFromPcd )
{
do
{
++VarNameFromPcd;
++VarLen;
}
while ( *VarNameFromPcd );
}
VariableNameSize = 2 * VarLen;
Var->VariableNameSize = 2 * VarLen; // SMRAM write
Var->VariableDataSize = Size; // SMRAM write
if ( &Var->VariableGuid != PtrFromPcd )
{
CopyMem(&Var->VariableGuid, PtrFromPcd, 0x10); // SMRAM write
VariableNameSize = Var->VariableNameSize;
}
pVariableName = &Var->VariableName;
NameSize = VariableNameSize;
if ( VariableNameSize && pVariableName != NameFromPcd )
CopyMem(pVariableName, PtrFromPcd + 16, VariableNameSize); // SMRAM write
DataSize = Size;
VariableData = pVariableName + NameSize;
if ( Size && VariableData != Buffer )
{
CopyMem(VariableData, Buffer, Size); // SMRAM write
DataSize = Size;
}
++Param->Count;
Var = &VariableData[DataSize];
Offset += NameSize + DataSize + 28;
}
FreePool(Buffer);
}
NameIndex = 0;
while ( *NameFromPcd )
{
NameFromPcd = (NameFromPcd + 2);
++NameIndex;
}
PtrFromPcd = PtrFromPcd + 2 * NameIndex + 18;
}
Param->Crc32 = 0; // SMRAM write
Param->DataForCrc = Offset + 0x38; // SMRAM write
if ( Offset != 0xFFFFFFC8 )
{
Crc = -1;
i = 0;
do
{
Current = *(&Param->Signature + i++);
Crc = gCrc32Table[Crc ^ Current] ^ (Crc >> 8);
}
while ( i < (Offset + 0x38) );
Param->Crc32 = ~Crc; // SMRAM write
}
RaxReg = 0;
gEfiSmmCpuProtocol->WriteSaveState(gEfiSmmCpuProtocol, 4, EFI_SMM_SAVE_STATE_REGISTER_RAX, CpuIndex, &RaxReg);
return 0;
}
As we can see from the pseudocode, the RdiReg value (obtained from the RDI register with gEfiSmmCpuProtocol->ReadSaveState) is not validated before attempting to write to the buffer pointed to by RdiReg:
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 != 0x534D0F20 )
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;
Data = (RdiReg + 0x28);
*RdiReg = 'USR$'; // SMRAM write
Var = &Param->Var;
Param->Value4 = 3; // SMRAM write
Param->Count = 0; // SMRAM write
Param->Value14 = 0x200; // SMRAM write
SetMem(Data, 0x10, 0xFF); // SMRAM write
...
This primitive may allow an attacker to corrupt SMRAM and execute an arbitrary code.
Image preview
Disclosure timeline
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.
| Disclosure Activity | Date |
|---|---|
Lenovo PSIRT is notified | 2025-04-08 |
Lenovo PSIRT is confirmed issue | 2025-06-16 |
Lenovo PSIRT assigned CVE number | 2025-06-16 |
BINARLY public disclosure date | 2025-07-29 |
Image preview
Acknowledgements
Image preview
See if you are impacted now with our Firmware Vulnerability Scanner
Find Vulnerabilities, Generate SBOMs & CBOMs