β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-3719

Firmware load: registration-metadata size drives an unbounded SRAM PIO copy (*limit initialized to sram_size then blindly overwritten) and short-inflate copies uninitialized kernel heap to the NIC

Summary

mxge_load_firmware_helper takes the claimed uncompressed size from fw->version (the FIRMWS Makefile field), kmallocs fw_len, and PIO-copies fw_len bytes to sc->sram+MXGE_FW_OFFSET without ever comparing fw_len to the *limit the caller seeded with sc->sram_size - the code simply overwrites it. A crafted/corrupted fw module claiming > sram_size-1MB writes past the pmap_mapdev BAR mapping (kernel VA corruption). inflate() returning Z_STREAM_END with total_out < fw_len leaves the kmalloc'd tail uninitialized yet still shipped to the NIC; version==NULL gives kmalloc(0) + a 4-byte read before any check. The zlib-stream validation and header walk are sound - DF-3573/3631 firmware-trust family, hardening-grade (root-supplied input).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3719 Β· 6 files
FileTypeDescriptionSize
README.md β€” 2.9 KB ↓ raw
VERDICT.md β€” 1.5 KB ↓ raw
fix.diff β€” 613 B view raw
verdict.json β€” 2.3 KB view raw
manifest.json β€” 846 B view raw
env.txt β€” 583 B view raw

DF-3719 β€” mxge firmware image load: PIO copy unbounded vs the SRAM mapping; short-inflate copies uninitialized heap to the device

Vulnerability (firmware-file trust family, DF-3573/DF-3631)

sys/dev/netif/mxge/if_mxge.c mxge_load_firmware_helper() (:651-729):

  1. Unbounded SRAM write. The claimed uncompressed size is taken from the firmware registration metadata β€” fw_len = (size_t)fw->version (:685), the third field of FIRMWS = <file>:<name>:<uncompressed-size> in sys/dev/netif/mxge/fw//Makefile (e.g. mxge_eth_z8e:377284). A buffer of fw_len bytes is kmalloc'd (:686) and, after a header check (:699-705), the whole fw_len region is copied to the NIC via mxge_pio_copy(sc->sram + MXGE_FW_OFFSET + i, ...) (:713-719). Nothing relates fw_len to the SRAM window*: mxge_load_firmware passes size = sc->sram_size as *limit (:910-911) precisely so the helper could check β€” but the helper simply overwrites it with *limit = fw_len (:721) without ever comparing. A crafted/mismatched firmware module with claimed size > sram_size - MXGE_FW_OFFSET (~917 KB of usable space behind offset 1 MB in the ~1.87 MB mapping, :4290) PIO- writes past the end of the pmap_mapdev BAR mapping β†’ kernel VA corruption.
  2. Uninitialized heap to the device. inflate() may return Z_STREAM_END with zs.total_out < fw_len (compressed stream shorter than the registered size). The copy loop (:713) still copies fw_len bytes from the kmalloc'd (uninitialized) buffer β€” up to fw_len - total_out bytes of kernel heap contents are written into NIC SRAM.
  3. (Edge) a fw module registered with version == NULL yields fw_len == 0: kmalloc(0) then *(uint32_t *)(inflate_buffer + MCP_HEADER_PTR_OFFSET) (:700) reads 4 bytes at +0x3c out of a zero-sized allocation before any check runs.

The zlib stream itself is validated (!= Z_STREAM_END β†’ EIO, :692-696) and the header offset is bounds-checked (hdr_offset & 3 or hdr_offset + sizeof(*hdr) > fw_len, :701) β€” so the parse is sound; the missing piece is the size relationship to *limit and the true inflated length.

Threat model

Firmware images are kernel modules built from sys/contrib/dev/mxge/.fw.uu (DF-3573/DF-3631 family): loading one requires root (kldload), so this is a root-supplied-input hardening gap (Low), not an unprivileged attack β€” but it is also the guard against a corrupted* shipped image (build-box compromise, truncated file where metadata and payload disagree).

Verification

Hardware-gated: mxge_load_firmware_helper runs at attach (:4341 via mxge_select_firmware) β€” the guest has no Myricom device. Source-certain; inconclusive per family convention.

Fix

fix.diff: clamp fw_len to zs.total_out after a successful inflate, and reject images that don't fit *limit - MXGE_FW_OFFSET before the copy loop (git-apply clean).

VERDICT.md
↓ download raw

DF-3719 VERDICT

Status: inconclusive (hardware-gated; attach-time path needs a Myricom NIC), confidence: certain source-level.

Source proof

  1. fw_len comes from fw registration metadata, not the stream: :685 fw_len = (size_t)fw->version; (FIRMWS third field, fw/*/Makefile).
  2. Copy loop :713-719 writes fw_len bytes at sc->sram + MXGE_FW_OFFSET with no comparison to sc->sram_size even though the caller seeded limit = sc->sram_size (:910-911); :721 overwrites limit with fw_len.
  3. Short-inflate: only status != Z_STREAM_END is rejected (:692); a stream that ends early leaves inflate_buffer's tail uninitialized and still copied for fw_len bytes.
  4. version==NULL edge β†’ kmalloc(0) + 4-byte read at +0x3c before any bounds check (:700).

Zlib-stream validation and the header walk are correct β€” this finding is strictly the missing fw_len↔SRAM and fw_len↔total_out relationships.

Not reproduced

Attach path requires the device (firmware_get runs from mxge_select_firmware at attach :4341). Guest: no 0x14c1 device (env.txt).

Fix

fix.diff: fw_len = min(fw_len, zs.total_out) + reject fw_len+MXGE_FW_OFFSET

*limit. git apply --check clean; not buildable without hardware.

Fix status (batch validation)

Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

fix.diff git apply --check clean; not buildable without mxge hardware.

findings/poc/DF-3719/fix.diff
↓ fix.diffper-fix-DF-3719

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-3719/VERDICT.md; fix.diff (total_out clamp + SRAM fit check, git-apply clean); env.txt

PoC changes

No seed PoC; hardware-gated.

Verified recommended fix

Clamp fw_len to zs.total_out after successful inflate and reject fw_len + MXGE_FW_OFFSET > *limit before the PIO copy loop.

Verdict

Source-certain, hardware-gated (attach-time path; DF-3573/DF-3631 firmware-file trust family): mxge_load_firmware_helper (if_mxge.c:651-729) takes the claimed uncompressed size from firmware registration metadata (:685 fw->version, the FIRMWS Makefile field), PIO-copies fw_len bytes to sc->sram+MXGE_FW_OFFSET (:713-719) without ever comparing fw_len to the *limit the caller seeded with sc->sram_size (:910-911, overwritten at :721) - a crafted/corrupted image claims > sram_size-1MB and the copy walks off the pmap_mapdev BAR mapping; additionally a zlib stream that legitimately ends short of fw_len (inflate returns Z_STREAM_END with total_out < fw_len) leaves the kmalloc'd tail uninitialized yet still copied to the NIC, and a NULL version (fw_len=0) reads 4 bytes at +0x3c of a zero-sized allocation (:700). Root-supplied input (kldload), so hardening-grade (Low), same as DF-3573/DF-3631.