EDK II - Off-by-One Error in PxeBcSelectBootMenu
The local stack array Blank[PXEBC_DISPLAY_MAX_LINE] (70 bytes, line 279) is indexed at line 349 (Blank[MenuArray[Select]->DescLen] = 0;) and line 426 (Blank[MenuArray[LastSelect]->DescLen] = 0;) using the raw DescLen field sourced from the DHCP4 vendor option BOOT_MENU sub-option (option 43, tag 9).
- Advisory
- BYTERAY-2026-0238
- CWE
- CWE-193
- 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
The local stack array Blank[PXEBC_DISPLAY_MAX_LINE] (70 bytes, line 279) is indexed at line 349 (Blank[MenuArray[Select]->DescLen] = 0;) and line 426 (Blank[MenuArray[LastSelect]->DescLen] = 0;) using the raw DescLen field sourced from the DHCP4 vendor option BOOT_MENU sub-option (option 43, tag 9). DescLen is a UINT8 (range 0–255) read directly from the DHCP packet with no bounds validation before being used as an array index. When DescLen >= 70, the null byte write goes out of bounds on the stack, up to 185 bytes past the buffer end. The sibling function PxeBcDisplayBootItem (line 33) correctly clamps with Len = (UINT8)MIN(PXEBC_DISPLAY_MAX_LINE, Len), proving the developer knew DescLen could exceed 70 but forgot to apply the same protection to the Blank writes. The buffer was sized for display (70) but the index uses the full unclamped input range — a fencepost/boundary-validation error where the valid range is 0–69 but access reaches 70–255.
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:349(and:426) - Function:
PxeBcSelectBootMenu - Category:
Stack buffer overflow / boundary validation error
Technical Details
Controllability: EXTERNAL_UNTRUSTED — Confidence: High
Data Flow
The DescLen field originates from the DHCP4 ACK packet's Vendor Option 43, BOOT_MENU sub-option (tag 9), parsed at PxeBcDhcp4.c:151 where the raw packet data is cast directly to PXEBC_BOOT_MENU_ENTRY* with no bounds validation on DescLen (a UINT8 with range 0–255, per PxeBcDhcp4.h:223). The offer type is set to PxeOfferTypeDhcpPxe10 at PxeBcDhcp4.c:585 based on the presence of the PXEClient vendor class option (attacker-controlled). After the DHCP exchange completes at PxeBcBoot.c:914, the attacker-supplied MENU_PROMPT Timeout=255 causes PxeBcSelectBootPrompt to return EFI_SUCCESS at PxeBcBoot.c:122, which triggers PxeBcSelectBootMenu(FALSE) at line 928. Inside that function, a 70-byte stack buffer Blank[PXEBC_DISPLAY_MAX_LINE] is allocated at line 279. The menu entries are walked at lines 314–319 with DescLen used as an iteration step and no bounds check against 70. On the first menu-display iteration (Select=0), line 349 executes Blank[MenuArray[Select]->DescLen] = 0, writing a null byte at stack offset DescLen past the buffer — up to 185 bytes out of bounds. A second write site at line 426 exhibits the same defect.
Affected Callers
PxeBcBoot(PxeBcBoot.c:928— interactive menu path)
Sanitization / Security Controls
IS_VALID_BOOT_MENUbitmap check (PxeBcBoot.c:298) — only verifies the BOOT_MENU sub-option is present; does not validateDescLenbounds. Effective for its limited purpose but does not mitigate this issue.MenuSize == 0guard (PxeBcBoot.c:310) — prevents an empty menu; does not constrain individualDescLenvalues.PxeBcDisplayBootItemclamp (PxeBcBoot.c:33) —Len = (UINT8)MIN(PXEBC_DISPLAY_MAX_LINE, Len)exists only in the sibling function, not inPxeBcSelectBootMenuwhere theBlank[]writes occur. Its absence in the vulnerable function confirms the bug.- DHCP exchange (
PxeBcBoot.c:914) — DHCP is an unauthenticated protocol; no security boundary exists between the attacker-supplied ACK and the firmware. - Timeout=255 auto-display (
PxeBcBoot.c:121-122) — attacker-controlled bypass of the user-keystroke gate (F8/Ctrl-M).
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: High
Realism: High — DHCP is an unauthenticated protocol; all required packet fields are attacker-controlled, and the OOB write fires automatically with no user interaction.
Reproduction
- Attacker positions as rogue DHCP server on the L2 segment reachable by the victim.
- Victim firmware triggers PXE network boot and broadcasts DHCPDISCOVER.
- Attacker responds with crafted DHCPOFFER + DHCPACK containing: option 60 =
PXEClient, Vendor Option 43 with DISCOVER_VENDOR_OPTION bit set (yieldsPxeOfferTypeDhcpPxe10), MENU_PROMPT sub-option with Timeout=255 (forcesEFI_SUCCESSwithout user keystroke), and BOOT_MENU sub-option with one entry whoseDescLen = 0x60–0xFF(96–255). PxeBcDiscoverBootFilecompletesDhcp(),PxeBcSelectBootPromptreturnsEFI_SUCCESS, andPxeBcSelectBootMenu(FALSE)is called at line 928.- On the first
do-loop iteration (Select=0), line 349 writesBlank[MenuArray[0]->DescLen] = 0, placing a null byte up to 185 bytes past the 70-byte buffer, corrupting adjacent stack locals and potentially the saved return address or frame pointer.
Impact
Stack-based out-of-bounds write (null byte) during pre-boot firmware execution in a ring-0 firmware-privilege context with no OS protections. While a single null byte limits arbitrary code execution, it can corrupt the saved return address or frame pointer, causing a denial-of-service crash of the boot path and potentially controlled redirection of execution depending on stack layout.
Remediation and Mitigation
As of commit 70741a15d9a714323a5598eac47727e60507e22f this issue is unfixed upstream and no CVE has been assigned. Clamp the index to the buffer size before using it to fix the off-by-one access. 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.