sysctl_ifdata IFDATA_LINKSPECIFIC blindly copyouts driver-registered if_linkmib to unprivileged readers β sl(4) registers its ENTIRE sl_softc (5736 bytes: ifnet function pointers, softc/tty/heap addresses) β reproduced unprivileged kernel-pointer disclosure
Summary
sysctl_ifdata() exports ifp->if_linkmib/if_linkmiblen verbatim through the world-readable sysctl net.link.generic.ifdata.<ifindex>.2; sysctl reads need no privilege. sl(4) sets if_linkmib=sc; if_linkmiblen=sizeof *sc for ALL 4 units at module-load SYSINIT - no tty needed, kldload if_sl suffices. VERIFIED on guest 3/3 byte-identical as uid 1001: 5736 bytes per sl unit with 91 kernel-pointer qwords incl. exact &sloutput and &slioctl (cross-checked kldstat base + nm offsets) plus per-unit softc kernel addresses. KASLR/module-layout + kernel-heap disclosure; standard companion primitive for an independent corruption bug. Fix validated (linkmib=NULL/0 in if_sl; module rebuilt): len=0, zero pointers.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3469 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| read_leak.c | β | 3.0 KB | view raw | |
| build.sh | β | 90 B | view raw | |
| run.sh | β | 131 B | view raw | |
| build.log | β | 68 B | view raw | |
| run.log | β | 81.7 KB | view raw | |
| run.2.log | β | 81.7 KB | view raw | |
| run.3.log | β | 81.7 KB | view raw | |
| run.root.log | β | 81.7 KB | view raw | |
| patched_run.log | β | 523 B | view raw | |
| env.txt | β | 596 B | view raw | |
| fix.diff | β | 703 B | view raw | |
| VERDICT.md | β | 4.2 KB | β raw | |
| verdict.json | β | 3.9 KB | view raw |
DF-3469 β VERDICT
Status: reproduced (impact: leak, confidence: certain)
Root cause (path:line)
sys/net/if_mib.c:178-189βsysctl_ifdata()caseIFDATA_LINKSPECIFICdoesSYSCTL_OUT(req, ifp->if_linkmib, ifp->if_linkmiblen): an unconditional copyout of whatever buffer/length the driver registered. Sysctl reads are unprivileged in DragonFly; only writes are gated (sys/kern/kern_sysctl.c:1446-1450,SYSCAP_NOSYSCTL_WR). The handler holds the global ifnet lock across the copyout (that lock issue is the known DF-0757, not re-reported here); the content export is the defect reported here.sys/net/sl/if_sl.c:218-219β sl(4) setsif_linkmib = sc; if_linkmiblen = sizeof *sc, i.e. the rawstruct sl_softcβ an SNMP-MIB habit from the 1990s. The struct (sys/net/sl/if_slvar.h:48-66) embedsstruct ifnet sc_if(function pointersif_output,if_ioctl, serializer/TAILQ pointers),struct tty *sc_ttyp,u_char *sc_mp / *sc_ep / *sc_buf, twostruct callout(function pointers), andstruct slcompress sc_comp. Registration happens for allNSLunits in theslattach()SYSINIT at module load β no tty is required.
Reachability for an unprivileged user requires only that the sl interface
exists (kldload if_sl or device sl kernels); the read itself is a plain
unprivileged sysctl.
How it was reproduced
Stock INVARIANTS kernel (DragonFly 6.5-DEVELOPMENT #0, x86_64, guest up via dfbsd-qemu/vm.sh):
- Root:
kldload if_slβ sl0..sl3 attach (ifcount 6). - Root ground truth:
kldstatβ if_sl.ko @ 0xffffffff82600000;nm /boot/kernel/if_sl.koβ sloutput +0x11f0, slioctl +0x1520. - Built
read_leak.c(rawsysctl(3)on OID {4,18,0,2,idx,2}). - Unprivileged user (uid 1001) ran it: each sl unit returns
5736 bytes with 91 kernel-pointer qwords, including exact
matches
+0x150 = 0xffffffff826011f0 (= &sloutput)and+0x168 = 0xffffffff82601520 (= &slioctl)(run.log/run.2.log/run.3.log, byte-identical MD5 across 3 runs).
That satisfies the leak criterion: leaked bytes match known kernel-memory values (symbol addresses verified against the loaded module's base + symbol offsets) across >= 3 runs, by an unprivileged local user.
Exploit chain ceiling
Pure information disclosure: defeats KASLR/module randomization (text
addresses), leaks module-bss addresses (the softc's own address via
self-referential list heads), and after a SLIP session would additionally
leak a struct tty * and heap buffer addresses (sc_ep is kmalloc'd in
slinit). This is the standard companion primitive for making an
independent heap-corruption bug reliably exploitable; no direct
privilege escalation from this bug alone.
Fix validation
fix.diff (authored against the read-only sys/ tree, applied to the
guest's /usr/src copy only): sl.c registers if_linkmib = NULL /
if_linkmiblen = 0 (nothing sensible was ever consumed from this MIB).
- Rebuilt only the
if_sl.komodule in-guest (cd /usr/src/sys/net/sl && make obj && make), loaded the patched module on a freshvm.sh reset with-srcguest before the stock module. - Baseline (stock module): unprivileged dump = 5736 bytes / 91 pointer qwords (run.log).
- Patched: unprivileged dump =
linkspecific len=0, 0 pointer qwords for sl0..sl3 (patched_run.log). Bad behavior gone β fix_status=fixed.
(The stock if_sl cannot be kldunloaded once loaded β SYSINIT attach with
no SYSUNINIT β hence the fresh-reset A/B rather than in-place swap.)
Negative results / notes
- Other in-tree
if_linkmibusers are scalar-only: vlan (sys/net/vlan/if_vlan.c:151-154: int+uint16+uint16), xe (if_xe.c:268-269: dot3Stats counter array). sl is the only pointer-bearing export found. - The
IFDATA_GENERALwrite-back path is root-only and restores type/mtu/etc. from ifp before writing if_data (if_mib.c:145-156), so no unprivileged stats/mtu tampering.
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
fixedApplied fix.diff to the guest /usr/src copy (if_linkmib=NULL/if_linkmiblen=0), rebuilt only if_sl.ko (cd /usr/src/sys/net/sl && make obj && make), loaded it on a fresh reset before the stock module. Baseline: unprivileged dump 5736 bytes/91 pointer qwords. Patched: linkspecific len=0, 0 pointer qwords for sl0..sl3 (patched_run.log). Leak eliminated.
fix.diff; patched_run.log (len=0 for all sl units, uid 1001); module link line in build.log header note
Confirmed kernel references
Detail
Exploit chain
uid 1001 -> raw sysctl(3) on OID {4,18,0,2,ifindex,2} -> 5736-byte struct sl_softc copyout -> extract &sloutput/&slioctl (text/KASLR defeat), softc kernel address, (post-SLIP) tty* + heap buffer addresses; disclosure-only ceiling - companion primitive for exploiting an independent memory-corruption bug, no direct escalation from this bug alone.
Evidence (decisive lines)
['run.log/run.2.log/run.3.log (uid=1001, 91 pointer qwords, MD5-identical; +0x150=0xffffffff826011f0=&sloutput, +0x168=0xffffffff82601520=&slioctl)', 'run.root.log (root reference)', 'patched_run.log (fix validated: len=0)', 'env.txt (kldstat base 0xffffffff82600000; nm offsets sloutput +0x11f0, slioctl +0x1520)', 'VERDICT.md']
PoC changes
Seed sketch had wrong sysctl path semantics: DF sysctl(8) CLI cannot address numeric children of handler nodes ('sysctl net.link.generic.ifdata.3.2' -> unknown oid), so the PoC uses the raw sysctl(3) syscall with explicit OID {CTL_NET=4, PF_LINK=18, generic=0, ifdata=2, ifindex, IFDATA_LINKSPECIFIC=2}; initial draft also used PF_LINK=16 (AF_LINK is 18) and missed inttypes.h/unistd.h includes.
Verified recommended fix
sl.c: stop registering the raw softc as the link MIB (if_linkmib=NULL, if_linkmiblen=0); if an SNMP-style MIB is ever needed, export a dedicated scalar struct.
Verdict
Unprivileged local kernel-pointer disclosure reproduced on the stock guest: with if_sl loaded (kldload if_sl; all 4 sl units register their whole softc as linkmib at SYSINIT time, no tty needed), any local user reads net.link.generic.ifdata.
No comments yet.