β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-3469

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3469 Β· 13 files
FileTypeDescriptionSize
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
VERDICT.md
↓ download raw

DF-3469 β€” VERDICT

Status: reproduced (impact: leak, confidence: certain)

Root cause (path:line)

  1. sys/net/if_mib.c:178-189 β€” sysctl_ifdata() case IFDATA_LINKSPECIFIC does SYSCTL_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.
  2. sys/net/sl/if_sl.c:218-219 β€” sl(4) sets if_linkmib = sc; if_linkmiblen = sizeof *sc, i.e. the raw struct sl_softc β€” an SNMP-MIB habit from the 1990s. The struct (sys/net/sl/if_slvar.h:48-66) embeds struct ifnet sc_if (function pointers if_output, if_ioctl, serializer/TAILQ pointers), struct tty *sc_ttyp, u_char *sc_mp / *sc_ep / *sc_buf, two struct callout (function pointers), and struct slcompress sc_comp. Registration happens for all NSL units in the slattach() 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):

  1. Root: kldload if_sl β†’ sl0..sl3 attach (ifcount 6).
  2. Root ground truth: kldstat β†’ if_sl.ko @ 0xffffffff82600000; nm /boot/kernel/if_sl.ko β†’ sloutput +0x11f0, slioctl +0x1520.
  3. Built read_leak.c (raw sysctl(3) on OID {4,18,0,2,idx,2}).
  4. 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.ko module in-guest (cd /usr/src/sys/net/sl && make obj && make), loaded the patched module on a fresh vm.sh reset with-src guest 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_linkmib users 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_GENERAL write-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

fixed
baseline reproduced→ patch + rebuild →patched clean

Applied 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
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 (same kernel; patched if_sl.ko module loaded in place of stock)

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..2 (IFDATA_LINKSPECIFIC) and receives sizeof(struct sl_softc)=5736 bytes containing 91 kernel-pointer qwords, including exact &sloutput (0xffffffff826011f0) and &slioctl (0xffffffff82601520) verified against kldstat module base + nm offsets, plus module-bss/softc self-addresses. 3/3 byte-identical unprivileged runs. Fix (linkmib=NULL/len=0 in sl.c) rebuilt as a module and validated: patched dump len=0, zero pointers.