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)
PoC verification
Evidence pack
findings/poc/DF-4024 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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.
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 yieldsm_len = 16379β a 14,331-byte lie. Every consumer that trustsm_len/m_pkthdr.len(ETHER_BPF_MTAPinether_input, headermtod/pullups past the first 2 KB,m_copydatabcopy ofmin(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_LENmay exceed the accumulated chain (k Γ 2048 + last-frag bytes);m_copydatathen walksm_nextpast NULL β KASSERT panic on INVARIANTS,NULL->m_lenpage fault on GENERIC (sys/kern/uipc_mbuf.cm_copydata, KASSERTs compiled out). -
total_len < ETHER_CRC_LEN(device claims 0..3) is safe: the negativem_lenis dropped byether_input'sm->m_len < sizeof(struct ether_header)guard (sys/net/if_ethersubr.c,ether_input) β same benign sub-case as ale'sm_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_testablefix.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
Confirmed kernel references
- sys/dev/netif/re/if_re.c:1142
- sys/dev/netif/re/if_re.c:1187
- sys/dev/netif/re/if_re.c:1209
- sys/dev/netif/re/if_re.c:1226
- sys/dev/netif/re/if_re.c:1228
- sys/dev/netif/re/if_re.c:964
- sys/dev/netif/re/if_re.c:975
- sys/dev/netif/re/if_re.c:850
- sys/dev/netif/re/if_revar.h:54
- sys/dev/netif/re/if_rereg.h:139
- sys/net/if_ethersubr.c:1502
- sys/kern/uipc_mbuf.c:1671
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.
No comments yet.