DF-5539 / verdict.json
{ "finding_id": "DF-5539", "status": "reproduced", "reproduced": true, "impact": "leak", "confidence": "certain", "verdict": "iicbb_print_child() (sys/bus/iicbus/iicbb.c:164-186) reads an uninitialized stack byte oldaddr (declared :169, address passed to the parent reset at :173) whenever the parents iicbb_reset returns anything other than IIC_ENOADDR, prints it to console/dmesg (:181-182) and writes it back as the restored bus address (:179). No in-tree parent can reach that branch with oldaddr written: lpbb_resets ppbus-busy path returns 0 without touching *oldaddr (lpbb.c:186-190), viabb/cxm always return IIC_ENOADDR. Reproduced on the DragonFly guest with a production-code KLD harness that loads the stock in-tree iicbb.ko under a fake GPIO parent (emulated I2C slave, faithful lpbb busy-path reset): 16/16 attach events printed on iicbb0 addr 0xff -- the untouched stack slot. Machine-code proof: the stock iicbb.ko disassembly of iicbb_print_child contains no write to -0x21(%rbp), only the lea passing &oldaddr and two reads. The same harness drove the full transfer state machine green (write echo, read pattern, NACK->IIC_ENOACK with bus stopped, repeated-start), so the checklist (a)-(d) was exercised live. fix.diff (init oldaddr=0 + else-branch only on IIC_NOERR) validated by rebuilding only iicbb.ko: output becomes deterministic addr 0x0 with zero uninitialized bytes and an unchanged state machine.", "exploit_chain": "", "evidence": "run.out + dmesg.out: 16x iicbus0: <Philips I2C bus> on iicbb0 addr 0xff and green [fakebb] state-machine markers (write ok=1 read_ok=1, nacked error=2 IIC_ENOACK bus_started_after=0, repstart byte=0xc0, resets_with_oldaddr=1). stock_disasm.txt: iicbb_print_child has NO write to -0x21(%rbp) (only lea at +0x3d and movzbl reads at +0x69/+0xb1). fix_disasm.txt: patched prologue adds movb $0x0,-0x21(%rbp). fix_dmesg.out: addr 0x0, zero occurrences of the uninitialized byte.", "kernel_refs": [ "sys/bus/iicbus/iicbb.c:169", "sys/bus/iicbus/iicbb.c:173", "sys/bus/iicbus/iicbb.c:177-183", "sys/dev/misc/lpbb/lpbb.c:186-190", "sys/dev/powermng/viapm/viapm.c:507-514", "sys/dev/video/cxm/cxm_i2c.c:273-296", "sys/kern/subr_bus.c:2004", "sys/kern/subr_bus.c:3007-3013" ], "poc_changes": "No seed PoC existed (pass-2 finding verified in the same run). Authored the family-standard production-code KLD harness from scratch: iicbb_reg.ko registers the real iicbb_driver on a fake fakebb bus; iicbb_harness.ko instantiates fakebb0 on nexus0 with a full iicbb_if vtable (bit-level emulated slave: START/STOP edge detection, SCL-edge sampling, ACK/NACK injection, TX pattern) whose reset returns 0 without writing *oldaddr (lpbb busy-path faithful), pre-sprays the kstack with a compile-time pattern, and drives the real iicbus API. Iterations: (1) kmod.mk clobbers CFLAGS so -D spray flags were moved to a generated spray_conf.h; (2) 64-frame spray hit the kstack guard page (double fault) -- reduced to 24 frames; (3) module-registration ordering inside one .ko made the attach cascade lag fakebb_attach -- split the (fakebb,iicbb) DRIVER_MODULE into a separate preload KLD.", "attempts": 5, "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": 1900, "guest_dirty": 0, "build_cmd": "cd /root/df5539 && ./build.sh", "run_cmd": "cd /root/df5539 && ./run.sh", "code_hash": "f5cf242ca2b84957a6e26b4c2603440c136e8472e42d52cd370f95043d7e6ae3", "notes": "Leak is one byte of uninitialized kernel stack to console/dmesg (readable by unprivileged users); trigger is boot-time attach or module load (root). Severity Low; no escalation path for this class. The stack spray could not claim the exact slot (in-cascade device_add_child/kmalloc internals rewrite that depth after the spray) -- the disassembly no-write proof plus 16/16 deterministic 0xff disclosures carry the evidence. Adjacent out-of-file observations for follow-up: iicbus.c:144-146 plain device_add_child leaves NULL ivars while iic.c:115 iicbus_get_addr() dereferences them (NULL-deref hazard when a child driver probes ic/iic/iicsmb children); iic.c:366-369 I2CRDWR copies out IIC_M_RD buffers even after a failed transfer (uninitialized kmalloc heap disclosure to userspace).", "recommended_fix": "Initialize u_char oldaddr = 0 in iicbb_print_child and enter the address-print/restore branch only when the parent reset returns IIC_NOERR (see fix.diff).", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff (initialize oldaddr + gate the else-branch on error == IIC_NOERR) applied to the guests /usr/src copy of sys/bus/iicbus/iicbb.c and only iicbb.ko rebuilt (module, no kernel rebuild). Baseline stock module: 16/16 addr 0xff uninitialized disclosures. Patched module: deterministic addr 0x0, zero occurrences of the uninitialized byte, transfer state machine unchanged (write_ok=1 read_ok=1). Patched disassembly adds the initializing movb $0x0,-0x21(%rbp) store. Guest /usr/src restored to pristine and stock module reloaded afterwards.", "fix_evidence": "fix.diff; fix_build.log; fix_dmesg.out (iicbus0: <Philips I2C bus> on iicbb0 addr 0x0, zero addr 0xff); fix_disasm.txt line 16 (movb $0x0,-0x21(%rbp)); dmesg.out (baseline 16x addr 0xff).", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root:dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (patched iicbb.ko only)" } |