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)
PoC verification
Evidence pack
findings/poc/DF-3993 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 β includingcayman_asic(radeon_asic.c:1686-1693), whose VM manager is enabled, so even theRADEON_CS_USE_VMbranch (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.
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)
-
User control of the ring index.
radeon_cs_parser_init()reads the user FLAGS chunk:ring = p->chunks[i].kdata[1]atsys/dev/drm/radeon/radeon_cs.c:357.radeon_cs_get_ring()(radeon_cs.c:204-245) mapsRADEON_CS_RING_UVD(3)βR600_RING_TYPE_UVD_INDEX(5) andRADEON_CS_RING_VCE(4)βTN_RING_TYPE_VCE1_INDEX(6) with no family/has_uvd/has_vcegate β compare the DMA case, which does gate by family (radeon_cs.c:224-234), proving the omission is not a design invariant. -
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). -
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_parseis the 5th pointer instruct 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). -
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_testablefix.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/)
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.
No comments yet.