← ALL ADVISORIES

MEDIUM BYTERAY-2026-0269Disclosed

EDK II - Unbounded NVMe Namespace Allocation in NvmeDiscoverNamespaces

This is a variant of Finding 2.

Advisory
BYTERAY-2026-0269
CWE
CWE-789
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 MdeModulePkg/Bus/Pci/NvmExpressPei/NvmExpressPei.c.
Credit
Argus-Systems

Executive Summary

This is a variant of Finding 2. NvmeDiscoverNamespaces allocates AllocateZeroPool(Private->ControllerData->Nn * sizeof(PEI_NVME_NAMESPACE_INFO)) at line 176, where Nn (Number of Namespaces) is a UINT32 field read from the NVMe Identify Controller data returned by an external NVMe device. The only guard is an integer-overflow check at NvmExpressPeiHci.c:675: if (Private->ControllerData->Nn > MAX_UINT32 / sizeof(PEI_NVME_NAMESPACE_INFO)) return EFI_UNSUPPORTED. This prevents wraparound but imposes no magnitude cap. In PEI, AllocateZeroPool goes through PeiServicesAllocatePool (MdePkg/Library/PeiMemoryAllocationLib/MemoryAllocationLib.c:407), which fails for any size above 0xFFF8 - sizeof(EFI_HOB_MEMORY_POOL) (MdeModulePkg/Core/Pei/Memory/MemoryServices.c:864). On X64, where PEI_NVME_NAMESPACE_INFO is 40 bytes, any Nn above 1638 therefore makes the allocation return NULL. The result is not checked: NvmeDiscoverNamespaces still runs the enumeration loop (lines 184-190), and for each active namespace EnumerateNvmeDevNamespace writes a record through &Private->NamespaceInfo[ActiveNamespaceNum] (lines 107-112 and 136-141). With NamespaceInfo == NULL, these are writes to physical memory starting at address 0, one 40-byte record per active namespace, with contents partly taken from the device (Eui64, 1 << Lbads, Nsze - 1).

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 (MdeModulePkg)
  • Affected commit: 70741a15d9a714323a5598eac47727e60507e22f (master, 2026-06-21). The code is present on that commit and later revisions that have not changed it.
  • Location: MdeModulePkg/Bus/Pci/NvmExpressPei/NvmExpressPei.c:176
  • Function: NvmeDiscoverNamespaces
  • Category: CWE-789 (Memory Allocation with Excessive Size Value)

Technical Details

Controllability: EXTERNAL_UNTRUSTED — confidence: high

Data Flow

A malicious NVMe device responds to the NvmeIdentifyController admin command (sent at NvmExpressPeiHci.c:664) with a crafted Identify Controller data structure. The Nn field (Number of Namespaces, UINT32 at offset 516) is fully device-controlled. The overflow-only guard at line 675 checks if (Private->ControllerData->Nn > MAX_UINT32 / sizeof(PEI_NVME_NAMESPACE_INFO)) return EFI_UNSUPPORTED, which prevents 32-bit wraparound but allows Nn up to MAX_UINT32 / 40 (about 107M) on X64. NvmeInitPrivateData then calls NvmeDiscoverNamespaces (NvmExpressPei.c:350), which at line 176 executes Private->NamespaceInfo = AllocateZeroPool(Private->ControllerData->Nn * sizeof(PEI_NVME_NAMESPACE_INFO)). Any request above the PEI pool limit (MemoryServices.c:864, about 64 KB, i.e. Nn > 1638 on X64) fails and NamespaceInfo is NULL, with no check. The loop at line 184 iterates up to Nn times and calls EnumerateNvmeDevNamespace for each ID. For each namespace the device reports as active (Ncap != 0, line 101), lines 107-112 compute NamespaceInfo = &Private->NamespaceInfo[DeviceIndex] and write NamespaceId, NamespaceUuid = NamespaceData->Eui64 and Controller, and lines 136-141 write the Media fields, including BlockSize = 1 << Lbads and LastBlock = Nsze - 1 from the device's Identify Namespace data. These writes land at physical address DeviceIndex * 40 and continue upward from 0 for as many active namespaces as the device reports.

Affected Callers

  • NvmeInitPrivateData (NvmExpressPei.c:350)
  • NvmeInitControllerDataFromPciDevice (NvmExpressPei.c:555, via NvmeInitPrivateData)
  • NvmeInitControllerFromHostControllerPpi (NvmExpressPei.c:644, via NvmeInitPrivateData)

Sanitization / Security Controls

  • Line 675: Overflow-only guard Nn > MAX_UINT32 / sizeof(PEI_NVME_NAMESPACE_INFO): INEFFECTIVE, allows any Nn large enough to make the PEI pool allocation fail.
  • PEI pool size limit (MemoryServices.c:864): makes the allocation fail rather than exhaust memory, which is what turns a large Nn into a NULL NamespaceInfo.
  • No NULL check on Private->NamespaceInfo after line 176, and none in EnumerateNvmeDevNamespace before the writes at lines 107-112 and 136-141.

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

  1. Attacker has physical access to install an NVMe device (PCIe slot or M.2 port) on a controller that NvmExpressPei (PEI-phase NVMe driver) enumerates; attacker controls device firmware or can emulate an NVMe device.
  2. Program the device to return Nn = 2000 in the Identify Controller response (above 1638, so 2000 * 40 bytes exceeds the PEI pool limit, and below the line 675 limit of MAX_UINT32 / 40), and to report namespaces as active (Ncap != 0, Ms == 0) in the Identify Namespace responses.
  3. Boot the target. Firmware sends Identify Controller, receives the crafted Nn, passes the overflow check, and AllocateZeroPool at line 176 returns NULL.
  4. The enumeration loop calls EnumerateNvmeDevNamespace for each namespace ID; each active namespace writes a 40-byte record at address ActiveNamespaceNum * 40, starting at 0, which faults if page 0 is not mapped or otherwise overwrites low physical memory.

Impact

Writes through a NULL-based pointer during PEI: one 40-byte record per active namespace is written sequentially starting at physical address 0, with fields partly chosen by the device (Eui64, block size, last LBA). Depending on how low memory is mapped and used at that point, this either faults (denial of service during boot) or corrupts low physical memory with partly device-controlled data. The write targets are fixed (address 0 upward), not chosen freely by the attacker. Requires a malicious or emulated NVMe device on a controller enumerated by NvmExpressPei, which is only included on some platforms.

Remediation and Mitigation

As of commit 70741a15d9a714323a5598eac47727e60507e22f this issue is unfixed upstream and no CVE has been assigned. Cap the attacker-influenced allocation size against a sane upper bound before allocating. 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.

References