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

rcu_read_lock() is cpu_ccfence() (no exclusion) and reclaim is a fixed 1s delay line β€” __kfree_rcu/call_rcu free objects while readers are still inside their critical sections (deterministic 3/3 UAF + RIP control, demonstrated)

Summary

The read side is a compiler fence (readers may preempt/block in-section) and reclaim is a fixed 1s timer with no reader coupling - any read-side section outliving 1s is handed freed memory. Demonstrated deterministically 3/3 with the real drm.ko __kfree_rcu ring: reader blocked 2s in-section, object freed at +1s, slot reclaimed with controlled content, waking reader called the planted function pointer; RIP-control trap at attacker RIP. 7 shipped call sites in the drm tree. Same protocol fix validated (3/3 -> 0/6).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5126 Β· 16 files
FileTypeDescriptionSize
rcu_race.c β€” 11.3 KB view raw
Makefile β€” 217 B ↓ download
build.sh β€” 89 B view raw
run.sh β€” 191 B view raw
run_fixed.sh β€” 523 B view raw
build.log β€” 5.6 KB view raw
run.log β€” 966 B view raw
run.2.log β€” 547 B view raw
run.fixed.log β€” 1.3 KB view raw
panic.txt β€” 1.1 KB view raw
env.txt β€” 514 B view raw
dmesg.txt β€” 7.3 KB view raw
drmbuild.log β€” 25.0 KB view raw
fix.diff β€” 2.5 KB view raw
VERDICT.md β€” 4.0 KB ↓ raw
verdict.json β€” 3.9 KB view raw
VERDICT.md
↓ download raw

DF-5126 VERDICT β€” no-op read side + fixed 1s delay line

Status: REPRODUCED (deterministic 3/3 per run; RIP control; fix validated)

Root cause

Therefore any read-side critical section that survives longer than the delay (stalled, preempted, or blocked β€” all permitted by the shim) has its object freed underneath it by the shipped __kfree_rcu()/call_rcu() path.

Runtime proof (stock /boot/kernel/drm.ko, 6-CPU guest)

Stage B of the harness (writer cpu0 / reader cpu2; __kfree_rcu is the REAL drm.ko symbol; victim allocated/freed with the shim's own kzalloc/kfree (M_DRM) so slab zones match):

  1. reader: rcu_read_lock(); v = rcu_dereference(gp); tsleep(2s); read v->magic; rcu_read_unlock();
  2. writer: rcu_assign_pointer(gp, NULL); __kfree_rcu(v1); tsleep(1.4s); [32-object poison reclaim spray]
  3. The shim's timer (drm: expand RCU cpu 0 to 16 in dmesg β€” the real ring) frees v1 at ~+1s β€” well inside its own grace model β€” while the reader is provably still inside its critical section.
  4. Reader wakes at +2s, still in-section: reads MAGIC_POISON and calls v->v_fp β€” a function pointer that exists only as data written into the freed-and-reclaimed object.

Results: 3/3 rounds, every baseline run (runs 1, 2, 3 and the extra trigger rounds). With rip_panic=1 the reclaimed fp = 0xffffffff52170000 and the kernel took Fatal trap 12: page fault ... instruction pointer = 0xffffffff52170000 (panic.txt) β€” arbitrary kernel RIP control from the UAF.

Distinguishing from neighbors

  • DF-5033 (amdgpu_bo_list.c): lockless idr_find() vs synchronous kfree in idr_grow() β€” a different subsystem and an immediate free; that finding cites linux_rcu.c:187 only as context. This finding is about the RCU shim's own grace-period model (delay line + no-op read side) and is demonstrated through the shim's exported __kfree_rcu.
  • DF-1881 (linux_fence.c/reservation): a specific unsafe refcount helper; not the shim's grace model.

Exploit chain / ceiling

Identical to DF-5125's (same object, same reclaim discipline): blocked/preempted reader -> __kfree_rcu fires at +1s -> reclaim with controlled content -> reader consumes poisoned fields -> indirect call through reclaimed fp -> RIP control (fatal trap shown). Real-world ceiling: every drm kfree_rcu/call_rcu victim reachable from unprivileged GPU clients (fences, bo lists, gem objects, reservation arrays). This guest has no GPU/render node, so the shipped call sites cannot be driven end-to-end here β€” environment blocker, documented; the primitive (UAF + RIP control via the shipped shim) is fully reproduced.

Fix validation

fix.diff (shared with DF-5125): rcu_read_lock()/rcu_read_unlock() now maintain a global nesting counter (rcu_read_acquire/rcu_read_release); rcu_timer() defers element execution while rcu_readers != 0; the blocked reader therefore holds off the free until after its unlock β€” exactly the semantics RCU promises. Post-fix stage B: 0 hits in 6 rounds (including rip_panic=1 trigger rounds), guest healthy. Baseline was 3/3 every run.

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

fix.diff applied in guest /usr/src, drm.ko rebuilt, identical harness rebuilt against fixed headers: stageB_uaf 0/6 rounds (was 3/3 per run), canary 0, guest survived rip_panic=1 trigger rounds. Delay-line free now correctly deferred until the blocked reader exits its section.

run.fixed.log; drmbuild.log; fix.diff
↓ fix.diffper-fix-DF-5126

Confirmed kernel references

Detail

Exploit chain

reader cpu2 blocks 2s INSIDE rcu_read_lock (legal under this shim: preempt_disable is cpu_ccfence) holding v -> writer cpu0 swaps gp and calls the shipped __kfree_rcu(v) -> shim's per-cpu ring (dmesg: 'drm: expand RCU cpu 0 to 16') kfrees v at ~+1s per its fixed delta>=hz rule -> same-cpu slab reclaim with controlled {magic,fp} -> reader wakes still in-section, reads poison (UAF read) and calls v->v_fp (kernel indirect call via freed-object content) -> fp=0xffffffff52170000: Fatal trap 12, RIP = attacker value. uid=0 end-to-end blocked on this guest only by absent GPU hardware (no render node); primitive fully reproduced.

Evidence (decisive lines)

run.log/run.2.log (stageB_uaf 3/3, poison_reads 3, canary 3 every run), panic.txt (rip=0xffffffff52170000), run.fixed.log (post-fix stageB_uaf 0 across 6 rounds incl rip_panic=1, guest up), fix.diff

PoC changes

Shared harness with DF-5125 (stage B is this finding's trigger); all harness repairs listed in DF-5125/verdict.json poc_changes apply. Deterministic by construction: reader's 2s in-section sleep spans the shim's 1s grace line.

Verified recommended fix

Account read-side critical sections (rcu_read_acquire/release in rcu_read_lock/unlock) and defer rcu_timer reclamation while any reader is in flight -- see fix.diff.

Verdict

The drm RCU shim's read side (rcu_read_lock -> preempt_disable -> cpu_ccfence) provides no preemption/blocking exclusion while its reclaim side is a fixed 1-second delay line with no reader tracking (linux_rcu.c:81-83), so __kfree_rcu/call_rcu free objects while readers are still inside their critical sections. Reproduced deterministically (3/3 every run): a reader blocked 2s inside rcu_read_lock() had its object freed at ~+1s by the REAL drm.ko __kfree_rcu ring, the slot reclaimed with controlled content, and then read poisoned data and CALLED a function pointer written into the freed object; with rip_panic=1 the kernel took a fatal trap at the attacker-chosen RIP (0xffffffff52170000). fix.diff (read-side nesting counter + deferral of rcu_timer reclamation while readers are in flight) drives stage-B hits to 0.