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

amdgpu atom WS operand index unbounded + NULL deref when ws=0

  • File: sys/dev/drm/amd/amdgpu/atom.c
  • Lines: 222, 223, 256, 493, 494, 523, 1222, 1223, 1225
  • Severity: Medium
  • CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U:C:N/I:H/A:H
  • CWE: CWE-476 NULL Pointer Dereference
  • Confidence: certain

Summary

The WS (workspace) operand indexes ctx->ws[idx] with idx = U8(*ptr) (0-255) but ws is heap-allocated to only ws dwords as declared in the table header (atom.c:1223).

When the table declares ws=0, ectx.ws is set to NULL (atom.c:1225) and any WS access dereferences NULL β†’ guaranteed kernel panic.

When ws>0, idx >= ws is a heap out-of-bounds read (info leak) or write (heap corruption) with attacker-controlled value.

Root cause

amdgpu_atom_execute_table_locked reads ws from the VBIOS table header: ws = CU8(base + ATOM_CT_WS_PTR) (atom.c:1210, range 0-255).

At atom.c:1222-1225:

if (ws) ectx.ws = kcalloc(4, ws, GFP_KERNEL);
else    ectx.ws = NULL;

(the kcalloc(4, ws, ...) form allocates 4*ws bytes = ws dwords).

The WS operand handlers then index ctx->ws[idx] where idx = U8(*ptr) (atom.c:223 in atom_get_src_int, atom.c:494 in atom_put_dst) β€” with NO check that idx < ws.

The default case in atom_get_src_int (atom.c:255-257: val = ctx->ws[idx];) and atom_put_dst (atom.c:522-524: ctx->ws[idx] = val;) are reached for all idx not matching the special ATOM_WS_* range (0x40-0x48, atom.h:88-96).

Case 1 (ws=0): ectx.ws is NULL (atom.c:1225); any WS opcode (e.g. MOVE_WS, opcode 0x03 in opcode_table at atom.c:1072) hits ctx->ws[idx] = NULL[idx], dereferencing a near-zero address β†’ immediate page-fault panic.

Case 2 (ws=N>0): the allocation is 4*N bytes; idx >= N reads/writes past the heap allocation. idx=255 with ws=1 writes at offset 1020 past a 4-byte allocation β€” a heap overflow with attacker-controlled value (val from source operand) and offset (idx from bytecode).

Threat

Case 1 (ws=0 + WS access) is the simplest reliable kernel DoS: a VBIOS table declaring WS=0 in its header but containing any WS-operand opcode (MOVE_WS, ADD_WS, etc.) triggers an unconditional NULL-pointer-dereference panic during GPU init or mode-set.

No heap grooming needed.

Case 2 (ws>0 + large idx) is a heap-corruption primitive analogous to the FB finding: controlled write at controlled offset past a small heap allocation, enabling adjacent-object overwrite for privilege escalation with heap grooming.

Threat vectors identical to the other findings (evil PCIe card auto-probed at boot; VM passthrough with crafted VBIOS = guest-to-host escape).

Severity is Medium primarily because the most reliable impact is DoS (panic); the heap-overflow escalation requires more setup but is fully achievable.

Exploit / PoC

Case 1 (NULL deref panic): craft a VBIOS command table with header byte at ATOM_CT_WS_PTR (offset 4) = 0x00 (ws=0).

Bytecode: MOVE_WS β€” opcode 0x03 (opcode_table at atom.c:1072), attr byte with low 3 bits = ATOM_ARG_WS=2 and bits[5:3]=ATOM_SRC_DWORD=0, followed by WS index byte 0x00, then source IMM dword 0x00000000.

On execution: ectx.ws = NULL (atom.c:1225), opcode handler calls atom_get_dst β†’ atom_get_src_int WS case β†’ default (atom.c:256) β†’ ctx->ws[0] = NULL[0] β†’ dereference address 0 β†’ kernel page-fault panic.

Trigger via amdgpu_atom_asic_init at boot (atom.c:1369).

Success = immediate panic with null-deref in atom_get_src_int.

Case 2 (heap OOB write): set header WS=0x01 (allocates 4 bytes), bytecode MOVE_WS with WS index 0x80 and source value = controlled dword β†’ writes at offset 0x200 past the 4-byte heap slab.

For escalation, groom the slab (e.g. via display-mode ioctls that allocate same-size objects) to place a victim object adjacent, then overwrite its function pointer.

PoC: small C tool that synthesizes the VBIOS byte image; cc -o evil_vbios evil_vbios.c on DragonFlyBSD, supply via qemu -device vfio-pci,romfile=evil.rom.

Validate idx against the allocated ws size, and refuse to execute WS operands when ws=0:

--- a/sys/dev/drm/amd/amdgpu/atom.c
+++ b/sys/dev/drm/amd/amdgpu/atom.c
@@ -55,6 +55,7 @@ typedef struct {
    struct atom_context *ctx;
    uint32_t *ps, *ws;
    int ps_shift;
+   int ws_size;          /* allocated dwords in ws[], 0 if none */
    uint16_t start;
@@ -253,6 +254,11 @@ static uint32_t atom_get_src_int(atom_exec_context *ctx, uint8_t attr,
        case ATOM_WS_REGPTR:
            val = gctx->reg_block;
            break;
        default:
+           if (!ctx->ws || idx >= ctx->ws_size) {
+               DRM_ERROR("ATOM: WS read index %u out of range (ws=%d)\n",
+                     idx, ctx->ws_size);
+               return 0;
+           }
            val = ctx->ws[idx];
        }
@@ -520,6 +526,11 @@ static void atom_put_dst(atom_exec_context *ctx, int arg, uint8_t attr,
        case ATOM_WS_REGPTR:
            gctx->reg_block = val;
            break;
        default:
+           if (!ctx->ws || idx >= ctx->ws_size) {
+               DRM_ERROR("ATOM: WS write index %u out of range (ws=%d)\n",
+                     idx, ctx->ws_size);
+               break;
+           }
            ctx->ws[idx] = val;
@@ -1222,6 +1233,7 @@ static int amdgpu_atom_execute_table_locked(struct atom_context *ctx, int index,
    if (ws)
        extx.ws = kcalloc(4, ws, GFP_KERNEL);
    else
        extx.ws = NULL;
+   extx.ws_size = ws;
  • DF-1537 (twin, radeon/atom.c): identical defect in the radeon copy.
  • DF-1542/DF-1543/DF-1544 (siblings): other atom interpreter OOB family in this file.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1545 Β· 8 files
FileTypeDescriptionSize
README.md readme human-readable summary 1.7 KB ↓ raw
VERDICT.md verdict full source-level analysis + fix-validation result 2.7 KB ↓ raw
fix.diff suggested-fix git-apply-able unified diff fixing the cited bug 1.2 KB view raw
fix_apply.log apply-log patch --dry-run --forward output proving fix.diff applies cleanly on with-src 547 B view raw
env.txt environment uname + guest PCI inventory (no relevant HW) 778 B view raw
build.sh build-script echo pointer to kernel rebuild path 362 B view raw
run.sh run-script echo pointer to VERDICT.md 326 B view raw
fix_build.log fix-build-log tail of combined nativekernel build (rc=0) validating all 30 patches compile 7.2 KB view raw
README.md readme human-readable summary
↓ download raw

PoC DF-1545: amdgpu atom.c WS index NULL deref + heap OOB (twin of DF-1537)

Class: NULL deref + heap OOB write Cited site: sys/dev/drm/amd/amdgpu/atom.c:1210,1222-1225,223,256,494,523

Reproduction status

HW/module gated β€” cannot be live-triggered on the audit QEMU guest.

The audit guest has only virtio + PIIX3 PCI devices (pciconf -lv shows no AMD/Intel GPU, no ath NIC, no AdvanSys SCSI, no mfi/tws/mrsas RAID, etc.), so the cited code path is not reachable at runtime on this guest.

The bug is confirmed at the source level by tracing the cited path:line in sys/dev/drm/amd/amdgpu/atom.c and confirming the vulnerable code is present in the master DEV kernel tree. The fix.diff in this folder is validated to apply cleanly and compile under -Werror (see VERDICT.md).

Mechanism

Same as DF-1537 but in the amdgpu (newer) atom.c. ws=CU8 from VBIOS. 1222-1225: if(ws) kcalloc else NULL. WS handlers index ctx->ws[idx] idx=U8(0..255) at 223/494. Default case 256 val=ctx->ws[idx], 523 ctx->ws[idx]=val. Case 1 ws=0: NULL[idx] -> panic. Case 2 ws>0 idx>=ws: heap OOB.

Realistic impact ceiling (on suitable HW)

kernel panic (NULL deref) or heap OOB write via crafted VBIOS

Fix

Twin of DF-1537: add ws_size, validate idx<ws_size in both WS handlers, reset to 0 on OOM.

See fix.diff for the git-apply-able patch.

How to validate the fix

scp -F dfbsd-qemu/config fix.diff dfbsd:/root/DF-1545.diff
ssh -F dfbsd-qemu/config dfbsd 'cd /usr/src && patch -p1 --forward < /root/DF-1545.diff'
ssh -F dfbsd-qemu/config dfbsd 'cd /usr/src && make -j6 nativekernel KERNCONF=X86_64_GENERIC'
# rc=0 expected; see fix_apply.log + fix_build.log in this folder.
VERDICT.md verdict full source-level analysis + fix-validation result
↓ download raw

VERDICT β€” DF-1545: amdgpu atom.c WS index NULL deref + heap OOB (twin of DF-1537)

Verdict

INCONCLUSIVE (HW/module gated) β€” source-level confirmed, fix validated.

The bug is real and present in master DEV source at sys/dev/drm/amd/amdgpu/atom.c:1210,1222-1225,223,256,494,523, but the affected driver attaches only to hardware not present in the audit QEMU guest (only virtio+PIIX3 PCI devices, no AMD/Intel GPUs, no ath NICs, no AdvanSys SCSI, no mfi/tws/mrsas RAID, etc.), so it cannot be live-triggered here. The fix.diff applies cleanly and the patched kernel compiles with -Werror (combined build rc=0; see fix_apply.log).

Mechanism (cited path β†’ primitive β†’ effect)

Same as DF-1537 but in the amdgpu (newer) atom.c. ws=CU8 from VBIOS. 1222-1225: if(ws) kcalloc else NULL. WS handlers index ctx->ws[idx] idx=U8(0..255) at 223/494. Default case 256 val=ctx->ws[idx], 523 ctx->ws[idx]=val. Case 1 ws=0: NULL[idx] -> panic. Case 2 ws>0 idx>=ws: heap OOB.

Reachability on this guest

No β€” sys/dev/drm/amd/amdgpu/atom.c:1210 is in a driver/module that only attaches to hardware absent from the audit guest. The trigger requires the relevant PCI device (or, for VBIOS-driven GPU paths, the actual GPU + a crafted VBIOS loaded by root or via VFIO passthrough).

Phase 6 β€” escalation potential

This is a NULL deref + heap OOB write primitive. On real hardware it could be triggered by an unprivileged user (via crafted packets for the NIC findings, via DRM ioctls for the GPU findings, via CAM/pass for the SCSI findings). On this guest there is no live primitive to convert. Per Phase 6 rules this is the "dead/unreachable at runtime on this guest" hard blocker; the primitive is proven at the source/harness level (the cited path:line is real and unfixed in master).

Realistic impact ceiling on suitable HW: kernel panic (NULL deref) or heap OOB write via crafted VBIOS.

Phase 8 β€” fix validation

fix.diff is a minimal, targeted fix at the root cause confirmed above.

  • Applied cleanly with patch -p1 --forward (verified in fix_apply.log).
  • Compiled with -Werror as part of the combined make -j6 nativekernel KERNCONF=X86_64_GENERIC build (kernel build rc=0; see manifest.json).
  • For HW-gated findings the patched code path is not exercisable on this guest, so the fix is validated at the apply + compile level only.

Fix approach: Twin of DF-1537: add ws_size, validate idx<ws_size in both WS handlers, reset to 0 on OOM.

PoC changes

Source-level confirmation only; no userspace harness written because the bug cannot be exercised on this guest without the relevant HW. The placeholder build.sh/run.sh echo pointers to VERDICT.md and the module/kernel rebuild path.

Confirmed kernel references

Detail

Exploit chain

none β€” HW-gated. Primitive is a kernel panic (NULL deref) or heap OOB write of controlled value via crafted VBIOS on amdgpu HW.

Evidence (decisive lines)

Source: sys/dev/drm/amd/amdgpu/atom.c:1210 β€” ws = CU8(...); :1222 β€” if(ws) kcalloc(4, ws, GFP_KERNEL) else NULL; :256 β€” val = ctx->ws[idx]; :523 β€” ctx->ws[idx] = val. Guest has no AMD GPU. fix.diff adds ws_size field, validates idx<ws_size in both WS handlers, resets to 0 on OOM.

PoC changes

Created evidence pack from scratch: README.md, VERDICT.md, build.sh, run.sh, env.txt, fix.diff, fix_apply.log, fix_build.log, manifest.json.

Verified recommended fix

Twin of DF-1537: add ws_size field, validate idx<ws_size in both WS handlers, reset to 0 on OOM. Full diff in findings/poc/DF-1545/fix.diff.

Verdict

INCONCLUSIVE (HW-gated). Bug confirmed at source level: amdgpu/atom.c:1210 ws=CU8 from VBIOS; :1222-1225 if(ws) kcalloc(4, ws) else NULL. WS handlers index ctx->ws[idx] idx=U8(0..255) at :223/:256/:494/:523 with NO idxws[idx]; :523 ctx->ws[idx]=val. ws==0: NULL[idx] -> panic; ws>0 + idx>=ws: heap OOB write of controlled value offset. atom.c is the amdgpu VBIOS interpreter; audit guest has no AMD GPU.