Skip to navigation Skip to main content Skip to footer

Intel BIOS Advisory – Memory Corruption in HID Drivers 

08 August 2023

By NCC Group

This advisory is the third in a series of posts that cover vulnerabilities I found while auditing the “ICE TEA” leak, which resulted in the exposure of Intel’s and Insyde Software’s proprietary BIOS source code. The other two blog posts can be found here (TOCTOU in Intel SMM) and here (multiple memory safety issues in Insyde Software SMM).

In this post, I will be focusing on two additional BIOS vulnerabilities. The first bug impacts the Bluetooth keyboard driver (HidKbDxe in BluetoothPkg) and the second bug impacts a touch panel driver (I2cTouchPanelDxe in AlderLakePlatSamplePkg).  

Both vulnerabilities were fixed by Intel on August 8th with the 2023.3 IPU release. Intel’s advisory can be found here

What The HID?

To understand the issues that I will be covering in this blog, it is necessary to very briefly introduce the relevant portions of the HID specification – the Report Descriptor data structure. 

First of all, HID stands for Human Interface Device. Unsurprisingly, HID devices allow humans to interact with a computer. Products like keyboards, mice, gaming controllers, and touchscreens are all examples of HID devices. 

The HID specification requires that every device must define the format of its data packets in a structure known as the Report Descriptor. The HID device sends this Report Descriptor to the host to convey various things, such as how many different packet types (Reports) are supported, the packet sizes, and the purpose (Usage) of each packet.  

How information is organized in a Report Descriptor (Section 5.4 of the HID spec)

Shown below is the Report Descriptor for my mouse. Note the defined Usages for the Button and Wheel, which I think we’d all agree are typical mouse-like features we expected to find. By the way, each element in a Report Descriptor (the below rows) is called an Item

0x05, 0x01, // Usage Page (Generic Desktop Ctrls) 
0x09, 0x02, // Usage (Mouse) 
0xA1, 0x01, // Collection (Application) 
0x09, 0x01, //   Usage (Pointer) 
0xA1, 0x00, //   Collection (Physical) 
0x05, 0x09, //     Usage Page (Button) 
0x19, 0x01, //     Usage Minimum (0x01) 
0x29, 0x08, //     Usage Maximum (0x08) 
0x15, 0x00, //     Logical Minimum (0) 
0x25, 0x01, //     Logical Maximum (1) 
0x75, 0x01, //     Report Size (1) 
0x95, 0x08, //     Report Count (8) 
0x81, 0x02, //     Input (Data, Var, Abs, No Wrap, Linear, Preferred State, No Null Position) 
0x05, 0x01, //     Usage Page (Generic Desktop Ctrls) 
0x09, 0x30, //     Usage (X) 
0x09, 0x31, //     Usage (Y) 
0x09, 0x38, //     Usage (Wheel) 
0x15, 0x81, //     Logical Minimum (-127) 
0x25, 0x7F, //     Logical Maximum (127) 
0x75, 0x08, //     Report Size (8) 
0x95, 0x03, //     Report Count (3) 
0x81, 0x06, //     Input (Data, Var, Rel, No Wrap, Linear, Preferred State, No Null Position) 
0xC0,       //   End Collection 
0xC0,       // End Collection 

Once the host driver has processed the Report Descriptor, it is then able to communicate with the peripheral device. It does this by sending or receiving Report packets. There are three types of Reports: 

  • Input Reports: Data sent from the device to the host (e.g., a mouse reporting that a button has been pressed)
  • Output Reports: Data sent from the host to the device (e.g., the host requesting that your keyboard turn on/off an LED)
  • Feature Reports: Data sent in either direction (e.g., configuration or calibration data) 

The important takeaway from all of this is that these Reports and Report Descriptors are fully attacker controlled. A malicious HID device could send arbitrary data in these packets. For example, a descriptor could have malformed Report Size or Report Count values, or it could contain an excessive number of Usage Pages or Collections. The host driver that communicates with the HID peripheral must parse these structures extremely carefully to avoid memory safety problems. 

Memory Corruption Due to Malformed Bluetooth Keyboard HID Report (CVE-2022-44611)

CVSS 6.9  

Impact

A remote attacker that is positioned within Bluetooth proximity to the victim device can corrupt BIOS memory by sending malformed HID Report structures. 

Description

The BtHidParseReportMap() function (not shown) is responsible for performing the initial shallow parsing of the Report Descriptor that was received from a Bluetooth keyboard. After this parsing is complete, the Report Descriptor is saved, and any driver feature that needs it can call the GetReportFormatList() function.  

Shown below is one example where the Descriptor is accessed so that the driver can determine whether the keyboard has LEDs so that it can turn them on or off by sending an Output Report.  

EFI_STATUS SetKeyLED ( IN HID_KB_DEV *HidKbDev  ) 
{ 
  ... 
  LIST_ENTRY     *Link; 
  LIST_ENTRY     *Cur; 
  ... 
  HidKbDev->Hid->GetReportFormatList(HidKbDev->Hid,  Link); 
  Cur = GetFirstNode (Link); 
  ... 

This function first walks the Report’s items and searches for all LED-related Usage Pages, which it then processes to calculate the total size of the Output Report in HidKbDev->OutLedReportSize.  

  ... 
  UINT8          *Data; 
  ... 
  HID_REPORT_FMT *ReportItem; 
  ... 
  if (HidKbDev->OutLedReportSize == 0){ 
    while (!IsNull (Link, Cur)) { 
      ReportItem = ITEM_FROM_LINK(Cur); 
      if (ReportItem->UsagePage == BT_HID_LED_USAGE_PAGE) 
        HidKbDev->OutLedReportSize = HidKbDev->OutLedReportSize  
                        + (ReportItem->ReportCount * ReportItem->ReportSize); 
      Cur = GetNextNode (Link, Cur); 
    } 
    Cur = GetFirstNode (Link); 
  } 
  Data = (UINT8 *)AllocateZeroPool(HidKbDev->OutLedReportSize/8); 
  ... 

Unfortunately, the function doesn’t properly account for Usage Pages with zero-sized Report Count fields, nor does it properly handle Usage Pages whose product of Count and Size is not a multiple of 8. So, for the sake of argument, let’s imagine the malicious Bluetooth keyboard that presents a malformed descriptor that is composed of multiple LED Usage Pages, structured like this: 

  • Almost all pages have a ReportCount=0 and a ReportSize=7 
  • Followed by a single page that has a ReportCount=1 and a ReportSize=8 

This will result in HidKbDev->OutLedReportSize being equal to 8, because the sum that is calculated by the while-loop (0*7 + 0*7 + … + 1*8) is simply 8. In this case, Data would point to a 1-byte buffer that is allocated on the heap. 

Next, the function will walk the Report list for a second time, once again seeking all LED-related Usage Pages.  

  UINT32         ByteIndex; 
  UINT32         BitIndex; 
  ... 
  ByteIndex = 0;  
  BitIndex  = 0;  
  ... 
  while (!IsNull (Link, Cur)) { 
    ReportItem = ITEM_FROM_LINK(Cur); 
    if (ReportItem->UsagePage == BT_HID_LED_USAGE_PAGE) { 
      ... 
      for (Index = ReportItem->UsageMin; Index <= ReportItem->UsageMax; Index++) { 
        if (HidKbDev->LedKeyState[Index] == TRUE) { 
          BitMask = Data[ByteIndex]; 
          BitMask = BitMask | (1 << BitIndex); 
          Data[ByteIndex] = BitMask; 
        } 
 
        BitIndex += ReportItem->ReportSize; 
        if (BitIndex == 7){ 
          ByteIndex ++; 
          BitIndex = 0;  
        } 
      }     
    }     
    Cur = GetNextNode (Link, Cur); 
  } 
  ... 

Above, the attacker has control over the UsageMin and UsageMax items, which can take on values as large as 255, even though LedKeyState[] has only 78 elements. This will cause the for-loop to take an excessive number of iterations.

Note also that BitIndex is attacker-controlled because it was derived from the attacker-controlled item named ReportItem->ReportSize. As I hinted earlier, the malformed descriptor will contain multiple Usage Pages which all have a Report Size of 7. This will force BitIndex to be 7 on each iteration of the for-loop, causing the “if(BitIndex==7)” condition to be entered each time. This will force ByteIndex to be incremented an excessive number of times.

Ultimately, this can lead to memory corruption, because ByteIndex is used as an offset adjustment when writing into Data[], the previously allocated 1-byte heap buffer.

The malformed Report Descriptor that triggers this bug might look something like this: 

... 
// Multiple LED Usage Pages with: 
// - The broadest UsageMin/Max range (0-255) 
// - ReportSize of 7 and ReportCount of 0 
0x05, 0x08,        //     Usage Page (LED) 
0x19, 0x00,        //     Usage Minimum (0x00) 
0x29, 0xFF,        //     Usage Maximum (0xFF) 
0x75, 0x07,        //     Report Size (7) 
0x95, 0x00,        //     Report Count (0) 
// Repeated approximately 50 times ... 
... 
// NCC: One single Usage Page where ReportSize=8 and Count=1 
0x05, 0x08,        //     Usage Page (LED) 
0x19, 0x00,        //     Usage Minimum (0x00) 
0x29, 0xFF,        //     Usage Maximum (0xFF) 
0x75, 0x08,        //     Report Size (8) 
0x95, 0x01,        //     Report Count (1) 
... 

In terms of impact and exploitability, I should admit that I didn’t PoC this bug, and instead discovered it by pure code review. Although I haven’t ruled out exploitation, I admit that it may be difficult to translate this out-of-bounds write into arbitrary code execution for two reasons: 

  1. Due to how the value of BitMask is entangled with BitIndex and Data[ByteIndex]. That is, the BitMask value is read from Data[], modified, and written back into the same position. 
  2. The out-of-bounds writes are limited by the maximum value of ByteIndex, which is constrained by two additional factors: 
    • The range of UsageMin to UsageMax (255), which controls the number of iterations taken by the for-loop. 
    • The number of Usage Pages that can fit in the Descriptor, which controls the number of iterations taken by the while-loop. A rough calculation shows that approximately 50 Usage Pages can fit in the 512-byte report map (BT_HID_REPORT_MAP_LEN). 

Memory Corruption When Parsing Touch HID Report Stack

Impact

This vulnerability impacts another class of HID devices: touch panels, which transmit HID data over an I2C bus to the host. Unlike the previous bug which was exploitable by remote-but-nearby attackers via Bluetooth, this bug can only be exploited by a physical attacker who disassembles the laptop and tampers with I2C bus traffic. This may be accomplished by: 

  1. Implanting an interposer device which actively mutates HID reports as they are transmitted over the I2C serial bus. 
  2. Flashing malicious firmware onto the existing touch panel. 
  3. Replacing the entire touch panel or its microcontroller with one under the attacker’s control. 

An attacker that achieves this degree of physical access will be able to present the BIOS with a malformed HID Report. While parsing the tokenized report, memory corruption will occur in the BIOS, which could lead to code execution. 

Although the potential impact is high due to the possibility of code execution, the overall risk rating is tempered by the physical access requirement. Intel has a policy to not issue CVEs for vulnerabilities that involve “open chassis” attacks. 

Description

In the following code snippet, we can observe that a descriptor may contain multiple COLLECTION tokens. If an excessive number of these token types are present, the array index named CollectionCount will be repeatedly incremented. This can cause the value to become larger than the maximum valid index of the Stack->TempReport.Collection[] array.  

VOID UpdateStack( PARSER_STACK* Stack, TOKEN Token, INPUT_REPORT_TABLE* ReportTable ) 
{ 
  switch (Token.ID) { 
  ... 
  case COLLECTION: 
    ... 
    else if (Token.Value == LOGICAL) 
    { 
      if (Stack->TempReport.Collection[Stack->TempReport.CollectionCount - 1].ValidCollection  
            == TRUE) 
      { 
        Stack->TempReport.CollectionCount += 1; 
        Stack->TempReport.Collection[Stack->TempReport.CollectionCount - 1].BitsTotal =  
          Stack->TempReport.Collection[Stack->TempReport.CollectionCount - 2].BitsTotal; 
      } 
      Stack->TempReport.Collection[Stack->TempReport.CollectionCount - 1].ValidCollection =  
        TRUE; 
    } 
    break; 
  ... 

If an attacker were to trigger this bug, all subsequent writes to the Collection[] array (256 elements in size) would corrupt memory beyond the end of the report stack, which resides on the heap. 

Disclosure Timeline 

  • October 12, 2022: NCC Group reports both vulnerabilities to Intel. Intel’s triage bot responds immediately indicating that the report has been sent to reviewers for disposition. 
  • December 7, 2022: NCC Group requests an update. Intel responds that the Bluetooth vulnerability is accepted as valid, but indicates that the I2C vulnerability is not valid due to the physical access requirement. However, Intel nonetheless intends to patch the weakness, so public disclosure is discouraged until they have shipped a BIOS update. 
  • January 18, 2023: Intel shares the CVE number and indicates that disclosure is targeted for later this year, in August. 
  • August 8, 2023: Disclosure publication in IPU 2023.3