# 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);` then
  `if (pool->base.ipps[i] == NULL)` (`:1049-1050`)
* `pool->base.transforms[j] = dce120_transform_create(ctx, i);` then
  `if (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 wrote `ipps[j_m] == i` with `j_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, and
  `dce120_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), with
  `DC: 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:

```diff
-		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.
