β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4047

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4047 Β· 9 files
FileTypeDescriptionSize
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.

VERDICT.md
↓ download raw

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)

  1. Parser selection is itself BIOS-controlled: dal_bios_parser_create (bios_parser_interface.c:35-48) tries firmware_parser_create (bios_parser2/command_table2.c) first and falls back to the v1 bios_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.
  2. 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 in dce112/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) by command_table_helper.c:38-57.
  3. Revision is BIOS-controlled: BIOS_CMD_TABLE_PARA_REVISION / BIOS_CMD_TABLE_REVISION read frev/crev out of the mapped VBIOS command master table (command_table.c:42-48, atom.c:1405-1420).
  4. 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-168 wires dig1/dig2 when their crev == 1), init_transmitter_control :367-396 (crev 2 β†’ v2, crev 4 β†’ v4).
  5. 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 every EXEC_BIOS_CMD_TABLE), :678 (clock_source_id_to_ref_clk_src, before input validation completes).
  6. CWE-457 aggravator: init_transmitter_control :367-375 switches on uninitialized stack crev/frev when the command is absent, because amdgpu_atom_parse_cmd_header (atom.c:1412-1413) returns false without writing the out-params.
  7. No NULL-check exists anywhere on bp->cmd_helper->X in 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_testable
baseline no→ patch + rebuild →patched clean

In-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
↓ fix.diffper-fix-DF-4047

Confirmed kernel references

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