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

cayman_init dropped the CP1/CP2 compute-ring r600_ring_init (upstream and in-tree si.c/cik.c have them) β€” cayman_cp_resume evaluates order_base_2(0) = 1UL<<64 (C-standard UB) and programs CP_RB1/CP_RB2_CNTL/BASE with zero-size garbage on every Cayman/Aruba boot

Summary

ni.c inits only GFX/DMA0/DMA1 rings; CP1/CP2 never get r600_ring_init, yet cayman_cp_resume iterates them: order_base_2(0) expands to 1UL<<64 (undefined shift) then writes derived RB_BUFSZ and CP_RB1/2_BASE=0 to the GPU. In-tree proof of divergence (si.c/cik.c init their compute rings; upstream ni.c does too). No attacker (boot path only, memory-safe consumers reject), but C-UB, wrong MMIO config, and compute permanently unusable on this family. fix.diff restores the dropped blocks verbatim.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3986 Β· 5 files
FileTypeDescriptionSize
README.md β€” 2.5 KB ↓ raw
VERDICT.md β€” 2.8 KB ↓ raw
fix.diff β€” 752 B view raw
manifest.json β€” 1.3 KB view raw
verdict.json β€” 2.8 KB view raw

DF-3986 β€” drm/radeon ni.c: port dropped the CP1/CP2 compute-ring r600_ring_init from cayman_init β€” order_base_2(0) UB (1UL << 64) and garbage compute-ring MMIO programming at every Cayman/Aruba boot

Where

sys/dev/drm/radeon/ni.c:2434-2443 (cayman_init):

    ring = &rdev->ring[RADEON_RING_TYPE_GFX_INDEX];
    ring->ring_obj = NULL;
    r600_ring_init(rdev, ring, 1024 * 1024);

    ring = &rdev->ring[R600_RING_TYPE_DMA_INDEX];      /* CP1/CP2 inits MISSING */
    ring->ring_obj = NULL;
    r600_ring_init(rdev, ring, 64 * 1024);
    ...

Upstream Linux ni.c (cayman_init) initializes five rings β€” GFX, CP1, CP2 (1 MB each), DMA0, DMA1. In-tree corroboration that compute rings must be initialized: si.c:6876-6894 (SI) and cik.c:8655-8679 (CIK) both call r600_ring_init for CAYMAN_RING_TYPE_CP1/2_INDEX. The DFly port kept the consumers but dropped the producers:

  • cayman_cp_resume (ni.c:1698-1734) loops over {GFX, CP1, CP2} and computes rb_cntl = order_base_2(ring->ring_size / 8) (ni.c:1704) β€” with ring_size == 0 this evaluates ilog2(roundup_pow_of_two(0)) β†’ 1UL << flsl(ULONG_MAX) β†’ 1UL << 64 (C-standard undefined shift; sys/dev/drm/include/linux/log2.h:33-36, 100). It then programs CP_RB1_CNTL/CP_RB2_CNTL with the resulting RB size field and CP_RB1/2_BASE = ring->gpu_addr >> 8 = 0 (ni.c:1713-1721) β€” garbage compute-ring configuration written to real silicon at every Cayman/Aruba boot.
  • radeon_fence_driver_start_ring(CP1/CP2) is still called (ni.c:2225-2235) and cayman_cp_resume explicitly leaves the rings ready = false (ni.c:1739-1740).

Why impact stays Info

  • Userspace cannot select CP1/CP2 on this family: radeon_cs_get_ring maps RADEON_CS_RING_COMPUTE to CAYMAN_RING_TYPE_CP{1,2}_INDEX only for family >= CHIP_TAHITI (radeon_cs.c:218-224); CAYMAN/ARUBA map COMPUTE to the GFX ring.
  • Even if something queued to an uninitialized ring, radeon_ring_alloc rejects ndw > ring->ring_size/4 (radeon_ring.c:109-111), so no ring-buffer OOB write is reachable.
  • Residual impact: standards-UB executed in the boot path (currently benign 0 on x86 shl semantics) + wrong MMIO config for the compute rings (functional: compute unusable on Cayman/Aruba in this port).

Hardware-gated (needs a Cayman/Aruba GPU; the audit guest has none β€” see ../DF-3981/env.txt). Info severity, source-certain.

Fix

fix.diff β€” restore the two dropped blocks (verbatim upstream shape): r600_ring_init(rdev, &rdev->ring[CAYMAN_RING_TYPE_CP{1,2}_INDEX], 1024*1024).

VERDICT.md
↓ download raw

DF-3986 VERDICT β€” dropped CP1/CP2 ring init in cayman_init (ni.c)

Status: inconclusive (hardware-gated: no AMD Northern-Islands GPU on the audit guest β€” vgapci is QEMU std VGA, env evidence in ../DF-3981/env.txt). Source-level certainty: certain (divergence provable in-tree).

Divergence proof

Consumers of CP1/CP2 ring state in this file: - cayman_cp_resume ridx[] = {GFX, CP1, CP2} (ni.c:1636-1640) iterates all three: rb_cntl = order_base_2(ring->ring_size / 8) (ni.c:1704), WREG32(cp_rb_base[i], ring->gpu_addr >> 8) (ni.c:1720); - radeon_fence_driver_start_ring(CP1) / (CP2) (ni.c:2225, 2231); - ready = false for both at ni.c:1739-1740.

Producers: cayman_init calls r600_ring_init only for GFX (1 MB) and DMA0/DMA1 (64 KB) (ni.c:2434-2443). r600_ring_init (r600.c:2789-2807) is the only setter of ring->ring_size before resume; CP1/CP2 therefore reach cayman_cp_resume with ring_size == 0 and gpu_addr == 0.

Upstream Linux ni.c initializes all five rings (GFX/CP1/CP2 at 1 MB, DMA0/DMA1 at 64 KB) in cayman_init; the in-tree siblings si.c (si.c:6876-6894) and cik.c (cik.c:8655-8679) do the same for their compute rings. The DFly port dropped exactly the two CP blocks.

UB and misprogramming

order_base_2(n) = ilog2(roundup_pow_of_two(n)) (sys/dev/drm/include/linux/log2.h:100 and :33-36): roundup_pow_of_two(0) = 1UL << flsl(0 - 1) = 1UL << flsl(ULONG_MAX) = 1UL << 64 β€” shift count equals the width of unsigned long: undefined behavior per C11 6.5.7. On x86-64 the compiler emits shl which masks the count mod 64, yielding 1 β†’ order_base_2(0) == 0 β†’ RB_BUFSZ field 0 with ring base 0 programmed into CP_RB1/CP_RB2 at every Cayman/Aruba boot (functionally meaningless config; compute rings never enabled since ready=false).

Why no memory-safety impact

  • radeon_cs_get_ring (radeon_cs.c:203-234): COMPUTE β†’ CP1/CP2 only for family >= CHIP_TAHITI; CAYMAN/ARUBA (< TAHITI) map COMPUTE to GFX.
  • radeon_ring_alloc (radeon_ring.c:104-137) fails -ENOMEM for any ndw > ring->ring_size/4, so a zero-sized ring cannot be written.
  • radeon_ring_supports_scratch_reg/debugfs only read the ring array.

Residual: C-standard UB in the boot path + wrong MMIO values + compute rings permanently unusable on this family in this port (upstream supports compute on Cayman). Info severity.

Fix validation

Not runnable (no AMD GPU in the guest; vgapci0 is 1234:1111 QEMU VGA). fix.diff restores the two upstream init blocks verbatim.

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 applies cleanly against sys/dev/drm/radeon/ni.c; cannot be kernel-build/boot validated without NI hardware.

fix.diff in pack; upstream-shape init blocks
↓ fix.diffper-fix-DF-3986

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (divergence proof, UB expansion, no-impact argument); fix.diff (restores the two upstream init blocks verbatim, patch --dry-run clean)

PoC changes

None possible: no AMD GPU on the guest; the finding is a provable source divergence.

Verified recommended fix

Restore the two dropped r600_ring_init blocks for CAYMAN_RING_TYPE_CP1/CP2_INDEX in cayman_init (fix.diff).

Verdict

cayman_init (ni.c:2434-2443) initializes only GFX/DMA0/DMA1 rings; the CP1/CP2 compute-ring r600_ring_init blocks present in upstream Linux ni.c (and mirrored in-tree by si.c:6876-6894 and cik.c:8655-8679) were dropped in the port. Consumers remained: cayman_cp_resume (ni.c:1636-1734) computes order_base_2(ring->ring_size/8) for CP1/CP2 with ring_size==0 (ni.c:1704), which expands to ilog2(roundup_pow_of_two(0)) = 1UL<<flsl(ULONG_MAX) = 1UL<<64 - undefined shift per C11 6.5.7 (log2.h:33-36,100) - and programs CP_RB1/CP_RB2 with RB_BUFSZ derived from it and base 0 (ni.c:1713-1721) on every Cayman/Aruba boot; fence start for CP1/CP2 still runs (ni.c:2225,2231). No kernel memory-safety impact: radeon_cs_get_ring maps COMPUTE to CP1/CP2 only for family >= CHIP_TAHITI (radeon_cs.c:218-224), and radeon_ring_alloc rejects ndw > ring_size/4 (radeon_ring.c:109-111). Info-severity porting divergence + boot-path UB; hardware-gated (guest has no AMD GPU - vgapci is QEMU 1234:1111, ../DF-3981/env.txt) so no runtime verification possible.