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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0245 Β· 10 files
FileTypeDescriptionSize
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
README.md readme human reproduce doc
↓ download 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).

VERDICT.md verdict thread-migration accounting-mismatch trace
↓ download raw

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

Fix verification

not_testable

compile 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.