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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4496 Β· 10 files
FileTypeDescriptionSize
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.

VERDICT.md
↓ download raw

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:

  1. 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 no kfree(pool). construct()'s error arm res_create_fail (:1050-1053) runs destruct(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 only kfree(dce110_pool) is in dce100_destroy_resource_pool (:838-845), reachable only through 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.
  2. Guest-side control-flow model (df4496.c, guest cc 8.3, full output in run.log/run.2.log): exact control-flow copy of :1056-1071 with allocation accounting β€” failure path ends with live_allocs=1 (the orphaned shell) while all children were reclaimed by the modeled destruct(); 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_testable
baseline no→ patch + rebuild →patched clean

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

Confirmed kernel references

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.