# DF-4047 — NULL command_table_helper callbacks reachable via crafted-VBIOS command revisions (DCE10/11/11.2)

## What

`sys/dev/drm/amd/display/dc/bios/command_table.c` dispatches its command
handlers by the **BIOS-controlled** command-table revision. The DCE110 and
DCE112 helper tables ship three NULL function pointers:

```
sys/dev/drm/amd/display/dc/bios/dce110/command_table_helper_dce110.c
	.assign_control_parameter    = NULL
	.clock_source_id_to_ref_clk_src = NULL
	.transmitter_bp_to_atom      = NULL
sys/dev/drm/amd/display/dc/bios/dce112/command_table_helper_dce112.c  (same)
```

`dal_bios_parser_init_cmd_tbl_helper()` selects the dce110 table for
DCE_VERSION_10_0 **and** DCE_VERSION_11_0, and the dce112 table for
DCE_VERSION_11_2/11_22 (`command_table_helper.c:38-57`). On those ASICs a
VBIOS image whose command headers declare **old revisions** selects handlers
that immediately call one of those NULL pointers:

| crafted VBIOS revision | handler selected | NULL call (call site) |
|---|---|---|
| DIGxEncoderControl ∉ {2,4,5} (e.g. absent) **and** DIG1EncoderControl crev=1 | `encoder_control_dig1_v1` | `bp->cmd_helper->assign_control_parameter(...)` — `command_table.c:204` |
| same with DIG2EncoderControl crev=1 | `encoder_control_dig2_v1` | `command_table.c:219` |
| UNIPHYTransmitterControl crev=2 | `transmitter_control_v2` | `bp->cmd_helper->transmitter_bp_to_atom(...)` — `command_table.c:507` |
| UNIPHYTransmitterControl crev=4 | `transmitter_control_v4` | `cmd->clock_source_id_to_ref_clk_src(...)` — `command_table.c:678` |

A call through a NULL function pointer is an immediate kernel page fault
(panic) at the first encoder/transmitter setup after attach.

**Aggravator (CWE-457):** `init_transmitter_control()`
(`command_table.c:367-396`) declares `uint8_t frev, crev;` **uninitialized**
and only `BREAK_TO_DEBUGGER()`s when `BIOS_CMD_TABLE_REVISION()` fails —
`amdgpu_atom_parse_cmd_header()` returns false **without writing** the out
params when the master-table entry is absent (`sys/dev/drm/amd/amdgpu/atom.c:1405-1420`),
so `switch (crev)` runs on an uninitialized stack byte and can select the
v2/v4 handlers (or NULL) with no attacker precision needed.

Honest DCE10+ VBIOSes declare DIGx v4/v5 and UNIPHY v5/v6, so the NULLs are
never reached on healthy hardware (and DCE8.0's helper table is fully
populated) — the trigger is a crafted or corrupt VBIOS image (reflashed ROM,
GPU passthrough with a modified ROM blob), the same threat model as DF-3942.

## Reproduce (guest, userland dispatch model — no AMD GPU required)

```
cc -O2 -Wall -o null_helper_dispatch null_helper_dispatch.c
./null_helper_dispatch
```

## Expected output (run.log)

```
DCE11.0, DIGx rev absent/0 + DIG1 crev=1  -> assign_control_parameter
DCE11.0, UNIPHYTransmitterControl crev=2 -> transmitter_bp_to_atom
DCE11.0, UNIPHYTransmitterControl crev=4 -> clock_source_id_to_ref_clk_src
DCE11.2, DIGx rev 0 + DIG2 crev=1        -> assign_control_parameter
DCE11.0 honest (DIGx v4, UNIPHY v5)     -> safe (no NULL call)
DCE8.0  crafted (DIGx 0/DIG1 v1, UNIPHY v2/v4) -> safe (dce80 table fully populated)
```

Kernel runtime is hardware-gated (no AMD GPU in the QEMU guest); the in-kernel
manifestation is a NULL function-pointer call → `panic: fault on non-write
address 0x0`-class page fault during `dal_bios_parser` command dispatch.
