⬢ DragonFlyBSD Kernel Audit
DF-5765 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-5765",
  "status": "reproduced",
  "reproduced": true,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "REPRODUCED as panic:BADFREE2 (3/3 unpatched race sessions, identical trace _kfree+0x593 <- iconv_sysctl_add+0x172) plus an on-console dangling-handoff probe (live handle refcount=-2046). iconv_xlat_open/iconv_xlat_close claim and drop the cspair lifetime reference (iconv_xlat.c:63/:73) with no lock, while iconv_sysctl_adds error path frees the cspair and its table under iconv_lock (iconv.c:439-442 -> 218-225) unconditionally (no cp_refcount check) and iconv_open/iconv_close never take that lock (iconv.c:233-273); a racing open sleeps in kobj_create (M_WAITOK) between the unlocked list-walk hit and the refcount claim, then writes into freed memory and caches a dangling d_table, corrupting the kernel heap until the production kfree catches it. Impact ceiling is a root-triggered race on a stock system: kern.iconv.add writes need SYSCAP_NOSYSCTL_WR (kern_sysctl.c:1446-1447) and iconv_open is mount(2)-time-only (vfs.usermount=0 default), so no unprivileged->uid0 chain exists; classified Low severity / DoS-class panic. fix.diff (serialize open/close under iconv_lock + copyin-before-publish + refcount-checked reap) validated on the identical rig: 2x8M churn rounds + 21.3M balanced opens with DANGLING=0, positive-control add still works, no panic.",
  "exploit_chain": "root: loop sysctl kern.iconv.add (ia_datalen=256, ia_data=unmapped -> EFAULT error path) to publish+unconditionally-free the zz9to:zz9from cspair and table while root-driven mount-role threads (production iconv_open->iconv_convstr->iconv_close) collide -> open lands on the pair, sleeps in kobj_create(M_WAITOK), pair is freed under it -> cp_refcount++ writes into freed chunk, d_table dangles -> conv/close write/read freed memory repeatedly (observed refcount=-2046 on a reused chunk) -> allocator freelist corruption -> panic:BADFREE2 in iconv_sysctl_adds kfree. Unprivileged escalation: not possible (both entry paths privilege-gated).",
  "evidence": "run.log + panic.txt (run1 console: BADFREE2, cpuid=2); run.2.log + panic.2.txt (run2: interleaved PROOF refcount=-204 + BADFREE2 cpuid=5); panic.3-stale-build.txt (cleanest PROOF line: d_csp=0xfffff80116c2bae0 refcount=-2046 d_table=0xfffff8008da91600); run_fixed.log + fix_build.log (validated fix: FIX_RUN_RC=0, opens=21335040 DANGLING=0, cslist all-zero); setup.log (verbatim production-source md5s)",
  "kernel_refs": [
    "sys/libiconv/iconv_xlat.c:54-66",
    "sys/libiconv/iconv_xlat.c:68-76",
    "sys/libiconv/iconv_xlat.c:60",
    "sys/libiconv/iconv_xlat.c:61",
    "sys/libiconv/iconv_xlat.c:63",
    "sys/libiconv/iconv_xlat.c:73",
    "sys/libiconv/iconv.c:233-273",
    "sys/libiconv/iconv.c:243",
    "sys/libiconv/iconv.c:245",
    "sys/libiconv/iconv.c:264",
    "sys/libiconv/iconv.c:397-443",
    "sys/libiconv/iconv.c:426-431",
    "sys/libiconv/iconv.c:439-442",
    "sys/libiconv/iconv.c:218-225",
    "sys/libiconv/iconv.c:85-98",
    "sys/kern/kern_sysctl.c:1446-1447"
  ],
  "poc_changes": "Self-authored pass-2 rig (no prior seed): KLD compiles verbatim production iconv.c+iconv_xlat.c (Makefile mirrors production module build); harness adds per-CPU racer kthreads (CPU0 left free; 1-tick sleep every 1024 attempts after the first attempt livelocked userland -- pure-spin kthreads starve user LWPs), a read-only post-open refcount probe via a mirror struct, and sysctls hw.xlatrace.{ctl,stats}; userland hammer needed errno.h added; run.sh uses dotted sysctl names.",
  "attempts": 7,
  "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": 4800,
  "guest_dirty": 0,
  "build_cmd": "cd /root/xlatrace && sh build.sh   # verbatim-copy production iconv.c/iconv_xlat.c/iconv_converter_if.m (md5 recorded), make -m /usr/src/share/mk SYSDIR=/usr/src/sys, cc -O2 -g -o hammer hammer.c",
  "run_cmd": "cd /root/xlatrace && kldload ./xlatrace_harness.ko && sh run.sh   # racers (production iconv_open/convstr/close) + ./hammer 8000000 x2 (production kern.iconv.add EFAULT churn); unpatched: panic BADFREE2 + PROOF lines; fixed: clean RC=0",
  "code_hash": "9776e802f6fd303c343eb4833a5c1cdddacf6568cb74d5cca08349699cd91155",
  "notes": "Attempt timeline: A,B unpatched full races -> BADFREE2 both (panic.txt, panic.2.txt); C,D fix-build attempts that STILL panicked -> proven stale-objdir builds (MAKEOBJDIRPREFIX mixup between build.sh and rebuild; behaviorally impossible under the v2 patch, which yields opens=0 during EFAULT churn) -> counted as additional unpatched-behavior repros (C transcript lost to console rotation, D = panic.3-stale-build.txt); E short fixed race (4M iters, opens=0, DANGLING=0); F positive control (valid add csid=1, 257k balanced opens); G full fixed run.sh (2x8M, RC=0, 21.3M opens, DANGLING=0). Fix required BOTH parts: serializing the handoff alone leaves the unconditional free-while-referenced in iconv_sysctl_adds error path. Guest reset with-src performed after the run. bt_input.c and hda_acer_c720_patch.c audited clean in the same batch (see VERDICT.md negative notes).",
  "recommended_fix": "Hold iconv_lock across iconv_open/iconv_close converter dispatch, copy the table in before publishing the cspair in kern.iconv.add, and only reap the pair on the error path when cp_refcount==0 (fix.diff, sys/libiconv/iconv.c).",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Fixed: with fix.diff applied to the rigs verbatim production iconv.c and rebuilt clean (single objdir, source patch verified), the identical race rig that panicked 3/3 times stock survives baseline + 2x8M churn rounds with zero dangling handoffs (opens=21335040, DANGLING=0), kern.iconv.cslist shows no refcount anomalies, and a valid (positive-control) kern.iconv.add still registers a working pair that racers open/convert/close 257k+ times balanced. The previously-observed panic:BADFREE2 and PROOF lines are gone.",
  "fix_evidence": "run_fixed.log (full fixed run: FIX_RUN_RC=0, DANGLING=0, cslist 1 pairs, all refcounts 0); fix_build.log; panic.txt/panic.2.txt/panic.3-stale-build.txt (baseline BADFREE2 x3); fix.diff"
}