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

init_transmitter_control steers handler selection with uninitialized stack byte crev when the VBIOS master command table lacks dig1transmittercontrol (v2 twin of DF-4047's sub-issue)

Summary

A failed parse leaves crev unwritten (the atom parse returns false without touching it when the master table entry is 0) and BREAK_TO_DEBUGGER is WARN-only in release, so the switch runs on an uninitialized kernel-stack byte selecting the v1_6 handler (garbage 6) or NULL (bounded outcomes: EXEC fails cleanly or the NULL is guarded). Hardening-grade like the DF-4047 v1 twin. Initialize-crev fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4612 Β· 6 files
FileTypeDescriptionSize
README.md β€” 2.2 KB ↓ raw
VERDICT.md β€” 2.9 KB ↓ raw
env.txt β€” 273 B view raw
fix.diff β€” 726 B view raw
manifest.json β€” 734 B view raw
verdict.json β€” 2.9 KB view raw

DF-4612 β€” init_transmitter_control steers handler selection with uninitialized crev (command_table2.c)

What

sys/dev/drm/amd/display/dc/bios/command_table2.c:176-192

static void init_transmitter_control(struct bios_parser *bp)
{
    uint8_t frev;
    uint8_t crev;

    if (BIOS_CMD_TABLE_REVISION(dig1transmittercontrol, frev, crev) == false)
        BREAK_TO_DEBUGGER();
    switch (crev) {          /* <-- crev uninitialized when the parse failed */

BIOS_CMD_TABLE_REVISION β†’ amdgpu_atom_parse_cmd_header() (sys/dev/drm/amd/amdgpu/atom.c:1405-1420) returns false without writing *crev when the master command table has no dig1transmittercontrol entry (if (!mct[index]) return false;, atom.c:1412-1413). BREAK_TO_DEBUGGER() is WARN_ON(1) in release builds (os_types.h:76-79) β€” it warns and falls through into switch (crev) on an uninitialized stack byte.

Twin of the uninit crev steering in init_transmitter_control sub-issue of DF-4047 (which covered the v1 file command_table.c). This is the v2 (firmware-parser, command_table2.c) instance.

Build / run

Not runnable on the audit QEMU guest: the path requires an AMD GPU whose VBIOS is parsed by the DC firmware parser (amdgpu driver attach). The guest has a virtio-vga adapter only (env.txt β€” no drm/amd, no AMD PCI device).

Expected behavior (if hardware were present)

With a VBIOS whose master command table lacks the dig1transmittercontrol pointer, crev is an uninitialized kernel-stack byte: * garbage == 6 β†’ binds transmitter_control_v1_6 for a table that does not exist; the later EXEC fails cleanly at atom.c:1206-1207 (base == 0 β†’ -EINVAL). * garbage != 6 β†’ bp->cmd_tbl.transmitter_control = NULL, which the caller checks (bios_parser2.c:1004) and returns BP_RESULT_FAILURE.

So the worst observable effect is an uninitialized-stack read steering control flow (info leak into handler selection / MSan-class defect), not an OOB. Defense-in-depth fix: initialize crev and return early on parse failure.

Contents

  • VERDICT.md β€” full analysis and verdict
  • env.txt β€” guest evidence (no AMD GPU present)
  • fix.diff β€” git-apply-able fix
  • manifest.json, verdict.json
VERDICT.md
↓ download raw

VERDICT β€” DF-4612

Status: not_reproduced (target hardware absent from the audit guest β€” no AMD GPU / amdgpu driver instance; VBIOS command-table parsing never executes there). The defect is proven statically, line-accurate, with certain confidence.

Narrative

init_transmitter_control() (command_table2.c:176-192) asks the ATOM master command table for the dig1transmittercontrol entry's revision:

uint8_t frev; uint8_t crev;
if (BIOS_CMD_TABLE_REVISION(dig1transmittercontrol, frev, crev) == false)
        BREAK_TO_DEBUGGER();
switch (crev) {
case 6:  bp->cmd_tbl.transmitter_control = transmitter_control_v1_6; break;
default: bp->cmd_tbl.transmitter_control = NULL; break;
}

amdgpu_atom_parse_cmd_header() (amdgpu/atom.c:1405-1420):

int idx = CU16(ctx->cmd_table + offset);
u16 *mct = (u16 *)(ctx->bios + ctx->cmd_table + 4);
if (!mct[index])
        return false;               /* <-- *frev/*crev NEVER written */
*frev = CU8(idx + 2);
*crev = CU8(idx + 3);

So on a VBIOS image whose master command table has a zero pointer for the dig1transmittercontrol slot (malformed / attacker-reflashed / truncated image β€” the image is otherwise barely validated at this point per DF-4345), crev keeps its uninitialized stack value and steers the switch.

BREAK_TO_DEBUGGER() is ASSERT(0) β†’ WARN_ON(1) in release (dc/os_types.h:72-79): warns, does not stop execution.

Impact assessment (honest ceiling)

  • uninitialized stack byte read that influences control flow;
  • garbage 6 β†’ binds v1_6 handler; its later EXEC_BIOS_CMD_TABLE resolves the master-table entry to base == 0 and amdgpu_atom_execute_table_locked returns -EINVAL (atom.c:1206-1207) β†’ clean BP_RESULT_FAILURE;
  • garbage != 6 β†’ NULL pointer stored; all call sites NULL-check (bios_parser2.c:1004) β†’ clean failure.

No OOB, no info leak out of the kernel. Severity Low (hardening class), consistent with the DF-4047 v1 twin. The realistic attack prerequisite (a VBIOS image you control on the card) already implies greater power, which is why this stays Low.

Why not reproduced on the guest

env.txt: the audit guest is QEMU with a virtio-vga GPU (vgapci0 chip=0x11111234), no AMD display PCI device, no amdgpu/drm-amd module loaded. command_table2.c is only reachable during amdgpu DC construction. No PoC can exercise it there. Static trace is complete and cited.

Fix

Initialize crev = 0 and bail out of init_transmitter_control() when BIOS_CMD_TABLE_REVISION fails (setting the handler to NULL, which callers already handle). See fix.diff.

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

Baseline never manifests on this guest (no AMD GPU); fix is a one-line initialization with no behavioral risk. Cannot build an amdgpu DC kernel test on the QEMU std-vga guest.

fix.diff initializes crev=0 at declaration; parse failure then takes the default NULL-handler path which bios_parser2.c:1004 already guards.
↓ fix.diffper-fix-DF-4612

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (line-accurate trace: command_table2.c:176-192, amdgpu/atom.c:1405-1420, dc/os_types.h:72-79 WARN_ON release semantics, bios_parser2.c:1004 NULL check); env.txt (no AMD display device / driver on guest)

PoC changes

none - no PoC possible without AMD hardware; static trace only

Verified recommended fix

Initialize crev=0 in init_transmitter_control so a failed BIOS_CMD_TABLE_REVISION cannot steer handler selection with an uninitialized stack byte.

Verdict

init_transmitter_control (command_table2.c:181-183) switches on an uninitialized stack byte crev whenever amdgpu_atom_parse_cmd_header fails (returns false at amdgpu/atom.c:1412-1413 without writing *crev, i.e. the VBIOS master command table lacks the dig1transmittercontrol entry). BREAK_TO_DEBUGGER() is WARN_ON(1) in release, so execution falls through into switch(crev). Outcome is bounded: crev!=6 stores NULL (callers NULL-check, bios_parser2.c:1004) and crev==6 binds a handler whose EXEC then fails cleanly with -EINVAL (atom.c:1206-1207). No OOB, no leak - Low/hardening, the command_table2.c twin of DF-4047's v1 sub-issue. Not reproducible on the audit guest: no AMD GPU (virtio-vga only, see env.txt), so the DC firmware parser never runs.