ng_fec_addport arpcom type confusion: writes a node pointer at +sizeof(ifnet)+16 and 6 known MAC bytes at +sizeof(ifnet) past ANY non-arpcom ifnet allocation (lo0), reads 6 bytes OOB disclosed as the fec MAC (kernel pointer leak via gif), and corrupts live gif_softc fields -> kernel GPF
Summary
addport casts any ifnet to struct arpcom with no if_type check and writes ac->ac_netgraph (8-byte node pointer) and ac->ac_enaddr (6 bundle-MAC bytes), and on the first-member path READS ac_enaddr into the fec interface MAC. Measured: sizeof(ifnet)=928, ac_enaddr at +928, ac_netgraph at +944. lo0's ifnet is a standalone kmalloc(928) - both writes land outside the allocation (proven by dumpif before/after; absorbed by slab slack on this build - latent cross-object corruption). gif_softc has gif_psrc/gif_pdst pointers right after gif_if: enslaving a configured gif1 leaks 6 bytes of the gif_psrc kernel pointer to unprivileged readers via 'ifconfig fec1' (ether f0:2d:0a:4f:00:f8 = 0xfffff8004f0a2df0); enslaving an unconfigured gif0 overwrites gif_psrc with garbage -> first 'ifconfig gif0' faults in gif_ioctl (Fatal trap 9 GPF, reproduced). REPRODUCED with documented lab enablers (defect code stock): heap OOB write + kernel-pointer disclosure readable WITHOUT privilege + deterministic panic via victim deref. Control-plane trigger root-only on stock DFly. Fix validated (reject non-IFT_ETHER members): lo0/gif rejected EPROTONOSUPPORT, lo0 tail unchanged, vtnet membership unaffected.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3501 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.6 KB | β raw | |
| VERDICT.md | β | 4.1 KB | β raw | |
| run.log | β | 4.2 KB | view raw | |
| leak_sample.txt | β | 907 B | view raw | |
| panic.txt | β | 576 B | view raw | |
| fecdrive.c | β | 3.7 KB | view raw | |
| Makefile | β | 56 B | β download | |
| dumpif.c | β | 1.9 KB | view raw | |
| setup_guest.sh | β | 1.1 KB | view raw | |
| fix.diff | β | 710 B | view raw | |
| verdict.json | β | 3.3 KB | view raw | |
| manifest.json | β | 1006 B | view raw |
DF-3501 β ng_fec_addport arpcom type confusion: OOB write past non-arpcom ifnet allocations + kernel-pointer leak + live-field corruption (panic)
- File:
sys/netgraph/fec/ng_fec.c(v1 netgraph, LIVE) - Lines: 378-405 (
ng_fec_addport), specifically 379 (ac->ac_netgraph = priv->node), 386-392 (first-member MAC copy), 398-405 (MAC read-back) - Severity: Medium (kernel heap OOB write + info leak + kernel panic; control plane root-only on stock DFly) β memcorrupt bucket
Root cause
ng_fec_addport() looks up ANY interface by name (ifunit()) and
unconditionally casts it to struct arpcom * with no if_type
validation. struct arpcom extends struct ifnet by 24 bytes
(ac_enaddr[6] at +0, ac_netgraph at +16 past the ifnet). For any
non-arpcom member this yields:
- an 8-byte write of the ng node pointer at member_ifp + sizeof(ifnet) + 16,
- a 6-byte write of the bundle MAC at member_ifp + sizeof(ifnet) + 0,
- a 6-byte READ of the same location (first-member path) disclosed as the fec interface's MAC address (world-readable).
Measured on the guest: sizeof(struct ifnet) = 928, off(ac_enaddr) = 928,
off(ac_netgraph) = 944. lo0's ifnet is a standalone
kmalloc(sizeof(struct ifnet)) (sys/net/if_loop.c:111) β the writes land
outside the 928-byte allocation. For gif, struct gif_softc has gif_if
first followed by gif_psrc/gif_pdst pointers (sys/net/gif/if_gif.h:50-53)
β the writes land on live driver fields.
Reproduction summary (full detail in run.log)
- OOB write proof (lo0, second member) β dumpif.ko shows lo0's tail
(bytes past the ifnet allocation) all zeros before, and after
add lo0: vtnet0's MAC52:54:00:12:34:56at +928 and the fec node pointer0xfffff8004f1ae2e0at +944. On this build the slab chunk class for 928 bytes is 960, so the writes fall in the 32-byte inter-chunk slack; any layout/size change (or a member ifnet without slack) makes this adjacent-object corruption. - Kernel-pointer leak (gif1, first member) β
ifconfig fec1showsether f0:2d:0a:4f:00:f8= low 6 bytes ofgif1->gif_psrc(0xfffff8004f0a2df0): a kernel heap pointer read past the ifnet and disclosed to unprivileged userspace (reading interface MACs needs no privilege). - Live-field corruption β panic (gif0, second member) β adding an
unconfigured gif0 writes fec1's MAC bytes over
gif0->gif_psrc(NULL β garbage non-NULL) and the node pointer overgif_ro[0]; the firstifconfig gif0dereferences it:Fatal trap 9: general protection fault β¦ Stopped at gif_ioctl+0x9f(panic.txt).
Harness
The stock control plane cannot reach addport on this INVARIANTS build (DF-3499 then DF-3500 facet 1 panic first), so verification uses the lab module (HEAD source + documented enablers E1/E2/E3 β none touch the defect) driven from thread context by fecdrive.ko:
sh setup_guest.sh # apply enablers, build, load sysctl -n debug.fecdrive=create sysctl -n debug.fecdrive="add vtnet0" # first member (ether, legal) sysctl -n debug.fecdrive="add lo0" # OOB write past lo0's ifnet sysctl -n debug.fecdrive="addg gif1" # pointer leak via fec1 MAC sysctl -n debug.fecdrive="addg gif0" # gif_psrc corruption ifconfig gif0 # -> kernel GPF panic
Fix (validated)
Reject non-ethernet members in addport (bifp->if_type != IFT_ETHER β
EPROTONOSUPPORT). Validated in-guest: lo0/gif1/gif0 rejected with
"fec0: iface β¦ is not an ethernet interface", dumpif shows lo0's tail
unchanged (all zeros), gifs remain fully functional, vtnet0 membership
still works.
DF-3501 VERDICT β reproduced (OOB write + kernel-pointer leak + panic)
What was run
Guest: DragonFly 6.5-DEVELOPMENT #0 (INVARIANTS). ng_fec.ko rebuilt from HEAD /usr/src with three documented lab enablers (E1: DF-3499 bcopy guard; E2: rmnode skips ng_fec_stop; E3: delport serializes member if_ioctl) β none touch ng_fec_addport's arpcom accesses, which are stock. Node operations driven from thread context with fecdrive.ko because the stock csock path panics earlier (DF-3499/DF-3500).
Measurements (dumpif.ko)
sizeof(struct ifnet)=928, sizeof(struct arpcom)=952, off(ac_enaddr)=928, off(ac_netgraph)=944. lo0 ifp=0xfffff8008e0ead40, a standalone kmalloc(928) (if_loop.c:111); slab chunk class 960 (zoneindex rounds 928 up to the next 64 multiple).
1) OOB write past lo0's ifnet allocation
before: tail+0=0, tail+16=0 (all zero)
after create + add vtnet0 + add lo0:
tail+0 = 0000563412005452 (= 52 54 00 12 34 56 = vtnet0's MAC,
written at lo0_ifp+928..933 via
ng_fec.c:404 through the arpcom cast)
tail+16 = fffff8004f1ae2e0 (= fec0's ng_node pointer, written at
lo0_ifp+944..951 via ng_fec.c:379)
Both writes are outside the 928-byte allocation. On this build they
land in the 32-byte inter-chunk slack of the 960-byte chunk class; the
primitive is layout-dependent (any change in sizeof(struct ifnet), the
member's allocation geometry, or slab classing turns it into
adjacent-object corruption). The write itself is fully proven by the
dump delta.
2) Kernel heap pointer leak (unprivileged readback)
gif1 configured (ifconfig gif1 tunnel 10.0.2.15 10.0.2.2 allocates
gif_psrc), added as FIRST member of a second fec node:
ifconfig fec1 -> ether f0:2d:0a:4f:00:f8
= low 6 bytes of gif1->gif_psrc = 0xfffff8004f0a2df0 (kmem pointer
pattern). Interface MACs are readable without privilege
(getifaddrs/SIOCGIFADDR), so this is a kernel-heap-pointer disclosure
to unprivileged users (6 bytes per enslaved non-arpcom interface; on
KASLR-less guests it directly reveals the kmem base region).
3) Live-field corruption -> kernel panic
gif0 created unconfigured (gif_psrc == NULL), added as second member:
fec1's MAC bytes (the leaked pointer bytes) are written over
gif0's gif_psrc (gif_softc+928..933) and the ng node pointer over
gif_softc+944 (gif_ro[0]). First ifconfig gif0:
Fatal trap 9: general protection fault while in kernel mode
Stopped at gif_ioctl+0x9f: movzbl (%rsi),%ecx
(gif_ioctl dereferences the non-NULL garbage gif_psrc β
sys/net/gif/if_gif.c gif_getconfig path.)
Fix validation
Candidate fix (reject if_type != IFT_ETHER in addport) applied on top of the enablers, module rebuilt, same scenario re-run: add vtnet0 = 0 (ether member still works, fec0 MAC = vtnet0's MAC); add lo0 = 43 (EPROTONOSUPPORT, "fec0: iface lo0 is not an ethernet interface"); add gif1 / add gif0 = 43 likewise; dumpif lo0 tail unchanged (all zeros β no OOB write); ifconfig gif0/gif1 fully functional (no corruption); no leak (no fec MAC derived from foreign memory). fix_status: fixed.
Honest scope notes
- Control-plane trigger: netgraph control socket (root on stock DFly). The leak READBACK side needs no privilege once the state exists.
- The lo0-write lands in allocator slack on this exact build (proven write, latent cross-object impact); the gif path corrupts live objects in-allocation (type confusion) and panics deterministically.
- Exploitability toward uid0: the write value/offset are fixed (node pointer at +16; 6 known MAC bytes at +0) and the trigger is root-only on stock config, so no privesc chain is claimed.
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
fixedConfirmed kernel references
Detail
Exploit chain
create fec node -> add_iface
Evidence (decisive lines)
run.log (full dumpif before/after + leak + panic sequence); leak_sample.txt (f0:2d:0a:4f:00:f8 = low bytes of 0xfffff8004f0a2df0); panic.txt (Fatal trap 9 at gif_ioctl+0x9f); fecdrive.c/dumpif.c sources; VERDICT.md fix-validation section
PoC changes
none to the defect code; lab enablers E1/E2/E3 (documented in setup_guest.sh / ../DF-3500) required to reach addport on the INVARIANTS guest; fecdrive.ko drives the node from thread context
Verified recommended fix
Reject non-ethernet members: if (bifp->if_type != IFT_ETHER) return EPROTONOSUPPORT in ng_fec_addport
Verdict
ng_fec_addport blindly casts any ifnet to struct arpcom with no if_type check: for non-arpcom members it writes an 8-byte ng-node pointer at member_ifp+sizeof(struct ifnet)+16 and 6 known MAC bytes at +0, and (first-member path) reads 6 bytes from the same location, disclosed as the fec interface MAC. Proven on the guest three ways: (1) dumpif delta on lo0 (standalone kmalloc(928) ifnet): vtnet0's MAC and the fec node pointer appear at +928/+944 past the allocation (absorbed by 32B slab slack on this build β write proven, cross-object impact latent); (2) unprivileged leak: ifconfig fec1 shows the low 6 bytes of gif1->gif_psrc (0xfffff8004f0a2df0 kmem pointer); (3) deterministic panic: adding gif0 overwrites gif_softc.gif_psrc with garbage -> ifconfig gif0 -> GPF in gif_ioctl. Fixed and validated: if_type != IFT_ETHER rejected (EPROTONOSUPPORT), lo0 tail unchanged, gifs functional.
No comments yet.