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

re_rxeof trusts the NIC-DMA'd 14-bit GFRAGLEN (RE_RXBYTES) with no bound vs MCLBYTES: single-fragment m_len up to 16379 on a 2048-byte cluster -> 14,331-byte heap OOB read into delivered frames; multi-fragment m_pkthdr.len past the fragment chain -> m_copydata NULL-walk panic β€” DF-3604/3759/3933/3989 family, re(4) instance

Summary

total_len = RE_RXBYTES(cur_rx) comes straight from the NIC-DMA'd descriptor and is applied unvalidated: single-fragment EOF sets m_len up to 16379 on a 2048-byte cluster -> consumers read up to 14,331 bytes past the cluster into adjacent kernel heap delivered in packet payloads (info leak); multi-fragment EOF sets m_pkthdr.len possibly exceeding the chain -> m_copydata walks m_next past NULL (INVARIANTS KASSERT / GENERIC NULL page fault). RE_RxMaxSize caps legit frames at 1522, so any larger length is a device lie. Live on the default driver (jumbo is dead code β€” DF-1336 latent). Guest-verified extents via trigger model; hardware-gated live. Chain-geometry validation fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4024 Β· 11 files
FileTypeDescriptionSize
README.md β€” 1.2 KB ↓ raw
VERDICT.md β€” 4.4 KB ↓ raw
trigger.c β€” 2.7 KB view raw
build.sh β€” 104 B view raw
run.sh β€” 158 B view raw
build.log β€” 26 B view raw
run.log β€” 1.1 KB view raw
env.txt β€” 708 B view raw
fix.diff β€” 1.7 KB view raw
manifest.json β€” 1.3 KB view raw
verdict.json β€” 4.9 KB view raw

DF-4024 evidence pack

re(4) instance of the device-fed-length family (DF-3604 bce / DF-3759 alc / DF-3933 age / DF-3989 ale): re_rxeof applies the 14-bit descriptor GFRAGLEN (RE_RXBYTES, if_revar.h:54 / if_rereg.h:139) with no bound: single-frag EOF if_re.c:1228-1229 m_len up to 16379 on a 2048-byte cluster -> 14331-byte heap OOB read into delivered frames / BPF (leak) or fault multi-frag EOF if_re.c:1209/1226 m_pkthdr.len beyond the fragment chain -> m_copydata walks m_next past NULL -> panic total_len<4 SAFE (ether_input drops negative m_len)

  • trigger.c: hardware-precondition + geometry model (build.sh/run.sh); run.log: RC=0 with the exact extents.
  • env.txt: guest has no Realtek NIC (virtio-net only), source md5 match.
  • fix.diff: validate total_len vs re_rxbuf_size + accumulated chain (new sc->re_rxchain_len), drop with ierrors (git apply --check clean).

Expected on real (faulty/malicious) RTL8169-class hardware: a descriptor claiming len=16383 for a small frame delivers kernel heap past the RX cluster into received packet payloads.

VERDICT.md
↓ download raw

DF-4024 β€” re_rxeof trusts the NIC-DMA'd 14-bit GFRAGLEN with no bound vs MCLBYTES: heap OOB read up to ~14.3 KB past the 2 KB RX cluster into delivered frames (kernel heap info leak), and m_pkthdr.len beyond the fragment chain β†’ panic

File: sys/dev/netif/re/if_re.c (pass 2) Class: CWE-125 out-of-bounds read (kernel heap info leak / panic) Family: DF-3604 (bce) / DF-3759 (alc) / DF-3933 (age) / DF-3989 (ale) β€” this is the re(4) instance; pass 1 filed only DF-1336 (the exact-MCLBYTES truncation bug, opposite direction, latent behind RE_JUMBO) against this file. The device-fed-length trust documented here is live on the default driver (RE_JUMBO is not defined anywhere in the tree; RE_C_CONTIGRX / RE_F_USE_JPOOL are never set β€” the jumbo path is dead code and the active RX path is re_newbuf_std + MCLBYTES clusters). Threat model: faulty or malicious RTL8169SC/8168/8101E/8125 NIC DMA'ing crafted completion descriptors (PCIe device trust β€” same model the family uses). Not remotely triggerable on a correctly functioning NIC. Status: source-level certain; runtime hardware-gated (guest is virtio-net only β€” env.txt), inconclusive per family convention.

Root cause

re_rxeof() reads the frame length straight out of the descriptor the NIC owns:

total_len = RE_RXBYTES(cur_rx);      /* if_re.c:1142 */
                                     /* if_revar.h:54: cmdstat & 0x3FFF */
                                     /* if_rereg.h:139: 14-bit GFRAGLEN */

total_len is a 14-bit device-fed value (0..16383). The error-summary filter (RE_RDESC_STAT_RXERRSUM, :1187) is itself device-fed and bounds nothing. The value is then applied with no validation against the buffer geometry:

  • single-fragment EOF (:1227-1229): m->m_pkthdr.len = m->m_len = (total_len - ETHER_CRC_LEN); on an mbuf whose cluster is MCLBYTES = 2048 (re_newbuf_std, :964-975, m_getcl + m->m_len = MCLBYTES). A descriptor claiming 16383 yields m_len = 16379 β€” a 14,331-byte lie. Every consumer that trusts m_len/m_pkthdr.len (ETHER_BPF_MTAP in ether_input, header mtod/pullups past the first 2 KB, m_copydata bcopy of min(m->m_len - off, len) bytes) reads adjacent kernel heap, which is then delivered in packet payloads to sockets/BPF (info leak), or faults (panic) when the cluster ends a mapping.

  • multi-fragment EOF (:1208-1226): m_pkthdr.len = total_len - ETHER_CRC_LEN may exceed the accumulated chain (k Γ— 2048 + last-frag bytes); m_copydata then walks m_next past NULL β€” KASSERT panic on INVARIANTS, NULL->m_len page fault on GENERIC (sys/kern/uipc_mbuf.c m_copydata, KASSERTs compiled out).

  • total_len < ETHER_CRC_LEN (device claims 0..3) is safe: the negative m_len is dropped by ether_input's m->m_len < sizeof(struct ether_header) guard (sys/net/if_ethersubr.c, ether_input) β€” same benign sub-case as ale's m_devget tot<=0.

Legit frames are capped by RE_RxMaxSize programmed to sc->re_rxbuf_size (MCLBYTES, :850; and re.c:1103-1105 programs MTU+22 β‰ˆ 1522), so any total_len > 2048 on a single descriptor β€” or a total exceeding the accumulated chain β€” is a device lie faithfully applied.

Impact

On real re(4) hardware with a lying device: kernel heap contents delivered into received frames (readable by any local user with a socket on the interface β€” the leaked bytes ride in packet payloads), or kernel panic. Inbound-only: no write primitive.

Verification

trigger.c (build/run logs in this pack) prints the exact OOB extents from the masks/geometry: 16379-2048 = 14,331 bytes past the cluster for the single-frag lie; the multi-frag table shows the chain-walk overshoot. Hardware absence on the guest documented in env.txt.

Fix

Validate total_len at the EOF descriptor against sc->re_rxbuf_size + accumulated chain bytes, drop (ierrors + free chain + re-arm descriptor) otherwise; track the chain length in a new sc->re_rxchain_len. See fix.diff (applies clean; keeps the driver's own error-drop idiom).

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 uses the driver's own error-drop idiom on the already-compiled path and applies clean (git apply --check); cannot be runtime-validated without re(4) hardware.

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

Confirmed kernel references

Detail

Exploit chain

On real (faulty/malicious) RTL8169SC/8168/8101E/8125 hardware: DMA a completion descriptor with EOF, no error bits, GFRAGLEN=16383 -> driver builds an mbuf claiming 16379 bytes over a 2048-byte cluster -> leaked kernel-heap bytes ride into received packet payloads (readable by any local user with a socket on reX) or the stack faults -> panic. Inbound-only: no write primitive.

Evidence (decisive lines)

run.log (model output: 16379-2048=14331-byte OOB extent, chain-walk table, RC=0); build.log; env.txt (virtio-net only, md5 match); VERDICT.md (path:line trace); trigger.c; fix.diff (total_len bound vs re_rxbuf_size + tracked chain length)

PoC changes

No seed PoC (only known finding was DF-1336). Authored trigger.c as hardware-precondition + cluster-geometry model per the DF-3989 convention; honest hardware-absent verification.

Verified recommended fix

In re_rxeof drop the frame (ierrors + re_free_rxchain + re_setup_rxdesc) when total_len < ETHER_HDR_LEN+ETHER_CRC_LEN or total_len > sc->re_rxbuf_size + accumulated chain bytes (new sc->re_rxchain_len), see fix.diff.

Verdict

Source-level certain, runtime hardware-gated (inconclusive per family convention). re_rxeof takes the received frame length from the NIC-DMA'd descriptor 14-bit GFRAGLEN (RE_RXBYTES at if_re.c:1142 = cmdstat & 0x3FFF, if_revar.h:54 / if_rereg.h:139) and applies it with NO bound: (1) single-fragment EOF if_re.c:1228-1229 sets m->m_pkthdr.len = m->m_len = total_len - 4 on an MCLBYTES (2048) cluster from re_newbuf_std (:964-975) -- a descriptor claiming 16383 yields m_len 16379, so every consumer trusting m_len (ether_input's ETHER_BPF_MTAP, mtod/m_copydata pulls past 2KB) reads up to 14331 bytes of ADJACENT KERNEL HEAP into delivered packet payloads (info leak to sockets/BPF) or faults; (2) multi-fragment EOF :1209/:1226 sets m_pkthdr.len = total_len-4 possibly EXCEEDING the accumulated chain (k*2048 + last-frag bytes) so m_copydata walks m_next past NULL -> KASSERT panic (INVARIANTS) or NULL->m_len page fault (GENERIC; sys/kern/uipc_mbuf.c m_copydata, KASSERTs compiled out); (3) total_len<4 sub-case is SAFE (negative m_len dropped by ether_input's m_len < ETHER_HDR_LEN guard, if_ethersubr.c) -- mirroring ale's m_devget tot<=0. The error-summary filter (:1187) is itself device-fed and bounds nothing. Legit frames are capped by RE_RxMaxSize = re_rxbuf_size (MCLBYTES, if_re.c:850; re.c:1103-1105 programs MTU+22=1522), so any total_len>2048 single-desc or beyond-chain total is a device lie. The jumbo path is DEAD (RE_JUMBO undefined tree-wide; RE_C_CONTIGRX/RE_F_USE_JPOOL never set) so DF-1336 stays latent while this length-trust bug is live on the default driver. Runtime: guest has virtio-net only, zero Realtek devices, no hw.re sysctl (env.txt); trigger.c (geometry model) builds and runs on the guest (run.log RC=0: 14331-byte single-frag extent + chain-walk table). re(4) instance of the DF-3604(bce)/DF-3759(alc)/DF-3933(age)/DF-3989(ale) family; pass 1 filed only DF-1336 against this file.