Duplicate BO_HANDLES/FENCE/SYNCOBJ_OUT chunks overwrite parser state, permanently leaking bo_list + all BO refs, user-fence BO refs, and syncobj refs
Summary
The chunk loop never rejects a second stateful chunk: a fresh bo_list overwrites p->bo_list, the fence chunk overwrites uf_entry.tv.bo, syncobj-out overwrites the array. parser_fini releases each exactly once, so the first objects leak forever - an unbounded kernel-memory/GTT exhaustion primitive (N attacker-chosen BO refs per ioctl via repeated handles). Reject-duplicates fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4263 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.7 KB | β raw | |
| VERDICT.md | β | 3.7 KB | β raw | |
| dup_chunk_leak.c | β | 5.8 KB | view raw | |
| build.sh | β | 65 B | view raw | |
| run.sh | β | 89 B | view raw | |
| build.log | β | 782 B | view raw | |
| run.log | β | 869 B | view raw | |
| env.txt | β | 516 B | view raw | |
| kmod_baseline_build.log | β | 4.0 KB | view raw | |
| fix_build.log | β | 4.0 KB | view raw | |
| fix.diff | β | 839 B | view raw | |
| verdict.json | β | 3.5 KB | view raw |
DF-4263 β duplicate CS chunks (BO_HANDLES / FENCE / SYNCOBJ_OUT) leak kernel refs
File: sys/dev/drm/amd/amdgpu/amdgpu_cs.c (pass 2)
Severity: Medium (unprivileged local resource-exhaustion DoS) β CWE-401
Confidence: certain (static proof; leak arithmetic exact)
Build
cc -O2 -Wall -o dup_chunk_leak dup_chunk_leak.c
Run (as any user on amdgpu hardware)
./dup_chunk_leak
Creates one 1 MiB GTT BO, then submits 1000 CS ioctls whose chunk array
holds two AMDGPU_CHUNK_ID_BO_HANDLES chunks, each listing the same
handle 64 times. Every CS permanently leaks 64 amdgpu_bo references plus
one whole amdgpu_bo_list (kref 1, never dropped). Repeat (or raise
NENTRY/ROUNDS) until GTT/kernel-memory exhaustion.
Root cause (path:line)
- The chunk-dispatch loop
amdgpu_cs_parser_init()(amdgpu_cs.c:151-222) never rejects a duplicate stateful chunk: - BO_HANDLES (:200-211) β
amdgpu_cs_bo_handles_chunk()(:78-101) βamdgpu_bo_list_create(..., &p->bo_list)assigns a new list (kref 1, holds oneamdgpu_boref per entry βamdgpu_bo_list.c:83,103) overp->bo_listwithout dropping the previous one.amdgpu_cs_parser_fini()puts it exactly once (amdgpu_cs.c:807-808). First list + all its BO refs leak forever. - FENCE (:186-198) β
amdgpu_cs_user_fence_chunk()(:37-76) doesamdgpu_bo_ref()then overwritesp->uf_entry.tv.bo(:52); fini unrefs once (:815-819) β one BO ref leaked per extra FENCE chunk. - SYNCOBJ_OUT β
amdgpu_cs_process_syncobj_out_dep()(:1138-1163) overwritesp->post_dep_syncobjsand resetsnum_post_dep_syncobjs = 0(:1148-1151), leaking the previous array and everydrm_syncobjreference it holds (drm_syncobj_findtakes a ref βdrm_syncobj.c:83-94); fini releases only the last array (:797-799). - The ioctl-level guard that exists (
amdgpu_cs.c:587-589) only rejectsbo_list_handle+chunk, not chunk+chunk.
Reachability: AMDGPU_CS is DRM_AUTH|DRM_RENDER_ALLOW
(`amdgpu_kms.c:1206). All chunk types need only well-formed β₯min-size
kdata (FENCE β₯8B, BO_HANDLES β₯24B, SYNCOBJ_OUT β₯4B β enforced at
:188/:202 and by floor-division at :1145-1146).
Guest verification (this audit)
Same hardware gate as DF-4262 (no AMD GPU β no /dev/dri): trigger
builds (build.log), run exits at open (run.log); baseline + patched
in-tree compile of amdgpu_cs.c both rc=0 with -Werror
(kmod_baseline_build.log, fix_build.log).
Fix
fix.diff: reject duplicates β -EINVAL when p->uf_entry.tv.bo is
already set (FENCE), when p->bo_list is already set (BO_HANDLES), and
when p->post_dep_syncobjs is already allocated (SYNCOBJ_OUT).
DF-4263 evidence pack β verdict
Reproduced? NOT at runtime β hardware-gated (no AMD GPU on the audit guest, /dev/dri absent β env.txt/run.log). Defect: PROVEN at code level (certain): permanent, unbounded leak of kernel objects + BO references per CS ioctl, by an unprivileged render-node client.
Proof chain
- BO_HANDLES (the heavyweight variant):
a.
amdgpu_cs_parser_init()loop (amdgpu_cs.c:151-222) dispatches every BO_HANDLES chunk (:200-211) toamdgpu_cs_bo_handles_chunk()with no duplicate guard. b.amdgpu_cs_bo_handles_chunk()(:78-101) callsamdgpu_bo_list_create(p->adev, p->filp, info, data->bo_number, &p->bo_list)β the list is stored via the out-param, replacing any previous value ofp->bo_listwithout dropping it. c.amdgpu_bo_list_create()(amdgpu_bo_list.c:61-151) returns the list withkref_init= 1 (:83) and holds oneamdgpu_bo_ref()per entry (:103). The only put that ever runs for it isamdgpu_cs_parser_fini()(amdgpu_cs.c:807-808,amdgpu_bo_list_put) β which fires exactly once, for the LAST list. The first list object (kmalloc'd, sizeof(list) + n*sizeof(entry)) and every BO reference it holds leak permanently. d. Rate: bo_number is user-chosen (bounded only by the kvmalloc in amdgpu_bo_list.c:226); the same GEM handle may appear many times. Each CS ioctl therefore leaks an attacker-chosen number of BO references (each BO β₯ PAGE_SIZE of GTT/VRAM + kernel structs) plus one list. GEM handles are created once and reused across CS calls, so N CS ioctls Γ M entries = NΓM leaked refs with only M live BOs β unbounded exhaustion without allocating more GPU memory. - FENCE variant:
amdgpu_cs_user_fence_chunk()(:37-76) takesamdgpu_bo_ref()(:50) and assignsp->uf_entry.tv.bo = &bo->tbo(:52). A second FENCE chunk overwrites the pointer; fini unrefs once (:815-819). Net +1 BO ref per extra FENCE chunk. - SYNCOBJ_OUT variant:
amdgpu_cs_process_syncobj_out_dep()(:1138-1163) allocates a freshp->post_dep_syncobjsarray (:1148) and resetsp->num_post_dep_syncobjs = 0(:1151). A second SYNCOBJ_OUT chunk (the wholeamdgpu_cs_dependencies()loop :1170-1188 processes every chunk) orphans the first array together with everydrm_syncobjreference taken bydrm_syncobj_find()(drm_syncobj.c:83-94 β the reference must be released with drm_syncobj_put; fini only puts the last array, amdgpu_cs.c:797-799). - The only existing duplicate guard (amdgpu_cs.c:587-589) rejects
ioctl-arg
bo_list_handlecombined with a BO_HANDLES chunk β it does NOT protect chunk-vs-chunk duplication, which is what the PoC uses.
Guest verification performed
- trigger compiles (build.log); runs to the hardware gate (run.log)
- baseline amdgpu_cs.c in-tree compile rc=0 with -Werror (kmod_baseline_build.log)
- stacked fix (DF-4262+4263+4264+4265 diffs) applied via
patch -p1(hunks at 190/209/1155) and recompiled rc=0 with -Werror (fix_build.log) β fix compiles; runtime leak measurement needs HW.
Exploit chain: memory-exhaustion DoS only (no memory-safety violation: the leaked objects stay valid, they are simply never freed).
Recommended fix: reject duplicate stateful chunks (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_testablefix.diff (three duplicate-rejection guards) applied on the guest stacked with the other amdgpu_cs.c fixes (hunks @190/@209/@1155) and compiled in-tree with -Werror rc=0 (fix_build.log). Runtime leak-measurement before/after impossible (no AMD GPU); compile-only validation.
fix_build.log rc=0; guest patch output 'Hunk #1 succeeded at 190 / #2 at 209 / #3 at 1155'.
Confirmed kernel references
- sys/dev/drm/amd/amdgpu/amdgpu_cs.c:151
- sys/dev/drm/amd/amdgpu/amdgpu_cs.c:200
- sys/dev/drm/amd/amdgpu/amdgpu_cs.c:78
- sys/dev/drm/amd/amdgpu/amdgpu_cs.c:88
- sys/dev/drm/amd/amdgpu/amdgpu_cs.c:52
- sys/dev/drm/amd/amdgpu/amdgpu_cs.c:1148
- sys/dev/drm/amd/amdgpu/amdgpu_cs.c:797
- sys/dev/drm/amd/amdgpu/amdgpu_cs.c:807
- sys/dev/drm/amd/amdgpu/amdgpu_cs.c:815
- sys/dev/drm/amd/amdgpu/amdgpu_bo_list.c:83
- sys/dev/drm/amd/amdgpu/amdgpu_bo_list.c:103
- sys/dev/drm/drm_syncobj.c:83
Detail
Exploit chain
Resource-exhaustion DoS only - leaked objects remain valid, are simply never freed; no memory-safety violation.
Evidence (decisive lines)
dup_chunk_leak.c (two BO_HANDLES chunks x 64 duplicate handles x 1000 rounds); run.log (hardware gate); kmod_baseline_build.log + fix_build.log (in-tree -Werror compile before/after the duplicate-rejection guards).
PoC changes
Trigger written from scratch: embeds the 24-byte drm_amdgpu_bo_list_in as chunk kdata (>=6 dwords passes the :202 size check), entries supplied via bo_info_ptr user pointer, plus one minimal valid IB chunk so the CS survives ib_fill.
Verified recommended fix
Reject duplicate stateful chunks: -EINVAL when p->uf_entry.tv.bo (FENCE), p->bo_list (BO_HANDLES) or p->post_dep_syncobjs (SYNCOBJ_OUT) is already set.
Verdict
Code-certain unprivileged kernel reference leak family: duplicate AMDGPU_CHUNK_ID_BO_HANDLES / _FENCE / _SYNCOBJ_OUT chunks in one CS ioctl overwrite parser state (p->bo_list at amdgpu_cs.c:88-89; p->uf_entry.tv.bo at :52; p->post_dep_syncobjs at :1148-1151) without dropping the previous objects; amdgpu_cs_parser_fini() releases each exactly once (:797-799, :807-808, :815-819), so the first bo_list plus every amdgpu_bo ref it holds (amdgpu_bo_list.c:83,103), the first user-fence bo ref, and the first syncobj array + all its drm_syncobj refs leak permanently. Each CS can leak an attacker-chosen number of BO refs (same GEM handle repeated in the entries array), giving unbounded kernel-memory/GTT exhaustion by an unprivileged render-node client. Runtime reproduction hardware-gated on this guest (no AMD GPU, /dev/dri absent), hence untested; the leak is fully proven at code level.
No comments yet.