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

User-selected UVD/VCE CS ring dereferences NULL asic->ring[] slot on asic families whose dispatch table lacks the ring β€” kernel panic from unprivileged CS ioctl

Summary

radeon_cs_parser_init dereferences p->rdev->asic->ring[p->ring]->cs_parse / ->ib_parse where p->ring comes from the user-supplied FLAGS chunk via radeon_cs_get_ring, whose RADEON_CS_RING_UVD/VCE cases have no family gating. The hub tables leave those slots NULL (ring[UVD]=NULL on all pre-RV610 + R600; ring[VCE1]=NULL on every table except trinity/si/ci/kv, including cayman whose VM manager is enabled so even the USE_VM branch crashes). cs_parse is at offset 0x20 -> read at 0x20 -> fatal page fault, inside parser_init before any handle lookup: one FLAGS chunk {0,3} suffices. Unprivileged via render node (no master needed). Affects R100..Cayman (VCE) / R100..R600 (UVD). Panic/DoS only.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3993 Β· 11 files
FileTypeDescriptionSize
README.md β€” 3.5 KB ↓ raw
VERDICT.md β€” 3.2 KB ↓ raw
cs_nullring.c β€” 3.7 KB view raw
build.sh β€” 90 B view raw
run.sh β€” 96 B view raw
build.log β€” 383 B view raw
run.log β€” 383 B view raw
env.txt β€” 558 B view raw
fix.diff β€” 2.1 KB view raw
verdict.json β€” 3.1 KB view raw
manifest.json β€” 1.0 KB view raw

DF-3993 β€” CS ioctl ring dispatch β†’ NULL asic->ring[] slot deref (kernel panic)

Finding

radeon_cs_parser_init() dereferences p->rdev->asic->ring[p->ring] at sys/dev/drm/radeon/radeon_cs.c:376 (non-VM path) and :381 (VM path) before any check that the ring slot exists for this asic family. p->ring is chosen by radeon_cs_get_ring() (radeon_cs.c:204-245) from the second dword of the user-supplied RADEON_CHUNK_ID_FLAGS chunk (radeon_cs.c:354-357), and the UVD / VCE cases perform no family gating:

case RADEON_CS_RING_UVD:                       /* radeon_cs.c:236 */
        p->ring = R600_RING_TYPE_UVD_INDEX;    /* = 5 */
        break;
case RADEON_CS_RING_VCE:                       /* radeon_cs.c:239 */
        p->ring = TN_RING_TYPE_VCE1_INDEX;     /* = 6 */
        break;

The dispatch tables in the audited hub file sys/dev/drm/radeon/radeon_asic.c only populate the ring slots that exist per family. Every designated initializer leaves unlisted indices NULL:

  • r100_asic (radeon_asic.c:214-216), r200_asic (282-284), r300_asic (378-380), r300_asic_pcie (446-448), r420_asic (514-516), rs400_asic (582-584), rs600_asic (650-652), rs690_asic (718-720), rv515_asic (786-788), r520_asic (854-856): only [RADEON_RING_TYPE_GFX_INDEX].
  • r600_asic (radeon_asic.c:950-953): GFX + DMA only β†’ [R600_RING_TYPE_UVD_INDEX] (5) and [TN_RING_TYPE_VCE1_INDEX] (6) NULL.
  • rv6xx_asic … cayman_asic: UVD (5) wired, VCE (6/7) NULL β€” including cayman_asic (radeon_asic.c:1686-1693), whose VM manager is enabled, so even the RADEON_CS_USE_VM branch (radeon_cs.c:381) dereferences the NULL slot.
  • Only trinity_asic (1804-1813), si_asic (1942-1951), ci_asic (2112-2121), kv_asic (2225-2234) wire the VCE rings.

struct radeon_asic_ring (sys/dev/drm/radeon/radeon.h:1821-1847) places cs_parse at offset 0x20 (5th pointer on LP64), so the unchecked asic->ring[5]->cs_parse is a kernel read at absolute address 0x20 β†’ fatal page fault β†’ kernel panic.

Crash matrix (user-selectable)

user ring id asic family crash site
RADEON_CS_RING_UVD (3), no USE_VM r100 … r600 (all pre-RV610 + R600) radeon_cs.c:376
RADEON_CS_RING_VCE (4), no USE_VM every family except TN/SI/CI/KV radeon_cs.c:376
RADEON_CS_RING_VCE (4), USE_VM cayman (VM enabled, VCE ring NULL) radeon_cs.c:381

(UVD+VM on pre-cayman is rejected earlier at radeon_cs.c:365-369 because the VM manager is disabled there.)

Reachability

RADEON_CS ioctl is registered DRM_AUTH|DRM_RENDER_ALLOW (sys/dev/drm/radeon/radeon_kms.c:912) β€” same attack surface as DF-3763 / DF-3953 (unprivileged local user with access to the DRI device: render node by design needs no master; on a master-less primary node any user can become DRM master and authenticate). The crash happens inside radeon_cs_parser_init() β€” before any GEM handle lookup or IB validation β€” so the ioctl needs only a well-formed FLAGS chunk: {chunk_id=3, length_dw=2, chunk_data={flags=0, ring=3}}.

Reproduce (real hardware only)

cc -O2 -o cs_nullring cs_nullring.c
./cs_nullring            # on any pre-RV610 / R600 radeon with radeonkms
# expected: instant kernel panic, "Fatal kernel mode data access: read at 0x20"

Guest status

Not reproducible on the audit QEMU guest: it has no /dev/dri, no DRM kmod, and QEMU provides no radeon VGA device model at all (see env.txt). Status: inconclusive β€” source-level certainty, hardware-gated.

VERDICT.md
↓ download raw

DF-3993 β€” VERDICT

Status: inconclusive (source-certain, hardware-gated) β€” kernel NULL-deref panic (local DoS), unprivileged, on radeon-equipped systems.

What was verified (source level, line-by-line)

  1. User control of the ring index. radeon_cs_parser_init() reads the user FLAGS chunk: ring = p->chunks[i].kdata[1] at sys/dev/drm/radeon/radeon_cs.c:357. radeon_cs_get_ring() (radeon_cs.c:204-245) maps RADEON_CS_RING_UVD(3)β†’ R600_RING_TYPE_UVD_INDEX(5) and RADEON_CS_RING_VCE(4)β†’ TN_RING_TYPE_VCE1_INDEX(6) with no family/has_uvd/has_vce gate β€” compare the DMA case, which does gate by family (radeon_cs.c:224-234), proving the omission is not a design invariant.

  2. The dispatch hub leaves those slots NULL. In the audited file sys/dev/drm/radeon/radeon_asic.c, the per-family .ring[] designated initializers only set existing rings; all other slots are NULL: - r100/r200/r300(+pcie)/r420/rs400/rs600/rs690/rv515/r520 tables: GFX only (radeon_asic.c:214-216, 282-284, 378-380, 446-448, 514-516, 582-584, 650-652, 718-720, 786-788, 854-856); - r600_asic: GFX+DMA, UVD/VCE NULL (radeon_asic.c:950-953); - cayman_asic: VCE NULL while VM is enabled (radeon_asic.c:1686-1693); - only trinity/si/ci/kv wire VCE (1804-1813, 1942-1951, 2112-2121, 2225-2234).

  3. Unchecked deref before the op-level NULL check. radeon_cs.c:375-386: c if ((p->cs_flags & RADEON_CS_USE_VM) == 0) { if (p->rdev->asic->ring[p->ring]->cs_parse == NULL) { /* line 376 */ The NULL check protects the op inside the ring struct β€” not the ring slot itself. cs_parse is the 5th pointer in struct radeon_asic_ring (sys/dev/drm/radeon/radeon.h:1821-1847, offset 0x20 LP64) β†’ kernel read at absolute address 0x20 β†’ fatal page fault. Same shape on the VM branch, line 381 (->ib_parse, offset 0x1c).

  4. Reachability. DRM_IOCTL_DEF_DRV(RADEON_CS, radeon_cs_ioctl, DRM_AUTH|DRM_RENDER_ALLOW) (sys/dev/drm/radeon/radeon_kms.c:912) β€” the exact surface of DF-3763/DF-3953 (unpriv local user + DRI node access). The crash precedes all GEM/IB validation, so a single FLAGS chunk {0, 3} suffices; no buffers or handles needed.

Why not "reproduced"

No radeon hardware exists on the audit guest and QEMU has no radeon device model (evidence: env.txt β€” no /dev/dri, no DRM kmod, only virtio-VGA 0x1234:1111). Same posture as the accepted DF-3763/DF-3953 verdicts (inconclusive/panic, source-certain).

Impact ceiling

NULL+0x20 read whose result only feeds a comparison β€” the outcome is a kernel panic (local DoS), not control flow or memory corruption beyond the fault. No route to uid=0 from this primitive.

Fix

Gate UVD/VCE ring selection by family/feature in radeon_cs_get_ring() and add a NULL-slot guard before the member deref in radeon_cs_parser_init() (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

fix.diff adds the family gates and a NULL-slot guard (radeon_cs.c); not buildable/testable here because the guest has no radeon hardware or DRM kmod to build the module against β€” kernel-build validation requires a radeon-equipped target.

findings/poc/DF-3993/fix.diff (unified diff, never applied to sys/)
↓ fix.diffper-fix-DF-3993

Confirmed kernel references

Detail

Evidence (decisive lines)

README.md (crash matrix + table citations), VERDICT.md (line-by-line trace), cs_nullring.c (working trigger for real hw: single FLAGS chunk {0, RADEON_CS_RING_UVD}), build.log (guest toolchain BUILD OK + absent /dev/dri), env.txt (no radeon device), fix.diff (family gates + NULL-slot guard)

PoC changes

n/a (seed authored during this run; layout verified against sys/dev/drm/include/uapi/drm/radeon_drm.h:950-991)

Verified recommended fix

Gate UVD/VCE ring ids by family in radeon_cs_get_ring() and check asic->ring[p->ring] != NULL before the ->cs_parse/->ib_parse deref in radeon_cs_parser_init()

Verdict

Source-certain NULL-deref kernel panic, hardware-gated: radeon_cs_parser_init() dereferences asic->ring[p->ring] (radeon_cs.c:376/381) where p->ring comes from the user FLAGS chunk via radeon_cs_get_ring() (radeon_cs.c:236-242) with no family gating, while the per-family dispatch tables in radeon_asic.c (r100..r600: ring[R600_RING_TYPE_UVD_INDEX]=NULL, all families except TN/SI/CI/KV: ring[TN_RING_TYPE_VCE1_INDEX]=NULL) leave those slots NULL. cs_parse sits at offset 0x20 of struct radeon_asic_ring -> kernel read at 0x20 -> fatal page fault before any GEM/IB validation, reachable by an unprivileged local user through DRM_IOCTL_RADEON_CS (DRM_AUTH|DRM_RENDER_ALLOW, radeon_kms.c:912), the same surface as DF-3763/DF-3953. Cannot be reproduced on the audit guest: no /dev/dri, no DRM kmod, and QEMU has no radeon device model at all (env.txt); trigger compiles clean on the guest toolchain (build.log) and exits reporting the absent device.