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

radeon fb creation never validates framebuffer size against the backing GEM object β€” display engine scans out megabytes past a tiny BO, disclosing adjacent VRAM on the physical display

Summary

The DRM core validates format/width/height/pitch but never resolves handles or compares image size to the buffer object; radeon_user_framebuffer_create and radeon_framebuffer_init fill the fb with no size check either - nothing between addfb2 and scanout/pin ever compares pitch*height against the BO. A 16KB dumb buffer backs a 1920x1080 XRGB8888 fb (8,294,400B) and the display engine reads ~8MB past the object end - adjacent VRAM contents (other processes' buffers) appear on screen. Unprivileged with DRM master. Info-leak ceiling (visual). amdgpu_display.c:523 carries the identical gap (follow-up). Per-plane min-size fix (validated -Werror).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4181 Β· 8 files
FileTypeDescriptionSize
VERDICT.md verdict verification verdict + full path:line trace 3.5 KB ↓ raw
rfb_oob.c trigger CREATE_DUMB 64x64 + ADDFB2 1920x1080 on the tiny BO 3.6 KB view raw
build.sh build build command 87 B view raw
run.sh run run command 144 B view raw
run.log runlog guest run: open /dev/dri/card0 ENOENT (no GPU) 162 B view raw
env.txt env guest environment 621 B view raw
fix.diff suggested-fix per-plane BO min-size check in radeon_framebuffer_init (git-apply-able) 1.9 KB view raw
verdict.json verdict machine verdict 2.9 KB view raw
VERDICT.md verdict verification verdict + full path:line trace
↓ download raw

DF-4181 β€” Verification Verdict

Status: inconclusive (hardware-gated: no radeon GPU in the QEMU guest) Impact: none on this guest (display-engine OOB read past a GEM object on affected hardware β€” info disclosure of adjacent VRAM to the physical display) Confidence: certain (absence of any size validation provable from source)

Mechanism (source trace)

  1. DRM_IOCTL_MODE_ADDFB2 β†’ drm_mode_addfb2 β†’ drm_internal_framebuffer_create (drm_framebuffer.c:277). framebuffer_check() (drm_framebuffer.c:165-275) validates: format known (via __drm_format_info), width/height within mode_config min/max, width*cpp <= UINT_MAX, height*pitches[i] + offsets[i] <= UINT_MAX (:208), pitches[i] >= width*cpp (:211). It never looks at the buffer object at all (it doesn't have it β€” handles are resolved by the driver's fb_create).
  2. radeon_user_framebuffer_create() (radeon_display.c:1311-1348): drm_gem_object_lookup() on handles[0] (:1320), rejects imported dma-bufs (:1328), allocates the fb (:1334) and calls radeon_framebuffer_init() (:1340). No comparison of required image size vs BO size.
  3. radeon_framebuffer_init() (radeon_display.c:1294-1309): drm_helper_mode_fill_fb_struct() fills pitches/format; then straight to drm_framebuffer_init() β€” again no size check.
  4. Scanout path: set_config/page_flip pin the BO (radeon_bo_pin(..., RADEON_GEM_DOMAIN_VRAM, ...) β€” pinning does not check fb dimensions either) and program the display base address + pitch; the display engine then fetches pitch * crtc_vdisplay bytes starting at the BO β€” reading far past its end when the fb is larger than the BO. The flip path repeats this (radeon_display.c:524-539, radeon_crtc_page_flip_target pins new_rbo and computes pitch_pixels = fb->pitches[0] / fb->format->cpp[0]).
  5. Same gap exists in amdgpu_display_user_framebuffer_create (amd/amdgpu/amdgpu_display.c:523) β€” this DRM vintage predates the drm_gem_fb size checks; upstream later moved radeon/amdgpu to drm_gem_fb_create which checks per-plane min-size vs BO size.

Impact: an unprivileged user with DRM master/modeset rights (any desktop session via logind) creates a small VRAM BO, registers an oversized fb on it, and modesets: the panel then displays the contents of VRAM immediately following the BO β€” other applications' buffers. The leak is to the physical display (photo/screen-capture by a second GPU path is not available), so the ceiling is visual disclosure; no kernel memory is involved and no write primitive exists. Rating: Medium (info leak, default desktop configs, needs radeon KMS).

Why it cannot be reproduced on this guest

QEMU guest has no ATI/AMD GPU: no /dev/dri, radeon.ko not loaded. rfb_oob builds and runs; open("/dev/dri/card0") fails ENOENT (run.log).

Fix validation

fix.diff adds the per-plane offsets[i] + pitches[i] * DIV_ROUND_UP(height, vsub) <= radeon_bo_size() check in radeon_framebuffer_init(). Applies cleanly; radeon.ko module built with the patch (fix_build.log β€” module build, since radeon is not part of X86_64_GENERIC). Expected behavioral change on hardware: the PoC ADDFB2 returns -EINVAL.

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

inconclusive
baseline no→ patch + rebuild →patched clean

Compile-validated only: fix.diff applied cleanly and radeon.ko built with -Werror as part of the batch nativekernel build (fix_build.log: radeon_display.c compiled, '--- radeon.ko ---' linked, zero errors). Behavioral test impossible on this guest (no GPU).

fix_build.log excerpt (radeon_display.c -Werror KLD_MODULE compile + radeon.ko link).
↓ fix.diffper-fix-DF-4181

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (core-vs-driver responsibility trace); rfb_oob.c (CREATE_DUMB 64x64 + ADDFB2 1920x1080; success criterion: ADDFB2==0 bug vs -EINVAL fixed); run.log (no GPU, build OK); fix.diff + fix_build.log (radeon.ko built -Werror with fix)

PoC changes

n/a (first-pass PoC authored in this run; raw ioctls, no libdrm dependency)

Verified recommended fix

Per-plane check in radeon_framebuffer_init: offsets[i] + pitches[i]*DIV_ROUND_UP(height, vsub) must be <= radeon_bo_size(obj), else -EINVAL.

Verdict

Source-certain missing size validation: radeon_user_framebuffer_create (radeon_display.c:1311) / radeon_framebuffer_init (radeon_display.c:1294) never compare the requested scanout image size against the backing GEM object; the DRM core's framebuffer_check (drm_framebuffer.c:165-275) only enforces format validity, pitch>=widthcpp (:211) and heightpitch+offsets<=UINT_MAX (:208) and never sees the object. A 16 KiB BO therefore backs a 1920x1080 XRGB8888 fb (8,294,400 B); the display engine then scans out ~8 MB past the object end in VRAM, disclosing adjacent VRAM contents (other applications' buffers) on the physical display; the flip path repeats this with the undersized pinned BO (radeon_display.c:524-539). amdgpu_display.c:523 has the identical gap in this tree (pre-drm_gem_fb vintage). Not exercisable on this guest: no GPU, no /dev/dri (run.log).