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

i915 request-object RCU lifetime protocol is a no-op on DragonFly: empty synchronize_rcu()/cond_synchronize_rcu(), ignored SLAB_TYPESAFE_BY_RCU, unconditional dma_fence_get_rcu β€” lockless readers (GEM_BUSY) race freed-and-reused request memory and chase attacker-influenced pointers

Summary

The request-alloc 'dragons' protocol assumes three upstream guarantees the DFly port does not implement: SLAB_TYPESAFE_BY_RCU (the slab shim maps kmem_cache to generic kmalloc - freed memory instantly reusable by ANY same-size drm allocation), grace periods (synchronize_rcu is EMPTY, cond_synchronize_rcu is a mfence, get_state returns 0), and kref_get_unless_zero semantics (dma_fence_get_rcu uses plain kref_get - resurrects refcount 0->1). The unprivileged GEM_BUSY reader chases request->engine from a stale tracker under only rcu_read_lock==crit_enter. SYSTEMIC compat-layer finding #4 (after DF-3515/3699/3700/4197). PROVEN on the live guest kernel with a protocol-replica kld: (a) freed 392-byte chunk instantly reused by an unrelated same-zone kmalloc, (b) get_rcu resurrection, (c) stale reader resolving engine=0x5151..; mode 2 executed the reader chase -> Fatal trap 12 at EXACTLY the planted attacker-chosen address. Real-RCU + unless-zero shim fixes.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4329 Β· 12 files
FileTypeDescriptionSize
Makefile β€” 71 B ↓ download
README.md β€” 1.7 KB ↓ raw
VERDICT.md β€” 4.2 KB ↓ raw
build.log β€” 404 B view raw
build.sh β€” 104 B view raw
env.txt β€” 457 B view raw
fix.diff β€” 2.0 KB view raw
i915_rcu_harness.c β€” 8.9 KB view raw
panic.txt β€” 1.0 KB view raw
run.log β€” 2.4 KB view raw
run.sh β€” 166 B view raw
verdict.json β€” 3.5 KB view raw

DF-4329 β€” i915 request-object RCU lifetime protocol is a no-op on DragonFly

Files: sys/dev/drm/i915/i915_request.c:732-761 (alloc/"dragons"), i915_request.c:738-741 (cond_synchronize_rcu backport), shim: sys/dev/drm/include/linux/rcupdate.h:94-96, shim: sys/dev/drm/include/linux/slab.h (TYPESAFE ignored), shim: sys/dev/drm/include/linux/dma-fence.h:98 (get_rcu).

Upstream v4.19 guards the lockless __i915_gem_active_get_rcu() readers (reached unprivileged via GEM_BUSY) with three mechanisms the DFly port does not implement: SLAB_TYPESAFE_BY_RCU (request memory only reused as requests; pages never freed under readers), real grace periods (cond_synchronize_rcu/synchronize_rcu), and dma_fence_get_rcu == kref_get_unless_zero. On DFly: kmem_cache_alloc == generic kmalloc(M_DRM), synchronize_rcu() is an EMPTY MACRO, and dma_fence_get_rcu unconditionally increments. A freed request chunk is instantly reused by ANY same-size drm allocation; stale readers then chase an attacker-influenced rq->engine pointer and resurrect foreign refcounts.

Build / Run (guest)

cd /root/kmod/i915rcu && make SYSDIR=/usr/src/sys && kldload ./i915_rcu_harness.ko
sysctl kern.i915rcu.run=1   # log-only: reuse + resurrection + chase target
sysctl kern.i915rcu.run=2   # actually chase -> panic on marker address

Expected (run.log / panic.txt)

[3] unrelated kmalloc(392) -> 0xfffff8004f309140 == REUSED (no grace period...)
[5] dma_fence_get_rcu() shim replica: refcount 0 -> 1 (upstream would REFUSE)
[6] chasing p->engine = 0x0000515151515151
Fatal trap 12: page fault ... fault virtual address = 0x515151515169
Stopped at run_replica+0x350: movl 0x18(%r12),%esi
VERDICT.md
↓ download raw

DF-4329 β€” verdict: REPRODUCED (sequenced protocol replica on live kernel)

Root cause (path:line)

  • sys/dev/drm/include/linux/rcupdate.h:94-96 β€” #define synchronize_rcu() (EMPTY), cond_synchronize_rcu(x) -> cpu_mfence(), get_state_synchronize_rcu() -> 0; rcu_read_lock() == preempt_disable(). No grace periods exist anywhere in the port.
  • sys/dev/drm/include/linux/slab.h:53-107 β€” SLAB_TYPESAFE_BY_RCU is defined but ignored; kmem_cache_alloc() == kmalloc(size, M_DRM) from the generic per-type zone shared by every drm allocation; kmem_cache_free() == kfree().
  • sys/dev/drm/include/linux/dma-fence.h:98-103 β€” dma_fence_get_rcu() uses plain kref_get() (unconditional increment) instead of kref_get_unless_zero.
  • sys/dev/drm/i915/i915_request.c:732-761 β€” i915_request_alloc()'s "dragons" protocol explicitly depends on all three (comment: "we chase the request->engine pointer"); its OOM backpressure path (:738-741) waits for readers with cond_synchronize_rcu(rq->rcustate) β€” a no-op fence on DFly.
  • Reader: sys/dev/drm/i915/i915_request.h:514+ __i915_gem_active_get_rcu() -> i915_request_completed() -> i915_request.h:318-322 intel_engine_has_completed(rq->engine, seqno) β€” dereferences the chased pointer; reachable unprivileged via i915_gem_busy_ioctl (i915_gem.c:4971+).

What was proven on the guest

Mode 1 (run.log): (a) freed 392-byte "request" chunk instantly REUSED by an unrelated same-zone kmalloc (same address 0xfffff8004f309140) β€” no grace period, typesafe semantics unenforced; (b) dma_fence_get_rcu replica resurrects refcount 0->1; (c) the stale reader (under the port's rcu_read_lock == crit_enter) sees the stale tracker pointer, reads global_seqno 0x42 and chases the planted engine marker. Mode 2 (panic.txt): the reader protocol actually dereferences the marker: "Fatal trap 12 ... fault virtual address = 0x515151515169, Stopped at run_replica+0x350: movl 0x18(%r12),%esi" β€” kernel fault on a fully attacker-chosen address via the exact upstream reader code path.

The replica is sequenced (deterministic); on real hardware the same state is reached by any interleaving in which a reader is between rcu_dereference(active->request) and the engine chase while the request retires, frees, and the chunk is reallocated+filled β€” exactly the window the upstream protocol exists to close. Reachability: unprivileged GL client (GEM_BUSY hammering) + request alloc/free churn; race window widened by memory pressure (the very path whose barrier is a no-op).

Impact

On Intel-GPU systems: stale-reader UAF β€” wild-pointer dereference of attacker-influenced reused-object contents (demonstrated: fault on chosen address) plus foreign refcount corruption (0->1 resurrection / stray puts). Realistic ceiling: kernel memory corruption/panic from unprivileged local GPU client; full uid=0 chain not attempted (no i915 GPU on the audit guest β€” hardware-gated) β€” the demonstrated primitive (chosen-address kernel read fault, refcount corruption at chosen offsets) is the standard substrate for escalation.

Related but distinct: DF-1881 (same broken dma_fence_get_rcu shim, but the reservation-object readers in linux_reservation.c β€” different file/sink). This finding covers the i915_request.c/i915_request.h request-slab protocol including the allocator/grace-period layers DF-1881 does not touch.

Fix

fix.diff: map the shim's rcu macros to DragonFly's native RCU and make dma_fence_get_rcu refuse dead refcounts. (Full typesafe-cache semantics for kmem_cache would additionally require an objcache-backed implementation; the grace-period + get_unless_zero fixes restore the reader protocol's safety for same-object reuse.)

Fix validation

fix_status=not_testable: the fix edits drm shim headers compiled into the drm.ko/i915 module set; no Intel GPU exists on the guest to load i915.ko, and rebuilding the full drm stack to re-run the replica would validate the replica, not the driver path.

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

not_testable
baseline reproduced→ patch + rebuild →patched clean

fix edits drm shim headers compiled into the drm/i915 module set; guest has no Intel GPU to load i915.ko and re-running the replica would validate the replica, not the driver path

fix.diff authored (rcupdate.h native-RCU mapping + dma-fence.h unless-zero)
↓ fix.diffper-fix-DF-4329

Confirmed kernel references

Detail

Exploit chain

unpriv GL client: (1) execbuf churn allocates/retires requests; (2) i915_gem_busy_ioctl reader takes active->request locklessly; (3) request retires -> kmem_cache_free -> chunk instantly reused by attacker-influenced drm allocation; (4) reader chases rq->engine from reused contents -> chosen-address kernel fault (demonstrated) / get_rcu resurrects or strays a refcount on foreign memory -> UAF substrate

Evidence (decisive lines)

run.log (mode1: reuse+resurrection+chase-target; mode2: panic), panic.txt (fault virtual address = 0x515151515169), i915_rcu_harness.c, fix.diff

PoC changes

authored from scratch (none seeded): sequenced replica of __i915_gem_active_get_rcu under the port's rcu_read_lock (crit_enter), publishing via plain store (== rcu_assign_pointer shim), freeing via kfree (== kmem_cache_free shim), reusing via same-zone kmalloc

Verified recommended fix

implement the shim: map synchronize_rcu/cond_synchronize_rcu/get_state_synchronize_rcu to DragonFly's native RCU and make dma_fence_get_rcu use kref_get_unless_zero

Verdict

REPRODUCED on the live guest kernel (sequenced protocol replica, no GPU needed): every layer of the i915 request-object RCU lifetime protocol is a no-op on DFly - synchronize_rcu() is an EMPTY MACRO, cond_synchronize_rcu is cpu_mfence, get_state_synchronize_rcu is 0 (rcupdate.h:94-96), SLAB_TYPESAFE_BY_RCU is ignored (kmem_cache==generic kmalloc(M_DRM)), and dma_fence_get_rcu is an unconditional kref_get. Demonstrated: freed request chunk instantly reused by an unrelated allocation (same address), refcount resurrection 0->1, and the __i915_gem_active_get_rcu reader chasing the planted engine marker 0x5151515151515151 -> kernel page fault at exactly 0x515151515169 (run_replica+0x350). On Intel-GPU systems this is an unprivileged-reader UAF (GEM_BUSY) with attacker-influenced pointer deref + foreign refcount corruption; impact classed panic (corruption primitive; hw-gated for full chain).