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

dce110_create_resource_pool leaks the kzalloc'd pool on construct() failure (DF-4318 twin) and dc->fbc_compressor is never destroyed anywhere (dce110_compressor_destroy has zero callers)

Summary

On construct() failure the function returns NULL without kfree(pool) - construct's fail arm already ran destruct(pool) which frees only sub-objects, and the caller never received the pointer. Plus the fbc_compressor is created but its destroy function has zero callers tree-wide. Guest allocation-accounting model: live_allocs=2 baseline vs 0 fixed. kfree + compressor-destroy fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4353 Β· 9 files
FileTypeDescriptionSize
df4353.c β€” 5.2 KB view raw
build.sh β€” 86 B view raw
run.sh β€” 89 B view raw
build.log β€” 496 B view raw
run.log β€” 614 B view raw
env.txt β€” 966 B view raw
fix.diff β€” 712 B view raw
VERDICT.md β€” 3.1 KB ↓ raw
README.md β€” 1.3 KB ↓ raw

DF-4353 β€” dce110_create_resource_pool leaks the pool on construct() failure; dc->fbc_compressor is never destroyed (DF-4318/DF-4309 family)

Build / Run (guest model)

cc -O2 -Wall -o df4353 df4353.c
./df4353

Expected: LEAK CONFIRMED (model) β€” after a failed construct the kzalloc'd struct dce110_resource_pool and the fbc compressor remain live (2 orphaned allocations); the fixed failure arm leaves 0.

Root cause

  1. dce110_resource.c:1398-1414 β€” on construct() failure the function returns NULL without kfree(pool). construct's failure arm already ran destruct(pool) (:1394), which frees only the sub-objects; the caller (core/dc.c dc_construct) never received the pointer. The DF- 4318 twin at the dce110 instance (pool embeds the whole resource_pool base, core_types.h:120-198 β€” ~KB per failed bind).
  2. Sibling: dc->fbc_compressor = dce110_compressor_create(ctx) (:1372-1373) β€” dce110_compressor_destroy() (dce110_compressor.c:439) has zero callers in the tree; the compressor outlives teardown on every path (DF-4309 flavor).

Fix

fix.diff (git apply --check clean): kfree(pool) in the failure arm (destruct already ran inside construct) + destroy the compressor in construct's res_create_fail arm.

VERDICT.md
↓ download raw

DF-4353 VERDICT

Status: untested (kernel path) β€” code defect certain; guest model demonstrates the orphaned allocations and the fix accounting.

Root cause (line-accurate)

  1. dce110_create_resource_pool (dce110_resource.c:1398-1414): 1403: pool = kzalloc(sizeof(struct dce110_resource_pool)); 1409: if (construct(num_virtual_links, dc, pool, asic_id)) 1410: return &pool->base; 1412: BREAK_TO_DEBUGGER(); 1413: return NULL; <-- no kfree(pool) construct()'s res_create_fail arm (:1393-1395) runs destruct(pool) which frees only the sub-objects (opps/transforms/ipps/mis/tgs, engines/i2cs, stream encs, clock sources, audios, abm, dmcu, dccg, irqs β€” :703-773). The pool allocation itself is orphaned; the caller (core/dc.c dc_construct β†’ dce110_create_resource_pool) never received the pointer and can only destruct(dc). This is the exact DF-4318 error-path shape at the DCE11 (Carrizo/Stoney) instance; the pool embeds the full struct resource_pool base (core_types.h:120-198, incl. 12-pointer stream_enc[] and the engine arrays) β€” ~KB per failed pool build. Failure triggers include any sub-object create failure (OOM), irq-service create failure (:1307-1310), resource_construct failure (:1378-1380), and a VBIOS reporting no external DP clock source (:1253-1257); rebind/hotplug loops amplify.

  2. Sibling asymmetry (DF-4309 flavor): construct() also does if (dc->config.fbc_support) dc->fbc_compressor = dce110_compressor_create(ctx); (:1372-1373). grep -rn dce110_compressor_destroy β€” the destroy function (dce110_compressor.c:439) has zero callers anywhere in the tree: the compressor is allocated on dc and never freed on any teardown path.

Verification

  • Kernel path untestable on the audit guest: no AMD GPU (pciconf as root shows i440FX/PIIX + virtio only; env.txt); amdgpu.ko present but unbindable.
  • Guest model df4353.c (cc 8.3; build.log/run.log) reproduces the allocation accounting of the failure arm (DF-4318's methodology): baseline failure leaves live_allocs=2 (pool + compressor) β€” "LEAK CONFIRMED (model)"; the fixed arm leaves 0 β€” "FIX MODEL CLEAN (model)".

Fix validation

Kernel-level validation impossible on this guest. fix.diff (git apply --check clean) adds kfree(pool) after the existing BREAK_TO_DEBUGGER in the failure arm (destruct already ran inside construct β€” no double free) and destroys dc->fbc_compressor in construct's res_create_fail arm. Note: the successful-path lifecycle gap (dc_destroy never freeing fbc_compressor) lives in core/dc.c and is not reachable from this file; the local fix closes the error arm, the model shows both allocations accounted.

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

Kernel fix validation impossible on this guest (no DCE11 hardware). fix.diff is git-apply-clean against sys/; destruct() already ran inside construct's failure arm so the added kfree cannot double-free, and the model shows the patched failure arm ends at 0 live allocations.

fix.diff; df4353.c fixed arm ('FIX MODEL CLEAN (model)')
↓ fix.diffper-fix-DF-4353

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log ('LEAK CONFIRMED (model)', live_allocs=2 baseline vs 0 fixed); VERDICT.md (line-accurate accounting incl. zero-caller grep for dce110_compressor_destroy); env.txt (no AMD GPU); fix.diff (git apply --check clean)

PoC changes

No seed PoC (pass-2 checklist hint only). Authored df4353.c: control-flow model of dce110_create_resource_pool/construct/destruct with explicit allocation accounting, methodology identical to DF-4318's accepted model.

Verified recommended fix

kfree(pool) in dce110_create_resource_pool's failure arm and dce110_compressor_destroy(dc->fbc_compressor) in construct's res_create_fail arm (see fix.diff).

Verdict

Code defect certain: dce110_create_resource_pool (dce110_resource.c:1398-1414) kzallocs the pool and on construct() failure returns NULL without kfree - construct's res_create_fail arm already ran destruct(pool) (:1394) which frees only sub-objects, so the ~KB struct dce110_resource_pool (embeds the full resource_pool base, core_types.h:120-198) leaks per failed amdgpu pool build on Carrizo/Stoney (any sub-create OOM, irq-service failure :1307-1310, resource_construct failure :1378-1380, VBIOS without external DP clock :1253-1257; rebind loops amplify). DF-4318 twin at the dce110 instance. Sibling asymmetry (DF-4309 flavor): dc->fbc_compressor created at :1372-1373 is never destroyed - dce110_compressor_destroy (dce110_compressor.c:439) has zero callers tree-wide. Kernel-path reproduction impossible: the audit guest has no AMD GPU (pciconf: i440FX+virtio only). Guest-built allocation-accounting model (cc 8.3) confirms the failure arm orphans both allocations (run.log, 'LEAK CONFIRMED (model)') and the fix drives it to zero. Hardening-class leak, no privilege boundary crossed.