Header bannerHeader banner

[BRLY-2022-070] OOB write vulnerability in SMI handler leads to arbitrary code execution in SMM.

June 22, 2023

Summary

BINARLY efiXplorer team has discovered a OOB write vulnerability that allows a possible attacker to hijack execution flow of a code running in System Management Mode. Exploiting this issue could lead to escalating privileges to SMM.

Vulnerability Information

  • BINARLY internal vulnerability identifier: BRLY-2022-070
  • Dell PSIRT assigned CVE identifier: CVE-2023-32472
  • DSA identifier: DSA-2023-225/DSA-2023-225
  • CVSS v3.1: 7.2 High AV:P/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H

Affected Dell firmware with confirmed impact by Binarly team

Product Firmware version CPU Module name Module GUID Module SHA256
Edge Gateway 5200 105 Intel SmbiosDmiEdit e2a74738-8934-48f5-8412-99e948c8dc1b ecbed907011a179b468d0923bc8fff7a3208e4d4cd2b31c080b96deb5a1b4aa9

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 environment 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 the BIOS. Such a malicious firmware code in the BIOS could persist across operating system re-installs. Additionally, this vulnerability could potentially be used by threat actors to bypass security mechanisms provided by the UEFI firmware (for example, Secure Boot and some types of memory isolation for hypervisors).

Vulnerability description

Let's take Edge Gateway 5200's firmware (version: 105, module sha256: ecbed907011a179b468d0923bc8fff7a3208e4d4cd2b31c080b96deb5a1b4aa9) as an example.

The following code in the module actually allows to overflow a stack buffer:

  • a call to a gRT->GetVariable() offset: 0x147b
  • a call to a gRT->GetVariable() offset: 0x14a3
__int64 __fastcall sub_143C()
{
  __int64 v1; // [rsp+40h] [rbp+8h] BYREF

  v1 = 8i64;
  (gRT_0->GetVariable)(                         // <= first call (we can rewrite DataSize here)
    L"SmbiosEntryPointTable",
    &EFI_SMBIOS_NVRAM_DATA_GUID,
    0i64,
    &v1,
    &gSmbiosEntryPointTable);
  (gRT_0->GetVariable)(                         // <= second call
    L"SmbiosEntryPointTableF000",
    &EFI_SMBIOS_NVRAM_DATA_GUID,
    0i64,
    &v1,
    &gSmbiosEntryPointTableF000);
  v1 = 8i64;
  (gRT_0->GetVariable)(L"SmbiosV3EntryPointTable", &EFI_SMBIOS_NVRAM_DATA_GUID, 0i64, &v1, &qword_69E8);
  v1 = 8i64;
  (gRT_0->GetVariable)(L"SmbiosScratchBuffer", &EFI_SMBIOS_NVRAM_DATA_GUID, 0i64, &v1, &qword_69F8);
  v1 = 2i64;
  return (gRT_0->GetVariable)(L"MaximumTableSize", &EFI_SMBIOS_NVRAM_DATA_GUID, 0i64, &v1, &word_6B10);
}

This code is part of the SMI software handler (SwSmiHandler), which is registered as follows:

...
result = gSmst_1->SmmLocateProtocol(&EFI_SMM_SW_DISPATCH2_PROTOCOL_GUID, 0, &EfiSmmSwDispatch2Protocol);
if ( result >= 0 )
{
  RegisterContext.SwSmiInputValue = 0x50;
  result = EfiSmmSwDispatch2Protocol->Register(
              EfiSmmSwDispatch2Protocol,
              SwSmiHandler,
              &RegisterContext,
              &DispatchHandle);
  if ( result >= 0 )
  {
    RegisterContext.SwSmiInputValue = 0x51;
    result = EfiSmmSwDispatch2Protocol->Register(
                EfiSmmSwDispatch2Protocol,
                SwSmiHandler,
                &RegisterContext,
                &DispatchHandle);
    if ( result >= 0 )
    {
      RegisterContext.SwSmiInputValue = 0x52;
      result = EfiSmmSwDispatch2Protocol->Register(
                  EfiSmmSwDispatch2Protocol,
                  SwSmiHandler,
                  &RegisterContext,
                  &DispatchHandle);
      if ( result >= 0 )
      {
        RegisterContext.SwSmiInputValue = 0x53;
        v2 = (EfiSmmSwDispatch2Protocol->Register)(
                EfiSmmSwDispatch2Protocol,
                SwSmiHandler,
                &RegisterContext,
                &DispatchHandle);
        if ( v2 < 0 )
          return v2;
        return v0;
      }
    }
  }
}
...

The DataSize is initialized only once (before the first call to gRT->GetVariable() service).

If the length of SmbiosEntryPointTable NVRAM variable is greater than 8, the second call to gRT->GetVariable() service will overflow a global buffer (&gSmbiosEntryPointTableF000), which in turn could lead to arbitrary code execution.

In this case OOB write allow to rewrite SMM_RUNTIME_SERVICES vendor table address:

.data:00000000000069D8 gSmbiosEntryPointTableF000 dq ?
.data:00000000000069E0 qword_69E0      dq ?
.data:00000000000069E8 qword_69E8      dq ?
.data:00000000000069F0 qword_69F0      dq ?
.data:00000000000069F8 qword_69F8      dq ?
.data:0000000000006A00 qword_6A00      dq ?
.data:0000000000006A08 gST             dq ?
.data:0000000000006A10 gBS             dq ?
.data:0000000000006A18 ; SMM_RUNTIME_SERVICES vendor table address that attacker able to rewrite
.data:0000000000006A18 gRT             dq ?

Next call to gRT->GetVariable() will lead a code execution.

To fix this vulnerability the DataSize must be re-initialized with the size of SmbiosEntryPointTableF000 before calling gRT->GetVariable().

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 (YYYY-mm-dd)
Dell PSIRT is notified 2022-12-29
Dell PSIRT confirmed reported issue 2023-03-16
Dell PSIRT assigned CVE number 2023-06-15
Dell PSIRT provide patch release 2023-06-15
BINARLY public disclosure date 2023-06-21

Acknowledgements

BINARLY efiXplorer team

Tags
SMM