DF-5523 / verdict.json
{ "finding_id": "DF-5523", "status": "reproduced", "reproduced": true, "impact": "leak", "confidence": "certain", "verdict": "REPRODUCED: iicsmb_bread feeds (int)*count straight into iicbus_read (iicsmb.c:499) and iicsmb_bwrite (iicsmb.c:474) sends (int)count -- zero validation, unlike every other smbus driver (ichsmb enforces [1,32] with SMB_EINVAL). The only /dev/smb block-read caller passes an UNINITIALIZED stack byte: smb.c:217 u_char bcount; is never assigned before smbus_bread at smb.c:323 (the adjacent clamps touch s->rcount, not the passed pointer). Live on the production chain: the bridges transfer length read 0 in the parent process (zero-length successful block reads, value disclosed to userland in the copied-back smbcmd.rcount) and 255 in a forked child (255-byte wire transfer) -- same binary, same ioctl: the length is uninitialized kernel stack memory. No overflow exists on in-tree paths (smb.cs buffer is SMB_MAXBLOCKSIZE=1024 > u_char max; ipmi_ssif passes an initialized 32 into a 32-byte buffer) -- that hypothesis was explicitly checked and killed. Root-gated disclosure + nondeterministic bus behavior => Low.", "exploit_chain": "root opens /dev/smb0 (0600) and issues SMB_BREAD -> smbioctls uninitialized bcount -> iicsmb_bread reads (int)*count off the callers stack byte -> iicbus_read transfers that many bytes on the wire -> *count (the bytes value) returns to smbioctl -> s->rcount = min(rcount, bcount) is copied back to userland with the ioctl: a 1-byte uninitialized-kernel-stack disclosure, and a stack-junk-chosen transaction length (0..255) visible on the bus.", "evidence": "run.baseline.log (decisive: READ req_len=0 x6 in the parent; READ req_len=255 ... READ gave 255 bytes in the forked child -- same slot, environment-dependent value; rcount=0 disclosed; groom-mode negative result documented); run.fixed.log (deterministic req_len=1/req_len=8, rcount=8, six identical ioctls; out-of-range counts -> SMB_EINVAL before any wire traffic); fix.diff (bridge validation + smb.c seeding)", "kernel_refs": [ "sys/bus/iicbus/iicsmb.c:474", "sys/bus/iicbus/iicsmb.c:485", "sys/bus/iicbus/iicsmb.c:499", "sys/dev/smbus/smb/smb.c:211", "sys/dev/smbus/smb/smb.c:217", "sys/dev/smbus/smb/smb.c:319", "sys/dev/smbus/smb/smb.c:323", "sys/dev/smbus/smb/smb.c:327", "sys/dev/smbus/smb/smb.h:62", "sys/bus/smbus/ichsmb/ichsmb.c:80" ], "poc_changes": "shared rig with DF-5521; proto mode extended with a groom mode (prior BWRITE copyin 32x0x99 then BREAD) to attempt controlled grooming of the bcount slot -- the compiler placed the slot outside the copyin region in this build (documented); the cross-process 0-vs-255 variance in proto/race modes is the actual demonstration", "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": 9600, "guest_dirty": 0, "build_cmd": "same as DF-5521 (shared rig)", "run_cmd": "/root/df5521_poc proto (dmesg: READ req_len=<uninit byte>); /root/df5521_poc race child context: READ req_len=255; fixed: proto -> deterministic", "code_hash": "16fea8b1ba1d070dbd19dae52d1bcdaa1d4d6aeb3c9d4ad4d534a430677d5eb4", "notes": "The smb.c half (bcount uninitialized) is the root cause of the observable; the finding is filed against iicsmb.c because the bridge accepting an unbounded, unvalidated count is what turns the callers bug into kernel-controlled wire behavior -- and ichsmbs identical entry-point validation proves the bridge-side check is the expected invariant. The bcount-seeding half of the fix lives in smb.c and is flagged for that files audit. No memory overflow: SMB_MAXBLOCKSIZE is 1024 (smb.h:62), not 32.", "recommended_fix": "Validate count/*count in [1,32] at both iicsmb block entry points (mirror ichsmb) AND initialize bcount in smbioctls SMB_BREAD (bcount = s->rcount after the clamp). See fix.diff (validated live).", "fix_status": "fixed", "fix_baseline_reproduced": true, "fix_patched_reproduced": false, "fix_verdict": "Stock: transfer length = uninitialized byte (observed 0 parent / 255 child; value disclosed via rcount). Patched (fix.diff: bridge [1,32] validation + smb.c bcount seeding): six consecutive BREADs all deterministic (req_len=1 count-byte read then req_len=8, rcount=8, identical rbuf); garbage counts (0 or 255) now rejected with SMB_EINVAL before any wire traffic. Validated as part of the combined fixed-module build shared with DF-5521/5522/5525.", "fix_evidence": "run.fixed.log vs run.baseline.log; fix.diff" } |