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

amdgpu fb creation never validates framebuffer size against the backing GEM object (twin of DF-4181; covers BOTH legacy DCE and DC scanout stacks) β€” display engine scans out megabytes past a tiny BO, disclosing adjacent VRAM on the physical display

Summary

amdgpu_display_user_framebuffer_create resolves the handle and hands mode_cmd to amdgpu_display_framebuffer_init, which only fills and registers the fb struct. Nothing between ioctl and hardware compares pitch*height+offsets against amdgpu_bo_size(): DRM core checks format/dims/pitch but never sees the BO. A 16KB BO accepts a 1920x1080 XRGB8888 fb needing 8,294,400 bytes. UNLIKE the radeon twin, this single create fn is registered for BOTH display stacks (legacy amdgpu_mode_funcs DCE8/10/11 AND DC amdgpu_dm_funcs DCE12/DCN) - every ASIC this driver supports is affected. Scanout pins and programs GRPH_X_END/PITCH/VIEWPORT with no re-check (dce_v8/10/11 + DC prepare_fb + page-flip). Unprivileged DRM master; visual-disclosure ceiling. PoC built to the hardware gate; per-plane min-size fix compile-validated via standalone amdgpu.ko -Werror build. DF-1717 untouched.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4183 Β· 10 files
FileTypeDescriptionSize
VERDICT.md verdict verification verdict + full path:line trace 5.3 KB ↓ raw
amdgpu_fb_oob.c trigger CREATE_DUMB 64x64 + ADDFB2 1920x1080 on the tiny BO (twin of DF-4181 rfb_oob.c) 4.8 KB view raw
build.sh build build command 99 B view raw
build.log buildlog guest compile of the trigger 13 B view raw
run.sh run run command 150 B view raw
run.log runlog guest run: open /dev/dri/card0 ENOENT (no GPU) 161 B view raw
env.txt env guest environment: no AMD/ATI PCI, no /dev/dri, no drm modules 445 B view raw
fix.diff suggested-fix per-plane BO min-size check in amdgpu_display_framebuffer_init (git-apply-able) 2.0 KB view raw
fix_build.log fixbuild amdgpu.ko standalone module build with the fix: -Werror -DKLD_MODULE, rc=0, zero warnings 1.2 MB ↓ download
verdict.json verdict machine verdict 4.6 KB view raw
VERDICT.md verdict verification verdict + full path:line trace
↓ download raw

DF-4183 β€” Verification Verdict (amdgpu twin of DF-4181)

Status: inconclusive (hardware-gated: no AMD/ATI 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, width/height within mode_config limits (:288-298), height*pitches[i] + offsets[i] <= UINT_MAX (:208), pitches[i] >= width*cpp (:211), block alignment for some formats. It never looks at the buffer object (it cannot β€” GEM handles are resolved by the driver's fb_create, dispatched at :310).
  2. amdgpu_display_user_framebuffer_create() (amdgpu_display.c:523-558): drm_gem_object_lookup() (:531), rejects imported dma-bufs (:539 β€” the DF-1717 neighborhood), kzalloc (:544), calls amdgpu_display_framebuffer_init() (:550). No comparison of required image size vs BO size. It is registered as .fb_create for both display stacks in this tree: - legacy: amdgpu_mode_funcs (amdgpu_display.c:560-561) β€” used by DCE8/DCE10/DCE11 ASICs; - DC (DAL): amdgpu_dm_funcs (amdgpu_dm.c:945) β€” used by DCE12/DCN. So the gap is ASIC-uniform: every amdgpu chip this driver supports.
  3. amdgpu_display_framebuffer_init() (amdgpu_display.c:506-520): drm_helper_mode_fill_fb_struct() (:513) fills pitches/format from mode_cmd; then straight to drm_framebuffer_init() (:514) β€” again no size check and no other content validation (the DRM core owns the format check). Same helper is reused by the kernel-internal fbdev path (amdgpu_fb.c:248) whose sizes are kernel-computed (not attacker-chosen), so the fix belongs in this helper and covers both callers harmlessly.
  4. Scanout paths pin and program without re-checking fb dimensions vs amdgpu_bo_size(): - legacy DCE11: dce_v11_0_crtc_do_set_base β€” amdgpu_bo_pin(abo, AMDGPU_GEM_DOMAIN_VRAM) (dce_v11_0.c:1904), fb_location = amdgpu_bo_gpu_offset(abo) (:1910), then programs GRPH_X_END = fb->width / GRPH_Y_END = fb->height (:2073-2074), GRPH_PITCH = pitches[0]/cpp (:2076-2077) and VIEWPORT_SIZE = crtc mode (:2088-2091): the display engine fetches pitch * height bytes from the pinned base address regardless of the object's true size. Same shape: dce_v10_0.c:1862, dce_v8_0.c:1791. - DC: dm_plane_helper_prepare_fb (amdgpu_dm.c:3484) β†’ amdgpu_bo_pin(rbo, domain) (:3518); plane attributes consume fb pitches/dims downstream β€” no size gate. - page flip: amdgpu_display_crtc_page_flip_target (amdgpu_display.c:146) β†’ amdgpu_bo_pin(new_abo, …) (:191) β€” the flip work then swaps the scanout base to the undersized pinned BO (amdgpu_display.c:109).

Impact: an unprivileged user with DRM master/modeset rights (any desktop session via logind) creates a 16 KiB VRAM BO (64x64x32 dumb buffer), registers a 1920x1080 XRGB8888 fb on it (8,294,400 B required), and modesets/flips: the panel then displays the contents of VRAM immediately following the BO β€” other applications' buffers. The disclosure is to the physical display, so the ceiling is visual; no kernel memory involved, no write primitive. Rating: Medium (info leak, default desktop configs, needs amdgpu KMS) β€” identical to DF-4181 (radeon).

Upstream context: this DRM vintage predates drm_gem_fb_create() with its per-plane BO-size checks; upstream later moved amdgpu fb creation onto that helper, closing the gap there.

Why it cannot be reproduced on this guest

The QEMU guest has no ATI/AMD GPU: no PCI vendor-0x1002 device (pciconf), no /dev/dri at all, and no drm modules loaded (env.txt). amdgpu_fb_oob builds and runs; open("/dev/dri/card0") fails ENOENT (run.log, exit 2). The gate is hardware absence β€” without a PCI AMD GPU the amdgpu drm device node cannot exist β€” so the finding is source-certain but runtime-untestable here (same classification as DF-4181 for radeon).

Fix validation

fix.diff adds the per-plane offsets[i] + pitches[i] * DIV_ROUND_UP(height, vsub) <= amdgpu_bo_size() check in amdgpu_display_framebuffer_init() (amdgpu_display.c:506), i.e. exactly the DF-4181 radeon fix transplanted to the amdgpu helper β€” covering the user path for both the legacy (amdgpu_mode_funcs) and DC (amdgpu_dm.c:945) fb_create registrations. Applied cleanly to the guest /usr/src (md5 21c72601… β†’ f68f1ac9…), amdgpu.ko built standalone with -Werror -DKLD_MODULE (make -j6 in sys/dev/drm/amd/amdgpu: rc=0, 28.7 s, zero error/warning lines, --- amdgpu.ko --- linked with the patched amdgpu_display.o β€” fix_build.log:90). Source reverted to pristine after the build. 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 to the guest /usr/src (md5 21c72601... -> f68f1ac9...) and amdgpu.ko built standalone with -Werror -DKLD_MODULE (make -j6 in sys/dev/drm/amd/amdgpu: rc=0 in 28.7s, zero error/warning lines, amdgpu.ko 3.7 MB linked with the patched amdgpu_display.o; fix_build.log:90). Source reverted to pristine after the build. Behavioral test impossible on this guest (no AMD GPU).

fix_build.log line 90 (amdgpu_display.c compile with -Werror -DKLD_MODULE), '--- amdgpu.ko ---' link line, zero 'error:'/'warning:' matches; md5 sequence 21c726011a45df09bacd7632af9f2e81 -> f68f1ac92d89aaeb3985c00c4d684eec -> 21c726011a45df09bacd7632af9f2e81 in the run transcript.
↓ fix.diffper-fix-DF-4183

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (core-vs-driver responsibility trace incl. DC path); amdgpu_fb_oob.c (CREATE_DUMB 64x64 + ADDFB2 1920x1080; success criterion: ADDFB2==0 bug vs -EINVAL fixed); build.log + run.log (build OK, open /dev/dri/card0 ENOENT, exit 2); env.txt (no vendor-0x1002 PCI, no drm klds); fix.diff + fix_build.log (amdgpu.ko built standalone -Werror -DKLD_MODULE with fix, rc=0)

PoC changes

Adapted from DF-4181's rfb_oob.c: same raw-ioctl trigger (no libdrm), amdgpu path citations and the amdgpu-specific fb_create registration sites added; UAPI identical for amdgpu.

Verified recommended fix

Per-plane check in amdgpu_display_framebuffer_init: offsets[i] + pitches[i]*DIV_ROUND_UP(height, vsub) must be <= amdgpu_bo_size(obj), else -EINVAL (mirrors DF-4181 radeon fix).

Verdict

Source-certain missing size validation, amdgpu twin of DF-4181: amdgpu_display_user_framebuffer_create (amdgpu_display.c:523) / amdgpu_display_framebuffer_init (amdgpu_display.c:506) never compare the requested scanout image size against the backing GEM object, and this one create function serves BOTH display stacks (.fb_create registered in amdgpu_mode_funcs at amdgpu_display.c:561 for legacy DCE8/10/11 and in amdgpu_dm.c:945 for DC). 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 (fb_create dispatched at :310). A 16 KiB BO therefore backs a 1920x1080 XRGB8888 fb (8,294,400 B); every scanout path then pins and programs without re-checking - legacy dce_v11_0_crtc_do_set_base pins (dce_v11_0.c:1904) and programs GRPH_X_END/Y_END=fb->width/height + GRPH_PITCH (:2073-2077) + VIEWPORT_SIZE (:2088-2091), same shape dce_v10_0.c:1862 / dce_v8_0.c:1791; DC dm_plane_helper_prepare_fb pins (amdgpu_dm.c:3518); the flip path pins the new BO (amdgpu_display.c:191) - so the display engine fetches pitch*height bytes from the undersized object and discloses adjacent VRAM contents on the physical display. Not exercisable on this guest: no AMD/ATI PCI device, no /dev/dri (run.log, env.txt); hardware-gated like DF-4181.