DF-3759 / verdict.json
{ "finding_id": "DF-3759", "status": "inconclusive", "reproduced": 0, "impact": "none", "confidence": "certain", "verdict": "Source-level certain: alc_rxeof (sys/dev/netif/alc/if_alc.c:3307) takes the received frame length from the controller-DMAd RRS 14-bit field (RRD_BYTES, if_alcreg.h:1240) and uses it to set m->m_pkthdr.len (:3368) and the last mbufs m_len (:3372, :3386) with NO bound against nsegs * alc_buf_size (1536) or ETHER_CRC_LEN, on MCLBYTES(2048) clusters from alc_newbuf (:3187). A faulty/malicious AR8131/8132/8151/8152/816x/817x can hand the stack an mbuf describing up to 16379 bytes in a 2048-byte cluster -> ~14.3KB heap OOB read past the cluster into bpf/socket receive paths (kernel heap info leak to an unprivileged local user), or lengths that underflow to negative m_len inside the delivered chain via the CRC-strip arithmetic at :3375-3381 (mbuf invariant violation -> panic). Runtime verification honestly inconclusive per family convention: the audit guest has virtio-net only, no Atheros PCI device (env.txt NO_ATHEROS_PCI, ifconfig=vtnet0 lo0, no hw.alc sysctl, if_alc.ko present but not loaded), so alc_attach/alc_rxeof are unreachable. Guest /usr/src copy is byte-identical to the audited tree (md5 fb1a46ef2b2559de4aa5d508e03efcd5). trigger.c (userland arithmetic model of :3368-3386) builds and runs on the guest (run.log TRIGGER_RC=0) and prints the exact corrupted lengths for the three device-controlled shapes. Distinct from this files DF-1207 (index half: rxdesc[rx_cons] OOB) - this is the length half; same descriptor, different sink, different primitive. Same class as DF-3604 (bce), DF-1478 (my), DF-1481 (vr), DF-1490 (tx), DF-1514 (ste), DF-1519 (lge), DF-1562 (pcn), DF-1351 (nfe); FreeBSD head has the identical missing bound (inherited defect, verified against FreeBSD sys/dev/alc/if_alc.c:3640,3701-3715).", "exploit_chain": "On real hardware: (1) device DMAs an RRS with RRD_VALID set, rd_cnt=1, bytes=16383, no error bits; (2) alc_rxeof hands the stack an mbuf with m_len=m_pkthdr.len=16379 backed by a 2048-byte cluster; (3) an unprivileged user with a TCP/UDP socket on alc0 receives up to ~14.3KB of kernel heap adjacent to the cluster (info leak); variant bytes=100/rd_cnt=15 delivers negative m_len (-21404/-19872) -> mbuf walker panic (local DoS). No write primitive from this bug alone.", "evidence": "run.log (trigger model output: OOB and negative lengths computed deterministically + presence probe), env.txt (NO_ATHEROS_PCI / NO_ALC_KLD / no hw.alc / md5 of guest src == audited src), VERDICT.md (path:line trace), trigger.c (annotated hardware precondition), fix.diff (drop-frame bound, git-apply clean)", "kernel_refs": [ "sys/dev/netif/alc/if_alc.c:3307", "sys/dev/netif/alc/if_alc.c:3368", "sys/dev/netif/alc/if_alc.c:3372", "sys/dev/netif/alc/if_alc.c:3375", "sys/dev/netif/alc/if_alc.c:3386", "sys/dev/netif/alc/if_alc.c:3400", "sys/dev/netif/alc/if_alc.c:3187", "sys/dev/netif/alc/if_alc.c:3653", "sys/dev/netif/alc/if_alcreg.h:1240", "sys/dev/netif/alc/if_alcreg.h:554" ], "poc_changes": "No seed PoC. Authored trigger.c as an annotated hardware-precondition + userland arithmetic model of if_alc.c:3368-3386 (a syscall-level PoC is meaningless for a DMA-device bug); honest hardware-absent verification per family convention (DF-3604 pattern).", "attempts": 2, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 110, "guest_dirty": 0, "build_cmd": "cc -O2 -Wall -Wextra -o trigger trigger.c", "run_cmd": "vm.sh run_root cc -O2 -Wall -Wextra -o /root/trigger /root/trigger.c && /root/trigger (after scp of trigger.c)", "code_hash": "c18dd86967142cdf805b33af85b5734a0447ef11f2e3e4f94da2c7e710025297", "notes": "Pass-2 result for sys/dev/netif/alc/if_alc.c (GLM 5.3). First model build had a NULL-deref in the models own out-param (fixed in place; final TRIGGER_RC=0). Not a re-report of DF-1207: DF-1207 is the rxdesc[] index half, DF-3759 is the mbuf length half - both were re-derived independently and only the length half is new.", "recommended_fix": "In alc_rxeof, drop the frame when alc_rxlen > nsegs * sc->alc_buf_size or alc_rxlen < ETHER_CRC_LEN (see fix.diff).", "fix_status": "not_testable", "fix_baseline_reproduced": 0, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff adds the nsegs*alc_buf_size / ETHER_CRC_LEN bound using the files own error-frame early-return idiom (cf. if_alc.c:3322-3324; buffers reused in place). Cannot be runtime-validated: guest has no Atheros device, so neither baseline nor patched behavior is exercisable. The patch touches only existing symbols on an already-compiled path (git-apply clean).", "fix_evidence": "fix.diff" } |