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

tulip_srom_decode() unbounded media-info pointer walk: OOB WRITE past tulip_softc.tulip_mediainfo[10] into the softc tail and kernel heap

Summary

The SROM decoder advances the media-info pointer mi once per well-formed media block with blocks taken as a raw EEPROM byte (max 255) and no bound against the 10-entry array. >=11 blocks write past the array over tulip_setup_tag/map/dma_addr, the live tulip_setupbuf kernel pointer, tulip_setupdata[48], tulip_boardid[24], tulip_rombuf[128] and into adjacent kernel heap, and store OOB pointers into sc->tulip_mediums[] that tulip_media_set() dereferences later. On 21142/21143 the decode gate skips the SROM CRC entirely. HARNESS on the guest: 1:1 logic model with exact softc-tail layout and canaries - 13 OOB writes, tulip_setupbuf CLOBBERED. Attacker-crafted or reprogrammed DEC 2114x SROM/EEPROM (malicious PCI card or brief physical access): 28-byte-stride OOB writes of SROM-controlled data; clobbering tulip_setupbuf forges a kernel pointer that tulip_txput_setup() bcopy()s 192 bytes through - potential arbitrary kernel write with a fully crafted EEPROM. Low solely due to the hardware precondition (same precedent as DF-1205, the OOB *read* side of the same parse - different array, different primitive).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3756 Β· 10 files
FileTypeDescriptionSize
srom_mediainfo_model.c β€” 8.0 KB view raw
build.sh β€” 114 B view raw
run.sh β€” 117 B view raw
build.log β€” 284 B view raw
run.log β€” 1.9 KB view raw
env.txt β€” 865 B view raw
fix.diff β€” 1.2 KB view raw
VERDICT.md β€” 6.0 KB ↓ raw
manifest.json β€” 1.2 KB view raw
verdict.json β€” 3.6 KB view raw
VERDICT.md
↓ download raw

DF-3756 VERDICT β€” OOB write past tulip_mediainfo[10] in tulip_srom_decode()

Status: inconclusive (kernel path untestable on this guest β€” no DEC 2114x

hardware; defect itself source-certain and logic-model-CONFIRMED)

Root cause

tulip_srom_decode() (sys/dev/netif/de/if_de.c:2073) parses the card's serial EEPROM ("SROM") contents byte by byte using a walking pointer dp (the OOB read side of that walk is known finding DF-1205). Independently of the read walk, the function also fills a fixed 10-entry array through a second walking pointer:

  • sys/dev/netif/de/if_devar.h:483 β€” tulip_media_info_t tulip_mediainfo[10];
  • if_de.c:2080 β€” tulip_media_info_t *mi = sc->tulip_mediainfo;
  • if_de.c:2121 (21041 branch) / if_de.c:2183 (2114x branch) β€” blocks = *dp++; β€” raw, unvalidated SROM byte (0–255)
  • block loop if_de.c:2122 / if_de.c:2184 β€” for every well-formed media block the code writes through mi and advances it with no bound check:
  • case 0 (21140 GPR): writes if_de.c:2205-2222, mi++ at if_de.c:2223
  • case 1 (21140 MII): mi++ at if_de.c:2276 (PHY found)
  • case 2 (21142 SIA): writes if_de.c:2289-2322, mi++ at if_de.c:2323
  • case 3 (21142 MII): mi++ at if_de.c:2373
  • case 4 (21143 SYM): writes if_de.c:2386-2400, mi++ at if_de.c:2401
  • 21041 extended blocks: mi++ at if_de.c:2138

Nothing relates blocks (≀255) to the 10 available slots. An SROM leaf with β‰₯11 type-0 blocks (4 bytes each; 23 fit in a 128-byte SROM) drives mi past the array into the remainder of the heap-allocated softc and beyond:

tulip_mediainfo[10]  (+280B region)
tulip_setup_tag       if_devar.h:488
tulip_setup_map       if_devar.h:489
tulip_setup_dma_addr  if_devar.h:490
tulip_setupbuf        if_devar.h:491   <- real kernel pointer (DMA-mapped buffer)
tulip_setupdata[48]   if_devar.h:492
tulip_boardid[24]     if_devar.h:493
tulip_rombuf[128]     if_devar.h:494
... then past the softc allocation into adjacent kernel heap

sizeof(tulip_media_info_t) = 28 (x86-64); the model's crafted 23-block SROM produced 13 OOB writes reaching +364 bytes past the array, clobbering tulip_setup_tag, tulip_setup_map, tulip_setupbuf (the pointer later used as the bcopy() destination in tulip_txput_setup(), if_de.c:3637), 13/48 tulip_setupdata words, and 8 tulip_boardid bytes (run.log). Additionally each block stores sc->tulip_mediums[media] = mi (if_de.c:2206/2290…), leaving OOB pointers that tulip_media_set() (if_de.c:247) dereferences at runtime.

Reachability / gates

tulip_pci_attach() β†’ tulip_read_macaddr() (if_de.c:4278) β†’ tulip_srom_decode() (if_de.c:2502). Decode is reached when TULIP_HAVE_ISVSROM is set, which happens when:

  1. the SROM CRC is valid (if_de.c:2480) β€” the CRC is 2 attacker-chosen bytes, trivially computed for a crafted image; or
  2. bytes 126/127 are 0xFF with the legacy zero-prefix format (if_de.c:2486-2497); or
  3. chipid β‰₯ 21142 β€” unconditionally, no CRC check at all (if_de.c:2498-2500).

So on 21142/21143-based cards, any EEPROM garbage with a plausible leaf pointer reaches the overflow. All of this runs at boot or module load, in device attach context.

Threat model

Requires attacker-crafted SROM content: a malicious or reprogrammed PCI card planted in the machine, or an attacker with brief physical access able to reprogram the card's serial EEPROM (the 2114x SROM is rewriteable on many boards). Not reachable remotely or by local users of a stock machine. Given that precondition the primitive is strong: 28-byte-stride OOB writes of SROM-controlled data over kernel heap including a live kernel pointer (tulip_setupbuf) whose later use (bcopy at if_de.c:3637, bus_dmamem_free paths) turns corruption into a write-through/freed-pointer primitive β†’ potential arbitrary kernel write with a fully crafted EEPROM. Severity Low because of the hardware precondition (same precedent as DF-1205); the memory-safety defect itself is certain.

Verification narrative

  1. Read the full driver + if_devar.h; established array sizes and every mi++ site (listed above). No bound check exists anywhere between mi = sc->tulip_mediainfo and the last mi++.
  2. Guest check (env.txt): driver pci/if_de compiled into X86_64_GENERIC, hw.de.* OID count = 0, no 0x1011 PCI devices β†’ no de(4) instance can exist β†’ kernel path not executable on this rig.
  3. Built and ran the 1:1 userspace logic model on the guest (build.log, run.log): 13 OOB writes confirmed, +364 bytes past the array, pointer members clobbered. Model arithmetic matches the kernel struct layout exactly (28B entries; verified by printing sizeof(tulip_media_info_t)).
  4. QEMU's upstream tulip device model (DEC 21143) has a hardcoded, valid SROM in hw/net/tulip.c; feeding a crafted EEPROM would require patching QEMU itself, so kernel-path reproduction is not possible on this rig without new infrastructure.

Classification: inconclusive / reproduced=0 / impact=none (on this guest). The bound violation is proven at source level and by the logic model; kernel-path execution requires hardware absent from the audit rig.

Bound mi against sc->tulip_mediainfo[10] at the top of both block loops (see fix.diff β€” also stops the 21041-branch variant at if_de.c:2138). The DP/rombuf read walk is DF-1205's to fix; this diff deliberately only bounds the mi writes.

Kernel references

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 authored (mi bound guard in both block loops); not kernel-tested: no DEC 2114x hardware on the guest, and a patched-kernel build cannot attach the driver either.

Same hardware absence as baseline: env.txt (hw.de.* count=0). Logic model's guard check pattern mirrors the proposed bound.
↓ fix.diffper-fix-DF-3756

Confirmed kernel references

Detail

Exploit chain

Crafted EEPROM on 21142/21143 (no CRC gate) or CRC-valid crafted SROM -> tulip_pci_attach -> tulip_read_macaddr (if_de.c:4278) -> tulip_srom_decode -> 13+ mi writes past tulip_mediainfo[10] -> clobber tulip_setupbuf pointer with SROM-controlled 32-bit pairs -> next multicast filter update bcopy()s 192 bytes through the forged pointer (if_de.c:3637) -> arbitrary kernel write; OOB tulip_mediums[] pointers additionally dereferenced at runtime by tulip_media_set (if_de.c:247). Requires attacker-supplied/reprogrammed PCI hardware.

Evidence (decisive lines)

run.log: 'OOB writes performed: 13' + 'tulip_setupbuf CLOBBERED (0x00000001a4a4a4a4)'; VERDICT.md root-cause walk with all mi++ sites; env.txt: kldstat pci/if_de present, sysctl hw.de.* count=0, no DEC PCI devices.

PoC changes

No seed PoC existed; authored from scratch. One iteration: first build failed on a mimic field name (gpr_gpdata vs model's anonymous struct), fixed and rebuilt clean.

Verified recommended fix

Bound mi against tulip_mediainfo[NELEM] at the top of both SROM block loops in tulip_srom_decode (fix.diff).

Verdict

Source-certain OOB write: tulip_srom_decode() advances the media-info pointer mi once per well-formed SROM block with no bound against tulip_softc.tulip_mediainfo[10] (if_devar.h:483); blocks is a raw EEPROM byte (if_de.c:2183), so >=11 blocks write past the array into the softc tail (tulip_setup_tag/map/dma_addr/setupbuf pointer, tulip_setupdata[48], tulip_boardid[24]) and further into the kernel heap. A 1:1 userspace logic model built and run on the audit guest confirmed 13 OOB writes reaching +364 bytes and clobbering the tulip_setupbuf kernel pointer. The kernel path itself executes only at device attach on a physical DEC 2114x card with a crafted EEPROM; the guest has the driver compiled in but zero DEC hardware (hw.de.* count=0, no 0x1011 PCI devices), and QEMU's tulip model has a hardcoded valid SROM, so kernel-path reproduction is impossible on this rig -> inconclusive. Decode gate: valid SROM CRC (attacker-computable) or, on 21142/21143, NO CRC CHECK AT ALL (if_de.c:2498-2500). Distinct from DF-1205 (OOB reads past tulip_rombuf): different array, write primitive.