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

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,669 and sys/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.

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

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1972 Β· 3 files
FileTypeDescriptionSize
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
VERDICT.md verdict source-only confirmation + mechanism + fix
↓ download raw

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

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED: 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.
↓ fix.diffcombined build rc=0

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.