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)
PoC verification
Evidence pack
findings/poc/DF-4357 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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.
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 oninfo->read_mmr_reg.countbeforekmalloc_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 ismin(size, alloc_size). Thealloc_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.cincluding-> 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_testablefix.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)
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).
No comments yet.