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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3864 Β· 11 files
FileTypeDescriptionSize
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 β†’ *bo load 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).

VERDICT.md
↓ download raw

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

fix.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
↓ fix.diffper-fix-DF-3864

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.