dce112_create_resource_pool leaks the kzalloc'd pool shell on construct() failure (DF-4318/4353 twin)
Summary
create_resource_pool returns NULL on construct() failure without kfree(pool); the failure arm runs destruct(pool) which frees only the sub-objects - never the ~1.5KB shell. Every failed amdgpu bind on a Polaris ASIC leaks it; a privileged rebind loop converts it into unbounded exhaustion. Same defect exists in upstream Linux today and in the accepted twins DF-4318/4353. One-line kfree fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4376 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.4 KB | β raw | |
| VERDICT.md | β | 2.7 KB | β raw | |
| build.sh | β | 141 B | view raw | |
| run.sh | β | 222 B | view raw | |
| env.txt | β | 793 B | view raw | |
| fix.diff | β | 525 B | view raw | |
| manifest.json | β | 1.1 KB | view raw | |
| verdict.json | β | 3.3 KB | view raw |
DF-4376 β dce112_create_resource_pool leaks the pool allocation on construct() failure (DF-4318/DF-4353 twin)
What
sys/dev/drm/amd/display/dc/dce112/dce112_resource.c:1343-1358:
struct resource_pool *dce112_create_resource_pool(
uint8_t num_virtual_links,
struct dc *dc)
{
struct dce110_resource_pool *pool =
kzalloc(sizeof(struct dce110_resource_pool), GFP_KERNEL);
if (!pool)
return NULL;
if (construct(num_virtual_links, dc, pool))
return &pool->base;
BREAK_TO_DEBUGGER();
return NULL; /* <-- pool never freed */
}
On failure, construct()'s res_create_fail arm (lines 1338-1340) runs
destruct(pool) (lines 697-767), which frees only the sub-objects (opps,
transforms, ipps, mis, tgs, engines, i2cs, stream_encs, clock_sources, audios,
abm, dmcu, dccg, irqs) β never the pool shell itself. The caller (DC core)
received NULL and cannot free what it never got. sizeof(struct
dce110_resource_pool) β 1.5 KB (resource_pool: ~100 pointers + counters +
func tables) leaks per failed attempt.
Trigger path
construct() fails when any of its ~40 sub-allocations fails (GFP_KERNEL
kzallocs under memory pressure), when dal_irq_service_dce110_create fails
(line 1253-1255), or when resource_construct() fails (audio/virtual stream
encoder creation, dc_resource.c:236-268). Each amdgpu bind attempt on a
DCE112 (Polaris 10/11) ASIC that hits a failure leaks one pool. Driver
(re)bind is a root operation, so the leak accrues under root-driven reprobe
loops or repeated failed binds at boot with persistent memory pressure.
Reproduce
Kernel reproduction requires an AMD Polaris GPU: the verification guest has
none (env.txt: pciconf -l shows only QEMU/virtio devices, 0 AMD devices),
so dce112_create_resource_pool cannot execute there. The defect is
static-certain: the failure arm provably returns without kfree, and destruct()
provably never frees the shell (it only touches pool->base.* members). This
is the exact twin of accepted DF-4318 (dce80/81/83) and DF-4353 (dce110);
upstream Linux carries the same leak today.
No buildable trigger exists for this guest; build.sh/run.sh record the
env-probe that establishes non-testability.
Fix
fix.diff β add kfree(pool) in the failure arm (destruct already ran, so
the shell free is the only missing piece; cannot double-free).
git apply --check clean against sys/.
DF-4376 VERDICT
Status: inconclusive (defect static-certain; kernel manifestation requires AMD DCE112 hardware absent from the verification guest β twin DF-4318/DF-4353 precedent).
Reproduced? Why / why not
- Source trace (certain).
-
dce112_create_resource_pool(dce112_resource.c:1343-1358) kzallocs the pool shell and, onconstruct()failure, executes onlyBREAK_TO_DEBUGGER(); return NULL;β nokfree(pool). -construct()'s failure arm (lines 1338-1341) callsdestruct(pool)(lines 697-767);destruct()frees sub-objects exclusively β it never freespoolitself (no statement touches the shell allocation). - Therefore every failed construction leaks exactly onesizeof(struct dce110_resource_pool)allocation (~1.5 KB: the resource_pool has mis/ipps/transforms/opps/tgs[6], stream_enc[12], engines/hw_i2cs/sw_i2cs[6], clock_sources[7], audios[6] plus counters and function pointers). - Failure arms reachable: sub-create OOM (clock sources 1168-1211, dccg 1213-1221, dmcu 1223-1231, abm 1233-1241, irqs 1250-1256, tg/mi/ipp/xfm/ opp loop 1258-1303, aux/i2c loop 1305-1321) andresource_constructfailure (1323-1325, via dc_resource.c:236-268). - Runtime: the guest has 0 AMD PCI devices (env.txt), amdgpu cannot bind,
dce112_create_resource_poolnever executes. No unprivileged trigger path exists; driver bind is privileged. Hence no guest run is possible βinconclusive, identical treatment to twins DF-4318/DF-4353 (untested/not_testablefamily).
Impact
Kernel heap leak, ~1.5 KB per failed amdgpu bind on Polaris; a root-driven rebind loop under induced memory pressure converts it into unbounded kernel memory exhaustion β eventual allocation failures/panic. No info leak, no corruption. Severity Low (privileged trigger + memory pressure), bucket base:dev β consistent with the twins.
Fix validation
fix.diff adds kfree(pool) in the failure arm. git apply --check passes
against sys/ (not applied). Cannot build/run an amdgpu kernel target on this
guest (no AMD hardware), so fix_status: not_testable; the patch is one
statement, and double-free is impossible because destruct(pool) does not
free the shell and nulls the sub-pointers it does free.
Guest hygiene
Guest left up; only /root scratch files. guest_dirty: 0.
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_testablenot_applicable->not_testable: no DCE112 hardware on the verification guest for a baseline or patched run. fix.diff (one added kfree(pool)) is git-apply-clean against sys/ (git apply --check OK); double-free impossible because construct's failure arm runs destruct(pool) exactly once and destruct never frees the shell nor is reachable again; identical fix shape as twin DF-4318/DF-4353 packs.
git apply --check findings/poc/DF-4376/fix.diff => OK; sys/ left unmodified (git status --porcelain sys/ empty)
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (static trace with all construct() failure arms listed: clock sources 1168-1211, dccg/dmcu/abm/irqs 1213-1256, per-pipe loop 1258-1303, aux/i2c loop 1305-1321, resource_construct 1323-1325); env.txt (guest uname 6.5-DEVELOPMENT, full pciconf -l with zero 0x1002 devices, amdgpu KLD buildable in tree but unattachable); fix.diff git-apply-clean
PoC changes
n/a (no seed; no runnable trigger exists without AMD hardware β pack documents the static proof and the environment probe)
Verified recommended fix
Add kfree(pool) to the failure arm of dce112_create_resource_pool (see fix.diff) β destruct(pool) already ran inside construct(), so the shell free completes the cleanup
Verdict
Defect static-certain: dce112_create_resource_pool (dce112_resource.c:1343-1358) returns NULL on construct() failure without kfree(pool); construct's res_create_fail arm (1338-1341) runs destruct(pool) (697-767) which frees only the sub-objects, never the shell, so every failed amdgpu bind on a DCE112/Polaris ASIC leaks sizeof(struct dce110_resource_pool) (~1.5KB). Twin of accepted DF-4318 (dce80/81/83) and DF-4353 (dce110); the same leak exists in upstream Linux today. Runtime reproduction impossible on the verification guest: AMD_PCI_COUNT=0 (env.txt), amdgpu never binds, and no unprivileged trigger path exists (driver bind is privileged) β hence inconclusive rather than not_reproduced, mirroring the twins' classification.
No comments yet.