[BRLY-2021-008] SMM callout vulnerability in SMM driver on Fujitsu device (SMM arbitrary code execution).

February 1, 2022

Summary

BINARLY efiXplorer team identified a SMM callout in a Fujitsu device, which allows an attacker to access the System Management Mode and execute arbitrary code.

Vulnerability Information

  • BINARLY internal vulnerability identifier: BRLY-2021-008
  • CERT/CC assigned case number: VU#796611
  • Insyde PSIRT assigned CVE identifier: CVE-2020-5953
  • CVSS v3.1: 7.5 High AV:L/AC:H/PR:H/UI:N/S:C/C:H/I:H/A:H

Affected Fujitsu pieces of firmware with confirmed impact by Binarly team

Device name Driver name Driver MD5 File GUID
Fujitsu LIFEBOOK E449/E459 AsfSecureBootSmm dc44d479dc494f0285fab534dced6c27 F3A3FCA1-466F-4978-AC84-2EA70FAE2BA2

Potential impact

An attacker can exploit this vulnerability to elevate privileges from ring 0 to ring -2, 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 additionally bypasses SMM-based SPI flash protections against modifications, which can help an attacker to install a firmware backdoor/implant into BIOS. Such a malicious firmware code in BIOS could persist across operating system re-installs. Additionally, this vulnerability potentially could be used by threat actors to bypass security mechanisms provided by UEFI firmware (for example, Secure Boot and some types of memory isolation for hypervisors).

Vulnerability description

The vulnerability exists in software System Management Interrupt (SWSMI) handler located at offset 0x474 in module AsfSecureBootSmm.SWSMI handler with number 0x56 dereferences gRT (EFI_RUNTIME_SERVICES) pointer to call a SetVariable() service, which is located outside of SMRAM.Hence, this can result in code execution in SMM (escalating privilege from ring 0 to ring -2).

Below is the decompiled code of the vulnerable handler:

EFI_STATUS __fastcall SwSmiHandler_474(__int64 a1, __int64 a2, UINTN *CpuIndex)
{
  EFI_STATUS Status; // rax
  UINTN CurrentCpuIndex; // rbx
  bool NotFound; // zf
  UINTN CpuNum; // r9
  char AsfSecureBootValue[4]; // [rsp+30h] [rbp-10h] BYREF
  void *RegisterValue; // [rsp+34h] [rbp-Ch] BYREF

  LODWORD(Status) = 0;
  if ( !g_ReadyToBootRegistered )
  {
    LODWORD(Status) = (gSmmCpu_10A0->ReadSaveState)(
                        gSmmCpu_10A0,
                        4i64,
                        EFI_SMM_SAVE_STATE_REGISTER_RBX,
                        *CpuIndex,
                        &RegisterValue);
    // compare RBX value with "$H2O" for each CPU
    for ( CurrentCpuIndex = 0i64; ; ++CurrentCpuIndex )
    {
      NotFound = CurrentCpuIndex == gSmst_1068->NumberOfCpus;
      if ( CurrentCpuIndex >= gSmst_1068->NumberOfCpus )
        break;
      Status = (gSmmCpu_10A0->ReadSaveState)(
                 gSmmCpu_10A0,
                 4i64,
                 EFI_SMM_SAVE_STATE_REGISTER_RBX,
                 CurrentCpuIndex,
                 &RegisterValue);
      if ( !Status && RegisterValue == '$H2O' )
      {
        NotFound = CurrentCpuIndex == gSmst_1068->NumberOfCpus;
        break;
      }
    }
    // if RBX value is "$H2O" for some CPU
    if ( !NotFound )
    {
      Status = (gSmmCpu_10A0->ReadSaveState)(
                 gSmmCpu_10A0,
                 4i64,
                 EFI_SMM_SAVE_STATE_REGISTER_RAX,
                 *CpuIndex,
                 &RegisterValue);
      if ( (Status & 0x8000000000000000ui64) == 0i64 )
      {
        AsfSecureBootValue[0] = BYTE1(RegisterValue);
        // Set "AsfSecureBoot" to dword from RAX
        Status = gRT_1018->SetVariable(L"AsfSecureBoot", &VendorGuid, 7u, 1ui64, AsfSecureBootValue);// vulnerability
        if ( (Status & 0x8000000000000000ui64) == 0i64 )
          LODWORD(Status) = x_GetSetVariablePtr();
      }
    }
  }
  CpuNum = *CpuIndex;
  LODWORD(RegisterValue) = Status;
  // WriteSaveState instead of ReadSafeState (to return the result)
  (gSmmCpu_10A0->ReadSaveState)(gSmmCpu_10A0, 4i64, EFI_SMM_SAVE_STATE_REGISTER_RAX, CpuNum, &RegisterValue);
  return 0i64;
}

The SetVariable() service will be called if two conditions are met:

  • g_ReadyToBootRegistered flag not set
  • the RBX register contain the value 0x2448324F ($H20) for some CPU

The handler is registered in the function located at offset 0x5E0.Below is the decompiled code of this function:

__int64 RegisterHandler()
{
  EFI_HANDLE DispatchHandle; // [rsp+20h] [rbp-18h] BYREF
  void *Registration; // [rsp+28h] [rbp-10h] BYREF
  EFI_SMM_SW_REGISTER_CONTEXT RegisterContext; // [rsp+50h] [rbp+18h] BYREF
  EFI_SMM_SW_DISPATCH2_PROTOCOL *EfiSmmSwDispatch2Protocol; // [rsp+58h] [rbp+20h] BYREF

  gSmst_1068->SmmLocateProtocol(&EFI_SMM_CPU_PROTOCOL_GUID_E00, 0i64, &gSmmCpu_10A0);
  gSmst_1068->SmmLocateProtocol(&EFI_SMM_SW_DISPATCH2_PROTOCOL_GUID_DF0, 0i64, &EfiSmmSwDispatch2Protocol);
  RegisterContext.SwSmiInputValue = 0x56i64;
  (EfiSmmSwDispatch2Protocol->Register)(EfiSmmSwDispatch2Protocol, SwSmiHandler_474, &RegisterContext, &DispatchHandle);
  gSmst_1068->SmmRegisterProtocolNotify(
    &EDKII_SMM_READY_TO_BOOT_PROTOCOL_GUID_E10,
    SetReadyToBootRegisteredFlag,
    &Registration);
  return 0i64;
}

The g_ReadyToBootRegistered flag will be set (by the SetReadyToBootRegisteredFlag() callback) after the SmmReadyToBoot event is signaled. Due to this fact the vulnerability cannot be exploited from the operating system. However, using EFI_BOOT_SERVICES and EFI_RUNTIME_SERVICES services such as SetVariable() is unsafe inside code intended to run in SMM (from SMRAM) because an attacker capable of executing code in DXE phase could exploit this vulnerability to escalate privileges to SMM (ring -2).

To exploit this vulnerability from DXE it is enough to:

  • overwrite the SetVariable() service address in the EFI_RUNTIME_SERVICES table with the shellcode address
  • trigger the SWSMI handler with number 0x56

To fix this vulnerability, it is essential to replace the usage of SetVariable() service from EFI_RUNTIME_SERVICES with EfiSmmVariableProtocol->SmmSetVariable().

Disclosure timeline

This bug is subject to a 90 day disclosure deadline. After 90 days elapsed or a patch has been made broadly available (whichever is earlier), the bug report will become visible to the public.

Disclosure Activity Date
Fujitsu PSIRT is notified 2021-09-10
Fujitsu PSIRT is confirmed issue 2021-09-14
CERT/CC created a case 2021-09-27
Insyde PSIRT assigned CVE number 2021-11-01
Insyde PSIRT provide patch release 2021-11-09
BINARLY public disclosure date 2022-02-01

Acknowledgements

BINARLY efiXplorer team

References

Tags
Fujitsu
SMM