DF-4958 / verdict.json
{ "finding_id": "DF-4958", "status": "reproduced", "reproduced": 1, "impact": "leak", "confidence": "certain", "verdict": "REPRODUCED live on the guest: led_parse() (sys/dev/misc/led/led.c:202-206) orphans the sbuf allocated at led.c:128 whenever the parsed spec comes back empty or sbuf_finish() errors -- the branch sets *sb = NULL without sbuf_delete(), so both callers cleanup (led.c:250-251, 275-276) sees NULL. 20000 userland write(fd,\"sz\",2) calls to a real /dev/led/leaktest node created by the trees own led_create() moved vmstat -m sbuf from 0/0 to 39.1K allocs / 1.53MB in use (2 M_SBUF kmallocs and 64 bytes per call, exactly as predicted from subr_sbuf.c:53-54,79,137); 20000 kernel-side led_set(name,\"s\") calls doubled it to 78.1K/3.05MB; a valid-spec control (\"sA\") and the fix.diff-patched module both stayed perfectly flat. Unbounded kernel heap leak, root-only via the 0600 /dev/led/* write path (or any kernel led_set() API caller). Inherited verbatim from FreeBSDs dev/led/led.c (equivalent upstream).", "exploit_chain": "", "evidence": "run.log (vmstat -m sbuf rows: 0/0 -> 39.1K/1.53M -> 78.1K/3.05M, control flat, fixed module flat); VERDICT.md table; build.log (trig.ko compiles /usr/src/sys/dev/misc/led/led.c verbatim via SRCS=led.c; trig_fixed.ko = same + one-line fix, hunk at led.c:204); fix.diff", "kernel_refs": [ "sys/dev/misc/led/led.c:202-206", "sys/dev/misc/led/led.c:128", "sys/dev/misc/led/led.c:250-251", "sys/dev/misc/led/led.c:275-276", "sys/kern/subr_sbuf.c:53-54", "sys/kern/subr_sbuf.c:79", "sys/kern/subr_sbuf.c:137" ], "poc_changes": "PoC authored fresh (no seed): KLD embedding the trees led.c unchanged + sysctl trigger for the led_set API path + userland wr(1) for the real /dev/led write path + valid-spec control + fixed-module variant for fix validation. One iteration: first ciphysink-style include of sys/bus.h removed from the sibling pack (unrelated file); trig packs built clean first try.", "attempts": 1, "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": 420, "guest_dirty": 0, "build_cmd": "sh build.sh (guest /root/df4958: make -m /usr/share/mk -f Makefile; -f Makefile.fixed; cc -o wr wr.c)", "run_cmd": "sh run.sh (guest /root/df4958: kldload trig.ko; sysctl hw.ledleak_create=1; ./wr 20000 sz; sysctl hw.ledleak_setn=20000; ./wr 20000 sA; kldload trig_fixed.ko; repeat)", "code_hash": "3f84c423dffd570aa4248ff37d90d81c53d9de369acfa86030be78628886cc72", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff adds the missing sbuf_delete(*sb) before *sb = NULL at led.c:204 (mirroring the default: case at led.c:199). Validated live at module level -- the deployment unit, since led.c ships as the led.ko module: trig_fixed.ko (tree led.c + this one line, verified by grep in build.log) re-ran BOTH trigger paths (20000 writes + 20000 led_set) with ZERO sbuf growth (InUse/MemUse stayed 78.1K/3.05M), while the stock module leaked 64 B/call on identical input. No kernel rebuild needed for this file.", "fix_evidence": "run.log last three sbuf snapshots (fixed PATH 1 and PATH 2 flat); build.log (204: sbuf_delete(*sb); in led_fixed.c); fix.diff", "notes": "Also present in FreeBSD head (led_parse equivalent branch has no sbuf_delete) -- cve_match equivalent. Trigger note: because of the separate *s <= u filter typo (DF-4959), the write-path trigger must use a char > u (\"sz\"); with intended semantics plain \"s\\n\" would leak too.", "recommended_fix": "led_parse(): sbuf_delete(*sb) before *sb = NULL in the finish-error/empty branch (one line, see fix.diff)." } |