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

Unbounded WS operand index and NULL dereference when ws=0

  • File: sys/dev/drm/radeon/atom.c
  • Lines: 233, 234, 266, 267, 507, 508, 536, 537, 1187, 1188, 1189, 1190
  • Severity: Medium
  • CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U:C:N/I:N/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.

When the table declares ws=0, ectx.ws is set to NULL, 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 an attacker-controlled value.

Root cause

atom_execute_table_locked reads ws from the VBIOS table header: ws = CU8(base + ATOM_CT_WS_PTR) (atom.c:1175, range 0–255).

At atom.c:1187-1190:

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

The WS operand handlers then index ctx->ws[idx] where idx = U8(*ptr) (atom.c:234, range 0–255) β€” with NO check that idx < ws.

The default case in atom_get_src_int (atom.c:266-268: val = ctx->ws[idx]) and atom_put_dst (atom.c:536-538: ctx->ws[idx] = val) are reached for all idx not matching the special ATOM_WS_* range (0x40–0x48).

Case 1 β€” ws=0: ectx.ws is NULL (atom.c:1190); any WS opcode (e.g. MOVE_WS) 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 and offset (val from source operand, 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 (e.g. MOVE_WS, ADD_WS) 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, VM passthrough with crafted VBIOS).

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

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 (atom-names.h:38), 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:1190), opcode handler calls atom_get_dst β†’ atom_get_src_int WS case β†’ default β†’ ctx->ws[0] = NULL[0] β†’ dereference address 0 β†’ kernel page-fault panic.

Trigger via atom_asic_init at boot.

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.

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

--- a/sys/dev/drm/radeon/atom.c
+++ b/sys/dev/drm/radeon/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;
@@ -263,6 +264,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];
        }
        break;
@@ -534,6 +540,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;
        }
--- a/sys/dev/drm/radeon/atom.c
+++ b/sys/dev/drm/radeon/atom.c
@@ -1190,6 +1196,7 @@ static int atom_execute_table_locked(struct atom_context *ctx, int index,
    if (ws)
        extx.ws = kzalloc(4 * ws, GFP_KERNEL);
    else
        extx.ws = NULL;
+   extx.ws_size = ws;
  • DF-1534 (sibling): unbounded recursion in atom_op_calltable.
  • DF-1535 (sibling): integer overflow in FB scratch bounds check.
  • DF-1536 (sibling): unbounded PS operand index stack OOB.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1537 Β· 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 322 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-1537: radeon atom.c WS index NULL deref + heap OOB

Class: NULL deref + heap OOB write Cited site: sys/dev/drm/radeon/atom.c:1175,1187-1190,234,267,508,537

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/radeon/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

ws=CU8(base+ATOM_CT_WS_PTR) u8 0..255. 1187-1190: if(ws) kzalloc(4*ws) else ectx.ws=NULL. WS handlers index ctx->ws[idx] where idx=U8(0..255) at 234/267/508/537 with NO idxws[idx], 537 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 up to ~1020-byte heap OOB read+write via crafted VBIOS

Fix

Add ws_size field to atom_exec_context; validate idx<ws_size in both WS handlers; reset ws_size 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-1537.diff
ssh -F dfbsd-qemu/config dfbsd 'cd /usr/src && patch -p1 --forward < /root/DF-1537.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-1537: radeon atom.c WS index NULL deref + heap OOB

Verdict

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

The bug is real and present in master DEV source at sys/dev/drm/radeon/atom.c:1175,1187-1190,234,267,508,537, 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)

ws=CU8(base+ATOM_CT_WS_PTR) u8 0..255. 1187-1190: if(ws) kzalloc(4*ws) else ectx.ws=NULL. WS handlers index ctx->ws[idx] where idx=U8(0..255) at 234/267/508/537 with NO idxws[idx], 537 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/radeon/atom.c:1175 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 up to ~1020-byte heap OOB read+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: Add ws_size field to atom_exec_context; validate idx<ws_size in both WS handlers; reset ws_size 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 up to ~1020-byte heap OOB read+write via crafted VBIOS on radeon HW.

Evidence (decisive lines)

Source: sys/dev/drm/radeon/atom.c:1175 β€” ws = CU8(...); :1187 β€” if(ws) kzalloc(4*ws) else NULL; :267 β€” val = ctx->ws[idx] (no bounds); :537 β€” ctx->ws[idx] = val (no bounds). Guest has no AMD GPU. fix.diff adds ws_size field, validates idx<ws_size in both WS handlers, and resets ws_size 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

Add ws_size field to atom_exec_context; set it in atom_execute_table_locked; validate idx<ws_size in both WS handlers (atom_get_src_int and atom_put_dst); reset ws_size to 0 on OOM. Full diff in findings/poc/DF-1537/fix.diff.

Verdict

INCONCLUSIVE (HW-gated). Bug confirmed at source level: radeon/atom.c:1175 ws=CU8(base+ATOM_CT_WS_PTR) u8 (0..255); :1187-1190 if(ws) kzalloc(4*ws) else NULL. WS handlers index ctx->ws[idx] where idx=U8(0..255) at :234/:267/:508/:537 with NO idx panic; ws>0 + idx>=ws -> up to ~1020-byte heap OOB. atom.c is the radeon VBIOS interpreter; audit guest has no AMD GPU.