Advisory ID:
BRLY-2025-017

Arbitrary calls to SmmSetVariable with unsanitised arguments in SMI handler

July 29, 2025
Severity:
Medium
CVSS Score
6
Public Disclosure Date:
July 29, 2025
CVE ID:

Summary

BINARLY REsearch team has discovered a SMI handler that passes attacker controlled arguments to SmmSetVariable() without any filtering/sanitisation.
Vendors Affected Icon

Vendors Affected

Lenovo
Insyde
Affected Products icon

Affected Products

Multiple

Potential Impact

An attacker can exploit this vulnerability to arbitrarily call SmmSetVariable with controlled parameters (VariableName, VendorGuid, DataSize and Data). This leads to the bypassing of various security mechanisms. In particular, SetVariable called from SMM may overwrite locked variables.

Vulnerability Information

  • BINARLY internal vulnerability identifier: BRLY-2025-017
  • Lenovo PSIRT assigned CVE identifier: CVE-2025-4424
  • Lenovo advisory: LEN-201013
  • CVSS v3.1: 6.0 Medium AV:L/AC:L/PR:H/UI:N/S:C/C:N/I:H/A:N

Affected firmware with confirmed impact by BINARLY team

Device name Firmware version OEM IBV Module name
ideacentre-aio-3-24arr9 O6KKT12A (2025-01-16) Lenovo Insyde EfiSmiServices
ideacentre-aio-3-24irh9 O6AKT1DA/1.0.0.29 (2024-08-09) Lenovo Insyde EfiSmiServices
yoga-aio-9-32irh8 O62KT24A (2024-08-08) Lenovo Insyde EfiSmiServices

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 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 };

It allow an attacker to arbitrarilly call gEfiSmmVariableProtocol->SmmSetVariable with chosen (VariableName, VendorGuid, DataSize and Data) parameters.

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

Acknowledgements

BINARLY REsearch team

References

Tags
Lenovo
Insyde
SMM
FWHunt
See if you are impacted now with our Firmware Vulnerability Scanner