dce100_create_resource_pool leaks the kzalloc'd dce110_resource_pool shell on construct() failure (DF-4318/4353/4376 family twin)
Summary
Returns NULL when construct() fails without kfree(pool); construct's fail arm runs destruct which frees only the sub-objects, never the ~2KB shell - the only shell kfree lives in destroy_resource_pool, reachable only for a successfully returned pool. One-shot leak per failed amdgpu bind on DCE10. Guest control-flow model verified (orphaned shell with children reclaimed). One-line kfree fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4496 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df4496.c | β | 4.4 KB | view raw | |
| build.sh | β | 86 B | view raw | |
| run.sh | β | 89 B | view raw | |
| build.log | β | 678 B | view raw | |
| run.log | β | 362 B | view raw | |
| run.2.log | β | 346 B | view raw | |
| env.txt | β | 266 B | view raw | |
| fix.diff | β | 265 B | view raw | |
| README.md | β | 2.5 KB | β raw | |
| VERDICT.md | β | 2.6 KB | β raw |
DF-4496 β dce100_create_resource_pool leaks the pool shell on construct() failure
File: sys/dev/drm/amd/display/dc/dce100/dce100_resource.c:1056-1071
Family: DF-4318 (dce80 Γ3) / DF-4353 (dce110) / DF-4376 (dce112) twin.
What
struct resource_pool *dce100_create_resource_pool(...)
{
struct dce110_resource_pool *pool =
kzalloc(sizeof(struct dce110_resource_pool), GFP_KERNEL); /* :1060 */
if (!pool)
return NULL;
if (construct(num_virtual_links, dc, pool))
return &pool->base;
BREAK_TO_DEBUGGER();
return NULL; /* :1070 β pool is never freed */
}
construct()'s error arm (res_create_fail, :1050-1053) runs
destruct(pool) (:678-745), which frees all successfully-created
sub-objects β but never the struct dce110_resource_pool shell itself
(dce100_destroy_resource_pool :838-845, the only kfree site, is reachable
only for a pool that was returned successfully). The caller
(dc_create_resource_pool, core/dc_resource.c:125-128) receives NULL and
cannot free an allocation it never saw.
struct dce110_resource_pool { struct resource_pool base; }
(dce110_resource.h:37-39) β the whole allocation is the shell (~2 KB of
pointer arrays). Triggered once per failed amdgpu bind on a DCE10/Tonga
ASIC (e.g. when any sub-object allocation fails at probe). One-shot leak,
no unprivileged trigger β impact is resource loss on an already-failing
probe, severity Low.
Reproduce (guest-side control-flow model β no AMD GPU required)
cc -O2 -Wall -o df4496 df4496.c # build.sh ./df4496 # run.sh
Expected decisive output (run.log, re-confirmed in run.2.log):
LEAK CONFIRMED (model): struct dce110_resource_pool is orphaned on construct() failure β dce100_resource.c:1066-1070 lacks kfree(pool).
with live_allocs=1 (the orphaned shell) after the failure path while all
children were reclaimed by destruct(), and live_allocs=0 after destroy
on the success path (proving the model's ownership accounting is faithful).
Why not a kernel PoC on the audit guest
env.txt: the guest has 0 AMD/ATI PCI devices; the amdgpu/dce100 path
never executes. Same situation as the accepted family twins
(DF-4318/DF-4353/DF-4376). The defect is source-certain (control flow is
six lines); runtime impact is hardware-gated.
Fix
fix.diff (git-apply clean against this tree): add kfree(pool); before
the BREAK_TO_DEBUGGER(); return NULL; failure return β identical to the
accepted DF-4318 fix.
DF-4496 VERDICT
Status: untested (kernel-level) β model-confirmed. Impact: none on this guest (hardware-gated). Confidence: certain (source).
Reproduced?
At the kernel level: no β and it cannot be on this guest. env.txt
records 0 AMD/ATI PCI devices; the dce100 pool-construction path only runs
during amdgpu bind on a DCE10 ASIC. Same verification ceiling as the
accepted family twins DF-4318 (dce80), DF-4353 (dce110), DF-4376 (dce112).
What was verified:
- Line-accurate source trace.
dce100_create_resource_pool(dce100_resource.c:1056-1071): kzalloc at :1060-1061, success return at :1066-1067, failure arm :1069-1070 returns NULL with nokfree(pool).construct()'s error armres_create_fail(:1050-1053) runsdestruct(pool)(:678-745), which frees only the sub-objects (opps/transforms/ipps/mis/tgs :682-701, engines/i2cs :703-714, stream_encs :716-719, clock sources :721-727, audios :729-732, dccg/abm/dmcu/irqs :734-744) β never the shell. The onlykfree(dce110_pool)is indce100_destroy_resource_pool(:838-845), reachable only through a pool that was returned successfully. The callerdc_create_resource_pool(core/dc_resource.c:125-128) receives NULL and cannot free an allocation it never saw. - Guest-side control-flow model (
df4496.c, guestcc 8.3, full output inrun.log/run.2.log): exact control-flow copy of :1056-1071 with allocation accounting β failure path ends withlive_allocs=1(the orphaned shell) while all children were reclaimed by the modeleddestruct(); success path drops to 0 after destroy, proving the ownership accounting is faithful. Two independent runs identical.
Exploit chain
None β one-shot, ~2 KB (sizeof(struct dce110_resource_pool),
dce110_resource.h:37-39) kernel-heap leak per failed amdgpu bind on a
DCE10/Tonga ASIC (any sub-object allocation failure at probe, e.g. OOM or
a failing resource_construct). Not reachable by unprivileged users;
severity Low, family precedent DF-4318/4353/4376.
Fix validation
fix.diff adds kfree(pool); before the failure return β identical to
the accepted DF-4318 fix shape. git apply --check passes against the
read-only tree. Fix not kernel-testable on this guest (no AMD device):
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 git-apply-clean against the read-only sys/ tree; one-line kfree(pool) before the failure return, identical shape to the accepted DF-4318 fix (dce80). Kernel-level validation impossible on this guest (0 AMD PCI devices); the control-flow model's patched variant trivially drains live_allocs to 0.
fix.diff; git apply --check output 'DF4496_APPLY_OK'; VERDICT.md 'Fix validation' section
Confirmed kernel references
- sys/dev/drm/amd/display/dc/dce100/dce100_resource.c:1060
- sys/dev/drm/amd/display/dc/dce100/dce100_resource.c:1066
- sys/dev/drm/amd/display/dc/dce100/dce100_resource.c:1070
- sys/dev/drm/amd/display/dc/dce100/dce100_resource.c:1050
- sys/dev/drm/amd/display/dc/dce100/dce100_resource.c:678
- sys/dev/drm/amd/display/dc/dce100/dce100_resource.c:838
- sys/dev/drm/amd/display/dc/core/dc_resource.c:125
Detail
Evidence (decisive lines)
run.log + run.2.log ('LEAK CONFIRMED (model) ... lacks kfree(pool)', failure-path live_allocs=1, success path 0 after destroy); VERDICT.md (line-accurate ownership trace); env.txt (0 AMD GPU on guest); fix.diff (git apply --check clean)
PoC changes
Authored df4496.c directly (no orchestrator seed): exact control-flow copy of dce100_create_resource_pool with destruct() modeled as children-only free, plus allocation counters; fixed one model compile error (construct() arity) before the successful run captured in run.log.
Verified recommended fix
Add kfree(pool) before the BREAK_TO_DEBUGGER()/return NULL failure arm of dce100_create_resource_pool (see fix.diff; identical to the accepted DF-4318 fix).
Verdict
Defect static-certain: dce100_create_resource_pool (dce100_resource.c:1056-1071) returns NULL on construct() failure without kfree(pool); construct's res_create_fail arm (:1050-1053) runs destruct(pool) (:678-745) which frees only the sub-objects, never the shell, so every failed amdgpu bind on a DCE10/Tonga ASIC leaks sizeof(struct dce110_resource_pool) (~2KB, dce110_resource.h:37-39). Twin of accepted DF-4318 (dce80)/DF-4353 (dce110)/DF-4376 (dce112). One-shot probe-time leak, no unprivileged trigger, severity Low. Kernel path untestable on the guest (pciconf: 0 AMD/ATI devices). Guest control-flow model (cc 8.3, two runs identical) reproduces the exact :1056-1071 flow with allocation accounting: failure path orphans exactly one allocation while destruct() reclaims all children; success path drains to zero after destroy.
No comments yet.