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)
PoC verification
Evidence pack
findings/poc/DF-4838 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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:
CONFIG_MMU_NOTIFIERis never defined (sys/dev/drm/kconfig.hhas no such define), sosys/dev/drm/amd/amdgpu/amdgpu_mn.h:37always takes the#elsestub branch:amdgpu_mn_register()β-ENODEV,amdgpu_mn_get()βNULL,amdgpu_mn_lock/unlock()β no-ops (amdgpu_mn.h:45-56).sys/dev/drm/amd/amdgpu/Makefiledoes not listamdgpu_mn.cinSRCS(contrastsys/dev/drm/radeon/Makefile:107whereradeon_mn.cis 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 insys/, andamdgpu_mn_ops(amdgpu_mn.c:356-367) initializes.invalidate_range_end, a member that does not exist in the shim'sstruct mmu_notifier_ops(sys/dev/drm/include/linux/mmu_notifier.h:40-46), and assigns 5-argint-returning handlers to 4-argvoidslots.amdgpu_gem_userptr_ioctl()(amdgpu_gem.c:285-290) unconditionallykprintf(...); 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
0amdgpu_mn symbols in the shipped module; kconfig grep count0; Makefile grep no match; stub string present in the binary.
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)
-
The shim is pure type scaffolding.
sys/dev/drm/include/linux/mmu_notifier.h:36-46definesstruct mmu_notifieras a bare ops pointer andstruct mmu_notifier_opswithrelease+invalidate_range_startonly (4-arg, void). It declares no registration function, holds no notifier list, andrg mmu_notifier sys/vm sys/platform sys/cpu sys/sysreturns 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). -
__mmu_notifier_register/mmu_notifier_unregister_no_releasedo not exist. Called atamdgpu_mn.c:416andamdgpu_mn.c:132;rgover all ofsys/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; andamdgpu_mn_opsatamdgpu_mn.c:356-367initializes.invalidate_range_end, a member absent from the shim struct, and assigns 5-argint-returning handlers (amdgpu_mn.c:248-252,296-300) into 4-argvoidslots β a hard type error). This is why: -
The build excludes it.
sys/dev/drm/amd/amdgpu/Makefile(read in full) never listsamdgpu_mn.c;sys/dev/drm/radeon/Makefile:105-107comments out bothradeon_mn.cand# CONFIG_MMU_NOTIFIER. Verified on the shipped guest:grep -c amdgpu_mnin the Makefile β no match;nm /boot/kernel/amdgpu.ko | grep -c amdgpu_mnβ 0 (run.log). -
CONFIG_MMU_NOTIFIERis undefined, soamdgpu_mn.h:37-57compiles 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). -
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 0body) would DMA into pages freed by munmap/madvise/exit β a physical-memory write primitive. - The DFly copy stripped upstream's
struct amdgpu_mn.kref(comparestruct amdgpu_mn, amdgpu_mn.c:72-90 β no refcount member;amdgpu_mn_get, amdgpu_mn.c:397-399 returns the hash hit with nokref_get). Upstream Linux 4.19 (this file's vintage) has both. Lifetime hangs solely on the mmreleasecallback schedulingamdgpu_mn_destroy(amdgpu_mn.c:144-151, 112-134). A drm_file shared across processes (SCM_RIGHTS/fork) cachesp->mn(amdgpu_cs.c:605); mm teardown of the creating process freesamnwhile the other process can still reachamdgpu_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_testableNo 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
Confirmed kernel references
- sys/dev/drm/include/linux/mmu_notifier.h:36
- sys/dev/drm/amd/amdgpu/amdgpu_mn.c:416
- sys/dev/drm/amd/amdgpu/amdgpu_mn.c:132
- sys/dev/drm/amd/amdgpu/amdgpu_mn.c:356
- sys/dev/drm/amd/amdgpu/amdgpu_mn.c:72
- sys/dev/drm/amd/amdgpu/amdgpu_mn.c:397
- sys/dev/drm/amd/amdgpu/amdgpu_mn.h:37
- sys/dev/drm/amd/amdgpu/amdgpu_mn.h:52
- sys/dev/drm/amd/amdgpu/amdgpu_gem.c:285
- sys/dev/drm/amd/amdgpu/amdgpu_cs.c:1222
- sys/dev/drm/radeon/Makefile:107
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.
No comments yet.