amdgpu_vm_init error paths dereference vm->root.base.bo before amdgpu_vm_bo_base_init assigns it β NULL-pointer panic on a render-node-reachable error path (unprivileged DoS)
Summary
vm->root.base.bo is published only at :3107, after reserve and clear_bo succeed. But error_unreserve calls amdgpu_bo_unreserve(vm->root.base.bo) == unreserve(NULL) -> ttm_bo_unreserve(&NULL->tbo), and error_free_root computes &vm->root.base.bo->shadow with base.bo==NULL which amdgpu_bo_unref then loads - both panic. amdgpu_vm_init runs on EVERY DRM open and clear_bo draws from the bounded shared IB pool plus TTM validation - exhaustible under load by unprivileged users -> local kernel panic. Upstream v4.20 identical. Use-the-local-root fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3864 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.3 KB | β raw | |
| VERDICT.md | β | 2.4 KB | β raw | |
| vm_init_error_race.c | β | 1.5 KB | view raw | |
| build.sh | β | 144 B | view raw | |
| run.sh | β | 162 B | view raw | |
| build.log | β | 106 B | view raw | |
| run.log | β | 223 B | view raw | |
| env.txt | β | 483 B | view raw | |
| fix.diff | β | 616 B | view raw | |
| manifest.json | β | 1.2 KB | view raw | |
| verdict.json | β | 2.5 KB | view raw |
DF-3864 β amdgpu_vm_init() error paths dereference vm->root.base.bo before it is assigned (NULL deref β kernel panic)
File: sys/dev/drm/amd/amdgpu/amdgpu_vm.c β amdgpu_vm_init() (3048-3148)
Root cause
vm->root.base.bo is only published by amdgpu_vm_bo_base_init() at
amdgpu_vm.c:3107 β i.e. AFTER amdgpu_bo_reserve() (3097) and
amdgpu_vm_clear_bo() (3101) succeed. But the error labels assume it is
already set:
r = amdgpu_bo_reserve(root, true);
if (r)
goto error_free_root; /* 3097-3099: base.bo == NULL */
r = amdgpu_vm_clear_bo(adev, vm, root, ...);
if (r)
goto error_unreserve; /* 3101-3105: base.bo == NULL */
amdgpu_vm_bo_base_init(&vm->root.base, vm, root); /* 3107 */
...
error_unreserve:
amdgpu_bo_unreserve(vm->root.base.bo); /* 3137: ttm_bo_unreserve(&NULL->tbo) */
error_free_root:
amdgpu_bo_unref(&vm->root.base.bo->shadow); /* 3140: &NULL->shadow */
amdgpu_bo_unref(&vm->root.base.bo);
- clear_bo failure β
amdgpu_bo_unreserve(NULL)βttm_bo_unreserve(&NULL->tbo)(amdgpu_object.h:165-168) β lock/atomic ops at a near-NULL address β panic. - reserve failure β
amdgpu_bo_unref(&vm->root.base.bo->shadow)β&NULL->shadow= small constant β*boload at low address β panic.
Reachability
amdgpu_vm_init() runs on EVERY DRM device open
(amdgpu_kms.c:990, amdgpu_driver_open_kms). amdgpu_vm_clear_bo() allocates
an IB from the shared, bounded IB pool (amdgpu_ib.c:279-287,
AMDGPU_IB_POOL_SIZE*64*1024) and does TTM validation β both fail under
memory/IB pressure, which an unprivileged render-node user can induce with
concurrent context creation + IB consumers. Unprivileged local DoS
(kernel panic) on amdgpu hardware.
The same defect exists in upstream Linux ~4.20 (verified against v4.20 sources β same labels, same ordering).
Trigger
vm_init_error_race.c β hammers the render node with concurrent
open()/close() (each open runs amdgpu_vm_init) while an IB-pool consumer
runs, forcing amdgpu_job_alloc_with_ib inside clear_bo to fail on some
open. On hardware with a DEBUG kernel the panic backtrace lands in
amdgpu_bo_unreserve/ttm_bo_unreserve called from amdgpu_vm_init.
Fix
fix.diff uses the local root pointer on both error labels (the variable
that IS valid on every path reaching them).
DF-3864 VERDICT β not_reproduced at runtime (hardware-gated); NULL-deref certain at source
Runtime
Guest has no AMD GPU: no /dev/dri node exists, amdgpu_driver_open_kms() /
amdgpu_vm_init() never execute. The trigger binary was compiled and run on
the guest: it exits reporting No such file or directory on
/dev/dri/renderD128 (see run.log). status = not_reproduced, reproduced = 0,
runtime impact none β source-level impact is an unprivileged local kernel
panic (DoS) on amdgpu hardware, Medium.
Source proof (no hardware needed)
Order of operations in amdgpu_vm_init (amdgpu_vm.c):
- 3093 amdgpu_bo_create(adev, &bp, &root) β local root only.
- 3097 amdgpu_bo_reserve(root, true); on failure β 3098
goto error_free_root β at this point vm->root.base.bo is still NULL
(caller kzalloc'd fpriv; base_init has not run).
- 3101 amdgpu_vm_clear_bo(...); on failure β 3104
goto error_unreserve β 3136-3137
amdgpu_bo_unreserve(vm->root.base.bo); = amdgpu_bo_unreserve(NULL) β
ttm_bo_unreserve(&bo->tbo) with bo == NULL (amdgpu_object.h:165-168)
β near-NULL address lock manipulation β page fault/panic.
- error_free_root: 3139-3140
amdgpu_bo_unref(&vm->root.base.bo->shadow); β computing
&vm->root.base.bo->shadow with base.bo == NULL yields the small constant
offsetof(amdgpu_bo, shadow); amdgpu_bo_unref then loads *bo from that
low address (amdgpu_object.c:817-825) β page fault/panic.
amdgpu_vm_clear_bo() failure is a normal errno path: it calls
amdgpu_job_alloc_with_ib (amdgpu_vm.c:855) which draws from the global
bounded IB pool (amdgpu_ib.c:279-287), plus ttm_bo_validate and
amdgpu_ttm_alloc_gart β all exhaustible under load by unprivileged
render-node users.
Call site: amdgpu_kms.c:990 β every open("/dev/dri/renderD128") by an
unprivileged user.
Upstream v4.20 carries the identical code (verified from upstream source); still a live defect in this tree.
Fix validation
fix.diff replaces both error-label derefs with the local root pointer;
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 switches the error labels to the local root pointer which is valid on every path reaching them; git apply --check clean; not exercisable without AMD hardware
findings/poc/DF-3864/fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (ordering proof: create 3093 -> reserve 3097 -> clear_bo 3101 -> base_init 3107 vs labels 3136/3139); build.log/run.log/env.txt
PoC changes
authored from scratch
Verified recommended fix
Use the local root pointer on both error labels (amdgpu_bo_unreserve(root); amdgpu_bo_unref(&root->shadow); amdgpu_bo_unref(&root)) - fix.diff
Verdict
Source-certain NULL dereference on a reachable error path: vm->root.base.bo is published only by amdgpu_vm_bo_base_init (amdgpu_vm.c:3107), but error_unreserve (:3136-3137) already does amdgpu_bo_unreserve(vm->root.base.bo) == unreserve(NULL) -> ttm_bo_unreserve(&NULL->tbo) (amdgpu_object.h:165-168) and error_free_root (:3139-3140) computes &vm->root.base.bo->shadow with base.bo==NULL (small constant address) which amdgpu_bo_unref then loads (amdgpu_object.c:817-825). Reached whenever amdgpu_bo_reserve (:3097) or amdgpu_vm_clear_bo (:3101) fails in amdgpu_vm_init, which runs on EVERY DRM open (amdgpu_kms.c:990); clear_bo draws an IB from the bounded shared pool (amdgpu_ib.c:279-287) and does TTM validation - exhaustible by unprivileged users under load -> unprivileged local kernel panic (DoS, Medium). Upstream v4.20 identical (verified). Guest: no AMD GPU, /dev/dri absent - trigger compiles (RC=0) and reports the gate (run.log): honest not_reproduced.
No comments yet.