NULL command_table_helper callbacks called via BIOS-controlled command revisions on DCE10/11/11.2 (assign_control_parameter/transmitter_bp_to_atom/clock_source_id_to_ref_clk_src) + uninit crev steering in init_transmitter_control
Summary
command_table.c selects command handlers solely from VBIOS-controlled frev/crev. The DCE110 and DCE112 helper tables ship three NULL function pointers (.assign_control_parameter, .clock_source_id_to_ref_clk_src, .transmitter_bp_to_atom). Crafted/corrupt VBIOS declaring DIGxEncoderControl absent + DIG1/DIG2EncoderControl crev=1 selects handlers that call the NULL helpers; UNIPHYTransmitterControl crev=2/4 likewise - kernel NULL function-pointer call at first encoder/transmitter setup. Aggravator: init_transmitter_control switches on UNINITIALIZED stack crev when the command is absent (parse_cmd_header returns false without writing out-params). Parser selection itself BIOS-controlled (falls back to v1 parser when the image lacks v2.2+/v1.4 headers). Guest dispatch model prints the exact NULL helper per crafted combo; honest-hardware negatives clean. Same family as DF-3942.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4047 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.3 KB | β raw | |
| VERDICT.md | β | 3.9 KB | β raw | |
| null_helper_dispatch.c | β | 5.6 KB | view raw | |
| build.log | β | 11 B | view raw | |
| run.log | β | 1.9 KB | view raw | |
| env.txt | β | 188 B | view raw | |
| fix.diff | β | 1.8 KB | view raw | |
| manifest.json | β | 1.4 KB | view raw | |
| verdict.json | β | 4.0 KB | view raw |
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.
DF-4047 VERDICT β NULL helper dispatch from BIOS-controlled command revisions (command_table.c)
Reproduced?
Source-certain; dispatch model verified on the guest (userland replica
null_helper_dispatch, build.log/run.log). The kernel manifestation is
hardware-gated: the guest has no AMD GPU, so no amdgpu/DC device attaches
and no VBIOS image is parsed. Per the DF-3942 convention (crafted/corrupt
VBIOS β NULL deref at attach) the persisted status is inconclusive,
impact panic, confidence certain.
Why this is real (path:line chain)
- Parser selection is itself BIOS-controlled:
dal_bios_parser_create(bios_parser_interface.c:35-48) triesfirmware_parser_create(bios_parser2/command_table2.c) first and falls back to the v1bios_parser_create(this file's parser) whenever the VBIOS lacks an atom rom header v2.2+ / display-object-info v1.4 (bios_parser2.c:1912-1975) β so a crafted VBIOS chooses both the v1 parser and the old command revisions in one image. - Helper tables with NULLs:
dce110/command_table_helper_dce110.c(.assign_control_parameter = NULL,.clock_source_id_to_ref_clk_src = NULL,.transmitter_bp_to_atom = NULL), same three indce112/command_table_helper_dce112.c; dce80's table is fully populated. Bound to bp->cmd_helper for DCE 10.0/11.0 (dce110) and 11.2/11.22 (dce112) bycommand_table_helper.c:38-57. - Revision is BIOS-controlled:
BIOS_CMD_TABLE_PARA_REVISION/BIOS_CMD_TABLE_REVISIONread frev/crev out of the mapped VBIOS command master table (command_table.c:42-48,atom.c:1405-1420). - Dispatch selects old handlers on old revisions:
init_dig_encoder_control :120-141(default β v1 path when DIGx β {2,4,5};init_encoder_control_dig_v1 :153-168wires dig1/dig2 when their crev == 1),init_transmitter_control :367-396(crev 2 β v2, crev 4 β v4). - The selected handlers call the NULL helper unconditionally as part of
building parameters:
:204,:219(assign_control_parameter),:507(transmitter_bp_to_atomβ executed before everyEXEC_BIOS_CMD_TABLE),:678(clock_source_id_to_ref_clk_src, before input validation completes). - CWE-457 aggravator:
init_transmitter_control :367-375switches on uninitialized stackcrev/frevwhen the command is absent, becauseamdgpu_atom_parse_cmd_header(atom.c:1412-1413) returns false without writing the out-params. - No NULL-check exists anywhere on
bp->cmd_helper->Xin this file (verified by grep β every other helper used is non-NULL in all three tables; only these three are NULL, and only in dce110/dce112).
Result: crafted/corrupt VBIOS on a DCE10/11/11.2 part β NULL function pointer call β kernel page fault at the first DIG encoder / transmitter setup (attach or first modeset). Same threat model and severity family as DF-3942 (Low, crafted VBIOS).
Model verification (guest)
null_helper_dispatch.c encodes the three helper tables and the
command_table.c dispatch logic 1:1; run on the guest it prints, for each
(DCE version, crafted revision) combination, the exact NULL helper that the
kernel would call β plus the two negative controls (honest DCE11 revisions
and any DCE8.0 combination) which select no NULL call. Output in run.log.
Fix
Guard the dispatch: refuse revisions whose handler requires a helper the
selected table does not provide, zero-initialize frev/crev, and fail
init_transmitter_control cleanly when the command header is absent.
fix.diff implements the minimal defensive form (zero-init + NULL handler
on parse failure + NULL-checks at the four call sites).
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_testableIn-kernel fix validation impossible on this guest (no AMD GPU; no amdgpu attach). fix.diff is a compile-shape guard (zero-init + NULL checks at dispatch), verified only at source level.
findings/poc/DF-4047/fix.diff
Confirmed kernel references
- sys/dev/drm/amd/display/dc/bios/command_table.c:204
- sys/dev/drm/amd/display/dc/bios/command_table.c:219
- sys/dev/drm/amd/display/dc/bios/command_table.c:367
- sys/dev/drm/amd/display/dc/bios/command_table.c:507
- sys/dev/drm/amd/display/dc/bios/command_table.c:678
- sys/dev/drm/amd/display/dc/bios/command_table_helper.c:38
- sys/dev/drm/amd/display/dc/bios/dce110/command_table_helper_dce110.c
- sys/dev/drm/amd/display/dc/bios/dce112/command_table_helper_dce112.c
- sys/dev/drm/amd/display/dc/bios/bios_parser_interface.c:35
- sys/dev/drm/amd/amdgpu/atom.c:1405
Detail
Evidence (decisive lines)
run.log (dispatch model: 4 crafted-revision combos -> named NULL helper; honest-DCE11 and DCE8 controls -> safe), VERDICT.md path:line chain, dce110/command_table_helper_dce110.c NULL entries quoted in README.md
PoC changes
n/a (authored fresh: userland dispatch model, no kernel changes)
Verified recommended fix
init_transmitter_control: zero-init frev/crev and refuse crev 2/4 when the helper entry is NULL; init_encoder_control_dig_v1: require assign_control_parameter != NULL (see fix.diff)
Verdict
Source-certain NULL function-pointer call reachable from BIOS-controlled data: the DCE110/DCE112 command_table_helper tables ship assign_control_parameter/clock_source_id_to_ref_clk_src/transmitter_bp_to_atom = NULL (dce110/command_table_helper_dce110.c, dce112/...dce112.c), and command_table.c selects handlers that call them based solely on VBIOS command revisions - encoder_control_dig1_v1:204 and encoder_control_dig2_v1:219 (assign_control_parameter), transmitter_control_v2:507 (transmitter_bp_to_atom), transmitter_control_v4:678 (clock_source_id_to_ref_clk_src). init_transmitter_control:367-375 additionally switches on UNINITIALIZED stack crev when the command is absent because amdgpu_atom_parse_cmd_header (atom.c:1412-1413) returns false without writing the out-params. Parser selection is itself BIOS-controlled (dal_bios_parser_create, bios_parser_interface.c:35-48, tries firmware_parser_create first and falls back to this v1 parser when the image lacks rom-header v2.2+/object-info v1.4), so a crafted VBIOS chooses both the v1 parser and the old command revisions in one image; on a DCE10/11/11.2 part it then NULL-calls at first encoder/transmitter setup -> kernel page fault/panic. Guest dispatch model (null_helper_dispatch.c) confirms every (DCE, revision) -> NULL-helper mapping plus both negative controls (honest DCE11 revisions and DCE8.0's fully-populated table are safe). Kernel runtime hardware-gated (no AMD GPU in guest) -> status inconclusive per DF-3942 convention; panic impact, Low severity (VBIOS-reflash / VM-ROM threat model).
No comments yet.