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

NULL-pointer dereference panic in radeon_vce_cs_reloc when a VCE command stream is submitted without a relocation chunk

Summary

parser_init only assigns chunk_relocs when the user supplies a RELOCS chunk and parser_relocs silently tolerates NULL; a VCE-ring CS whose IB contains any reloc-bearing command reaches cs_reloc's deref of chunk_relocs->length_dw - a fixed-offset NULL read panic. Both sibling paths guard exactly this case with 'No relocation chunk!'; the VCE parser forgot. Deterministic single-ioctl unpriv DoS on SI/CIK VCE hardware. Code-trace certain (guest has no radeon GPU); trigger built. Mirror-the-UVD-guard fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4608 Β· 10 files
FileTypeDescriptionSize
vce_null_relocs.c β€” 3.6 KB view raw
build.sh β€” 104 B view raw
run.sh β€” 118 B view raw
build.log β€” 107 B view raw
run.log β€” 456 B view raw
README.md β€” 2.5 KB ↓ raw
VERDICT.md β€” 2.5 KB ↓ raw
fix.diff β€” 974 B view raw
manifest.json β€” 839 B view raw
verdict.json β€” 2.9 KB view raw

DF-4608 β€” radeon_vce_cs_reloc() NULL-deref panic: CS submitted without a relocation chunk

File: sys/dev/drm/radeon/radeon_vce.c (pass 2) Severity: Medium (deterministic local kernel panic from unprivileged DRM access) Confidence: certain CWE: CWE-476

The path

A DRM_IOCTL_RADEON_CS submission is parsed by radeon_cs_parser_init() (radeon_cs.c:302-361): it sets p->chunk_relocs only if the user supplied a RADEON_CHUNK_ID_RELOCS chunk (radeon_cs.c:313-315) β€” omitting the chunk is perfectly legal. radeon_cs_parser_relocs() (radeon_cs.c:83-85) then returns 0 ("nothing to do") instead of rejecting, and p->relocs/p->chunk_relocs stay NULL (the parser was memset in radeon_cs_ioctl, radeon_cs.c:674).

Selecting a VCE ring (FLAGS chunk dword 1 = RADEON_CS_RING_VCE, no RADEON_CS_USE_VM) dispatches radeon_vce_cs_parse (radeon_asic.c:1769/2077). Its very first reloc-bearing command β€” e.g. 0x05000005 feedback buffer (radeon_vce.c:646-651) β€” calls radeon_vce_cs_reloc():

relocs_chunk = p->chunk_relocs;                       /* NULL */
offset = radeon_get_ib_value(p, lo);
idx = radeon_get_ib_value(p, hi);
if (idx >= relocs_chunk->length_dw) {                  /* NULL deref */

β†’ kernel page fault at offsetof(struct radeon_cs_chunk, length_dw) β†’ panic. Any local user with the DRM node open (typically unprivileged /dev/dri/renderD128 on desktops) can panic the machine, deterministically, with a single ioctl.

The guards everyone else has (and VCE forgot)

  • generic packet path radeon_cs_packet_next_reloc() β€” radeon_cs.c:846-850: if (p->chunk_relocs == NULL) { DRM_ERROR("No relocation chunk !"); return -EINVAL; }
  • UVD parser radeon_uvd_cs_parse() β€” radeon_uvd.c:705-709: the identical guard, at the head of the parser.

radeon_vce_cs_parse()/radeon_vce_cs_reloc() have no such check anywhere β€” a plain oversight when VCE support was ported.

Trigger

vce_null_relocs.c: raw ioctl (no libdrm), FLAGS chunk selecting the VCE ring + IB chunk containing a session command followed by a feedback-buffer command, and deliberately no RELOCS chunk.

Guest status

Builds clean with the guest cc 8.3. The audit QEMU guest has no radeon GPU (ls /dev/dri β†’ absent; pciconf -l | grep 1002 β†’ zero matches), so the ioctl cannot be exercised β€” trigger provided for affected hardware (TAHITI/PITCAIRN/VERDE/OLAND/ARUBA/BONAIRE/KAVERI/KABINI/HAWAII/MULLINS with radeonkms loaded and the VCE rings up).

Fix

Mirror the UVD guard at the head of radeon_vce_cs_parse() (fix.diff).

VERDICT.md
↓ download raw

DF-4608 VERDICT

Status: untested (hardware-required) β€” code-trace certainty

Proof chain (all path:line)

  1. radeon_cs_ioctl memsets the parser (radeon_cs.c:674) β†’ chunk_relocs NULL by default.
  2. radeon_cs_parser_init assigns p->chunk_relocs only for a RADEON_CHUNK_ID_RELOCS chunk (radeon_cs.c:313-315). A CS consisting of FLAGS+IB chunks only is fully accepted (IB length != 0 checked at :319; ring validity at :371 via radeon_cs_get_ring; VCE cs_parse non-NULL checked at :376).
  3. radeon_cs_parser_relocs early-returns 0 when chunk_relocs == NULL (radeon_cs.c:83-85) β€” the submission is NOT rejected.
  4. radeon_cs_ib_chunk β†’ radeon_cs_parse(rdev, ring, parser) (radeon_cs.c:466) β†’ (rdev)->asic->ring[r]->cs_parse(p) (radeon.h:2726) β†’ radeon_vce_cs_parse for TN_RING_TYPE_VCE1/2 (radeon_asic.c:1769, 2077).
  5. radeon_vce_cs_parse case 0x05000005 (radeon_vce.c:646-651) (or encode 0x03000001 :615, context 0x05000001 :631, bitstream 0x05000004 :638) β†’ radeon_vce_cs_reloc β†’ relocs_chunk = p->chunk_relocs (radeon_vce.c:478) β†’ if (idx >= relocs_chunk->length_dw) (radeon_vce.c:482) dereferences NULL + offsetof(length_dw)=4 β†’ unrecoverable kernel page fault β†’ panic.
  6. Comparative guards that prove intent: radeon_cs.c:846-850 (generic reloc path) and radeon_uvd.c:705-709 (UVD parser) both reject a missing relocs chunk with "No relocation chunk !". The VCE parser has no equivalent.

The trigger (vce_null_relocs.c) compiles clean on the guest (build.log) and performs the ioctl; on this guest it exits at the /dev/dri open (run.log: "No such file or directory ... this guest has no radeon GPU"); pciconf lists zero 1002: devices. Execution requires radeon hardware with the VCE block (SI/CIK asics) β€” not present in the QEMU guest, matching the audit precedent for GPU findings (cf. DF-4447). Deterministic panic when run on affected hardware; no memory-corruption primitive (NULL fixed offset read on x86-64 with the NULL page unmapped β†’ page fault β†’ panic).

Fix

fix.diff adds the same guard radeon_uvd_cs_parse has, at the head of radeon_vce_cs_parse. fix_status=not_testable (no radeon GPU on the audit guest).

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

fix.diff authored (UVD-equivalent guard); no radeon hardware on the audit guest, so no kernel build/run validation is possible.

findings/poc/DF-4608/fix.diff
↓ fix.diffper-fix-DF-4608

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-4608/VERDICT.md (six-step path:line proof chain); vce_null_relocs.c (raw-ioctl trigger, no libdrm); build.log (clean guest build); run.log (hardware-absence proof)

PoC changes

n/a (first evidence pack; trigger authored from scratch against the DF radeon source with hand-rolled _IOWR ioctl, no libdrm)

Verified recommended fix

Add 'if (p->chunk_relocs == NULL) { DRM_ERROR("No relocation chunk !\n"); return -EINVAL; }' at the head of radeon_vce_cs_parse(), mirroring radeon_uvd.c:705-709 (see fix.diff).

Verdict

radeon_vce_cs_reloc() dereferences p->chunk_relocs (NULL when the DRM_IOCTL_RADEON_CS submission omits the RADEON_CHUNK_ID_RELOCS chunk, which radeon_cs_parser_init accepts and radeon_cs_parser_relocs silently tolerates by returning 0) at radeon_vce.c:482 -> fixed-offset NULL read -> kernel panic. Deterministic single-ioctl local DoS for any user with DRM node access on SI/CIK radeon hardware with VCE. Both sibling paths guard this exact case (generic radeon_cs.c:846-850 and radeon_uvd.c:705-709 print 'No relocation chunk !' and return -EINVAL); the VCE parser simply forgot. The audit guest has no radeon GPU (no /dev/dri, zero 1002: PCI devices), so the trigger could not be executed -- trigger builds clean on the guest and is provided ready for affected hardware; code-trace certainty per VERDICT.md.