{
  "finding_id": "DF-5558",
  "status": "reproduced",
  "reproduced": true,
  "impact": "leak",
  "confidence": "certain",
  "verdict": "REPRODUCED on the DragonFly 6.5-DEVELOPMENT guest with production modules. iicioctl(I2CRDWR) (sys/bus/iicbus/iic.c:360,364-369) kmallocs each read-message buffer without M_ZERO and copies every IIC_M_RD buffer to userspace even when iicbus_transfer() failed, disclosing the fresh uninitialized heap chunk. Trigger: I2CRDWR with msgs[0]=1-byte write to a NACKed slave (IIC_ENOACK) and msgs[1]=read of N bytes -- the generic transfer aborts at msg 0, msg 1s buffer is never written, and the copyout loop still runs. 12/12 attempts at N=256/512/1024/2048 returned the error AND fully overwrote the user buffer; the kernel-side groomed M_TEMP marker (DF5558STALEHEAP!+seq+kernel-realm qwords, kmalloc+fill+kfreed via sysctl immediately before the ioctl) came back verbatim (hexdump in leak_sample.txt; +8 shift = allocator chunk header). Setup patches (disclosed, and themselves the fixes for the sibling findings found this pass): iicbus.ko carries the DF-5559 bus_read_ivar fix so the iic child can attach at all, and iic.ko carries the DF-5560 sc->sc_dev fix so the first ioctl does not panic in device_get_parent(NULL); the I2CRDWR logic under test is stock. fix.diff (gate the copyout on error == 0, matching FreeBSD) validated by rebuilding only iic.ko: 0/12 leaks, buffers stay at the 0xAA prefill, success-path reads unchanged. Reachability: /dev/iicN is 0600 root:wheel (iic.c:128-130), so default-config exposure requires root; embedded devfs rulesets exposing the node get an unprivileged repeatable stale-kernel-heap read oracle (KASLR/heap-layout disclosure primitive).",
  "exploit_chain": "open /dev/iicN (root or devfs-exposed) -> heap-spray kernel M_TEMP objects of the target size class and free them -> ioctl(I2CRDWR, [write-to-absent-slave, read N bytes]) -> ioctl returns IIC_ENOACK but the read buffer now holds the recycled kernel heap chunk -> repeat to harvest pointers/heap contents. No write primitive; info-leak chaining only.",
  "evidence": "run.log: 12x variant=B ... non-prefill=N marker=YES kptr-qwords=... <== LEAK + RESULT: UNINITIALIZED KERNEL HEAP DISCLOSED TO USERSPACE. leak_sample.txt: hexdump ff ff ff ff ff ff ff ff 41 4c 45 48 45 41 50 21 | seq | size | 5a filler | 0xffff-qb] pattern = the groomed kernel chunk itself. fix_run.log: 0/12, non-prefill=0 on patched iic.ko, sanity read still c0 c1 c2 c3. dmesg.txt: stack attach + BUS_READ_IVAR error=0 v=0 (fix active). panic path that motivated the setup patches: ../DF-5560/panic.txt.",
  "kernel_refs": [
    "sys/bus/iicbus/iic.c:360",
    "sys/bus/iicbus/iic.c:361-362",
    "sys/bus/iicbus/iic.c:364",
    "sys/bus/iicbus/iic.c:366-369",
    "sys/bus/iicbus/iiconf.c:371-378",
    "sys/bus/iicbus/iicbb.c:344-367",
    "sys/bus/iicbus/iic.c:128-130"
  ],
  "poc_changes": "No seed PoC existed (pass-2 finding verified in the same run). Authored from scratch: rdwrleak.c (userland I2CRDWR PoC: groomed single- and two-message failing transfers, ungroomed 200-iter hunt, success-path sanity), groom5558.ko (sysctl-driven M_TEMP marker groom + DF-5559 proofs), reusing the DF-5539 fakebb/iicbb harness verbatim. Iterations: (1) marker search had to switch from the full 16-byte magic to the 8-byte tail ALEHEAP! because the allocator chunk header consumes the first 8 bytes (leak appears shifted +8); (2) the device could not be reached on stock modules -- two sibling bugs (DF-5559 probe uninit-stack, DF-5560 sc_dev NULL panic) each block the path and required their fixes as disclosed setup; (3) the ungroomed tight loop shows 0 hits because the chunk recycles the same fds own zeroed contents -- the groom proves cross-source reuse.",
  "attempts": 4,
  "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": 4200,
  "guest_dirty": 0,
  "build_cmd": "cd /root/df5558 && sh build.sh (harness KLDs + rdwrleak; in-tree iicbus.ko/iic.ko builds logged in fix_build.log)",
  "run_cmd": "cd /root/df5558 && sh run.sh",
  "code_hash": "e7930fc6f8528f283324cb6f49d8801ba58e530e09c747745b3a601fc2ab9568",
  "notes": "Impact ceiling is a size-controlled stale-kernel-heap read oracle (up to 65535 bytes/message, unbounded messages). On this tree the node is 0600 root:wheel; devfs-exposed embedded configs are the unprivileged scenario. Not re-reported here: DF-1109 (nmsgs double-fetch), DF-1110/2309 (usrbufs leak), DF-1111/2310 (ignored copyin/copyout), DF-2308 (I2CREAD path), DF-2312 (nmsgs unbounded). Guest restored (vm.sh reset with-src) after the run.",
  "recommended_fix": "Gate the read-segment copyout on a successful transfer -- if ((m->flags & IIC_M_RD) && error == 0) copyout(...) -- keeping the kfree loop unconditional (see fix.diff); optionally also allocate with M_ZERO as defense in depth.",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff applied to the guests /usr/src copy of sys/bus/iicbus/iic.c (on top of the two disclosed setup patches), only iic.ko rebuilt and reloaded. Baseline stock I2CRDWR logic: 12/12 failing-transfer attempts overwrote the user buffer with the groomed kernel marker (marker=YES, 15-127 kernel-realm qwords). Patched: 0/12 -- non-prefill=0, marker=no, kptr-qwords=0 while the ioctl still returns the transfer error; success-path reads from the ACKing slave unchanged (c0 c1 c2 c3). Leak eliminated, functionality preserved.",
  "fix_evidence": "fix.diff; fix_build.log; fix_run.log (variant=B ... non-prefill=0 marker=no kptr-qwords=0 x12, groomed attempts done: leak_observed=0, sanity pattern_ok=YES); run.log (baseline 12/12 marker=YES); leak_sample.txt (hexdump of the disclosed chunk)."
}