/dev/smb SMB_BREAD hands amdsmb_bread an UNINITIALIZED kernel-stack count (smb.c:317/323): SMB_BREAD dead for 224/256 stack bytes, and for garbage in [1,32) the driver reports a larger count than it stored -> kernel-stack disclosure via smbioctl copyout; bwrite transmits the garbage length on the wire (DF-5458 twin)
Summary
amdsmb_bread stores min(hw_len,*count) bytes then sets *count=hw len regardless of stored capacity; the sole caller passes never-initialized bcount: g=0/33..255 -> EINVAL (deterministically broken SMB_BREAD), g in [1,32) with a healthy len=32 slave -> rc=0 count=32 with only g bytes stored - smbioctl copyouts min(rcount,32) from its 1024B stack buf disclosing up to 31 stack bytes/call (no DF-1091 hostility needed); bwrite transmits garbage as the on-wire BCNT. Production rig: g=1 -> 31 stale 0xAA bytes in the returned window (g=5 -> 27, g=31 -> 1); clamp-to-stored fix validated (g=1 -> count=1 stale=0); complete fix adds bcount=rcount in smb.c.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5507 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 4.0 KB | β raw | |
| VERDICT.md | β | 4.2 KB | β raw | |
| amdh_harness.c | β | 12.2 KB | view raw | |
| amdsmb.c | β | 14.2 KB | view raw | |
| Makefile | β | 311 B | β download | |
| build.sh | β | 90 B | view raw | |
| run.sh | β | 108 B | view raw | |
| fix.diff | β | 1.7 KB | view raw | |
| build.log | β | 1.4 KB | view raw | |
| build_fixed.log | β | 1.4 KB | view raw | |
| run.log | β | 2.3 KB | view raw | |
| run_fixed.log | β | 2.2 KB | view raw | |
| env.txt | β | 340 B | view raw | |
| verdict.json | β | 4.0 KB | view raw |
DF-5507 PoC β /dev/smb SMB_BREAD hands amdsmb_bread an uninitialized kernel-stack count: feature-dead for 224/256 stack bytes, and for garbage in [1,32) it returns a count larger than the bytes it stored β kernel-stack disclosure via smbioctl copyout
Root cause (amdsmb side of the DF-5458 twin)
amdsmb_bread (sys/bus/smbus/amdsmb/amdsmb.c:517-547) implements the
smbus(4) in/out-count contract: the caller passes its requested capacity
in *count; on success the driver stores min(hw_len, *count) bytes
into buf and sets *count to the hardware byte count len
(amdsmb.c:533-539):
amdsmb_ec_read(sc, SMB_BCNT, &len);
for (i = 0; i < len; i++) {
amdsmb_ec_read(sc, SMB_DATA + i, &data);
if (i < *count) /* stores only up to capacity */
buf[i] = data;
}
*count = len; /* reports hw len, not stored */
The only in-kernel caller, /dev/smb smbioctl
(sys/dev/smbus/smb/smb.c:318-330), passes a kernel-stack byte that is
never initialized:
u_char bcount; /* smb.c:317 */
...
case SMB_BREAD:
...
error = smbus_bread(bus, s->slave, s->cmd, &bcount, buf); /* :323 */
...
if (s->rcount > bcount) s->rcount = bcount; /* :327 */
error = copyout(buf, s->rbuf, s->rcount); /* :329 */
buf there is char buf[SMB_MAXBLOCKSIZE=1024] on the smbioctl kernel
stack (smb.c:211). Two consequences, both exercised through the real
production amdsmb_bread:
- EINVAL gate: garbage stack byte 0 or 33..255 β
amdsmb_breadrejects withSMB_EINVAL(amdsmb.c:524-525) β SMB_BREAD on amdsmb is dead for 224/256 possible stack bytes (deterministic failure; this is the exact twin of ichsmb's DF-5458, filed there against the same smb.c defect). - Kernel-stack disclosure: garbage byte g in [1,32) with a
perfectly healthy slave delivering len=32 β the driver stores only g
bytes but reports
*count = 32; smbioctl then copyoutsmin(rcount<=1024, 32)bytes frombuf, of which32 - gwere never written β up to 31 uninitialized kernel-stack bytes per call with a well-behaved slave (no DF-1091 hostility needed). Combined with the known slave-controlled count (DF-1091, len up to 255) the disclosure grows tomin(1024, len) - gβ₯ 254 bytes.
Additionally amdsmb_bwrite transmits whatever garbage g in [1,32] it
receives as the on-wire byte count (SMB_BCNT gets g β T3w), i.e. a
malformed-length Block Write, again the DF-5458 pattern.
Reachability: /dev/smbN (root on stock installs) β ioctl SMB_BREAD /
SMB_BWRITE. Hardware-gated (AMD-8111) β evidence via the production-code
KLD harness (see DF-5506 pack; amdsmb.c asserted byte-identical to
/usr/src).
Observed (run.log, T3/T3w β real production amdsmb_bread/bwrite)
g= 0 -> rc=256 SMB_EINVAL g= 1 -> rc=0 count=32 stale=31
g= 33 -> rc=256 SMB_EINVAL g= 5 -> rc=0 count=32 stale=27
g=255 -> rc=256 SMB_EINVAL g= 31 -> rc=0 count=32 stale= 1
g= 32 -> rc=0 count=32 stale= 0
bwrite: g in [1,32] -> transmitted_BCNT = g (garbage length on the wire)
Observed (run_fixed.log β fix.diff applied to the harness copy)
fix.diff clamps the driver out-count to what it actually stored:
g=1 -> count=1 stale=0 g=5 -> count=5 stale=0 g=31 -> count=31 stale=0 g=32 -> count=32 stale=0
The driver-side disclosure is gone for BOTH input vectors (garbage count
and the known DF-1091 hostile len: T4 fixed stops at register 0x24,
count=32). The EINVAL gate for gβ{0,33..255} remains by design until
smb.c initializes bcount (bcount = s->rcount; before smb.c:323) β
that one-line smb.c change is the complete upstream fix for the
uninitialized-input side and is recommended alongside.
Build & run
Same rig as DF-5506: guest# cd /root/amdh && sh build.sh && sh run.sh
(tests T3/T3w are this finding; T1/T2/T2b belong to DF-5506).
DF-5507 VERDICT β REPRODUCED (mechanism, through the production amdsmb_bread); driver-side fix validated; smb.c input-init remains upstream's one-liner
Bottom line
The /dev/smb layer passes an uninitialized kernel-stack byte as
amdsmb_bread's in/out count (sys/dev/smbus/smb/smb.c:317,323 β the
same defect twin-filed for ichsmb as DF-5458). amdsmb's handling of that
count (sys/bus/smbus/amdsmb/amdsmb.c:524-539) turns it into two
user-visible defects, both demonstrated by executing the unmodified
production source in-kernel against the AMD-8111 EC register emulator:
- For 224/256 possible garbage values (g=0, g>32)
amdsmb_breadreturns SMB_EINVAL β SMB_BREAD is deterministically broken on amdsmb exactly as DF-5458 showed for ichsmb (T3). - For g in [1,32) with a healthy slave (len=32) the driver stores only
g bytes into the caller's buffer but sets
*count = len(amdsmb.c: 539); smbioctl then copyoutsmin(rcountβ€1024, len)bytes from its 1024-byte kernel-stack buffer (smb.c:327-329), disclosinglen - gnever-written stack bytes β up to 31 per call with a well-behaved slave, and β₯254 when combined with the known slave-controlled count (DF-1091's lenβ€255). T3 measures the driver-side arithmetic exactly: g=1β31 stale, g=5β27, g=31β1, g=32β0. amdsmb_bwriteputs the garbage g on the wire as the block length (SMB_BCNTβg, T3w) β malformed Block Writes.
Impact ceiling: kernel-stack info disclosure to a /dev/smb opener
(root/operator-gated; the device is 0600 root on stock DragonFly) β
Medium like the ichsmb sibling DF-5457/DF-5458 pricing. Not a
memory-corruption primitive: the OOB is read-side disclosure only; the
kernel-memory WRITE in bread is bounded by the original *count β€ 32
(amdsmb.c:536-537).
Distinction from the excluded known findings
DF-1091/DF-2330 (excluded from this pass-2) cover the slave-controlled hw byte count: OOB MMIO register reads past the 32-byte bank and the inflated out-param. This finding's input vector is the caller-side uninitialized kernel-stack count (smb.c:317/323): it breaks SMB_BREAD independently of any slave misbehavior and creates the stale-stack-copyout even when the slave returns a perfectly legal len β€ 32. T4 in the shared run corroborates the known DF-1091 behavior (register reads to 0xFF with 8-bit address wraparound; count=255) and is explicitly labeled as corroboration, not a new finding.
Fix validation
fix.diff (shared with DF-5506's pack; amdsmb.c bread hunk) clamps
len to 32 (DF-1091 family hardening) and then to the caller's capacity
so the out-count always equals the bytes stored. Fixed-build T3: g=1β
count=1 stale=0, g=5βcount=5 stale=0, g=31βcount=31 stale=0 β the
driver can no longer report more bytes than it wrote; the disclosure
sink is closed for both input vectors (hostile len included: fixed T4
stops at register 0x24, returns count=32). The EINVAL gate for gβ{0,
33..255} is correct driver behavior for an invalid count; eliminating it
requires the smb.c fix (bcount = s->rcount; before smb.c:323), which
also un-breaks SMB_BREAD on both ichsmb and amdsmb.
Reproduction honesty
Same rig and caveats as DF-5506: AMD-8111 has no QEMU model; the production amdsmb.c (byte-identical to /usr/src, build.log:1) executes in-kernel against the EC register emulator. The g-truth-table is deterministic driver logic; the only emulated element is what the hw BCNT/DATA registers contain (len=32 healthy-slave case). The smb.c epilogue arithmetic cited for the copyout length is source-verified (smb.c:211,317-330) and matches DF-5458's live ichsmb observation of the same layer.
Kernel references
sys/bus/smbus/amdsmb/amdsmb.c:517-547 (bread), :524-525 (EINVAL gate), :533-539 (stores min(len,*count), reports len), :497 (bwrite gate); sys/dev/smbus/smb/smb.c:211 (1024-byte stack buf), :317 (uninit bcount), :318-330 (SMB_BREAD epilogue), :305-316 (SMB_BWRITE).
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
fixedBaseline: g=1 -> count=32 with 31 stale stack bytes headed for copyout; g in {0,33,255} -> EINVAL. Patched (fix.diff bread clamp): g=1 -> count=1, g=5 -> count=5, g=31 -> count=31, all with 0 stale bytes β the driver never reports more than it stored, closing the disclosure for the uninit-count vector AND the known DF-1091 hostile-len vector (fixed T4: count=32, highest register read 0x24). EINVAL for invalid g remains (correct) until smb.c initializes bcount.
run.log vs run_fixed.log T3/T3w/T4 lines; fix.diff (amdsmb.c bread hunk); build_fixed.log
Confirmed kernel references
- sys/bus/smbus/amdsmb/amdsmb.c:517
- sys/bus/smbus/amdsmb/amdsmb.c:524
- sys/bus/smbus/amdsmb/amdsmb.c:533
- sys/bus/smbus/amdsmb/amdsmb.c:536
- sys/bus/smbus/amdsmb/amdsmb.c:539
- sys/bus/smbus/amdsmb/amdsmb.c:497
- sys/dev/smbus/smb/smb.c:211
- sys/dev/smbus/smb/smb.c:317
- sys/dev/smbus/smb/smb.c:323
- sys/dev/smbus/smb/smb.c:327
- sys/dev/smbus/smb/smb.c:329
Detail
Evidence (decisive lines)
run.log T3/T3w (truth table incl. 31/27/1 stale-bytes arithmetic and transmitted_BCNT=g), run_fixed.log T3 (count==stored, 0 stale for all g; T4 hostile len clamped), build.log:1 (production-source byte identity), amdh_harness.c (shared rig, DF-5506 pack has full description), fix.diff (bread clamp hunk), VERDICT.md (distinction from DF-1091)
PoC changes
no seed existed (pass-2 fresh finding); shared production-code KLD rig with DF-5506 (T3/T3w tests added for this finding)
Verified recommended fix
amdsmb.c:533-539: clamp len to 32 then to the caller's capacity so *count == bytes stored (in fix.diff, validated); smb.c:323: initialize bcount = s->rcount; before the smbus_bread call (upstream one-liner closing the uninit input for all smbus controllers).
Verdict
REPRODUCED through the unmodified production amdsmb_bread: /dev/smb's SMB_BREAD passes an uninitialized kernel-stack byte as the in/out count (smb.c:317,323 β DF-5458's defect, amdsmb twin). Truth table (run.log T3): g=0/33/255 -> SMB_EINVAL (SMB_BREAD dead for 224/256 stack bytes); g in [1,32) with a healthy slave (len=32) -> rc=0, out count=32 while only g bytes were stored -> smbioctl copyouts min(rcount<=1024,32) bytes from its 1024-byte kernel-stack buf, disclosing 32-g never-written stack bytes (up to 31/call; >=254 combined with known DF-1091 hostile len). bwrite transmits garbage g as the on-wire block length (T3w). Driver-side fix validated in the same rig (run_fixed.log T3: out count == bytes stored for every g; hostile-len T4 clamped: count=32, register reads stop at 0x24, 0 stale bytes). The complete upstream fix additionally needs smb.c: bcount = s->rcount; before smb.c:323. Root-gated (/dev/smb 0600), hardware-gated (AMD-8111) β Medium per DF-5457/5458 sibling pricing. Distinct from excluded DF-1091/DF-2330: input vector is the caller-side uninit stack count, not the slave-controlled hw count; manifests with a perfectly well-behaved slave.
No comments yet.