# DF-4995 VERDICT — dcn10 irq table VLINE0 gap (NULL `.funcs`)

## Bottom line

**Reproduced at logic level** with a verbatim userland replica of the DCN10 irq
table (`irq_service_dcn10.c:110-331` — `hpd_ack`, the funcs tables, the entry
macros, the table body — plus `to_dal_irq_source_dcn10.c:41-108` with the real
`DCN_1_0__SRCID/CTXID` constants from `amd/include/ivsrcid/irqsrcs_dcn_1_0.h`),
the enum (`irq_types.h:40-148`), and the generic dispatch
(`irq_service.c:77-158`), run on the audit guest (DragonFly 6.5-DEVELOPMENT,
cc 8.3).

The shared enum runs to `DAL_IRQ_SOURCES_NUMBER=90`, with
`DC_IRQ_SOURCE_DC1_VLINE0..DC6_VLINE0` = indices 84..89
(irq_types.h:140-147). The dcn10 table body's last designator is
`vblank_int_entry(5)` == `DC_IRQ_SOURCE_VBLANK6` = 83
(irq_service_dcn10.c:325-331): **entries 84..89 stay zero-initialized, i.e.
`.funcs == NULL`**. This is the exact DF-4932 (dce110) family shape, now on
the DCN10 (Raven) table — the family tail.

`find_irq_source_info` (irq_service.c:82) bounds-checks only the index, so for
source 84..89 it happily returns `&info[84..89]`, and
`dal_irq_service_ack` (irq_service.c:152, invoked *first* by
`dal_irq_service_set` at irq_service.c:116) dereferences `info->funcs->ack`
with `funcs==NULL` — a read at `NULL + offsetof(.ack)` ⇒ page fault ⇒ hard
panic. `dal_irq_service_set` (irq_service.c:118) has the same unguarded
`info->funcs->set`.

Replica result (deterministic, run.log + run.2.log):
- baseline: `table[84..89] (.funcs == NULL)`, controls pass,
  `dal_irq_service_set(DC_IRQ_SOURCE_DC1_VLINE0=84)` → **Segmentation fault,
  exit 139** both runs;
- fixed (`-DFIX_DF4995`: six vline0 dummy entries + `!info->funcs` guards):
  `0 NULL-funcs entries`, dummy handlers log-and-return, **exit 0**.

Controls exercised (all pass, proving the dispatch itself is faithful):
- OOB index 90 rejected by `find_irq_source_info` (the index bound is *not* the bug);
- `PFLIP1` set goes down the dcn10 generic path (`.set==NULL`,
  `pflip_irq_info_funcs`) and programs HUBPREQ0_DCSURF_SURFACE_FLIP_INTERRUPT
  (0x2200 → 0x5 = ack_value 0x4 | enable_value 0x1 — the set-then-ack RMW shape);
- `HPD1` ack runs the dcn10-local `hpd_ack` replica verbatim: with
  `DC_HPD_SENSE_DELAYED=1` the polarity field is cleared
  0xffffffff → 0xfffffffb — `hpd_ack` itself is correct, the file's only live
  bespoke function;
- `to_dal_irq_source_dcn10` walk (src_ids {0x3C..0x41, 0x4F..0x54, 9} ×
  ext_ids {0..11, 0xdead}): 168 combos map to covered sources, **0 combos map
  to 84..89** — the IH-arrival path (amdgpu_dm_irq.c:477-487 →
  `dc_interrupt_to_irq_source` → `to_dal_irq_source`) can never synthesize a
  VLINE0 source on DCN10.

## Reachability — latent, traced honestly

Every in-tree caller of `dc_interrupt_set`/`dc_interrupt_ack`
(dc.c:1682-1693) was traced:

| Caller | Sources passed | Bounded by |
|---|---|---|
| `dm_irq_state` (amdgpu_dm_irq.c:536-559) | `dal_irq_type + acrtc->otg_inst`, dal_irq_type ∈ {PFLIP1=26, VBLANK1=78} (VUPDATE never used) | `otg_inst` ∈ [0,5] (tg instance, 6 OTGs) ⇒ ≤ PFLIP6/VBLANK6, all covered |
| `dm_set_vblank` (amdgpu_dm.c:3007-3015) | `IRQ_TYPE_VBLANK + acrtc->otg_inst` | same ⇒ ≤ VBLANK6=83 |
| `amdgpu_dm_hpd_init/fini` (amdgpu_dm_irq.c:627-686) | `dc_link->irq_source_hpd/_rx` | HPD1+link_index ≤ HPD6RX, all covered |
| suspend/early-resume/late-resume (amdgpu_dm_irq.c:350-416) | loops HPD1..HPD6RX | covered |
| `amdgpu_dm_irq_handler` (amdgpu_dm_irq.c:477-487) | `to_dal_irq_source(...)` result | covered ∪ {INVALID} (proven by the walk control) |

Nothing passes 84..89 into a dcn10 service today ⇒ **no unprivileged trigger
exists**; the panic is one future caller away (a DM change using VLINE0 for
freesync/PSR on DCN, a DCN port of a DCE path, or the
`DAL_VALID_IRQ_SRC_NUM` off-by-one at irq_types.h:158 which admits
`src == DAL_IRQ_SOURCES_NUMBER` — already noted under DF-4932). The guest has
no AMD GPU (0 PCI 0x1002 devices; amdgpu not loaded), so the kernel-side
trigger is hardware-gated anyway. No write primitive, no leak; the honest
ceiling is a kernel panic (local DoS) if a trigger ever appears — identical
classification to DF-4932/DF-4933 on dce110 and DF-4713 on the dispatch hole.

Severity Low (latent), confidence certain (the table gap and the NULL-deref
shape are compile-time facts; the fault is deterministic in the replica).

## Why this is a new finding, not a duplicate

DF-4932 filed the *dce110* table's identical gap; this file
(irq_service_dcn10.c) has its own table, and DF-4932's fix.diff covered only
the dce110 table (its irq_service.c guards protect all asics, but the tree is
read-only — nothing is applied). Each asic table must be made total over the
shared enum; dcn10 is the last table in the family after dce80/dce110/dce120
were checked (dce120/dce80 share the shape — noted to maintainers under
DF-4932; dcn10 is filed here).

## Negative results (classes hunted and killed on this file)

- **DF-4933 family (vblank_set underflow):** does NOT extend to dcn10 —
  `vblank_irq_info_funcs = {.set=NULL,.ack=NULL}` (irq_service_dcn10.c:152-155)
  and `vupdate/vblank_int_entry` set no `.src_id`; there is no
  `dcn10_vblank_set` and no pipe-offset arithmetic anywhere in the file; the
  generic set path uses only compile-time constants.
- **pflip HUBPREQ0..3 only:** PFLIP5/PFLIP6/PFLIP_UNDERLAY0 are explicit
  `dummy_irq_entry()` (irq_service_dcn10.c:277-279) — covered, and
  `dal_irq_service_dummy_set/ack` are true no-ops (dce110:177-192, log+return
  false), so dummy entries can't MMIO stale reg 0.
- **to_dal out-of-range:** switch with `default: DC_IRQ_SOURCE_INVALID` at
  both levels (irq_service_dcn10.c:100-106); INVALID is dummy-covered.
- **`hpd_ack` regs:** `status_reg`/`enable_reg` set by every `hpd_int_entry`/
  `hpd_rx_int_entry` designator; no runtime indexing.
- **construct/create (irq_service_dcn10.c:337-357):** `kzalloc` NULL-checked;
  `dal_irq_service_construct` is void-returning in this tree (irq_service.c:53-63)
  — if `init_data->ctx` were NULL the service would carry ctx==NULL to
  `dm_read_reg`, but the only caller (`dc_construct`) always passes a live
  `dc->ctx`; upstream-identical shape, no attacker input reaches it. Info-level
  at most; noted, not filed.
- **"process" function:** none exists in this DC version (irq_service.c has no
  dispatch-time process); the IH-side process NULL-call is DF-4483's
  amdgpu_irq.c hole, different file.
- **No user-controlled input reaches this file at all** — its inputs are
  compile-time tables and GPU-generated IV src_id/ext_id.

## Fix validation

- `fix.diff` (file-local): six `[DC_IRQ_SOURCE_DC{1..6}_VLINE0] =
  dummy_irq_entry()` designators completing the table over the shared enum —
  applied single-fix to guest `/usr/src` (`patch -V none -d /usr/src -p1`,
  Hunk #1 succeeded at 328; patched md5 `c31141c9ba1f50da042a2f63da779a97`
  recorded in amdgpu_module_build.log:790 *at build time*).
- Full `amdgpu.ko` module rebuild in `/usr/src/sys/dev/drm/amd/amdgpu`
  (`make -j6`, `-Werror` per the module CFLAGS): **RC=0**, amdgpu.ko linked
  (3741272 bytes), `irq_service_dcn10.c` compiled at log:578, **zero**
  `error:`/`warning:` diagnostics (amdgpu_module_build.log).
- Behavioral proof: replica fixed mode (`-DFIX_DF4995`) exercises exactly the
  patched shape — dummy handlers log and return, no crash, exit 0 (run.log
  FIXED section), vs baseline SIGSEGV 139.
- Revert: reverse patch applied, md5 back to pristine
  `6ec2956a479058297a5c730b09c32423`; `make clean` (0 .o/.ko left);
  /tmp scratch removed. Guest returned clean.
- The shared-code `!info->funcs` guards in `dal_irq_service_set/ack`
  (irq_service.c:118/:152) are DF-4932's fix territory (defense-in-depth
  protecting every asic table); recommended alongside this table fix.

## Artifacts

| file | what |
|---|---|
| dcn10_vline0gap.c | verbatim replica (enum, struct, hpd_ack, funcs, macros, table, to_dal, dispatch) |
| build.sh / build.log | cc -O2 -Wall -std=gnu11 (baseline + -DFIX_DF4995) |
| run.sh / run.log / run.2.log | baseline SIGSEGV 139 vs fixed exit 0, deterministic ×2 |
| fix.diff | six vline0 dummy entries (git-apply-able, -p1) |
| amd_fix_apply.log | pristine/patched md5s, hunk success |
| amdgpu_module_build.log | full -Werror amdgpu.ko build, RC=0 |
| env.txt | guest uname/cc, no AMD GPU, no drm klds, pristine md5s |

## 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.
