dce120 construct(): ipps/transforms NULL checks index slot [i] while allocation went to slot [j] β on Vega20 with harvested pipes the check trips on a never-written kzalloc slot and kills the entire display pool init (spurious false-failure)
Summary
With harvesting enabled the fill index j lags pipe index i; allocations go to [j] but NULL checks read [i] - slot [i] was never written (kzalloc pool), so the check ALWAYS sees NULL and construct() aborts display init on any Vega20 with a fused display pipe. The dangerous inverse (missed real alloc failure leaving NULL in pool) is provably impossible; unwind is clean. Hardware-conditional boot-time functional DoS, not attacker-triggerable; same mismatch still in upstream v6.6. Check-the-allocated-slot fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4448 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | β | 3.2 KB | β raw | |
| fix.diff | β | 683 B | view raw | |
| manifest.json | β | 795 B | view raw | |
| verdict.json | β | 2.1 KB | view raw |
DF-4448 β dce120 construct(): NULL checks index [i] while allocations go to [j]
File: sys/dev/drm/amd/display/dc/dce120/dce120_resource.c
Severity: Low Β· CWE: CWE-129 (improper validation of array index) / CWE-754
Root cause
With harvesting enabled (ASICREV_IS_VEGA20_P, dce120_resource.c:918), the
controller-creation loop skips fused-off pipes (:1022-1027), so the fill
index j lags the hardware pipe index i (j++ at :1075). Allocations
are correctly placed at slot [j] β but two of the NULL checks read slot
[i]:
pool->base.ipps[j] = dce120_ipp_create(ctx, i);thenif (pool->base.ipps[i] == NULL)(:1049-1050)pool->base.transforms[j] = dce120_transform_create(ctx, i);thenif (pool->base.transforms[i] == NULL)(:1057-1058)
When a pipe was skipped (j < i), slot [i] has never been written (the
pool is kzalloc'd in dce120_create_resource_pool, :1131-1132), so the
check reads a guaranteed-NULL slot:
- Missed-NULL direction β impossible (proved): slot
[i]can only be non-NULL if some earlier iteration wroteipps[j_m] == iwithj_m <= i_m < i_kβ contradiction. The bug therefore never leaves a NULL object in the pool. - False-failure direction β certain: the check trips on the never-written
slot and
goto controller_create_fail/goto res_create_fail(:1054,:1062) βconstruct()returns false,destruct()unwinds, anddce120_create_resource_pool()returns NULL (:1140-1141). On Vega20 parts with any fused display pipe, the whole display-computer pool fails to initialize at driver probe β amdgpu_dm/DC dead (display-console DoS), withDC: failed to create input pixel processor!/DC: failed to create transform!in dmesg.
All error labels collapse to the same destruct(pool) block
(:1116-1124), and destruct() is NULL-guarded for every slot β so the
spurious failure unwinds cleanly (no leak, no double-free; verified
destruct :515-584, dal_irq_service_destroy NULLs its target
irq_service.c:65-75).
Distinct from known DF-1628 (opps [j] NULL leaves the pool with a NULL OPP
because the goto is missing there entirely). Upstream v6.6 still carries
the same [i]/[j] mismatch (verified against torvalds/linux v6.6
dce120_resource.c) β long-standing, never fixed upstream.
Verification attempt
Guest (env: DragonFly 6.5-DEVELOPMENT, no AMD GPU, no /dev/dri) cannot
execute the DC construct path; the i/j invariant analysis above is the proof.
The behavior requires Vega20 hardware with fused pipes (mmCC_DC_PIPE_DIS
bits, read_pipe_fuses :901-907).
Recommended fix
Check the slot that was actually allocated:
- if (pool->base.ipps[i] == NULL) {
+ if (pool->base.ipps[j] == NULL) {
...
- if (pool->base.transforms[i] == NULL) {
+ if (pool->base.transforms[j] == NULL) {
See fix.diff.
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_testableTwo-line index fix authored against the read-only sys/ tree. Not buildable/runnable on the audit guest (no AMD GPU / no DC construct path).
findings/poc/DF-4448/fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
findings/poc/DF-4448/VERDICT.md (i/j invariant proof); dce120_resource.c:1049-1055 and :1057-1063 vs allocations at [j]; j++ at :1075; kzalloc pool at :1131
PoC changes
n/a
Verified recommended fix
NULL-check the slot that was allocated: ipps[j] and transforms[j] (fix.diff, 2-line change).
Verdict
In dce120 construct() with harvesting enabled (Vega20), ipps and transforms are allocated into slot [j] but NULL-checked at slot [i]; after a fused pipe is skipped (j<i) the check reads a never-written kzalloc slot, always NULL, so construct() spuriously fails and the whole DCE12 pool (and amdgpu_dm display init) dies at probe on any Vega20 with fused display pipes. Missed-NULL direction proved impossible (slot [i] cannot be non-NULL when j<i at check time), unwind is clean (all destruct paths NULL-guarded, dal_irq_service_destroy NULLs). Distinct from DF-1628 (opps missing goto). Upstream v6.6 still carries the same mismatch.
No comments yet.