# DF-4985 VERDICT — latent NULL-function-pointer matrix, unreachable on genuine hardware (Info hardening)

**Status: untested** (Info severity, hardware-gated: requires an AMD DCE10/DCE11 GPU plus a
generation-mismatched VBIOS; the audit guest has no AMD GPU so amdgpu never attaches — there
is no runnable path). All line references verified in this tree on 2026-09-25.

## 1. What the file is and who consumes it

`command_table_helper_dce110.c` builds the single shared
`struct command_table_helper` vtable (`:332-349`) returned by
`dal_cmd_tbl_helper_dce110_get_table()` (`:361-364`). It is compiled into the amdgpu kmod
(`sys/dev/drm/amd/amdgpu/Makefile:217`) and selected for DCE_VERSION_10_0 (Tonga/Fiji) and
DCE_VERSION_11_0 (Carrizo) (`sys/dev/drm/amd/display/dc/core/dc_resource.c:68,72-75`) by
BOTH helper dispatchers: v1 `command_table_helper.c:46,50` and v2
`command_table_helper2.c:47,51`. The v2 parser is tried first
(`bios_parser_interface.c:41-45`); it requires ROM header 2.2+ and object-info 1.4+
(`bios_parser2.c:1937-1944`), else the v1 parser (`bios_parser.c`) is the fallback.

## 2. The latent matrix (proven)

Five vtable members are NULL for dce110 — three explicitly
(`:344 .assign_control_parameter = NULL`, `:345 .clock_source_id_to_ref_clk_src = NULL`,
`:346 .transmitter_bp_to_atom = NULL`) and two implicitly via designated-initializer
zero-fill (`dc_clock_type_to_atom`, `transmitter_color_depth_to_atom`; struct declared at
`command_table_helper_struct.h:34-64`). Six consumer call sites invoke members without any
NULL check:

- `command_table.c:204` and `:219` — `encoder_control_dig1_v1/dig2_v1` call
  `bp->cmd_helper->assign_control_parameter(...)`.
- `command_table.c:507` — `transmitter_control_v2`; `:638` — `transmitter_control_v3`;
  `:764` — `transmitter_control_v4`; each calls `cmd->transmitter_bp_to_atom(...)`.
- `command_table.c:678` — `transmitter_control_v4` calls
  `cmd->clock_source_id_to_ref_clk_src(...)`.
- `command_table.c:2437` (v1) and `command_table2.c:722` (v2) — `set_dce_clock_v2_1` calls
  `cmd->dc_clock_type_to_atom(...)`.

Any of these with the dce110 table ⇒ call through NULL ⇒ immediate kernel panic in display
bring-up. The parser wrappers only guard their own `cmd_tbl.set_dce_clock` NULL
(`bios_parser.c:778`, `bios_parser2.c:1040`) — not the helper member.

## 3. Why genuine hardware never trips it (why this is Info, not High)

The VBIOS command-table revisions and the helper table are generation-locked:

- `init_transmitter_control` (`command_table.c:367-396`) binds v2/v3/v4 handlers only when
  the card's VBIOS `UNIPHYTransmitterControl` crev is 2/3/4 — DCE8-generation firmware, whose
  ASICs (CI/KV, `dc_resource.c:57-66`) select the **dce80** table — the only family member
  that implements `transmitter_bp_to_atom` and `clock_source_id_to_ref_clk_src`
  (`dce80/command_table_helper_dce80.c:341-345`). DCE10/11 firmware uses crev 5/6 →
  `transmitter_control_v1_5/v1_6`, which only use members dce110 implements.
- `encoder_control_dig1/dig2_v1` bind only for DIG encoder control rev < 3 (DCE8-era);
  DCE10/11 use `encoder_control_digx_v3`+ which do not call `assign_control_parameter`.
- `set_dce_clock` is invoked only from `dce112_set_clock` (`dce/dce_clocks.c:292-340`), wired
  exclusively into `dce112_funcs`/`dce120_funcs`/`dcn1_funcs` (`dce_clocks.c:745,752,758`).
  DCE10/11 resources wire `dce110_funcs` (`dce_clocks.c:762-767`;
  `dce110_resource.c:1267` → `dce110_dccg_create`), whose `set_dispclk = dce_psr_set_clock`
  → `dce_set_clock` uses `program_display_engine_pll` — never `set_dce_clock`. The ASICs that
  do call it (DCE11.2 Polaris, DCE12, DCN1) select dce112-family tables which implement
  `dc_clock_type_to_atom` (`dce112/command_table_helper_dce112.c:401`,
  `dce112/command_table_helper2_dce112.c:401`).

So the NULLs mirror upstream Linux's vintage design (the v1 paths for old-VBIOS revisions are
dce80's job; the SetDCEClock path is dce112+'s job). The only way to pair dce110's table with
a calling path is a cross-generation VBIOS transplant (DCE8-era command tables on a
DCE10/11 card) — an attacker who already flashes the machine's firmware, i.e. outside the
local/remote attacker model. Info hardening.

## 4. Attribution / family checks (orchestrator's checklist)

- **DF-2030** (dead `return 0` in `dig_encoder_sel_to_atom`) is filed on **this exact file**,
  lines 154-189, pass 1 — correctly attributed; NOT re-reported here. Its consumers
  (`command_table.c:798,855`, `command_table2.c:213`) feed a uint8_t params field; no
  memory-safety consequence (per DF-2030/DF-4934 analysis; upstream codified always-0).
- **DF-4935 shape** (implemented `dc_clock_type_to_atom` leaving `*atom_clock_type`
  uninitialized on default while returning true) does **not** exist in dce110: the file has no
  implementation at all — the member is implicitly NULL. The nearest analog here is the NULL
  member + `set_dce_clock_v2_1` unguarded call (`command_table.c:2437`, `command_table2.c:722`),
  which is the subject of this finding.
- **command_table_helper2_dce110 does not exist** — only the one shared file serves both
  parsers (dce112 is the only family with separate v1/v2 helper files).
- **Other switches in the file are clean** (negative result, verified line-by-line):
  `phy_id_to_atom` :34-65 (default→UNIPHYA), `signal_type_to_atom_dig_mode` :67-95
  (default→DVI), `clock_source_id_to_atom_phy_clk_src_id` :97-121 (default→P1PLL),
  `hpd_sel_to_atom` :123-152 (default→0) initialize outputs on every path.
  `clock_source_id_to_atom` :192-236 writes the output on every non-NULL-pointer path; the
  `atom_pll_id == NULL` early-true arm is never taken (all callers pass stack pointers —
  `command_table.c:2436`, `command_table2.c:721`). `engine_bp_to_atom` :238-281 omits
  ENGINE_ID_DACB/VGA but returns false and every caller
  (`command_table.c:1951,1990`, `command_table2.c:509`) downgrades to BP_RESULT_FAILURE.
  `encoder_action_to_atom` :283-306 / `disp_power_gating_action_to_atom` :308-329 default to 0.

## 5. Why no guest run

The audit QEMU guest has no AMD GPU (standard vga); amdgpu cannot attach, so neither the
v1 nor v2 parser is ever constructed on it. A PoC would require DCE10/11 hardware with a
cross-generation VBIOS image. Info severity → skipped per contract; status `untested`.

## 6. Fix (defense-in-depth, upstream-parity-safe)

Either give dce110 the shared implementations
(`.assign_control_parameter = dal_cmd_table_helper_assign_control_parameter`,
`.clock_source_id_to_ref_clk_src = dal_cmd_table_helper_clock_source_id_to_ref_clk_src`,
`.transmitter_bp_to_atom = dal_cmd_table_helper_transmitter_bp_to_atom` — exactly what dce80
does at `dce80/command_table_helper_dce80.c:341-345`) plus a `dc_clock_type_to_atom` that
initializes its output in the default arm, or add member NULL-guards at
`command_table.c:204,219,507,638,678,2437` and `command_table2.c:722`.

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

(No fix.diff for this item: documented recommendation only - see Fix above.)
