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

ichsmb_bread validates and transmits the caller's *count, but /dev/smb passes an uninitialized stack byte β€” SMB_BREAD deterministically fails EINVAL and garbage reaches the ICH_D0 device register

Summary

ichsmb_bread treats *count as input (rejecting <1||>32 and writing it to ICH_D0) but the sole caller declares u_char bcount uninitialized: LIVE-REPRODUCED on the q35/ipmi rig - every SMB_BREAD returns EINVAL 13/13 while SMB_BWRITE succeeds (block-read API dead on arrival), and a stale byte landing in 1..32 transmits kernel stack contents to the controller. bcount=32 + clamp-not-reject fix validated: identical rig returns real IPMI bytes (rc=0, rcount=3).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5458 Β· 7 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.1 KB ↓ raw
VERDICT.md β€” 2.5 KB ↓ raw
env.txt β€” 496 B view raw

DF-5458 PoC β€” ichsmb_bread's count contract is broken in both directions; /dev/smb passes an uninitialized stack byte

Root cause

  • ichsmb_bread() (ichsmb.c:416-461) treats *count as INPUT (validated 1..32 at ichsmb.c:424 and transmitted to the device as the expected block count in ICH_D0 at ichsmb.c:436 /* XXX? */) and as OUTPUT (overwritten with the received count at :441).
  • The only userland caller, smbioctl SMB_BREAD (smb.c:318-330), passes u_char bcount; declared uninitialized (smb.c:217) β€” ichsmb_bread validates and transmits stale kernel stack garbage.
  • Observable effects: SMB_BREAD fails with EINVAL whenever the stale byte is 0 or >32 (on this rig: deterministically, i.e. the block-read interface is dead), and when it happens to pass the gate, a garbage "expected count" is written to the ICH_D0 device register.

Live result (q35 rig, QEMU smbus-ipmi BMC at 0x20 answering real SSIF traffic)

BWRITE cmd=2 (SSIF req)            rc= 0      <- block-write path fully works
BREAD  cmd=3 (SSIF rsp)            rc=-1 errno=22 (EINVAL)   <- every time
... 12 further BREAD attempts, stack-dirtying syscalls in between: all EINVAL

The BMC is present and answering (BWRITE succeeds; the fixed module below reads real IPMI data through the identical path), proving the EINVAL comes from the uninitialized count gate, not from the bus.

Fix validation (same run as DF-5459's pack)

fix.diff: initialize bcount = 32 in smb.c and make ichsmb_bread clamp an invalid input instead of rejecting it (expect = (*count >= 1 && *count <= 32) ? *count : 32). With the patched modules, on the identical rig:

BREAD  cmd=3 (SSIF rsp)            rc= 0 errno=0 rcount=3 data=06 18 00 ...
fresh-process BREAD x3             rc= 0 ... (every attempt)
BREAD slave=0x50 cmd=1 (DDC)       rc=-1 errno=35 (clean timeout error, no garbage gate)

i.e. the interface goes from deterministically-broken to working.

Build & run

guest: cc -O0 -o /root/df5457_poc /root/df5457_poc.c
vm.sh run_root 'kldload ichsmb && kldload smb && /root/df5457_poc dev && /root/df5457_poc uninit'
VERDICT.md
↓ download raw

DF-5458 VERDICT β€” REPRODUCED (uninitialized kernel-stack byte gates and drives a device register) β€” fix validated

Bottom line

ichsmb_bread() consumes its *count parameter as an input bound AND as the output count (ichsmb.c:424 validate 1..32, :436 write to ICH_D0 "XXX?", :441 overwrite), but the only caller (smb.c smbioctl SMB_BREAD) passes u_char bcount; that is never initialized (smb.c:217). The result is: (1) SMB_BREAD is deterministically broken (EINVAL) whenever the stale kernel stack byte at that slot is 0 or >32 β€” on this rig it is, for every attempt, so the entire block-read interface is dead through /dev/smb; (2) when the garbage passes the gate, a junk "expected count" byte is written to the ICH SMBus device register ICH_D0.

Evidence

baseline_run.log (uninit section): 13/13 SMB_BREAD attempts β†’ errno=22 (EINVAL) against a BMC that answers the request (the paired BWRITE succeeds; with the fixed module the identical BREAD returns real IPMI bytes). Stack dirtying between calls (pipe write/read) did not change the slot β€” the byte sits above all callee frames and only smbioctl itself can write it, so on this guest it stays a stable invalid value.

fixed_run.log: with fix.diff (smb.c bcount = 32 + ichsmb_bread clamping the input), every BREAD succeeds: rc=0 rcount=3 data=06 18 00, fresh-process reads included; the DDC transaction now returns a clean timeout errno (35) instead of the garbage gate.

Impact

Functional denial of the SMB_BREAD interface driven by uninitialized kernel memory + transmission of a garbage count byte to the SMBus host controller. Not memory-unsafe by itself (all copies stay bounded by min()), but it is the same contract break whose write side (returning block_count unclamped) is DF-5457, and it makes the whole /dev/smb block-read API unusable on stock systems (users have no way to initialize the byte).

Fix

fix.diff in this pack (shared with DF-5457/DF-5459): smb.c initializes bcount = 32; ichsmb_bread no longer rejects on stale input β€” it clamps (expect), transmits expect to ICH_D0, and copies/returns min(block_count, 32).

Kernel references

sys/bus/smbus/ichsmb/ichsmb.c:424,436,440-441; sys/dev/smbus/smb/smb.c:217,318-330

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 reproduced→ patch + rebuild →patched clean

Baseline: SMB_BREAD 13/13 EINVAL with answering slave. Patched modules: every BREAD rc=0 with real IPMI data; DDC path returns clean errno 35.

fixed_run.log
↓ fix.diffper-fix-DF-5458

Confirmed kernel references

Detail

Evidence (decisive lines)

baseline_run.log (uninit section: 13x errno=22 with working slave), fixed_run.log (rc=0 rcount=3 data=06 18 00 on identical rig+modules-with-fix.diff), fix.diff

PoC changes

added 'dev' and 'uninit' modes to df5457_poc.c (SSIF Get Device ID exchange + stale-count experiments incl. stack-dirtying between calls)

Verified recommended fix

smb.c: initialize bcount (e.g. =32); ichsmb.c: clamp invalid *count input to 32 instead of returning SMB_EINVAL on stale caller memory.

Verdict

REPRODUCED: ichsmb_bread (ichsmb.c:424/436) validates and transmits the caller's *count, but smbioctl SMB_BREAD passes an uninitialized stack byte (smb.c:217) β€” on the live q35+ipmi-bmc-sim rig every /dev/smb SMB_BREAD deterministically fails EINVAL (13/13 attempts) while the identical slave answers BWRITE and (with the fix) returns real IPMI block data, proving the gate is stale kernel stack, not bus state. Fix (init bcount=32 + clamp input instead of rejecting) validated: BREAD works, rcount=3, real bytes returned.