DragonFly rbtree shim never maintains rb_root_cached.rb_leftmost β amdgpu_vm_fini's mapping-cleanup loop is dead code, leaking every mapping left in the interval tree at teardown
Summary
The DFly shim's rb_insert_color_cached discards its leftmost argument and rb_erase_cached never touches root->rb_leftmost; the initializer sets it NULL and nothing ever writes it. amdgpu_vm_fini's __DragonFly__ cleanup 'while (vm->va.rb_leftmost)' therefore never executes: any mapping still in vm->va at teardown leaks, including the dev_err('still active bo inside vm') case. Upstream uses rbtree_postorder_for_each_entry_safe - the DFly replacement is the divergence. Normally masked because GEM close removes mappings; kernel-internal/KFD teardown orderings can hit it. The same broken macro contract is a latent landmine for any other rb_leftmost user under this shim.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3865 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.0 KB | β raw | |
| VERDICT.md | β | 2.1 KB | β raw | |
| build.sh | β | 101 B | view raw | |
| run.sh | β | 112 B | view raw | |
| build.log | β | 35 B | view raw | |
| run.log | β | 137 B | view raw | |
| env.txt | β | 483 B | view raw | |
| fix.diff | β | 561 B | view raw | |
| manifest.json | β | 950 B | view raw | |
| verdict.json | β | 2.4 KB | view raw |
DF-3865 β DragonFly rbtree shim never maintains rb_root_cached.rb_leftmost; amdgpu_vm_fini()'s mapping-cleanup loop is dead code β permanent mapping leak
Files: sys/dev/drm/amd/amdgpu/amdgpu_vm.c (fini loop), sys/dev/drm/include/linux/rbtree.h (shim)
Root cause
The DFly compat shim implements the Linux leftmost-cached rbtree API without the cache:
- rbtree.h:100-101
rb_insert_color_cached(node, root, leftmost)β macro ignores theleftmostargument and only runs INSERT_COLOR; it never writesroot->rb_leftmost. - rbtree.h:102-103
rb_erase_cached(node, root)β only RB_REMOVE; never updatesroot->rb_leftmost. - rbtree.h:104
rb_first_cached= RB_MIN over the real tree (correct). - rbtree.h:175
LINUX_RB_ROOT_CACHEDinitializesrb_leftmost = NULL.
Consequently rb_root_cached.rb_leftmost is NULL for the lifetime of the
tree. amdgpu_vm_fini() (amdgpu_vm.c:3316-3334) replaced the upstream
postorder cleanup with:
while (vm->va.rb_leftmost) { /* amdgpu_vm.c:3325 β never true */
mapping = container_of((void *)vm->va.rb_leftmost, ...);
list_del(&mapping->list);
amdgpu_vm_it_remove(mapping, &vm->va);
kfree(mapping);
}
The loop body never executes: any amdgpu_bo_va_mapping still in the
interval tree at VM teardown is silently leaked (its 72-byte allocation
plus rb node), and the dev_err("still active bo inside vm") case
(amdgpu_vm.c:3313-3315) degenerates to leak-without-cleanup. This is a
DragonFly-specific divergence (upstream uses
rbtree_postorder_for_each_entry_safe, which works).
Normally the tree is empty at fini because the GEM close path removes all mappings; the leak manifests for mappings added by kernel-internal callers or any close-ordering that leaves stray mappings (KFD paths).
Fix
fix.diff swaps the dead loop for an rb_first()-based walk (RB_MIN works
under the shim). The shim macros should also grow real leftmost maintenance
for other future users β the same broken pattern is latent for anyone else
using rb_leftmost directly.
DF-3865 VERDICT β not_reproduced (hardware-gated); shim defect certain at source
Runtime
No AMD GPU on the audit guest β no VM is ever created β fini path never runs. status = not_reproduced, reproduced = 0. (The defect is provable purely from the shim macros; no execution needed.)
Source proof
#define rb_insert_color_cached(node, root, leftmost) \
linux_root_RB_INSERT_COLOR((struct linux_root *)(&(root)->rb_root), (node))
The leftmost parameter is discarded; no write to
root->rb_leftmost anywhere in the macro.
-
rbtree.h:102-103:
rb_erase_cachedlikewise only removes. -
rbtree.h:175:
#define LINUX_RB_ROOT_CACHED (struct rb_root_cached) { {NULL, }, NULL }βrb_leftmoststarts NULL and stays NULL forever. -
amdgpu_vm.c:3055
vm->va = LINUX_RB_ROOT_CACHED;β NULL leftmost. amdgpu_vm.c:3316-3334 (the DragonFly branch) loopswhile (vm->va.rb_leftmost)β dead; the kfree at 3333 is unreachable. Upstream v4.20 usesrbtree_postorder_for_each_entry_safehere (verified) β the DFly replacement is the divergence. -
Consequence: every mapping still present in
vm->vaat fini leaks (kfree skipped). Affected scenario: fini with non-empty tree (amdgpu_vm.c:3313 even prints "still active bo inside vm" for it).
Severity Low (bounded leak, rare path, no memory-safety violation), but a real, certain shim-contract bug β and a landmine for any other code that trusts rb_leftmost under this shim.
Fix validation
fix.diff replaces the dead loop with an rb_first()-based loop
(rbtree.h:97 rb_first = RB_MIN β functional under the shim);
git apply --check clean. Not exercisable on this guest: fix_status =
not_testable.
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_testablefix.diff replaces the loop with an rb_first()-based walk; git apply --check clean; not exercisable without AMD hardware
findings/poc/DF-3865/fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (macro-by-macro proof + fini loop); fix.diff swaps the loop for an rb_first()-based walk (rb_first=RB_MIN works under the shim)
PoC changes
authored from scratch (static-analysis finding; no runtime trigger possible)
Verified recommended fix
Replace the dead loop with while ((mapping = rb_entry_safe(rb_first(&vm->va.rb_root), struct amdgpu_bo_va_mapping, rb))) (fix.diff); separately fix the shim macros to maintain rb_leftmost
Verdict
Source-certain shim-contract bug, DFly-specific: rb_insert_color_cached (rbtree.h:100-101) discards its leftmost argument and rb_erase_cached (:102-103) never touches root->rb_leftmost, so rb_root_cached.rb_leftmost is NULL for the tree's lifetime (initializer rbtree.h:175). amdgpu_vm_fini's DFly-specific cleanup loop 'while (vm->va.rb_leftmost)' (amdgpu_vm.c:3325) therefore never executes - upstream's rbtree_postorder_for_each_entry_safe (which the #ifndef branch still uses) is functionally replaced by a no-op: every amdgpu_bo_va_mapping still in the tree at VM teardown leaks (kfree at :3333 unreachable), incl. the dev_err('still active bo inside vm') case at :3313. Leak-only (Low), normally masked because GEM close removes mappings first; kernel-internal/KFD-ordered teardowns can hit it. Guest has no AMD GPU: not reproducible at runtime; defect proven from the macros alone.
No comments yet.