EDK II - Out-of-Bounds Read in PxeBcSelectBootMenu
PxeBcSelectBootMenu iterates over variable-length PXEBC_BOOT_MENU_ENTRY structures from the DHCP4 vendor option 43 BOOT_MENU sub-option (tag 9) using a loop guarded only by MenuSize > 0 (line 314), which only proves at least 1 byte remains — insufficient for the 3-byte struct header (UINT16 Type + UINT8 DescLen).
- Advisory
- BYTERAY-2026-0284
- CWE
- CWE-125
- Vendor
- TianoCore
- Product
- EDK II
- Reported
- 2026-06-25
- Disclosure
- 2026-10-29
- Affected
- EDK II at commit 70741a15d9a714323a5598eac47727e60507e22f (master, 2026-06-21); affected code in NetworkPkg/UefiPxeBcDxe/PxeBcBoot.c.
- Credit
- Argus-Systems
Executive Summary
PxeBcSelectBootMenu iterates over variable-length PXEBC_BOOT_MENU_ENTRY structures from the DHCP4 vendor option 43 BOOT_MENU sub-option (tag 9) using a loop guarded only by MenuSize > 0 (line 314), which only proves at least 1 byte remains — insufficient for the 3-byte struct header (UINT16 Type + UINT8 DescLen). The loop body reads MenuItem->DescLen (1 byte at offset +2) and MenuArray[0]->Type (2 bytes at offset +0) without verifying the full header fits. The size calculation MenuSize = (UINT8)(MenuSize - (MenuItem->DescLen + 3)) at line 317 can UINT8-underflow when DescLen+3 > MenuSize, wrapping to a large value (e.g., ~252) and keeping the loop alive past the boot-menu data boundary. This wrap at line 317 is the root defect. The walk is capped by PXEBC_MENU_MAX_NUM (24 entries, PxeBcImpl.h:70), each at most 258 bytes, and starts inside the cached packet in PXEBC_PRIVATE_DATA (Private->ProxyOffer or Private->DhcpAck, line 288). The variable-length DescStr array is then read at line 333 via PxeBcDisplayBootItem using the attacker-controlled DescLen with no bounds check, printing the over-read bytes to the local console.
This advisory is one of a set reported to VulnCheck on 2026-06-25 for coordinated disclosure to the EDK II maintainers. After several follow-ups over the following months no response was received. 126 days after the initial report, ByteRay is publishing the findings in good faith so that affected integrators can assess and remediate them. All findings were identified against EDK II commit 70741a15d9a714323a5598eac47727e60507e22f (master, 2026-06-21).
Affected Products and Versions
- Vendor: TianoCore
- Product: EDK II (
NetworkPkg) - Affected commit:
70741a15d9a714323a5598eac47727e60507e22f(master, 2026-06-21). The code is present on that commit and later revisions that have not changed it. - Location:
NetworkPkg/UefiPxeBcDxe/PxeBcBoot.c:317(loop at line 314) - Function:
PxeBcSelectBootMenu - Category:
CWE-125 Out-of-bounds Read
Technical Details
Controllability: EXTERNAL_UNTRUSTED — confidence: high
Data Flow
The vulnerability trace begins with a rogue DHCP4 server sending a crafted OFFER/ACK packet containing vendor option 43 with a BOOT_MENU sub-option (tag 9). PxeBcDhcp4CallBack (PxeBcDhcp4.c:1174) receives the packet via the DHCP4 protocol on a Dhcp4RcvdOffer/Dhcp4RcvdAck event. PxeBcParseVendorOptions (PxeBcDhcp4.c:148–151) extracts VendorOption->BootMenu and BootMenuLen directly from the option 43 BOOT_MENU sub-option data with no validation — both values are externally untrusted. PxeBcDiscoverBootFile (PxeBcBoot.c:906–933) calls PxeBcSelectBootMenu during PXE boot. The loop guard at PxeBcBoot.c:314 checks only MenuSize > 0 (at least 1 byte), not the 3-byte struct header. The sinks are: line 317 reads MenuItem->DescLen (offset +2) and performs a UINT8 subtraction that can underflow when DescLen+3 > MenuSize, wrapping to ~252 and keeping the loop alive past the boundary; line 324 reads MenuArray[0]->Type (2 bytes) without a full-fit check; line 333 calls PxeBcDisplayBootItem(MenuArray[Index]->DescStr, MenuArray[Index]->DescLen), reading DescLen bytes from past the buffer into adjacent struct fields or heap, displaying them on the local console.
Affected Callers
PxeBcDiscoverBootFile
Sanitization / Security Controls
MenuSize == 0early return (PxeBcBoot.c:310–312) — ineffective: only checks for empty menu, does not validate struct header sizes orDescLenbounds.PXEBC_MENU_MAX_NUMcap onIndex(PxeBcBoot.c:314) — partial: limits the walk to 24 entries (at most about 24 * 258 bytes) but does not prevent OOB reads within a single entry.- Optional
EFI_PXE_BASE_CODE_CALLBACK_PROTOCOLuser callback — bypassable: skipped ifCallback==NULL; cannot validate internal struct field bounds regardless. - No sanitization points exist on the data path from network input to vulnerable sinks.
Reachability and Preconditions
Attacker position and trust level for this issue are stated in the Controllability line and Data Flow above, and the concrete trigger is in Reproduction below.
Exploitability: Medium
Realism: Low
Reproduction
- Attacker has access to the local network segment where PXE boot occurs; target machine has PXE boot enabled; no DHCP snooping or 802.1X port authentication.
- Set up rogue DHCP4 server.
- Respond to target's DHCP DISCOVER with crafted OFFER containing option 43 BOOT_MENU sub-option.
- Craft entry with
DescLen+3 > MenuSizeto trigger UINT8 underflow, or position entry at end of buffer so 3-byte header straddles boundary. - Firmware caches options and victim ACKs.
PxeBcSelectBootMenuiterates with underflowedMenuSize, reading past the buffer.PxeBcDisplayBootItemreadsDescLenbytes of OOB data and prints them on the local console.
Impact
Bounded OOB read (at most about 24 * 258 bytes from the cached packet in PXEBC_PRIVATE_DATA) by an unauthenticated on-link rogue DHCP or proxyDHCP server. The over-read bytes are only printed as boot menu text on the local console and used to pick the boot item Type; nothing is sent back to the network attacker, so there is no remote information disclosure. A crash is possible only if the walk reaches unmapped memory.
Remediation and Mitigation
As of commit 70741a15d9a714323a5598eac47727e60507e22f this issue is unfixed upstream and no CVE has been assigned. Before reading each field, validate that its full width fits within the remaining bytes derived from the packet or record size, and stop parsing or reject the input when it does not. The specific control that is absent or ineffective is identified in the Sanitization / Security Controls list under Technical Details.
Timeline
- 2026-06-25 - Reported to VulnCheck for coordinated disclosure to the TianoCore / EDK II security team.
- 2026-07 to 2026-10 - Several follow-ups sent; no response received.
- 2026-10-29 - Public disclosure in good faith, 126 days after the initial report.
Credits
Discovered and reported by Argus-Systems.