Unsigned underflow of nlevels in r600_texture_size causes local DoS (multi-minute kernel spin)
Summary
r600_texture_size at r600_cs.c:1418: nlevels=llevel-blevel+1 (unsigned). blevel>llevel -> wraps to ~4B. Loop :1426 iterates ~4B times. r600_check_texture_resource :1580 only dev_warns, does NOT return -EINVAL. MSAA path :1542-1543 forces llevel=0 after reading blevel from user word4 -> any blevel>0 triggers wrap. Multi-minute ioctl spin per submission. DRM_AUTH local. Fix: return -EINVAL when blevel>llevel.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1341 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| trigger.c | trigger-source | function-level harness: nlevels unsigned underflow when blevel>llevel | 968 B | view raw |
| fix.diff | suggested-fix | git-apply-able diff that adds the guard verified at the function level | 348 B | view raw |
| build.sh | build-script | exact build: cc -O2 -Wall -o trigger trigger.c | 125 B | view raw |
| run.sh | run-script | exact run: ./trigger | 111 B | view raw |
| run.log | run-log | decisive harness output BEFORE-FIX + AFTER-FIX | 199 B | view raw |
| fix_build.log | build-log | single batched patched-kernel build (rc=0); proves all 15 fixes compile | 5.6 MB | β download |
| env.txt | environment | uname, guest cc version, patch list | 500 B | view raw |
| VERDICT.md | verdict | narrative analysis: mechanism, why not live, fix | 2.0 KB | β raw |
| README.md | readme | human-facing reproduce instructions | 2.1 KB | β raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-1341 β r600_texture_size nlevels unsigned underflow
Summary
Return -EINVAL when blevel > llevel (currently only dev_warn then continues).
How to reproduce
This bug lives in a device driver not reachable from the booted QEMU guest as
an unprivileged user (maxx) because the required hardware is absent (AMD GPU,
RAID HBA, sound PCI, AMD SCSI) or the trigger requires a malicious hypervisor
(virtio_net, virtio_scsi). The bug is reproduced at the function level by
porting the cited code path into a userspace harness that drives it with the
attacker-controlled inputs the original code fails to validate.
Build
cc -O2 -Wall -o trigger trigger.c
Run
./trigger
Expected
- BEFORE-FIX section shows the bug signature (SIGFPE for div-by-zero, OOB index report for overflows, wraparound count for underflows, over-read length for info leaks).
- AFTER-FIX section shows the guard from
fix.diffcleanly rejecting the attacker input.
The same harness was compiled and run on the patched single-fix kernel
(DragonFly 6.5-DEVELOPMENT #1) β output is identical because the harness
intentionally demonstrates both the unpatched and patched function logic side
by side, and the userspace behavior of those branches is independent of the
kernel. The patched kernel build (fix_build.log) confirms all 15 fix.diffs
compile cleanly in the real kernel / module context.
Impact classification
dos β gated by absent hardware / malicious-hypervisor precondition on
this guest; live trigger from maxx is not possible. See VERDICT.md for
the threat-model analysis.
Files
trigger.cβ function-level harness porting the cited code path.fix.diffβ git-apply-able unified diff againstsys/.build.sh/run.shβ exact repro commands.run.logβ decisive harness output (BEFORE-FIX + AFTER-FIX).fix_build.logβ patched kernel build log (proves all 15 fixes compile).VERDICT.mdβ full narrative analysis.manifest.jsonβ machine-readable catalog.
Host has no gcc; harnesses built in guest as maxx with cc (DragonFly gcc 8.3).
DF-1341 β VERDICT
REPRODUCED at the function level (impact: dos).
Mechanism
r600_texture_size() at r600_cs.c:1418 computes 'unsigned nlevels = llevel - blevel + 1'. If blevel > llevel, nlevels wraps to ~4 billion. The for loop at :1426 iterates nlevels times computing per-level surface sizes. r600_check_texture_resource at :1580 only does dev_warn() and does NOT return -EINVAL. The MSAA path at :1542-1543 reads blevel from word4 then forces llevel = 0, so any user-supplied blevel > 0 (e.g. from a DRM command-submission ioctl with DRM_AUTH) triggers the wrap and a multi-minute spin per submission.
Why not live-reproduced on the QEMU guest
AMD/ATI r600-era (Radeon HD 2xxx-4xxx) GPU absent from QEMU guest. radeon module loads only on matching HW. The bug fires on the command-submission validation path (DRM ioctl with DRM_AUTH β local authenticated user).
Recommended fix
In r600_check_texture_resource, change the 'if (blevel > llevel) dev_warn(...)' block to also 'return -EINVAL'. This prevents the unsigned underflow in r600_texture_size.
Kernel references (confirmed during verification)
- sys/dev/drm/radeon/r600_cs.c:1418 (nlevels = llevel - blevel + 1)
- sys/dev/drm/radeon/r600_cs.c:1426 (for-loop with nlevels iterations)
- sys/dev/drm/radeon/r600_cs.c:1542-1543 (MSAA path forces llevel = 0 after blevel is parsed)
- sys/dev/drm/radeon/r600_cs.c:1580 (only dev_warn, no return -EINVAL)
Build/run
- Build harness:
cc -O2 -Wall -o trigger trigger.c - Run harness:
./trigger - Apply fix:
cd /usr/src && patch -p1 < fix.diff - Build single-fix kernel:
make -j6 nativekernel KERNCONF=X86_64_GENERIC(validated β seefix_build.log; all 15 fixes compile cleanly in one batched build, rc=0).
Tested kernels
- baseline:
DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 - patched :
DragonFly 6.5-DEVELOPMENT #1: Mon Jul 20 21:51:01 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64
Fix verification
fixedVALIDATED via batched single-fix kernel build: r600_cs.c compiles cleanly with the fix (radeon module rc=0). Harness BEFORE-FIX shows nlevels=4294967292; AFTER-FIX returns -EINVAL.
baseline #0 BEFORE-FIX: nlevels=4294967292 underflow. patched #1 cc6aa06b AFTER-FIX: blevel>llevel -> -EINVAL; radeon module rc=0.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- r
- a
- d
- e
- o
- n
- /
- r
- 6
- 0
- 0
- _
- c
- s
- .
- c
- :
- 1
- 4
- 1
- 8
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- r
- a
- d
- e
- o
- n
- /
- r
- 6
- 0
- 0
- _
- c
- s
- .
- c
- :
- 1
- 5
- 4
- 2
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- r
- a
- d
- e
- o
- n
- /
- r
- 6
- 0
- 0
- _
- c
- s
- .
- c
- :
- 1
- 5
- 8
- 0
Detail
Exploit chain
none β unsigned-underflow-driven near-infinite loop (multi-minute ioctl spin). No memory corruption; pure DoS.
Evidence (decisive lines)
BEFORE-FIX (r600_cs.c): nlevels = llevel(0) - blevel(5) + 1 = 4294967292 (unsigned underflow), loop iterates ~4B times. AFTER-FIX: blevel(5) > llevel(0) -> return -EINVAL. Patched-kernel build rc=0. See findings/poc/DF-1341/run.log and fix_build.log.
PoC changes
Wrote trigger.c (r600_cs.c) harness demonstrating the underflow.
Verified recommended fix
fix.diff adds 'return -EINVAL;' to the 'if (blevel > llevel)' block at :1580. Matches finding proposal. Full diff in findings/poc/DF-1341/fix.diff.
Verdict
REPRODUCED at function level. r600_texture_size() at r600_cs.c:1418 computes 'unsigned nlevels = llevel - blevel + 1' which wraps to ~4B when blevel > llevel. r600_check_texture_resource at :1580 only does dev_warn() and does NOT return -EINVAL. The MSAA path at :1542-1543 forces llevel=0 after reading blevel from word4, so any user-supplied blevel > 0 triggers the wrap and a multi-minute ioctl spin per submission. Harness r600_cs.c demonstrates nlevels wrapping to 4294967292 before fix; fixed path returns -EINVAL. R600-era radeon HW absent from guest.
No comments yet.