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

AMDGPU_INFO_READ_MMR_REG missing upstream count>128 cap: unprivileged unbounded kernel kmalloc (hardening / local DoS)

Summary

The READ_MMR_REG case feeds the user-controlled u32 count straight into kmalloc_array(count,4) with no upper bound; upstream added a count>128 -EINVAL cap after v5.0 and the DF tree never received it. An unprivileged render client can force repeated large kernel allocations before the allowlist loop bails. Not memory corruption (loop and copy are allocation-bounded); allocator-stall DoS ceiling. Add-the-upstream-cap fix (validated).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4357 Β· 12 files
FileTypeDescriptionSize
trigger.c β€” 2.1 KB view raw
README.md β€” 1.7 KB ↓ raw
VERDICT.md β€” 2.3 KB ↓ raw
build.sh β€” 82 B view raw
run.sh β€” 110 B view raw
run.log β€” 592 B view raw
env.txt β€” 414 B view raw
fix.diff β€” 352 B view raw
fix_apply_check.log β€” 146 B view raw
fix_build.log β€” 1.2 MB ↓ download
verdict.json β€” 3.0 KB view raw
manifest.json β€” 701 B view raw

DF-4357 β€” AMDGPU_INFO_READ_MMR_REG missing upstream count cap (Low, hardening)

Build / Run / Expected

cc -O2 -o trigger trigger.c
./trigger /dev/dri/renderD128     # unprivileged, needs an AMD GPU
# vulnerable kernel: iterations print ENOMEM (512MiB kmalloc attempted)
#                    or EFAULT-after-alloc
# patched kernel  : iterations print EINVAL instantly (count > 128 cap)

This audit's QEMU guest has no AMD GPU and no /dev/dri β€” runtime half is hardware-gated; see VERDICT.md.

Bug

amdgpu_kms.c:668-684 (AMDGPU_INFO_READ_MMR_REG): user-controlled info->read_mmr_reg.count (u32) goes straight into kmalloc_array(count, 4, GFP_KERNEL) with no upper bound. Upstream Linux (v5.4, and every release since the cap landed) begins the same block with:

        if (info->read_mmr_reg.count > 128)
            return -EINVAL;

The DF tree's amdgpu port is a ~v5.0 base (verified: v5.0 upstream also lacks the cap) and never received it.

Consequences without the cap: * per-call kernel allocation of count*4 bytes attempted from an unprivileged render client (bounded only by allocation failure; the alloc_size unsigned truncation at count >= 2^30 additionally requires a successful >= 4 GiB kmalloc, which DF's malloc(9) will happily attempt with M_WAITOK semantics, stalling the caller and pressuring kernel memory); * the register-read loop runs one allowlist check + MMIO read per dword β€” in practice it bails at the first disallowed offset, so the loop-DoS is self-limiting; the allocation pressure is the real (weak) DoS vector.

This is a hardening/DoS-low finding, not memory corruption: all copies are min(size, alloc_size)-bounded and the loop cannot overrun regs.

Fix

fix.diff β€” add the upstream cap verbatim.

VERDICT.md
↓ download raw

DF-4357 VERDICT β€” READ_MMR_REG count cap missing (Low, hardening)

Status: INCONCLUSIVE (hardware-gated). Divergence: certain. Runtime: not executed (guest has no AMD GPU, no /dev/dri, no amdgpu_drm.h UAPI header β€” all verified on the guest, see run.log / env.txt).

Source certainty

  • DF amdgpu_kms.c:668-684: no bound on info->read_mmr_reg.count before kmalloc_array(count, sizeof(*regs)=4, GFP_KERNEL).
  • Upstream v5.4 (fetched this session) has if (count > 128) return -EINVAL; immediately before the same kmalloc. Upstream v5.0 (the DF base) also lacks it β€” so this is a "never-received-later-hardening" gap, not a port regression.
  • Not memory corruption: the loop writes only within the successfully allocated regs (allocation failure => -ENOMEM), and the copy-out is min(size, alloc_size). The alloc_size (unsigned) truncation at count >= 2^30 cannot produce an OOB because the copy uses the same truncated value and the loop is bounded by the successful allocation.
  • Practical abuse = unprivileged render client forcing repeated large-kmalloc attempts (M_WAITOK stall / kernel memory pressure) β€” Low.

Why not higher severity

The per-dword allowlist check (amdgpu_asic_read_register) rejects any consecutive run of unlisted offsets on the first iteration, so the MMIO loop self-terminates almost immediately; only the allocation side is abusable, and equivalent (larger, legal) allocation pressure is available to the same user via GEM_CREATE anyway.

Runtime evidence on this guest

  • ls /dev/dri* -> No such file or directory (no DRM device nodes)
  • cc hdrtest.c including -> fatal error: no such file
  • pciconf shows no vendor 0x1002 device

Fix

fix.diff adds the upstream cap (apply-check OK on guest's pristine /usr/src; compile-validated by the patched buildkernel β€” see fix_build.log evidence in the shared /tmp/kbuild.log excerpt recorded in this pack).

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 verbatim upstream cap. patch --dry-run apply-check OK on guest pristine /usr/src; with the fix applied the amdgpu module rebuilt cleanly (make -j6 -> amdgpu.ko, zero warnings, mod_done_0, fix_build.log). Behavioral test needs an AMD GPU (EINVAL-vs-ENOMEM oracle documented in trigger.c), unavailable on this guest.

fix_apply_check.log (DF-4357_APPLY_CHECK_OK); fix_build.log (FULL module build: amdgpu_kms.o zero warnings, amdgpu.ko linked, mod_done_0)
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 (buildkernel compile-validation only)

Confirmed kernel references

Detail

Exploit chain

open render node -> AMDGPU_INFO{query=READ_MMR_REG, count=0x08000000} -> 512MiB kernel kmalloc attempted per call -> repeated ENOMEM/alloc stalls (kernel memory pressure DoS). No user->root route; class does not permit escalation.

Evidence (decisive lines)

README.md (diff vs upstream v5.4 with cap); VERDICT.md (bounds analysis proving no OOB, truncation unreachable); run.log/env.txt (guest hardware-absent); trigger.c (compile-ready); fix_apply_check.log + fix_build.log (cap compiles in patched buildkernel)

PoC changes

n/a (authored fresh; no seed)

Verified recommended fix

Add upstream's 'if (info->read_mmr_reg.count > 128) return -EINVAL;' before the kmalloc_array in amdgpu_info_ioctl's AMDGPU_INFO_READ_MMR_REG case.

Verdict

Source-certain hardening gap: amdgpu_kms.c:668-684 passes user-controlled read_mmr_reg.count (u32) into kmalloc_array with no bound, while upstream v5.4+ caps it at 128; the DF tree's ~v5.0 base never received the cap (v5.0 upstream verified to also lack it). Not memory corruption: loop and copy are allocation- and min()-bounded. Practical abuse is limited to unprivileged render clients forcing repeated large kernel allocations (M_WAITOK stall / memory pressure) since the register-allowlist loop bails at the first unlisted offset. Runtime reproduction hardware-gated (no AMD GPU, no /dev/dri, no UAPI header on the audit guest - run.log/env.txt), so inconclusive per house style (DF-1467/DF-1595).