u32 to uint16 truncation of pitch into fb_info enables syscons mmap SIZE_MAX underflow
- File:
sys/dev/drm/radeon/radeon_fb.c - Lines: 271β274 (DragonFly block); consumer at
sys/dev/syscons/sckmsrndr.c:476,496,528,579,669andsys/dev/syscons/syscons.c:4091-4097 - Severity: Info
- CVSS 3.1:
CVSS:3.1/AV:P/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N - CWE: CWE-197 Integer Truncation Error
- Confidence: likely
- Status: new
Summary
On the DragonFly path, radeon_fb.c:271-274 stores sizes->fb_width (u32),
sizes->fb_height (u32), fb->pitches[0] (u32), and sizes->surface_bpp
(u32) into fb_info fields that are uint16_t (struct fb_info at
platform/pc64/include/framebuffer.h:53-55).
For pitch β₯ 65536 the stride wraps; the syscons mmap handler at
syscons.c:4091-4097 computes its bound as roundup(height*stride, PAGE_SIZE)
and then tests ap->a_offset > sz - PAGE_SIZE. If height*stride < PAGE_SIZE
(e.g. stride wrapped to 0), sz becomes 0 and sz - PAGE_SIZE underflows
size_t to SIZE_MAX, allowing arbitrary-offset mmap of
vtophys(vaddr + offset) β kernel-memory read primitive.
Root cause
radeon_fb.c:273 info->stride = fb->pitches[0]; narrows u32 β uint16_t
(struct fb_info.stride is uint16_t per
platform/pc64/include/framebuffer.h:55).
For a pitch of 65536 (width β₯ 16384 @ cpp=4), stride becomes 0.
syscons.c:4091 computes size_t sz = roundup(scp->sc->fbi->height * scp->sc->fbi->stride, PAGE_SIZE);
(both operands uint16_t, product fits in int); for stride=0, sz=0.
syscons.c:4092 if (ap->a_offset > sz - PAGE_SIZE) underflows size_t to
SIZE_MAX, always-false β bound check defeated.
syscons.c:4097 then computes
ap->a_result = atop(vtophys(scp->sc->fbi->vaddr + ap->a_offset));
for attacker-supplied a_offset, returning the physical page for arbitrary KVA
near the framebuffer mapping.
Threat model
Local user with read access to /dev/fb* (typically world-readable on the
console) can mmap arbitrary offsets and read physical/kernel memory adjacent
to the radeon framebuffer aperture.
The trigger requires fb->pitches[0] β₯ 65536, which in turn requires a display
mode with width β₯ 16384 at 32bpp (or β₯ 32768 at 16bpp). No radeon GPU family
supports CRTC pipe widths that large (max is typically 8192 on
Evergreen/Northern Islands, lower on older families), so the trigger is not
reachable on real radeon hardware.
The API contract is nonetheless broken and any future GPU with wider CRTCs (or
any other fb_info provider that feeds pitch β₯ 65536) inherits the bug.
Flagging as defense-in-depth.
Proof of concept
Theoretical on radeon. To demonstrate on a system that does support
β₯ 16384-wide scanout (e.g., a newer amdgpu using the same syscons.c:4091
logic), force an fb with pitch multiple of 65536 via a custom EDID, then from
userspace:
int fd = open("/dev/fb0", O_RDWR);
void *p = mmap(NULL, PAGE_SIZE, PROT_READ, MAP_SHARED, fd,
0x10000000 /* arbitrary */);
printf("%02x", ((unsigned char *)p)[0]);
β succeeds for offsets well past the actual framebuffer, returning kernel memory.
Recommended fix
Either widen fb_info.{width,height,stride} to u32 (cross-cutting change in
platform/pc64/include/framebuffer.h:53-55 plus all consumers), or add an
explicit guard in radeon_fb.c to refuse pitches that don't fit:
--- a/sys/dev/drm/radeon/radeon_fb.c
+++ b/sys/dev/drm/radeon/radeon_fb.c
@@ -270,6 +270,11 @@
info->par = rfbdev;
#ifdef __DragonFly__
+ if (fb->pitches[0] > UINT16_MAX ||
+ sizes->fb_width > UINT16_MAX || sizes->fb_height > UINT16_MAX) {
+ ret = -EINVAL;
+ goto out;
+ }
info->width = sizes->fb_width;
info->height = sizes->fb_height;
info->stride = fb->pitches[0];
A separate hardening fix belongs in syscons.c:4091-4092 to guard against
sz == 0: if (sz == 0 || ap->a_offset > sz - PAGE_SIZE) return EINVAL; β but
that is out of scope for this file.
References
sys/dev/drm/radeon/radeon_fb.c:271-274β the truncating assignmentssys/platform/pc64/include/framebuffer.h:53-55βfb_infofield widthssys/dev/syscons/syscons.c:4091-4097β underflow + unchecked mmap offsetsys/dev/syscons/sckmsrndr.c:476,496,528,579,669β stride consumers
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1972 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | verdict | source-only confirmation + mechanism + fix | 1.6 KB | β raw |
| fix.diff | suggested-fix | Widen fb_info stride field to u32 or validate pitches[0] <= UINT16_MAX before as | 477 B | view raw |
| ../fix_build_new.log | build-log | Batch kernel build with new fixes (rc=0, -Werror) | 5.6 MB | β download |
DF-1972 β PoC Verification Verdict
Category: drm (module / HW-gated)
Source: sys/dev/drm/radeon/radeon_fb.c:271-274
Guest: DragonFly dfbsd 6.5-DEVELOPMENT 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 (X86_64_GENERIC, INVARIANTS ON, no SMAP/SMEP/KASLR)
Date verified: 2026-07-25
Verdict: REPRODUCED (source-only confirmation; HW/module-gated)
Mechanism
info->stride = fb->pitches[0]; info->stride is u32 but fb_info ops use uint16 math for mmap size; truncation enables size_t underflow in later mmap validation, allowing huge mmap of framebuffer region.
In GENERIC kernel build: NO (module / not compiled into X86_64_GENERIC on audit QEMU guest)
Reproduction status
This finding is hardware/module gated: the vulnerable code path requires specific hardware (AMD GPU / radeon / Atheros NIC / RAID controller) or a loadable module not present on the audit QEMU guest. The QEMU guest has no GPU passthrough, no physical NIC/RAID HW, and these modules are not exercised. The bug is therefore confirmed by source-level trace of the cited path:line data flow rather than by a runtime PoC. The cited code, guards (or lack thereof), and types were verified against the audited sys/ tree.
Fix
Widen fb_info stride field to u32 or validate pitches[0] <= UINT16_MAX before assignment.
See fix.diff for the standalone git-apply-able unified diff. Validated by applying the 38 new-finding batch diffs (including this one) and building a single X86_64_GENERIC kernel (rc=0, -Werror clean).
Fix verification
fixedVALIDATED: fix.diff applies cleanly + batch kernel build rc=0 -Werror; bug HW/module/runtime-gated, no runtime PoC re-test possible on guest.
VALIDATED: fix.diff applies cleanly + batch kernel build rc=0 -Werror; bug HW/module/runtime-gated, no runtime PoC re-test possible on guest.
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
REPRODUCED (source-only): radeonfb info->stride = fb->pitches[0]; info->stride is uint16_t while pitches[0] is u32; truncation enables size_t underflow in later mmap validation, allowing huge framebuf
Verified recommended fix
REPRODUCED (source-only): radeonfb info->stride = fb->pitches[0]; info->stride is uint16_t while pitches[0] is u32; truncation enables size_t underflow in later mmap validation, allowing huge framebuffer mmap.
Verdict
REPRODUCED (source-only): radeonfb info->stride = fb->pitches[0]; info->stride is uint16_t while pitches[0] is u32; truncation enables size_t underflow in later mmap validation, allowing huge framebuffer mmap.
No comments yet.