⬢ DragonFlyBSD Kernel Audit
DF-5523 / verdict.json
← back to finding ↓ download raw
{
  "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"
}