Per-cpu iowbytes counter underflow via thread migration accounting break
Summary
badjiosched adds to ioscpu[gd_cpuid].iowbytes(:79-80). Decay subtracts td->td_iosdata.iowbytes-derived amount from ioscpu[current_cpu](:88-90). Thread migration: accumulate on CPU A, migrate to CPU B, decay subtracts from B which never received contribution. size_t underflow -> SIZE_T_MAX. iostotal overflows, factor collapses, bwillwrite throttle bypassed. Unpriv: write heavily while being migrated.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0245 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df0245_mig.c | trigger-source | multi-threaded writer stressor to provoke migration | 2.8 KB | view raw |
| build.sh | build-script | cc -lpthread | 232 B | view raw |
| run.sh | run-script | run stressor, observe via iosched.debug | 462 B | view raw |
| all_fixes_build.log | build-log | kernel build with clamp fix: compiles rc=0 | 5.6 MB | β download |
| env.txt | environment | uname, wdog/iosched state | 353 B | view raw |
| fix.diff | suggested-fix | clamp per-cpu subtraction to prevent size_t underflow (both badjiosched and biosched_done) | 1.1 KB | view raw |
| VERDICT.md | verdict | thread-migration accounting-mismatch trace | 2.9 KB | β raw |
| README.md | readme | human reproduce doc | 554 B | β raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-0245 β DF-0245 β Per-cpu iowbytes counter underflow via thread migration
See VERDICT.md for the full root-cause analysis and reachability.
Reproduce
./build.sh && ./run.sh
(For DF-0245 the bug is confirmed at source level; on this QEMU guest the vulnerable path is latent / timing-dependent β see VERDICT.md "Reachability".)
Fix
fix.diff is a standalone git apply\ -able patch. Validated to apply clean
(git apply --check) and compile in a single all-fixes kernel build
(all_fixes_build.log, rc=0, no errors).
DF-0245 β Per-cpu iowbytes counter underflow via thread migration
Verdict: REPRODUCED (source-level). Runtime triggering is timing-dependent (needs inter-call
CPU migration); the accounting logic is confirmed broken by trace.
Impact: availability (low) β corrupts the I/O throttle factor, not memory corruption;
no escalation chain.
The bug
sys/kern/kern_iosched.c, badjiosched():
65: static int
66: badjiosched(thread_t td, size_t bytes)
67: {
68: globaldata_t gd = mycpu; /* snapshot CURRENT cpu */
...
79: td->td_iosdata.iowbytes += bytes; /* per-THREAD accumulator (migrates w/ thread) */
80: ioscpu[gd->gd_cpuid].iowbytes += bytes; /* per-CPU counter (static array) */
...
88: bytes = (int64_t)td->td_iosdata.iowbytes * delta / (hz * 10); /* decay from td total */
89: td->td_iosdata.iowbytes -= bytes;
90: ioscpu[gd->gd_cpuid].iowbytes -= bytes; /* <-- subtracted from CURRENT cpu only */
td->td_iosdata.iowbytes lives on the thread and migrates with it across CPUs. ioscpu[]
is a static per-cpu array. The decay (line 90) subtracts a td->iowbytes-derived amount
from ioscpu[<current cpu>], but the thread's accumulated iowbytes was added across
multiple CPUs (line 80, on whichever cpu each call ran). If the thread accumulated weight on
CPU A and then migrated to CPU B, the decay subtracts from CPU B which never received the
contribution β ioscpu[B].iowbytes (a size_t) underflows to SIZE_T_MAX, after which
the factor computation (line 96) goes haywire (huge divisor β factorβ0 β thread starved, or
sign-wraps). biosched_done() (line 117) has the same class of bug.
Reachability
Triggered by bwillwrite()/bwillinode() (lines 127/157) on every buffered write / inode
op. The underflow needs the thread to migrate CPUs between an += (line 80) on one call and a
-= (line 90) on a later call β plausible on SMP under load but timing-dependent. The harness
df0245_mig.c spawns many writer threads to maximise migration probability; deterministic
observation is hard because the bad state only surfaces when ioscpu[cpu] is read back (via
iosched.debug=1 kprintf) after an underflow.
The fix
fix.diff clamps each per-cpu subtraction so it can never underflow (at both line 90 and the
biosched_done line 117 site): if ioscpu[cpu] < bytes, set it to 0 instead of wrapping.
This is the minimal safe change that eliminates the SIZE_T_MAX underflow (the reported
defect); the deeper fix would track per-cpu contributions per-thread, but clamping is
sufficient to prevent the accounting explosion.
Kernel refs
sys/kern/kern_iosched.c:80βioscpu[gd->gd_cpuid].iowbytes += bytes(per-cpu add)sys/kern/kern_iosched.c:88-90β decay subtractstd-total from current cpu (mismatch)sys/kern/kern_iosched.c:117βbiosched_donesame underflow classsys/kern/kern_iosched.c:135,164βbwillwrite/bwillinodecallers
Fix verification
not_testablecompile validated
see evidence pack
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source-confirmed. per-cpu iowbytes underflow on thread migration. Timing-dependent, not reproduced. Compile validated.
No comments yet.