β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-5457

ichsmb_bread returns the raw slave-controlled block count (up to 255) while writing at most 32 bytes β€” /dev/smb SMB_BREAD then copies out up to 223 bytes of uninitialized kernel stack

Summary

The success path bcopies only min(block_count,*count) but returns the RAW slave count (0..255 from ICH_D0): smb.c clamps the user rcount (up to 1024) to that byte and copyouts from the 1024B stack buf of which <=32 were written - a count=255 slave yields 223 stale kernel-stack bytes (pointers/return addresses) in userspace. Same class as amdsmb DF-1091 and smb.c DF-4990. Fixed module built and run live on the first-ever-live ichsmb rig (q35+ipmi-bmc-sim); stock QEMU clamps hostile counts so the leak was modeled (exact epilogue arithmetic demonstrated) not manifested. min(block_count,32) clamp fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5457 Β· 8 files
FileTypeDescriptionSize
df5457_poc.c β€” 7.8 KB view raw
baseline_run.log β€” 1.9 KB view raw
fixed_run.log β€” 1.5 KB view raw
fix.diff β€” 7.2 KB view raw
README.md β€” 2.4 KB ↓ raw
VERDICT.md β€” 2.9 KB ↓ raw
env.txt β€” 496 B view raw
qemu_cmdline.txt β€” 392 B view raw

DF-5457 PoC β€” ichsmb_bread returns the raw slave-controlled block count (up to 255) while writing at most 32 bytes: kernel stack disclosure via /dev/smb SMB_BREAD

Root cause

On successful block read ichsmb_bread does (ichsmb.c:439-442):

if ((smb_error = ichsmb_wait(sc)) == SMB_ENOERR) {
        bcopy(sc->block_data, buf, min(sc->block_count, *count));  /* <=32 bytes */
        *count = sc->block_count;        /* raw slave byte, up to 0xFF */
}

sc->block_count comes from the slave-supplied count register ICH_D0 (ichsmb.c:574-576) with no clamp (the write side of that is DF-1076 β€” known, excluded here). The returned count violates the SMB_MAXBLOCKSIZE(=32) driver contract. smbioctl then does (smb.c:327-329):

if (s->rcount > bcount) s->rcount = bcount;   /* up to 255 */
error = copyout(buf, s->rbuf, s->rcount);     /* buf = 1024-byte kernel stack array */

buf (smb.c:211 char buf[SMB_MAXBLOCKSIZE] with SMB_MAXBLOCKSIZE=1024, smb.h:62) had at most 32 bytes initialized by the bcopy, so a slave that reports count=255 while the user (root) requests rcount=1024 gets up to 223 bytes of uninitialized kernel stack copied to userspace. Sibling of DF-1091/DF-2330 (amdsmb, same defect class, Medium) and DF-4990 (smb.c SMB_TRANS variant).

Live status on this rig

The kernel-side path is fully live (first ichsmb hardware in this audit): real SSIF block reads work through ichsmb_bread on the q35 rig (baseline_run.log / fixed_run.log). The >32 precondition cannot be produced by the stock QEMU: smbus_read_block() clamps a slave count larger than the 32-byte buffer to 0 (hw/i2c/smbus_master.c, QEMU 7.2: if (rlen > len) { rlen = 0; }), verified empirically (i2c-ddc at 0x50 returns a 0xFF count byte at offset 1 β€” reads come back count=0/timeout, and the container has no toolchain to build a patched QEMU). A malicious slave (patched QEMU per DF-1076's malicious_slave_qemu_patch.txt sketch, or an ATtiny on the bus) supplies the precondition directly.

The in-guest model mode reproduces the smb.c epilogue arithmetic exactly: count_in=32 initialized, copyout length 255 β†’ 223 stale-stack bytes (0xAA pattern) verified to reach the "userspace" buffer.

Build & run

guest: cc -O0 -o /root/df5457_poc /root/df5457_poc.c && /root/df5457_poc model

Fix

ichsmb.c: *count = min(sc->block_count, 32); (and clamp the bcopy the same way) β€” included in fix.diff (validated module build, -Werror clean).

VERDICT.md
↓ download raw

DF-5457 VERDICT β€” NOT REPRODUCED on the available rig (emulator clamps the hostile count); kernel-side path proven live; sink confirmed by trace + model

Bottom line

The unclamped count return at ichsmb.c:441 (*count = sc->block_count, slave-controlled 0..255) is real and reachable β€” the block-read path it sits on was exercised live end-to-end on the q35/ipmi-bmc-sim rig (first live ichsmb hardware in this audit; real SSIF block reads returned real IPMI bytes). The leak manifestation needs a slave that reports count>32; stock QEMU cannot produce one: smbus_read_block() (hw/i2c/smbus_master.c in QEMU 7.2) hard-clamps rlen > len(32) to 0, verified empirically with i2c-ddc (its EDID has 0xFF at offset 1, so an SMB_BREAD cmd=1 would deliver count=0xFF on real hardware β€” on QEMU it yields count=0/timeout instead). The container hosting this audit has no C toolchain, so a patched QEMU (DF-1076's malicious_slave_qemu_patch.txt sketch) could not be built. On a malicious-peripheral threat model (the same precondition DF-1076 was filed under, High) the arithmetic is deterministic: smb.c:327-329 clamps the user's rcount (up to 1024) to bcount (up to 255) and copyouts from the 1024-byte stack buf of which only <=32 bytes were written (model mode: 223 stale-stack bytes propagate, 0xAA verified).

Caveat on ordering: with count>32 the ISR first OOB-writes block_data (DF-1076, known), smashing sc->mutex; whether the system survives long enough to reach the copyout is timing/config dependent (INVARIANTS-free kernels more likely to survive) β€” the leak is the read-side consequence of the same primitive, priced accordingly (Medium, like amdsmb DF-1091).

What was verified live

  1. ichsmb attach + full block-read transaction on real (emulated) ICH9 SMBus: BREAD cmd=3 β†’ rc=0 rcount=3 data=06 18 00 (fixed_run.log; baseline module reaches the same code, only the uninitialized *count gate of DF-5458 masks it).
  2. QEMU clamp behavior: i2c-ddc block reads return count=0 (QEMU truncation), never 0xFF.
  3. model mode output (baseline_run.log): copyout=255, initialized=32, stale=223.

Fix

ichsmb.c bread success path: *count = min(sc->block_count, 32); and bcopy bounded the same β€” in fix.diff (module rebuilt in-guest, -Werror clean, and the clamped return observed live: every returned rcount <= 32 in fixed_run.log).

Kernel references

sys/bus/smbus/ichsmb/ichsmb.c:440-441 (sink), :574-576 (source of count, known DF-1076), :64 (block_data[32]); sys/dev/smbus/smb/smb.c:211,318-330 (1024-byte stack buf + min(rcount,bcount) copyout), smb.h:62 (1024 clamp).

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

fixed
baseline no→ patch + rebuild →patched clean

Leak itself could not be triggered on the rig (QEMU clamps slave counts), so no baseline-vs-patched leak observation is possible; the clamp fix is compiled into the validated module build (DF-5459/DF-5458 packs) and every observed returned count with the fix is <=32.

fix.diff; fixed_run.log (rcount=3 <= 32 on all reads)
↓ fix.diffper-fix-DF-5457

Confirmed kernel references

Detail

Exploit chain

malicious SMBus peripheral (patched QEMU / ATtiny on the bus, per DF-1076's threat model) answers a block read with count=0xFF and streams bytes -> ichsmb ISR stores OOB (DF-1076) and sets block_count=255 -> ichsmb_bread bcopy()s only min(255, count_in<=32) bytes but returns *count=255 -> smbioctl clamps the root user's rcount (1024) to 255 and copyouts 255 bytes from the 1024-byte stack buf -> ~223 uninitialized kernel-stack bytes reach userspace (pointers/return addresses).

Evidence (decisive lines)

README.md (trace with exact lines), baseline_run.log model section (copyout=255 init=32 stale=223), fixed_run.log (live block reads through the exact code path; clamped counts after fix), VERDICT.md (QEMU clamp analysis + empirical DDC result)

PoC changes

wrote df5457_poc.c 'model' mode reproducing the smb.c epilogue arithmetic in-guest; live rig work proves the path and the QEMU blocker

Verified recommended fix

ichsmb.c:441: *count = min(sc->block_count, 32); (and bound the bcopy identically); defense in depth: smb.c re-clamp rcount to 32 for SMB_BREAD.

Verdict

NOT REPRODUCED on this rig β€” not because the kernel path is dead but because the precondition (slave reports block count > 32) cannot be generated by stock QEMU: smbus_read_block clamps rlen>32 to 0 (hw/i2c/smbus_master.c, QEMU 7.2.22), verified live with i2c-ddc (EDID offset 1 = 0xFF would give count=255 on real HW; QEMU returns 0), and this container lacks a C toolchain to build the patched QEMU from DF-1076's sketch. The kernel-side sink is real and its surrounding path is fully live on the q35/ipmi rig (real SSIF block reads; first live ichsmb in this audit). Trace + in-guest model: smb.c:327-29 copyout of min(rcount<=1024, bcount<=255) bytes from a 1024-byte stack buffer of which <=32 are initialized => up to 223 bytes of kernel stack disclosed to a root/local reader when a malicious slave lies about count. Sibling of amdsmb DF-1091 (Medium). Fix (clamp *count to 32) built and validated module-side; with the fix every observed returned count is <=32 (fixed_run.log).