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

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)

No comments yet.

PoC verification

Evidence pack

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

VERDICT.md
↓ download raw

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

  1. Source trace (certain). - dce112_create_resource_pool (dce112_resource.c:1343-1358) kzallocs the pool shell and, on construct() failure, executes only BREAK_TO_DEBUGGER(); return NULL; β€” no kfree(pool). - construct()'s failure arm (lines 1338-1341) calls destruct(pool) (lines 697-767); destruct() frees sub-objects exclusively β€” it never frees pool itself (no statement touches the shell allocation). - Therefore every failed construction leaks exactly one sizeof(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) and resource_construct failure (1323-1325, via dc_resource.c:236-268).
  2. Runtime: the guest has 0 AMD PCI devices (env.txt), amdgpu cannot bind, dce112_create_resource_pool never 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_testable family).

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

not_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)
↓ fix.diffper-fix-DF-4376

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.