tl_intvec_rxeof trusts device-written 16-bit tlist_frsize: unbounded mbuf length on an MCLBYTES cluster (OOB read propagated up the network stack)
Summary
total_len comes from the device-DMA-written tlist_frsize u16 consumed with no clamp: m_len = total_len on an m_getcl MCLBYTES cluster. A faulty or hostile NIC setting FRAMECMP with frsize up to 65535 hands ether_input a ~63KB over-long mbuf on a 2KB buffer - OOB read whose contents are copied to userland via sockets/BPF (kernel heap info leak) or page-fault panic. TL_MAXRX is only a register hint to a compliant chip. Clamp-and-recycle fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4110 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.2 KB | β raw | |
| VERDICT.md | β | 2.4 KB | β raw | |
| presence_probe.sh | β | 521 B | view raw | |
| env.txt | β | 347 B | view raw | |
| fix.diff | β | 820 B | view raw | |
| manifest.json | β | 780 B | view raw | |
| verdict.json | β | 2.7 KB | view raw |
DF-4110 β tl(4): tl_intvec_rxeof trusts device-written tlist_frsize β unbounded mbuf length on an MCLBYTES cluster
What this is
tl_intvec_rxeof() (sys/dev/netif/tl/if_tl.c:1386-1441) reads the
received frame length straight out of the DMA-visible RX list header:
total_len = cur_rx->tl_ptr->tlist_frsize; /* if_tl.c:1405, u_int16_t, chip-written */
...
m->m_pkthdr.len = m->m_len = total_len; /* if_tl.c:1422 */
ifp->if_input(ifp, m, NULL, -1); /* if_tl.c:1437 */
The RX buffer is a single m_getcl() cluster β MCLBYTES = 2048 bytes
(if_tl.c:1347, tl_newbuf). The only bound the driver programs is the
TL_MAXRX register (if_tl.c:1941, tl_dio_write16(sc, TL_MAXRX,
MCLBYTES)) β a hint to a correctly-functioning chip, not a kernel-side
check. Nothing clamps total_len against MCLBYTES before it becomes the
mbuf length, so a device that writes a tlist_frsize of up to 65535 with
TL_CSTAT_FRAMECMP set hands the network stack an m_len/m_pkthdr.len
of up to 65535 on a 2048-byte allocation. ether_input and everything
above it (VLAN unwrap, IP demux, BPF taps, socket copies) walks that mbuf
linearly β up to ~63 KB out-of-bounds read whose contents are
propagated to userland (kernel heap info leak) or crash the kernel on
unmapped pages.
Sibling of the well-known device-length-trust family (the reason many
BSD drivers gained if (total_len > MCLBYTES) drop guards).
Threat model
Requires a malicious, compromised, or malfunctioning ThunderLAN NIC (or any PCIe device able to DMA into the RX lists β e.g. faulty DMA engine writing stale/garbage descriptor state). Not triggerable by an unprivileged local user on a healthy NIC; a remote peer cannot exceed TL_MAXRX on a compliant chip. Genuinely hardware-gated, hence severity Medium rather than High.
PoC status β untested (hardware absent)
The audit guest has vtnet0 lo0 only β no ThunderLAN PCI device
(presence_probe.sh). The OOB read cannot be entered on the guest, so no
PoC run was attempted; the finding is certain at source level.
Fix
Clamp tlist_frsize to MCLBYTES and recycle the descriptor (same shape
as the existing ENOBUFS path) β see fix.diff.
DF-4110 β VERDICT
Finding: tl_intvec_rxeof() uses the device-written 16-bit
tlist_frsize as the mbuf length without clamping it to the MCLBYTES
cluster backing the RX buffer (sys/dev/netif/tl/if_tl.c:1405 β 1422),
with if_input at :1437 propagating the over-long mbuf up the stack.
Status: untested. The audit guest has no ThunderLAN device
(ifconfig -l β vtnet0 lo0; no 0x104C/0x0E11 ThunderLAN PCI IDs β
see presence_probe.sh), so the corrupted-descriptor state this bug
consumes cannot be produced on the guest. No PoC run attempted; not a
false positive β the missing clamp is directly visible in the source and
the sink (m->m_pkthdr.len = m->m_len = total_len on a 2 KB cluster) is
unconditional on every received frame.
Source-level trace (proof):
1. tl_newbuf allocates the RX mbuf with m_getcl() β MCLBYTES = 2048
(if_tl.c:1347; buffer also what tl_dio_write16(sc, TL_MAXRX,
MCLBYTES) at if_tl.c:1941 advertises to the chip).
2. The ThunderLAN chip DMA-writes tlist_cstat/tlist_frsize into the
RX list header (struct tl_list_onefrag, if_tlreg.h:75-80) β both are
device-controlled memory.
3. On RXEOF the driver checks only TL_CSTAT_FRAMECMP (if_tl.c:1400),
takes total_len = cur_rx->tl_ptr->tlist_frsize (:1405) β a
u_int16_t the kernel never bounds β and assigns it to
m->m_pkthdr.len = m->m_len (:1422).
4. ifp->if_input (:1437) β ether_input walks the mbuf for
m_pkthdr.len bytes: for any device-fed value > 2048 this reads past
the 2 KB cluster (up to ~63 KB OOB), copying the bytes into the
network stack / sockets / BPF (kernel heap disclosure) or faulting on
an unmapped page (panic).
Impact ceiling: kernel heap info leak via any socket that receives the over-long frame + kernel panic; read primitive only, no write.
Fix: clamp total_len > MCLBYTES β count ierror and recycle the
descriptor in place (fix.diff, mirrors the existing ENOBUFS recycling at
if_tl.c:1407-1413). Cannot be validated on this guest (no hardware) β
fix_status not_tested.
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 authored against the read-only sys/ tree (patch --dry-run clean); cannot be validated on a guest without ThunderLAN hardware.
findings/poc/DF-4110/fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
findings/poc/DF-4110/VERDICT.md (source trace if_tl.c:1347/1400/1405/1422/1437/1941); presence_probe.sh + env.txt (hardware absent on guest); fix.diff (MCLBYTES clamp + in-place recycle)
PoC changes
No seed PoC. presence_probe.sh documents the hardware gate; the OOB read cannot be entered on a guest without a ThunderLAN NIC (or a way to inject device-side DMA state).
Verified recommended fix
Clamp total_len to MCLBYTES in tl_intvec_rxeof and recycle the descriptor in place (ENOBUFS-path shape) when exceeded.
Verdict
Source-certain, hardware-gated. tl_intvec_rxeof takes the received frame length from the device-DMA-written 16-bit tlist_frsize (sys/dev/netif/tl/if_tl.c:1405) and assigns it unconditionally as m->m_pkthdr.len = m->m_len (if_tl.c:1422) on an m_getcl() MCLBYTES=2048 cluster (if_tl.c:1347). No kernel-side clamp exists; TL_MAXRX=MCLBYTES (if_tl.c:1941) is only a register hint to a compliant chip. A faulty or hostile NIC that sets TL_CSTAT_FRAMECMP with tlist_frsize up to 65535 hands ether_input (if_tl.c:1437) an over-long mbuf: up to ~63KB out-of-bounds read past the cluster, contents propagated up the stack (kernel heap info leak via sockets/BPF) or page-fault panic. Guest has no ThunderLAN device (vtnet0 lo0 only; presence_probe.sh), so the PoC phase is honestly untested; not a false positive - the missing bound is unconditional on every RXEOF.
No comments yet.