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

i915: unauthenticated ADDFB2 u32 overflow in framebuffer offset/size math (tile-aligned geometry) defeats bo-bounds check β€” OOB GPU scanout read from wrong/adjacent GGTT mappings

Summary

intel_fb_offset_to_xy lacks the driver-side overflow guard upstream added in 70bbe53c6eac (v5.0): the core drm check uses the RAW plane height but the driver re-heights to tile alignment then computes offset/size in u32 - a crafted NV12/CCS fb with offsets[1] near 2^32 wraps the math, the final size check passes garbage-small, and scanout walks past the object's GGTT mapping (upstream author: 'we might scan out some data we're not supposed to see from other parts of the ggtt'). ADDFB2 is DRM_UNLOCKED-only (no AUTH/MASTER) - the only unauthenticated entry into this file; needs gen9+ NV12/CCS planes. File verified == upstream v4.20 + KPI shims + 2 backports (full differential in the pack) - every post-v4.20 upstream display fix is absent. No hardware in the rig - honestly untested with trigger PoC + backport fix authored.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5782 Β· 10 files
FileTypeDescriptionSize
README.md β€” 1.5 KB ↓ raw
VERDICT.md β€” 4.3 KB ↓ raw
addfb2_offset_overflow.c β€” 5.1 KB view raw
build.sh β€” 117 B view raw
run.sh β€” 108 B view raw
fix.diff β€” 1.2 KB view raw
verdict.json β€” 4.0 KB view raw
env.txt β€” 450 B view raw
differential/dfly_vs_v4.20.full.diff β€” 25.6 KB view raw
differential/upstream_4.20_to_v5.1.fb_overflow_hunk.txt β€” 950 B view raw

DF-5782 β€” i915 fb offset/size u32 overflow (missing upstream 70bbe53c6eac guard)

What

intel_fb_offset_to_xy() in sys/dev/drm/i915/intel_display.c:2398 lacks the driver-side "Catch potential overflows early" guard that upstream Linux added in commit 70bbe53c6eac ("drm/i915: Make sure fb gtt offsets stay within 32bits", landed v5.0). The DragonFly copy is vendored Linux v4.20 (proven by whole-file differential, see differential/), which predates that fix.

Build / Run / Expected (on gen9+ Intel hardware)

cc -O2 -o addfb2_offset_overflow addfb2_offset_overflow.c
./addfb2_offset_overflow
  • vulnerable driver: crafted NV12/CCS ADDFB2 accepted (ioctl returns 0) where fixed driver returns -ERANGE ("Bad offset 0x... or pitch ... for color plane")
  • igt testcases upstream uses: kms_big_fb/{x,y}-tiled-addfb-size-offset-overflow

On this audit rig

QEMU guest has Bochs VGA only (pciconf chip 0x1234:1111, no i915 attach, no drm kld). The trigger therefore cannot execute here; verification is by static code path proof + upstream differential β€” see VERDICT.md.

Contents

  • addfb2_offset_overflow.c β€” the trigger (card discovery + crafted ADDFB2)
  • differential/dfly_vs_v4.20.nonmechanical.txt β€” proof the DFly file == upstream v4.20 + KPI shims + 2 backports (no DFly-invented semantics)
  • differential/upstream_4.20_to_v5.1.fb_overflow_hunk.txt β€” the exact missing fix hunk
  • fix.diff β€” standalone backport of the guard (explicit u64 math, no helper dependency)
  • verdict.json, manifest.json, env.txt
VERDICT.md
↓ download raw

VERDICT β€” DF-5782

Status: untested (no Intel GPU on the audit rig; verified by line-level static analysis + upstream differential). Impact ceiling: OOB GPU scanout read from wrong/adjacent GGTT mappings β€” info-leak-class to the display and/or hardware upset. Upstream author's own impact statement for this bug family (commit 4e05047d3e00, the other half of the same check, which IS present): "in the worst case we might anger the hw by attempting scanout past the end of the ggtt, or we might scan out some data that we're not supposed to see from other parts of the ggtt." The display engine cannot write; this is a read-side primitive disclosed through the visible screen content.

Proven chain (all line refs into sys/dev/drm/i915/intel_display.c unless noted)

  1. Entry, unprivileged: DRM_IOCTL_MODE_ADDFB2 is registered DRM_UNLOCKED with no DRM_AUTH/DRM_MASTER in sys/dev/drm/drm_ioctl.c:671 β†’ any local user holding an open /dev/dri/card0 fd reaches intel_user_framebuffer_create() (intel_display.c:14770) β†’ intel_framebuffer_init() (14544) β†’ intel_fill_fb_info() (2487) β†’ intel_fb_offset_to_xy() (2398).

  2. Core check passes: sys/dev/drm/drm_framebuffer.c:205-209 rejects (u64)height * pitch + offset > UINT_MAX using the raw plane height.

  3. Driver re-heights and overflows: the driver aligns the plane height up to the tile height (intel_fb_align_height 1989-1996; intel_fill_fb_info 2587 DIV_ROUND_UP(y + height, tile_height)), and computes with u32 arithmetic: * intel_compute_aligned_offset :2360 offset = (tile_rows * pitch_tiles + tiles) * tile_size; * intel_fill_fb_info :2635 size = DIV_ROUND_UP((y + height) * fb->pitches[i] + x * cpp, tile_size); With offsets[1] ∈ [0, 0xffffffff] (only plane 1: offsets[0] is forced to 0 at :14706) and an 8192-wide tiled NV12/CCS geometry, the tile-aligned product crosses 2^32 and wraps after the core check already passed.

  4. Final sanity check fooled: the wrapped size is small, so mul_u32_u32(max_size, tile_size) > obj->base.size (:2643) passes while the real plane extent exceeds the object β†’ scanout runs past the object's GGTT mapping.

  5. The guard that should exist here (from upstream v5.0 70bbe53c6eac, absent in this tree β€” differential hunk stored in differential/upstream_4.20_to_v5.1.fb_overflow_hunk.txt): c height = drm_framebuffer_plane_height(fb->height, fb, color_plane); height = ALIGN(height, intel_tile_height(fb, color_plane)); /* Catch potential overflows early */ if (add_overflows_t(u32, mul_u32_u32(height, fb->pitches[color_plane]), fb->offsets[color_plane])) return -ERANGE;

Hardware gating (honest bound)

  • NV12 path only on gen9 KBL/CFL/GLK/CNL (intel_framebuffer_init :14690-14698 rejects NV12 on <gen9, SKL, BXT, β‰₯gen11).
  • CCS path needs I915_FORMAT_MOD_{Y,Yf}_TILED_CCS (gen9+ with CCS planes).
  • Creating the malicious fb needs no master; displaying it (to realize the leak) needs a plane update (DRM master) β€” or a cooperating compositor importing attacker-specified geometry (DRI3/pixmap-from-fd with crafted stride/offset is the realistic vector on a multi-user desktop).

Why not executed here

Guest is QEMU with Bochs VGA (pciconf -lv: chip=0x11111234); the i915 driver never attaches and /dev/dri/card* is absent β€” the trigger's own first step reports this (see env.txt). No panic/leak can be produced on this rig. Classification: untested with static+differential proof, per task instructions ("verification is code-reading + differential where arithmetic classes appear (no hardware)").

Fix validation

fix.diff is the faithful backport of 70bbe53c6eac written with explicit 64-bit math (the DFly tree has add_overflows but not add_overflows_t, so the diff avoids the helper). Not kernel-built here β€” no i915 hardware to boot; fix_status=not_tested.

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

not_testable
baseline no→ patch + rebuild →patched clean

fix.diff is a faithful standalone backport of upstream 70bbe53c6eac (explicit u64 math instead of add_overflows_t, which this tree lacks). Not built/tested: no Intel GPU on the rig, so baseline and patched kernels are both non-executable here; upstream igt kms_big_fb/*-addfb-size-offset-overflow covers both directions on real hardware.

fix.diff; differential/upstream_4.20_to_v5.1.fb_overflow_hunk.txt; upstream commit 70bbe53c6eac message ('framebuffer_check() in the core already does the same check, but as it doesn't know about tiling some things can slip through').
↓ fix.diffper-fix-DF-5782

Confirmed kernel references

Detail

Exploit chain

open /dev/dri/card0 (no auth/master needed) -> DRM_IOCTL_MODE_ADDFB2 with NV12 (gen9 KBL/CFL/GLK/CNL) or CCS modifier, 8192-wide tiled geometry, offsets[1] near 2^32 -> core drm_framebuffer.c:208 check passes (raw height) -> driver tile-aligns height and wraps u32 products in intel_fb_offset_to_xy/intel_compute_aligned_offset/intel_fill_fb_info -> mul_u32_u32 final check fooled -> fb registered with size smaller than true extent -> when displayed, display engine scans out past object end into adjacent GGTT mappings (upstream igt: kms_big_fb/x-tiled-addfb-size-offset-overflow).

Evidence (decisive lines)

VERDICT.md (full chain with path:line); differential/upstream_4.20_to_v5.1.fb_overflow_hunk.txt (the exact missing guard); differential/dfly_vs_v4.20.full.diff (proves base == upstream v4.20, no DFly semantic divergence); addfb2_offset_overflow.c (trigger source); env.txt (Bochs-only rig).

PoC changes

n/a β€” trigger authored fresh for this pack (ADDFB2 ioctl, card discovery, crafted NV12/X-tiled geometry with offsets[1] near 2^32); could not be compiled against an i915 device on this rig.

Verified recommended fix

Backport upstream 70bbe53c6eac: reject fbs whose tile-aligned height*pitch+offsets[color_plane] exceeds u32 in intel_fb_offset_to_xy() (fix.diff).

Verdict

Vendored-v4.20 i915 lacks upstream 70bbe53c6eac: intel_fb_offset_to_xy() (intel_display.c:2398) has no driver-side u32-overflow guard for tile-aligned height*pitch+offsets[], so a crafted NV12/CCS ADDFB2 (unauthenticated ioctl per drm_ioctl.c:671) wraps the offset/size math in intel_compute_aligned_offset (:2360) and intel_fill_fb_info (:2635), defeating the final bo-size check (:2643) and enabling OOB GPU scanout reads past the object's GGTT mapping (info-leak-class to the display; upstream author's stated worst case). Proven by whole-file differential against upstream v4.20 (file is v4.20 + KPI shims + 2 backports) and by the upstream fix hunk; NOT executed because the audit rig has Bochs VGA only (no i915 attach) β€” code-reading + differential verification per task instructions.