Header bannerHeader banner
Advisory ID:
BRLY-LOGOFAIL-2023-012

[BRLY-LOGOFAIL-2023-012] Memory Corruption vulnerability in DXE driver

June 20, 2024
Severity:
High
CVSS Score
8.2
Public Disclosure Date:
June 19, 2024

Summary

Binarly REsearch Team has discovered a multiple OOB Read/Write vulnerabilities in Insyde firmware due to improper input validation during TGA file processing.

Vendors Affected

Lenovo
AMI

Affected Products

ThinkCentre M75q Gen 2

Potential Impact

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

Summary

Binarly REsearch Team has discovered a multiple OOB Read/Write vulnerabilities in Insyde firmware due to improper input validation during TGA file processing.

Vulnerability Information

     
  • BINARLY internal vulnerability identifier: BRLY-LOGOFAIL-2023-012  
  • Insyde PSIRT assigned CVE identifier: CVE-2023-40238  
  • CVSS v3.1: 8.2 High AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H

Affected modules with confirmed impact by Binarly REsearch Team

Module name Module GUID Module SHA256
TgaDecoderDxe adcca887-5330-414a-81a1-5b578146a397 cb833bf61d29824650023385e43b29a226a8ed105269a0fc6c54044c23061d6d

Potential impact

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

Vulnerability description

The pseudocode of the vulnerable function is shown below:

unsigned __int64 __fastcall DecodeTGA(
        targa_header *Image,
        __int64 ImageData,
        __int64 *a3,
        unsigned __int64 *a4,
        _QWORD *a5,
        _QWORD *a6,
        _DWORD *a7,
        _BYTE *a8)
{
  unsigned __int64 v8; // rbx
  char x; // al
  unsigned __int64 Size; // rsi
  __int64 v14; // rcx
  __int64 v15; // rax
  unsigned __int64 v16; // rbp
  __int64 v17; // rcx
  __int64 v18; // rax
  __int64 v20; // rsi
  __int64 *v21; // r12
  int v22; // edx
  __int64 v23; // r11
  int v24; // r8d
  char *v25; // r8
  unsigned __int64 v26; // r11
  _BYTE *v27; // r9
  __int64 v28; // r10
  _BYTE *v29; // rdx
  char v30; // al
  __int16 *v31; // rdx
  char *v32; // r9
  char v33; // al
  __int16 *v34; // r9
  __int16 *v35; // r15
  char v36; // al
  unsigned __int64 v37; // r10
  __int64 v38; // r11
  __int16 *v39; // rdx
  char *v40; // r9
  char v41; // al
  int width; // edx
  __int64 v43; // r14
  char *v44; // r10
  char *v45; // r9
  int v46; // eax
  __int64 v47; // rcx
  char *v48; // r15
  unsigned __int64 v49; // r12
  char *j; // rbp
  char *v51; // r9
  int v52; // eax
  __int64 v53; // rax
  __int64 v54; // rbp
  unsigned __int64 v55; // r12
  __int64 i; // r15
  __int64 v57; // rax
  __int64 v58; // [rsp+60h] [rbp+8h]
  unsigned __int8 v59; // [rsp+68h] [rbp+10h]

  v8 = 0i64;
  if ( !Image || !a5 || !a6 || !a7 || !a8 )
    return 0x8000000000000002ui64;
  if ( ((Image->image_type - 2) & 0xF7) != 0 || Image->map_type > 1u )
    return 0x8000000000000003ui64;
  x = Image->x;
  switch ( x )
  {
    case 16:
      *a7 = 0;
      break;
    case 24:
      *a7 = 1;
      *a8 = 0;
      goto LABEL_15;
    case 32:
      *a7 = 2;
      break;
    default:
      *a7 = 3;
      *a8 = 0;
      return 0x8000000000000003ui64;
  }
  *a8 = 1;
LABEL_15:
  Size = 4i64 * (Image->height * Image->width);
  if ( Size >= 0x100000000i64 )
    return 0x8000000000000003ui64;
  if ( *a3 )
  {
    v16 = *a4;
    if ( *a4 < Size )
    {
      *a4 = Size;
      return 0x8000000000000005ui64;
    }
  }
  else
  {
    *a4 = Size;
    if ( sub_DD0() )
      v15 = sub_E6C(v14, (Size >> 12) + ((Size & 0xFFF) != 0));
    else
      v15 = sub_EA4(v14, Size);
    *a3 = v15;
    if ( !v15 )
      return 0x8000000000000009ui64;
    v16 = *a4;
  }
  if ( sub_DD0() )
    v18 = sub_E6C(v17, (v16 >> 12) + ((v16 & 0xFFF) != 0));
  else
    v18 = sub_EA4(v17, v16);
  v20 = v18;
  if ( !v18 )
    return 0x8000000000000009ui64;
  *a5 = Image->height;
  *a6 = Image->width;
  v21 = a3;
  v22 = (SHIBYTE(Image->map_first) >> 32) & 7;
  v23 = *a3;
  v24 = SHIBYTE(Image->map_first) / 8;
  v58 = *a3;
  if ( ((v22 + SHIBYTE(Image->map_first)) & 7) != v22 )
    ++v24;
  v25 = &Image->x + Image->map_type * *(&Image->map_first + 1) * v24 + Image->id_length + 2;
  v59 = SLOBYTE(Image->x) / 8;
  if ( Image->image_type != 2 )
  {
    if ( Image->image_type != 10 )
      goto LABEL_85;
    v26 = 0i64;
    if ( !(Image->height * Image->width) )
      goto LABEL_84;
    while ( 1 )
    {
      v27 = (v20 + 4 * v26);
      v28 = *v25 & 0x7F;
      v29 = v25 + 1;
      if ( v27 && v25 != -1i64 )
      {
        if ( *a7 )
        {
          if ( *a7 == 1 )
          {
            *v27 = *v29;
            v27[1] = v25[2];
            v27[2] = v25[3];
            v27[3] = 0;
            goto LABEL_43;
          }
          if ( *a7 != 2 )
            goto LABEL_43;
          *v27 = *v29;
          v27[1] = v25[2];
          v27[2] = v25[3];
          v30 = v25[4];
        }
        else
        {
          *v27 = 8 * *v29;
          v27[1] = (v25[2] << 6) | (v25[1] >> 2) & 0x38;
          v27[2] = 2 * (v25[2] & 0xFC);
          v30 = v25[2] & 0x80;
        }
        v27[3] = v30;
      }
LABEL_43:
      ++v26;
      if ( *v25 < 0 )
      {
        if ( !v28 )
        {
LABEL_69:
          v25 += v59 + 1;
          goto LABEL_70;
        }
        v34 = (v20 + 4 * v26 + 2);
        v26 += v28;
        while ( 2 )
        {
          v35 = v34 - 1;
          if ( v34 != &word_2 && v25 != -1i64 )
          {
            if ( *a7 )
            {
              if ( *a7 == 1 )
              {
                *v35 = *v29;
                *(v34 - 1) = v25[2];
                *v34 = v25[3];
                *(v34 + 1) = 0;
              }
              else if ( *a7 == 2 )
              {
                *v35 = *v29;
                *(v34 - 1) = v25[2];
                *v34 = v25[3];
                v36 = v25[4];
                goto LABEL_67;
              }
            }
            else
            {
              *v35 = 8 * *v29;
              *(v34 - 1) = (v25[2] << 6) | (v25[1] >> 2) & 0x38;
              *v34 = 2 * (v25[2] & 0xFC);
              v36 = v25[2] & 0x80;
LABEL_67:
              *(v34 + 1) = v36;
            }
          }
          v34 += 2;
          if ( !--v28 )
            goto LABEL_69;
          continue;
        }
      }
      v25 += v59 + 1;
      if ( v28 )
      {
        v31 = (v20 + 4 * v26 + 2);
        v26 += v28;
        while ( 1 )
        {
          v32 = (v31 - 1);
          if ( v31 == &word_2 || !v25 )
            goto LABEL_55;
          if ( !*a7 )
            break;
          if ( *a7 == 1 )
          {
            *v32 = *v25;
            *(v31 - 1) = v25[1];
            *v31 = v25[2];
            *(v31 + 1) = 0;
          }
          else if ( *a7 == 2 )
          {
            *v32 = *v25;
            *(v31 - 1) = v25[1];
            *v31 = v25[2];
            v33 = v25[3];
LABEL_54:
            *(v31 + 1) = v33;
          }
LABEL_55:
          v25 += v59;
          v31 += 2;
          if ( !--v28 )
            goto LABEL_70;
        }
        *v32 = 8 * *v25;
        *(v31 - 1) = (v25[1] << 6) | (*v25 >> 2) & 0x38;
        *v31 = 2 * (v25[1] & 0xFC);
        v33 = v25[1] & 0x80;
        goto LABEL_54;
      }
LABEL_70:
      if ( v26 >= Image->height * Image->width )
      {
        v21 = a3;
        goto LABEL_84;
      }
    }
  }
  v37 = 0i64;
  if ( !(Image->height * Image->width) )
    goto LABEL_85;
  v38 = (SLOBYTE(Image->x) / 8);
  v39 = (v18 + 2);
  do
  {
    v40 = (v39 - 1);
    if ( v39 != &word_2 && v25 )
    {
      if ( *a7 )
      {
        if ( *a7 == 1 )
        {
          *v40 = *v25;
          *(v39 - 1) = v25[1];
          *v39 = v25[2];
          *(v39 + 1) = 0;
          goto LABEL_83;
        }
        if ( *a7 != 2 )
          goto LABEL_83;
        *v40 = *v25;
        *(v39 - 1) = v25[1];
        *v39 = v25[2];
        v41 = v25[3];
      }
      else
      {
        *v40 = 8 * *v25;
        *(v39 - 1) = (v25[1] << 6) | (*v25 >> 2) & 0x38;
        *v39 = 2 * (v25[1] & 0xFC);
        v41 = v25[1] & 0x80;
      }
      *(v39 + 1) = v41;
    }
LABEL_83:
    v25 += v38;
    ++v37;
    v39 += 2;
  }
  while ( v37 < Image->height * Image->width );
LABEL_84:
  v23 = v58;
LABEL_85:
  width = Image->width;
  v43 = 4i64 * Image->width;
  switch ( (BYTE1(Image->x) >> 4) & 3 )
  {
    case 0:
      v54 = v20;
      v55 = 0i64;
      for ( i = v23 + 4i64 * (Image->height - 1) * width; v55 < Image->height; v54 += v57 )
      {
        if ( v43 && i != v54 )
          CopyMem(i, v54, v43);
        ++v55;
        v57 = 4i64 * Image->width;
        i -= v57;
      }
      goto LABEL_109;
    case 1:
      v48 = v20;
      v49 = 0i64;
      for ( j = (v23 + 4i64 * (Image->height - 1) * width); v49 < Image->height; v48 += v53 )
      {
        if ( v43 && j != v48 )
        {
          CopyMem(j, v48, v43);
          LOWORD(width) = Image->width;
        }
        v51 = j;
        v25 = &j[4 * width - 4];
        if ( j < v25 )
        {
          do
          {
            v52 = *v51;
            *v51 = *v25;
            v51 += 4;
            *v25 = v52;
            v25 -= 4;
          }
          while ( v51 < v25 );
          LOWORD(width) = Image->width;
        }
        ++v49;
        v53 = 4i64 * width;
        j -= v53;
      }
LABEL_109:
      v47 = v20;
      goto LABEL_110;
    case 2:
      goto LABEL_94;
    case 3:
      v44 = v20;
      if ( Image->height )
      {
        do
        {
          v45 = v44;
          v25 = &v44[4 * width - 4];
          if ( v44 < v25 )
          {
            do
            {
              v46 = *v45;
              *v45 = *v25;
              v45 += 4;
              *v25 = v46;
              v25 -= 4;
            }
            while ( v45 < v25 );
            LOWORD(width) = Image->width;
          }
          ++v8;
          v44 += 4 * width;
        }
        while ( v8 < Image->height );
      }
LABEL_94:
      *v21 = v20;
      v47 = v23;
LABEL_110:
      sub_E20(v47, *a4, v25);
      break;
  }
  return 0i64;
}

In the pseudocode above Image->height and Image->width are INT16 fields.

Consider the following code:

Size = 4 * (unsigned int)(Image->height * Image->width);
if ( Size >= 0x100000000 ) { ... }

When Image->width = 0xffff (-1) and Image->height = 0xffff (-1), Size = 4 * -1 * -1 = 4. This will result in multiple read and write operations outside the allocated buffer.

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)
Lenovo PSIRT is notified 2023-06-21
Lenovo ID (LEN-132940) is assigned 2023-06-22
CERT/CC is notified 2023-07-10
Insyde PSIRT confirmed reported issues 2023-09-10
Insyde PSIRT assigned CVE ID 2023-11-27
Insyde advisory release date 2023-12-06
BINARLY public disclosure date 2024-06-19

Acknowledgements

Binarly REsearch Team

Tags
Vulnerability
supply chain
FWHunt
See if you are impacted now with our Firmware Vulnerability Scanner