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

u32 wrap in SKL DDB data-rate sum over-subscribes the per-pipe DDB (un-carried Linux v5.2 u64 rework)

Summary

skl_get_total_relative_data_rate accumulates per-plane relative data rates in a u32. Each plane's rate reaches ~2.4e9 for a legal 8192x8192 ARGB8888 plane downscaled ~3x per axis; three overlapping planes sum to 4,829,822,976 which wraps to 534,855,680. The wrapped denominator inflates every plane's proportional share ~9x in skl_allocate_pipe_ddb, producing PLANE_BUF_CFG DDB entries up to 7789 blocks inside an 892-block per-pipe DDB. Nothing downstream bounds an entry against ddb_size: skl_compute_plane_wm's res_blocks >= ddb_allocation check is fed the same inflated share so it passes, and skl_ddb_allocation_overlaps only orders cross-PIPE updates. Programmed out-of-range DDB (10-bit field) = overlapping display buffer allocations = display FIFO underrun / pipe wedge requiring modeset or GPU reset. Linux v5.2 computes these rates in u64; this tree keeps the pre-v5.2 u32 arithmetic. VERIFIED source-level with an exact-integer PoC implementing the kernel's fixed16 helpers verbatim (wrap + DDB entries printed; run.log); runtime unverifiable (guest is virtio-gpu, no /dev/dri, i915 module-only). Local DRM master on SKL+ hardware; display DoS only - corrupted values flow only to display registers, never kernel memory. Same family as DF-1118 but distinct functions/sink/fix (v5.2 u64 rework backport, git apply --check clean). Also established: lines ~5130-9863 of this file are byte-identical to Linux v5.2; killed lockmgr-under-crit (systemic shim design), div-by-zero candidates, rps->work lifecycle, and all array bounds.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3572 Β· 9 files
FileTypeDescriptionSize
README.md β€” 3.3 KB ↓ raw
VERDICT.md β€” 4.2 KB ↓ raw
wm_datarate_wrap.py β€” 4.5 KB view raw
run.log β€” 1.5 KB view raw
upstream_v5.2_divergence.diff β€” 2.6 KB view raw
env.txt β€” 1.2 KB view raw
fix.diff β€” 3.2 KB view raw
manifest.json β€” 1.2 KB view raw
verdict.json β€” 4.8 KB view raw

DF-3572 β€” u32 wrap in SKL DDB data-rate sum (skl_get_total_relative_data_rate)

File: sys/dev/drm/i915/intel_pm.c (pass 2, GLM 5.3 re-audit) Lines: 4216–4262 (wrap at 4252/4259), sink at 4400–4466 Severity: Low β€” local DoS (display FIFO underrun / pipe wedge) by a DRM master on Intel SKL+ hardware with an extreme (but fully legal) plane configuration. Same impact family as pass-1 DF-1118 (skl_wm_method1/2 overflow), different code path and different sink (DDB allocation vs watermark levels).

Build / Run / Expected

  • Build: nothing to compile β€” this is an exact-integer-arithmetic reproduction of the kernel computation, runnable anywhere.
  • Run: python3 wm_datarate_wrap.py
  • Expected (see run.log): per-plane rates 2,415,353,856 + 1,207,234,560 + 1,207,234,560 = 4,829,822,976 wraps in the u32 accumulator to 534,855,680; skl_allocate_pipe_ddb then emits PLANE_BUF_CFG entries [0,3891] [3891,5840] [5840,7789] inside an 892-block pipe DDB.

Runtime verification on the audit guest is impossible: the guest has a virtio GPU (see env.txt), i915 never binds, /dev/dri does not exist. Source-level verification is the bar here (per orchestrator instruction).

Evidence

  • wm_datarate_wrap.py + run.log β€” the arithmetic proof (above).
  • upstream_v5.2_divergence.diff β€” hunk of diff linux-v5.1 intel_pm.c dragonfly intel_pm.c showing Linux v5.2 computes rate as u64 and total_data_rate as u64; this tree keeps the pre-v5.2 u32 form.
  • env.txt β€” guest: virtio vga 0x11111234, no /dev/dri, i915.ko shipped (module) but not in the stock X86_64_GENERIC kernel and not loaded; kldstat shows no i915.
  • fix.diff β€” backport of the u64 arithmetic (git-apply-able against sys/; never applied to the read-only tree).

Reachability chain (all under attacker-chosen DRM atomic ioctls)

  1. DRM master sets mode 2731x2731@60 (~605 MHz dotclock ≀ SKL max dotclock = cdclk 675 MHz). At modeset time no scaler is in use, so skl_check_pipe_max_pixel_rate (intel_pm.c:4119) passes trivially β€” that gate runs only from intel_crtc_compute_config (intel_display.c:10926), i.e. at modeset.
  2. Plane-only atomic updates (no modeset β†’ gate not re-run) enable: primary 8192x8192 ARGB8888 β†’ 2731x2731 (2.9996x/axis), sprite0/1 4096x8192 β†’ 1366x2731 (3.0x/axis). All within skl_max_scale (3 βˆ’ 2⁻¹⁢ per axis) and the sprite 4096-pixel src-width limit.
  3. skl_compute_ddb β†’ skl_get_total_relative_data_rate sums the three u32 rates: 4,829,822,976 > 2Β³Β² β†’ wraps to 534,855,680.
  4. skl_allocate_pipe_ddb divides alloc_size * rate by the wrapped total β†’ each plane's share is ~9x too large β†’ entries run to 7789 in an 892-block DDB (hardware field is 10 bits β€” values alias to wrong ranges). skl_compute_plane_wm's res_blocks >= ddb_allocation check is fed the same inflated share, so the watermarks validate.
  5. skl_ddb_entry_write programs the out-of-range PLANE_BUF_CFG values; the only overlap check (skl_ddb_allocation_overlaps, intel_display.c:12696) orders cross-pipe updates and never bounds an entry against ddb_size.
  6. Effect: overlapping display buffer allocations between planes β†’ display FIFO underruns / pipe wedge; recovery needs a modeset or GPU reset.
VERDICT.md
↓ download raw

DF-3572 β€” VERDICT

Status: not_reproduced at runtime (hardware absent); source-level divergence and arithmetic proven with certainty; impact ceiling is a local display DoS (Low).

What was verified and how

  1. Full-file pass-2 audit of sys/dev/drm/i915/intel_pm.c (9,863 lines): read end-to-end; the second half (~line 5130β†’EOF) is byte-identical to Linux v5.2 (verified by diff), the first half is a pre-v5.2 (β‰ˆv5.1) vintage with selected v5.3/v5.4 backports (SAGV for KBL/CFL, ICL DDB mask, cpp==8 dbuf_block_size fix, enabled DBUF_CTL_S2 read). Full divergence catalogue in the audit's negative notes.

  2. The finding: skl_plane_relative_data_rate (intel_pm.c:4216-4220) computes data_rate = width * height * cpp in u32 and mul_round_up_u32_fixed16() (i915_drv.h:199) truncates its u64 intermediate to u32 (WARN only); skl_get_total_relative_data_rate (intel_pm.c:4252,4259) accumulates the per-plane rates in a u32 total_data_rate. Linux v5.2 computes both in u64 (proof: upstream_v5.2_divergence.diff); this tree carries the pre-v5.2 form.

  3. Arithmetic proof (wm_datarate_wrap.py, run.log): three overlapping planes (primary 8192x8192, sprites 4096x8192, all ARGB8888, all ~3x-per-axis downscaled onto a 2731x2731@60 pipe) yield per-plane rates 2,415,353,856 / 1,207,234,560 / 1,207,234,560 β€” each fits u32 β€” but the sum 4,829,822,976 wraps to 534,855,680. The wrapped denominator inflates each plane's proportional share ~9x; skl_allocate_pipe_ddb (intel_pm.c:4440-4465) emits DDB entries [0,3891] [3891,5840] [5840,7789] inside an 892-block per-pipe DDB.

  4. No downstream guard: skl_compute_plane_wm's res_blocks >= ddb_allocation (intel_pm.c:4773-4775) is fed the same inflated share, so it passes; skl_ddb_allocation_overlaps (intel_pm.c:5115, used at intel_display.c:12696) only orders cross-PIPE updates; nothing bounds an entry against ddb_size. skl_ddb_entry_write (intel_pm.c:5012) then programs out-of-range PLANE_BUF_CFG values (10-bit field, i915_reg.h:6667 β€” values alias to different wrong ranges).

  5. Reachability: every input (mode, plane src/dst rects, formats) is attacker-chosen via the DRM atomic/setcrtc ioctls by a local DRM master; the per-axis 3x scaler cap (skl_max_scale, intel_display.c:13367, tmpclk1 = (1<<16)*3 - 1), the sprite 4096 src-width limit, and the 8192 max plane src size are all respected by the demonstrated config. The pixel-rate gate (skl_check_pipe_max_pixel_rate) is only invoked from intel_crtc_compute_config (intel_display.c:10926) at modeset; plane-only atomic updates bypass it. Preconditions: Intel SKL+ iGPU (the default DFly graphics driver on such hardware), cdclk β‰₯ ~605 MHz.

Why not reproduced on the guest

The audit guest is QEMU/KVM with a virtio GPU (vgapci0@pci0:0:2:0 chip=0x11111234), i915 never probes, /dev/dri does not exist, and i915 is not compiled into the stock X86_64_GENERIC kernel (i915.ko is shipped as a module β€” see env.txt). No Intel display hardware β‡’ the code path cannot execute. This is an honest hardware-unreachability record, per the orchestrator's instruction that source-level certainty is the bar for this file.

Exploit chain

Not an exploitation primitive: the corrupted values flow only into display registers (PLANE_BUF_CFG / watermark levels), not into kernel memory. The realistic ceiling is a display-engine wedge (underrun storm / hung pipe requiring modeset or GPU reset) β€” local denial of service. No route to uid=0 exists through this path.

Fix

Backport the v5.2 u64 arithmetic (fix.diff): compute the per-plane rate in u64 and accumulate total_data_rate in u64. Runtime fix validation is not possible on this guest (no i915 hardware); the diff is a faithful backport of the upstream-accepted computation.

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 backport of the upstream v5.2 u64 computation and applies cleanly to sys/dev/drm/i915/intel_pm.c (verified with git apply --check in a scratch copy; sys/ was not modified). Behavioral fix validation requires Intel SKL+ display hardware which the audit guest lacks (virtio GPU, i915 never loads), so baseline-vs-patched runtime comparison is not testable here; the arithmetic model in wm_datarate_wrap.py shows the patched types make the wrap impossible (u64 sum 4,829,822,976 no longer truncates).

fix.diff (git-apply-able, verified); VERDICT.md 'Fix' section; run.log wrap demonstration
↓ fix.diffper-fix-DF-3572

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log (exact-integer reproduction: wrap 4829822976 -> 534855680, DDB entries [0,3891] [3891,5840] [5840,7789] vs ddb_size 892); upstream_v5.2_divergence.diff (Linux v5.2 u64 vs this tree u32); env.txt (guest virtio-gpu, no /dev/dri, i915 not in GENERIC, not loaded); fix.diff (git-apply-able u64 backport, verified to apply cleanly against sys/dev/drm/i915/intel_pm.c)

PoC changes

no seed PoC existed (new pass-2 finding); wrote wm_datarate_wrap.py implementing the kernel's fixed16 helpers verbatim from i915_drv.h and the exact reachable plane/mode configuration (per-axis 3x scaler cap, sprite 4096 src-width cap, modeset-only pixel-rate gate)

Verified recommended fix

Backport the Linux v5.2 u64 arithmetic for skl_plane_relative_data_rate/skl_get_total_relative_data_rate (see fix.diff)

Verdict

Not runtime-reproducible on the audit guest: it has a virtio GPU (chip 0x11111234), /dev/dri does not exist, and i915 is not in the stock X86_64_GENERIC kernel (i915.ko is shipped as a module that only loads on real Intel iGPUs β€” where it is the default graphics driver). Source-level verification is conclusive: intel_pm.c:4252/4259 accumulate per-plane relative data rates (each up to ~2.4e9, produced by mul_round_up_u32_fixed16 at i915_drv.h:199 which truncates its u64 intermediate to u32) in a u32 total_data_rate; three legal overlapping planes (primary 8192x8192 + two 4096x8192 sprites, all ARGB8888, all ~3x-per-axis downscaled onto a 2731x2731@60 pipe) sum to 4,829,822,976 which wraps to 534,855,680. The wrapped denominator inflates every plane's proportional share in skl_allocate_pipe_ddb (intel_pm.c:4441/4456), emitting PLANE_BUF_CFG DDB entries up to 7789 blocks inside an 892-block per-pipe DDB; nothing bounds entries against ddb_size (skl_ddb_allocation_overlaps at intel_display.c:12696 only orders cross-PIPE updates) and the watermark ddb_allocation check is fed the same inflated share so it passes. Programmed out-of-range DDB = overlapping display buffer allocations = display FIFO underrun / pipe wedge β€” a local DoS by any DRM master on SKL+. Impact ceiling is display DoS only; corrupted values flow to display registers, not kernel memory, so no privesc route exists. Linux v5.2 computes these rates in u64 (upstream_v5.2_divergence.diff); this DragonFly tree keeps the pre-v5.2 u32 arithmetic.