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

amdgpu userptr MMU-notifier invalidation is entirely absent on DragonFly β€” inert shim, compiled-out amdgpu_mn.c, stubbed userptr ioctl; latent missed-invalidation GPU DMA + stripped-kref p->mn UAF if ever enabled

Summary

DFly has no MMU-notifier implementation at all (the shim header is pure type scaffolding; nothing in sys/vm ever calls ops); amdgpu_mn.c is absent from the module build AND could not compile if added (undeclared registration functions, wrong ops signatures); CONFIG_MMU_NOTIFIER undefined so amdgpu_mn.h stubs are live; and the GEM_USERPTR ioctl hard-fails -EINVAL with its body #if 0'd - binary-proven on the shipped amdgpu.ko (0 mn symbols + the stub string). As shipped: no attack surface. Latent for whoever enables it: register-only shim would give unpriv GPU compute a write-into-revoked-pages primitive (the DF-4819 pattern), and the DFly copy stripped upstream's amn kref (shared drm_file -> p->mn UAF). Restore-kref + do-not-enable-until-real-hooks fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4838 Β· 7 files
FileTypeDescriptionSize
README.md β€” 2.4 KB ↓ raw
VERDICT.md β€” 5.7 KB ↓ raw
static_check.sh β€” 1.1 KB view raw
run.log β€” 463 B view raw
fix.diff β€” 1.3 KB view raw
verdict.json β€” 3.5 KB view raw
manifest.json β€” 1.7 KB view raw

DF-4838 β€” amdgpu MMU-notifier invalidation entirely absent on DragonFly (latent)

What this is

Not an exploitable PoC β€” an absence proof. The amdgpu userptr MMU-notifier subsystem (amdgpu_mn.c) is "triple-dead" on DragonFly:

  1. CONFIG_MMU_NOTIFIER is never defined (sys/dev/drm/kconfig.h has no such define), so sys/dev/drm/amd/amdgpu/amdgpu_mn.h:37 always takes the #else stub branch: amdgpu_mn_register() β†’ -ENODEV, amdgpu_mn_get() β†’ NULL, amdgpu_mn_lock/unlock() β†’ no-ops (amdgpu_mn.h:45-56).
  2. sys/dev/drm/amd/amdgpu/Makefile does not list amdgpu_mn.c in SRCS (contrast sys/dev/drm/radeon/Makefile:107 where radeon_mn.c is explicitly commented out) β€” the file is never compiled. It could not compile anyway: __mmu_notifier_register / mmu_notifier_unregister_no_release (amdgpu_mn.c:416/132) have no definition or declaration anywhere in sys/, and amdgpu_mn_ops (amdgpu_mn.c:356-367) initializes .invalidate_range_end, a member that does not exist in the shim's struct mmu_notifier_ops (sys/dev/drm/include/linux/mmu_notifier.h:40-46), and assigns 5-arg int-returning handlers to 4-arg void slots.
  3. amdgpu_gem_userptr_ioctl() (amdgpu_gem.c:285-290) unconditionally kprintf(...); return -EINVAL; with the whole implementation #if 0-ed β€” no userptr BO can ever be created, so there is nothing to invalidate.

Consequence as shipped: no reachable impact (verified on the guest binary: /boot/kernel/amdgpu.ko contains zero amdgpu_mn symbols and the "not implemented" stub string). Latent hazard: if the file is ever wired into the build and the shim "completed" as a no-op register (the pattern of DF-4819, the dead shrinker), a writable GPU userptr would keep DMA-ing into revoked user pages after munmap/madvise/exit β€” plus the DFly copy stripped upstream's struct amdgpu_mn.kref, so the hash-hit path (amdgpu_mn.c:397-399) returns shared state with no refcount and lifetime hangs solely on the mm-release callback β€” a UAF the day the code goes live.

Reproduce

  • Host static proof: sh findings/poc/DF-4838/static_check.sh
  • Guest binary proof (output saved in run.log): vm.sh run_root 'nm /boot/kernel/amdgpu.ko | grep -c amdgpu_mn; strings /boot/kernel/amdgpu.ko | grep userptr'

Expected

  • 0 amdgpu_mn symbols in the shipped module; kconfig grep count 0; Makefile grep no match; stub string present in the binary.
VERDICT.md
↓ download raw

DF-4838 β€” VERDICT

Status: untested (by policy β€” Info severity, no runtime vulnerability exists to reproduce); impact as shipped: none. Confidence in the finding itself: certain.

Question this pass-2 audit had to answer

The orchestrator's key question: does the DragonFly MMU-notifier shim ever call into mn_ops? If not, amdgpu userptr invalidation NEVER happens β€” GPU writes to revoked user memory.

Answer: the shim never calls anything β€” there is nothing to call. But the feared primitive is closed one layer higher: amdgpu userptr objects cannot be created at all on DragonFly, so there is no GPU mapping of user memory whose revocation could be missed.

Evidence chain (all path:line against this tree)

  1. The shim is pure type scaffolding. sys/dev/drm/include/linux/mmu_notifier.h:36-46 defines struct mmu_notifier as a bare ops pointer and struct mmu_notifier_ops with release + invalidate_range_start only (4-arg, void). It declares no registration function, holds no notifier list, and rg mmu_notifier sys/vm sys/platform sys/cpu sys/sys returns nothing: the DragonFly VM has zero hooks into these ops. The only consumers in the entire tree are the drm drivers themselves (amdgpu_mn.c, radeon_mn.c, i915_gem_userptr.c).

  2. __mmu_notifier_register / mmu_notifier_unregister_no_release do not exist. Called at amdgpu_mn.c:416 and amdgpu_mn.c:132; rg over all of sys/ finds only these call sites (plus radeon/i915) β€” no definition, no declaration, not even a macro. The file therefore cannot compile if added to a build (implicit-declaration error; and amdgpu_mn_ops at amdgpu_mn.c:356-367 initializes .invalidate_range_end, a member absent from the shim struct, and assigns 5-arg int-returning handlers (amdgpu_mn.c:248-252, 296-300) into 4-arg void slots β€” a hard type error). This is why:

  3. The build excludes it. sys/dev/drm/amd/amdgpu/Makefile (read in full) never lists amdgpu_mn.c; sys/dev/drm/radeon/Makefile:105-107 comments out both radeon_mn.c and # CONFIG_MMU_NOTIFIER. Verified on the shipped guest: grep -c amdgpu_mn in the Makefile β†’ no match; nm /boot/kernel/amdgpu.ko | grep -c amdgpu_mn β†’ 0 (run.log).

  4. CONFIG_MMU_NOTIFIER is undefined, so amdgpu_mn.h:37-57 compiles the stub branch: amdgpu_mn_register β†’ -ENODEV (amdgpu_mn.h:52-55), amdgpu_mn_get β†’ NULL (47-51), amdgpu_mn_lock/unlock β†’ no-ops (45-46). The live callers degrade safely: amdgpu_cs.c:605 (p->mn = amdgpu_mn_get(...) β†’ NULL), amdgpu_cs.c:1222/1262/1269 (no-op lock/unlock of NULL), amdgpu_gem.c:329 (amdgpu_mn_register β€” unreachable, see 5).

  5. Userptr creation is closed at the ioctl. amdgpu_gem.c:285-290: kprintf("amdgpu_gem_userptr_ioctl: not implemented\n"); return -EINVAL; with the entire implementation #if 0-ed (amdgpu_gem.c:291-360+). The shipped binary carries the stub string (run.log). Without a userptr BO, amdgpu_cs_submit's userptr revalidation loop (amdgpu_cs.c:1222-1231) iterates over an empty set. No GPU mapping of user memory exists whose page revocation could go unnoticed β€” the feared "GPU writes to revoked user memory" primitive has no object to operate on.

Latent hazards documented (why this is filed at all)

  • If the notifier path is ever enabled by (a) adding amdgpu_mn.c to SRCS, (b) defining CONFIG_MMU_NOTIFIER, and (c) "implementing" the shim as a register-only no-op (exactly the DF-4819 dead-shrinker pattern), the invalidation callbacks would still never fire, and writable userptr BOs (write requires AMDGPU_GEM_USERPTR_REGISTER on Linux, enforced at amdgpu_gem.c in the #if 0 body) would DMA into pages freed by munmap/madvise/exit β€” a physical-memory write primitive.
  • The DFly copy stripped upstream's struct amdgpu_mn.kref (compare struct amdgpu_mn, amdgpu_mn.c:72-90 β€” no refcount member; amdgpu_mn_get, amdgpu_mn.c:397-399 returns the hash hit with no kref_get). Upstream Linux 4.19 (this file's vintage) has both. Lifetime hangs solely on the mm release callback scheduling amdgpu_mn_destroy (amdgpu_mn.c:144-151, 112-134). A drm_file shared across processes (SCM_RIGHTS/fork) caches p->mn (amdgpu_cs.c:605); mm teardown of the creating process frees amn while the other process can still reach amdgpu_mn_lock(p->mn) (amdgpu_cs.c:1222) β†’ UAF. Dead today; a trap for whoever enables the file.
  • Same family as DF-4586 (i915 mmgrab no-op latent), DF-4819 (dead shrinker).

Why no QEMU exploitation / fix validation

Severity Info; there is no runtime misbehavior on the shipped kernel β€” the guest binary proves the code is absent (nm count 0, run.log). Nothing to build, nothing to run, no fix to validate on a kernel (the fix is to keep userptr closed and to restore upstream parity for the day it is enabled β€” fix.diff restores the kref; the strategic fix is a real VM-side notifier implementation, out of scope for a single-file diff).

Bottom line

The feared "notifier never fires" is TRUE on DragonFly β€” but the attack surface it protects does not exist: userptr ioctls fail with -EINVAL and amdgpu_mn.c is not even compiled. Filed as Info/certain so the project has a record that (1) any future wiring of this file needs a real mmu_notifier implementation behind the shim, and (2) the kref must come back (fix.diff).

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 no→ patch + rebuild →patched clean

No runtime defect to A/B on the shipped kernel (code is compiled out); fix.diff restores upstream kref parity and adds an explicit warning so the dead path cannot be silently re-enabled against the inert shim.

findings/poc/DF-4838/fix.diff (sha256 90ab0086a130b350e784ae630c4bee8fc7a08e1615d827c5fb69c57d45958b85), authored against read-only sys/, never applied
↓ fix.diffper-fix-DF-4838

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-4838/run.log (guest: nm amdgpu.ko grep -c amdgpu_mn = 0; kconfig grep count 0; Makefile grep rc=1; stub string present; aac.ko likewise debug-free); VERDICT.md full path:line chain; static_check.sh runnable re-proof

PoC changes

n/a β€” absence proof, no seeded PoC to repair; static_check.sh + guest run.log authored fresh

Verified recommended fix

Keep userptr closed until sys/vm gains real MMU-notifier hooks; restore upstream's struct amdgpu_mn.kref (kref_get on the amdgpu_mn_get hash hit) per fix.diff; document the inert shim at amdgpu_mn.h's stub branch.

Verdict

The DragonFly MMU-notifier shim (sys/dev/drm/include/linux/mmu_notifier.h) is inert type scaffolding with no registration API and no VM-side callers, amdgpu_mn.c is absent from the amdgpu module SRCS (and cannot compile as-is: __mmu_notifier_register is undeclared anywhere and amdgpu_mn_ops initializes a nonexistent .invalidate_range_end member), CONFIG_MMU_NOTIFIER is never defined so amdgpu_mn.h compiles -ENODEV/NULL/no-op stubs, and amdgpu_gem_userptr_ioctl unconditionally returns -EINVAL with its body #if 0-ed. The feared 'GPU writes to revoked user memory' primitive therefore has no object to operate on as shipped (verified on the guest: /boot/kernel/amdgpu.ko contains 0 amdgpu_mn symbols and the 'not implemented' stub string). Latent: enabling the file without a real VM notifier implementation (the DF-4819 dead-shrinker pattern) plus the stripped-from-upstream struct amdgpu_mn.kref (amdgpu_mn_get returns hash hits unrefcounted) would yield missed-invalidation DMA and a p->mn UAF.