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.
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.
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:
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.
CVSS 6.9 <CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:L>
A remote attacker that is positioned within Bluetooth proximity to the victim device can corrupt BIOS memory by sending malformed HID Report structures.
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:
ReportCount=0
and a ReportSize=7
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:
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. ByteIndex
, which is constrained by two additional factors:
UsageMin
to UsageMax
(255), which controls the number of iterations taken by the for-loop. BT_HID_REPORT_MAP_LEN
). 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:
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.
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.
Introduction Since the first lattice-based cryptography results in [Ajtai96], lattices have become a central building block in quantum-resistant cryptosystems. Based on solving systems of linear equations, lattice-based cryptography adds size constraints or error terms to linear systems of equations, turning them into quantum-computer resistant one-way or trapdoor functions. Since the…
Cartographer is a Ghidra plugin that creates a visual "map" of code coverage data, enabling researchers to easily see what parts of a program are executed. It has a wide range of uses, such as better understanding a program, honing in on target functionality, or even discovering unused content in…
We are excited to announce the release of a new version of our open-source, multi-cloud auditing tool ScoutSuite (on GitHub)! This version includes multiple new rules and findings for Azure, which align with some of the latest CIS Benchmark checks, multiple bug fixes and feature enhancements, and minor finding template…