iicsmb block entry points perform zero count validation while the only /dev/smb caller passes an uninitialized stack byte: kernel-stack junk selects the on-wire transfer length (observed 0 and 255) and is disclosed back via smbcmd.rcount
Summary
bread feeds (int)*count into iicbus_read unchecked and bwrite sends unchecked - no [1,32] bound unlike every sibling; the /dev/smb path passes never-assigned bcount: same binary read req_len=0 in one process and 255 in a forked child (environment-dependent stack junk driving the wire length, disclosed via rcount). No overflow on in-tree paths (1024B buf) - explicitly checked. Bridge [1,32] validation + bcount seeding fix validated (deterministic, garbage rejected).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5523 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.0 KB | β raw | |
| VERDICT.md | β | 3.0 KB | β raw | |
| df5521_rig.c | β | 7.9 KB | view raw | |
| df5521_poc.c | β | 6.8 KB | view raw | |
| build.sh | β | 83 B | view raw | |
| load_chain.sh | β | 294 B | view raw | |
| build.log | β | 5.4 KB | view raw | |
| env.txt | β | 980 B | view raw | |
| run.baseline.log | β | 1.4 KB | view raw | |
| run.fixed.log | β | 900 B | view raw | |
| fix.diff | β | 1.4 KB | view raw | |
| manifest.json | β | 836 B | view raw | |
| verdict.json | β | 4.5 KB | view raw |
DF-5523 β iicsmb_bread/bwrite trust the caller's count with no validation; the only /dev/smb caller passes an uninitialized stack byte that selects the on-wire transfer length (observed 0 and 255) and leaks back through s.rcount
Files
df5521_rig.c / df5521_poc.c / build.sh / load_chain.sh
shared rig (see DF-5521); proto and groom and race modes
provide the three observations below.
run.baseline.log uninit count = 0 (parent process) and = 255 (forked
child) driving the wire length; value disclosed in
s.rcount
run.fixed.log deterministic after fix (validation + seeding)
fix.diff two-part fix: bridge-side validation ([1,32],
mirroring ichsmb) + smb.c initializes bcount
Reproduce
- chain up (load_chain.sh), rdelay=0
/root/df5521_poc protoβ dmesg showsREAD req_len=0(uninit byte)/root/df5521_poc race-style child (or any different process context) β dmesg showsREAD req_len=255β same slot, different junk- fixed modules β
protoβ deterministicreq_len=1/req_len=8, out-of-range counts rejected with SMB_EINVAL.
Why this is a real defect in the audited file
The smbus(4) block API bounds blocks at 32 bytes (SMBus spec; ichsmb
enforces count < 1 || count > 32 β SMB_EINVAL at both entry points,
ichsmb.c bwrite/bread). iicsmb_bread (iicsmb.c:499) feeds (int)*count
straight into iicbus_read() β no bound, no minimum β and iicsmb_bwrite
(iicsmb.c:474) likewise. The bridge is the only smbus driver in the tree
that performs zero validation on the caller-supplied count. Combined with
the smb.c defect (u_char bcount; at smb.c:217 is never assigned before
smbus_bread(bus, ..., &bcount, buf) at smb.c:323), an uninitialized
kernel stack byte picks the transaction length β and is disclosed to the
ioctl caller via the copied-back smbcmd.rcount (s->rcount = bcount
side of the min() at smb.c:327-328).
DF-5523 VERDICT β REPRODUCED (uninitialized kernel stack byte drives the SMBus transfer length through the unvalidated bridge count; one-byte disclosure to the caller)
The bug (two cooperating halves)
- Bridge (this file):
iicsmb_bread()uses(int)*countas theiicbus_read()length with no validation (iicsmb.c:499-500), andiicsmb_bwrite()sends(int)countbytes (iicsmb.c:474). Neither enforces the SMBus block bounds that every other smbus driver enforces (ichsmb_bwrite/ichsmb_bread:if (count < 1 || count > 32) return (SMB_EINVAL);). - Caller (
sys/dev/smbus/smb/smb.c, routed):smbioctl'su_char bcount;(smb.c:217) is never assigned beforesmbus_bread(bus, s->slave, s->cmd, &bcount, buf)(smb.c:323). The clamp lines above it touchs->rcount, which is not the value passed down.
Net effect on an iicsmb-backed bus: an uninitialized kernel stack byte
selects the on-wire transfer length (0..255), and β through
s->rcount = min(s->rcount, bcount) + the ioctl copy-back β that
uninitialized byte's value is disclosed to the caller.
Observations (production chain, stock modules)
- parent process, fresh stack:
READ req_len=0six times β zero-length "successful" block reads;smbcmd.rcountreturned 0 (the byte's value). - forked child (same binary, different stack history):
READ req_len=255β the bridge transferred 255 bytes driven by stack junk. - A 255-byte read stays inside smb.c's 1024-byte
buf(SMB_MAXBLOCKSIZE, smb.h:62), so there is no overflow on this in-tree path β the candidates with 32-byte buffers (ipmi_ssif) pass an initialized 32. (This audit explicitly killed the "uninit bcount overflows a 32-byte stack buffer" hypothesis on that ground.) groommode (pre-filling smbioctl's stack buf via a prior BWRITE copyin) did not reach the compiler's slot forbcountin this build; the 0-vs-255 cross-process variance is the demonstrated evidence that the value is uninitialized memory.
Impact
Kernel uninitialized-memory disclosure (1 byte) to a root-only caller plus nondeterministic bus behavior (arbitrary-length wire reads). Not exploitable beyond root on the in-tree paths (device node 0600, ipmi's count initialized). Severity Low.
Fix (validated)
fix.diff: (a) validate *count/count in [1,32] at both bridge entry
points (mirrors ichsmb; rejects garbage deterministically β observed:
baseline garbage 0/255 both out of range β SMB_EINVAL, no wire traffic);
(b) smb.c: seed bcount = s->rcount after the clamp so the length comes
from initialized state. Post-fix proto: deterministic
req_len=1/req_len=8, rcount=8, six consecutive identical ioctls.
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
fixedStock: 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.
run.fixed.log vs run.baseline.log; fix.diff
Confirmed kernel references
Detail
Exploit chain
root opens /dev/smb0 (0600) and issues SMB_BREAD -> smbioctl's uninitialized bcount -> iicsmb_bread reads (int)count off the caller's stack byte -> iicbus_read transfers that many bytes on the wire -> count (the byte's 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 (decisive lines)
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)
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
Verified recommended fix
Validate count/*count in [1,32] at both iicsmb block entry points (mirror ichsmb) AND initialize bcount in smbioctl's SMB_BREAD (bcount = s->rcount after the clamp). See fix.diff (validated live).
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 bridge's 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.c's 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.
No comments yet.