smbus_request_bus returns success without acquiring ownership: wake-path break skips the ownership re-check and the EWOULDBLOCK->0 fixup converts the SMB_DONTWAIT refusal into a grant (phantom grants / double grants)
Summary
The ownership loop never re-checks sc->owner after smbus_poll: a woken waiter executes 'if (error==0) break' and returns 0 without ever setting sc->owner (only the else branch sets it) - every contended woken request yields a phantom holder and the next requester is granted too; additionally the EWOULDBLOCK->0 fixup erases the DONTWAIT fast-fail. The iiconf twin loops back and re-checks; smbconf does not. Because iicsmb's transfer methods perform no arbitration of their own, a phantom grant puts two kernel threads inside one SMBus transaction: ichsmb KASSERT panic class, interleaved I2C wire transactions, silently corrupted IPMI SSIF/cyapa transfers. Reproduced on the unmodified production stack (fakebb rig hosting real iicbb/iicbus/iicsmb + in-kernel smbus): phantom grant after real release+wakeup, double grant 0x3, DONTWAIT returned 0 while owner held, and (with DF-5637) a grant with no release at all. Loop-back + claim-then-zero fix validated on a rebuilt kernel (0x3->0x0, DONTWAIT correctly refused).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5636 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df5636_rig.c | β | 22.5 KB | view raw | |
| df5636ctl.c | β | 1.5 KB | view raw | |
| iicbb_reg.c | β | 491 B | view raw | |
| Makefile.rig | β | 279 B | β download | |
| Makefile.reg | β | 228 B | β download | |
| build.sh | β | 133 B | view raw | |
| run.sh | β | 166 B | view raw | |
| build.log | β | 1.1 KB | view raw | |
| run.log | β | 5.2 KB | view raw | |
| run_baseline.log | β | 5.2 KB | view raw | |
| run_fixed.log | β | 2.9 KB | view raw | |
| env.txt | β | 512 B | view raw | |
| fix.diff | β | 2.2 KB | view raw | |
| VERDICT.md | β | 5.4 KB | β raw | |
| verdict.json | β | 5.1 KB | view raw |
DF-5636 VERDICT β REPRODUCED
Finding: smbus_request_bus() (sys/bus/smbus/smbconf.c:111-144) returns
success without acquiring ownership on two paths, because the ownership
loop never re-checks sc->owner after smbus_poll() and converts
poll's EWOULDBLOCK into success.
Root cause (path:line)
-
Wake path phantom grant β smbconf.c:126-141. When the waiter is woken (
smbus_pollreturns 0 fromtsleep), the loop executesif (error == 0) break;(smbconf.c:137-138) and returns 0 without re-entering the ownership check and without ever settingsc->owner = dev(only the else-branch at :134 sets it). Compare the iiconf.c twin loop (iiconf.c:99-114), which loops back and re-checks. Every contended request that is woken produces a caller that "holds" a bus nobody recorded: the next requester seesowner == NULL(or another device) and is granted too β two concurrent granted holders of the SMBus. -
DONTWAIT phantom grant β smbconf.c:131-132.
smbus_poll()returns EWOULDBLOCK forSMB_DONTWAIT(default case, smbconf.c:96-98); theif (error == EWOULDBLOCK) error = 0;fixup erases the only failure signal of the fast-fail path, and the samebreakreturns 0 whilesc->ownerstill belongs to another device.
The unconditional wakeup(sc) at smbconf.c:177 (fired on every release,
including the EACCES non-owner path) multiplies the trigger surface: any
release anywhere on the bus wakes parked requesters into the phantom-grant
path (proved by F3B: a grant while sc->owner verifiably still belonged to
another device, with no release at all having occurred).
Reproduction (rig, production modules, stock kernel)
Chain: fakebb0 β iicbb0 β iicbus0 β iicsmb0 β smbus0 (all real; smbus is compiled into the guest's X86_64_GENERIC β config line 335 β so the code under test was the in-kernel production copy; wire sanity round-trip passed before every test).
- F1 (
run.log): devA owns; devB parks; devA releases βT(devB): request RETURNED 0withsc->owner=0 (caller=devB) <<< PHANTOM GRANT; devC then granted immediately β double grant (summary=0x3). - F2:
request(devB, SMB_DONTWAIT)=0whileownerwas still another device β<<< PHANTOM GRANT (must be EWOULDBLOCK)(summary=0x1). - F3B: parked devB woken by a non-owner's EACCES release β
error=0 owner_seen=devA <<< GRANTED while another device verifiably OWNS the bus (no release ever happened)(summary=0x1).
Impact
Mutual exclusion is the smbus core's single responsibility: the transfer
methods of the bridge (iicsmb.c:276-507 quick/sendb/recvb/writeb/writew/
readb/readw/pcall/bwrite/bread) perform no arbitration of their own β
they rely entirely on the caller holding the bus. A phantom grant therefore
puts two kernel threads inside one SMBus transaction concurrently:
interleaved I2C wire transactions on iicbb/ig4-class controllers, and on
ichsmb the documented single-transaction state machine trips its
KASSERT(sc->ich_cmd == -1) (panic β the DF-5459 class, there caused by a
driver no-op, here by the core itself). In-tree contending children exist on
ordinary hardware (ipmi_ssif vs /dev/smb on server boards; cyapa's poll
thread vs anything else on laptops). Triggering requires two bus users and
either a wakeup (routine) or SMB_DONTWAIT (/dev/smb nonblocking ioctls,
cyapa/atmel O_NONBLOCK opens β all root-only devices, or kernel threads),
hence Medium severity / local DoS + silent SMBus transaction corruption
(e.g. interleaved IPMI SSIF responses) rather than an unprivileged path.
No memory corruption is involved (the protocol hands out grants, not bytes), so there is no uid=0 chain; the ceiling is mutual-exclusion bypass with the consequences above.
Fix validation (same guest, kernel rebuilt)
fix.diff (smbconf.c): re-check ownership after every poll return; treat a
WAIT poll timeout as a retry, never as success; keep DONTWAIT failures;
error = 0 only when ownership is claimed; plus the DF-5637/5638 legs
(release validates ownership before the bridge release, no EACCES wakeup,
PCATCH + hz-bounded park). Applied to /usr/src, quickkernel rebuilt
(smbconf.o recompiled), installed, rebooted (kernel #3):
- F1 β summary=0x0:
F1.3w ... owner=<devB>(the woken waiter claimed), devC parked until released,final owner=0 (clean=NULL). - F2 β
request(devB, SMB_DONTWAIT)=35 (EWOULDBLOCK: correctly refused). - F3B β
devB still parked (no bogus wakeup: GOOD on fixed code). - Park β
S2 smbreq,kill -9kills it.
Two intermediate fix iterations were required (first version leaked a stale
EWOULDBLOCK through the claim path, second returned EWOULDBLOCK after one
timeout instead of retrying); the final fix.diff in this pack is the
validated one. The residual limitations (crit-section MP-unsafety is
addressed separately as DF-5637; no detach drain β parking is now bounded
and killable, which defuses the DF-5563-class unload UAF for this file) are
noted in the pack README.
Classification
status=reproduced, reproduced=1, impact=dos, confidence=certain.
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
fixedfix.diff applied to /usr/src/sys/bus/smbus/smbconf.c, kernel rebuilt (quickkernel, smbconf.o recompiled) and rebooted (kernel #3): F1 summary 0x3->0x0 with the woken waiter CLAIMING owner and devC parking; F2 0x1->0x0 with SMB_DONTWAIT refused EWOULDBLOCK; F3B 0x1->0x0 'no bogus wakeup: GOOD on fixed code'; park S2/killable (was D4/unkillable). Baseline behaviors all gone.
run_fixed.log (full fixed-kernel dmesg section) + run.log baseline differential; fix.diff sha256 346fe6893056aecf467e64d12285a0de337461aee150737964c0c3f5f06fbc2a
Confirmed kernel references
- sys/bus/smbus/smbconf.c:126
- sys/bus/smbus/smbconf.c:131
- sys/bus/smbus/smbconf.c:134
- sys/bus/smbus/smbconf.c:137
- sys/bus/smbus/smbconf.c:96
- sys/bus/smbus/smbconf.c:177
- sys/bus/iicbus/iicsmb.c:250
- sys/bus/iicbus/iicsmb.c:276
- sys/bus/iicbus/iiconf.c:99
- sys/dev/smbus/smb/smb.c:240
- sys/dev/misc/ipmi/ipmi_ssif.c:81
- sys/dev/smbus/cyapa/cyapa.c:1176
Detail
Evidence (decisive lines)
run.log F1 lines 'T(devB): request RETURNED 0' + 'sc->owner=0 (caller=devB) <<< PHANTOM GRANT' and F1.4 double grant; F2 line 'SMB_DONTWAIT)=0 ... <<< PHANTOM GRANT (must be EWOULDBLOCK)'; F3B line 'GRANTED while another device verifiably OWNS the bus'; run_fixed.log shows owner=devB claim, EWOULDBLOCK refusals and 'GOOD on fixed code' on kernel #3
PoC changes
seed sketch rewritten as the family-standard fakebb KLD rig: real production chain (iicbb/iicbus/iicsmb modules + in-kernel smbus), three distinct device_t owner tokens, in-kernel kthreads with started/done handshakes and owner-pointer sampling after every request; two rig iterations (build.sh Makefile copy bug; sys/string.h->bzero; robust cleanup after the DF-5637 teardown wedge; hz/2 release timing so the wakeup, not the 1s poll timeout, drives the fixed-kernel F1)
Verified recommended fix
Rewrite the smbus_request_bus ownership loop to re-check sc->owner after every smbus_poll return (continue on wake and on WAIT poll-timeout, unwind the bridge request only on interruption or refused DONTWAIT), set error=0 only when ownership is claimed, keep DONTWAIT EWOULDBLOCK; validate smbus_release_bus ownership BEFORE the parent callback and drop the EACCES-path wakeup; make smbus_poll always PCATCH with an hz bound (see pack fix.diff).
Verdict
smbus_request_bus (sys/bus/smbus/smbconf.c:126-141) returns success without acquiring ownership on two paths: after any wakeup (the loop's 'if (error == 0) break' exits without re-checking sc->owner or setting sc->owner = dev) and for SMB_DONTWAIT (the 'if (error == EWOULDBLOCK) error = 0' fixup converts the fast-fail refusal into a phantom grant). Reproduced on the unmodified production smbus stack (fakebb rig hosting real iicbb/iicbus/iicsmb + in-kernel smbus): a woken waiter returned 0 while sc->owner stayed NULL, a second requester was granted immediately (double grant), SMB_DONTWAIT returned 0 while another device verifiably owned the bus, and a non-owner's EACCES release (smbconf.c:177 unconditional wakeup) woke a parked waiter into a success return while the real owner still held the bus. The iicsmb bridge transfer methods perform no arbitration of their own, so a phantom grant puts two kernel threads inside one SMBus transaction (ichsmb KASSERT panic class, interleaved wire transactions elsewhere). fix.diff (re-check after poll, no EWOULDBLOCK-to-success, PCATCH+hz park, validate-before-bridge-release) was applied to /usr/src, the kernel rebuilt and rebooted: all phantom grants gone, DONTWAIT refused with EWOULDBLOCK, woken waiters claim ownership, park killable.
No comments yet.