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

hsw_activate_psr2() overflows two 4-bit EDP_PSR2_CTL fields when sink reports max resync latency, corrupting IDLE_FRAME/FRAME_BEFORE_SU/TP2_TIME in one register write

Summary

get_sink_sync_latency accepts any sink-reported 0..15; activate_psr2 computes idle_frames = max(idle, latency+1) = 16 and FRAME_BEFORE_SU(latency+1)=16<<4, but both EDP_PSR2_CTL fields are 4-bit. With latency=15 the single register write yields 0xC0000310: IDLE_FRAME reads back 0 (below the HW minimum the code enforces), FRAME_BEFORE_SU=1 (wanted 16), and the spill ORs into TP2_TIME making it 50us - three timing fields corrupted. Proven by a guest-built demo mirroring the exact code (run.log). Crafted-sink (malicious dongle/monitor firmware) display DoS on PSR2-capable GLK/gen9/gen10; no kernel memory-safety impact. Reject-PSR2-at-15 fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4450 Β· 11 files
FileTypeDescriptionSize
README.md β€” 2.1 KB ↓ raw
VERDICT.md β€” 3.3 KB ↓ raw
psr2_reg_demo.c β€” 3.2 KB view raw
build.sh β€” 104 B view raw
run.sh β€” 99 B view raw
build.log β€” 138 B view raw
run.log β€” 1.0 KB view raw
env.txt β€” 541 B view raw
fix.diff β€” 809 B view raw
manifest.json β€” 984 B view raw
verdict.json β€” 2.8 KB view raw

DF-4450 β€” PSR2 EDP_PSR2_CTL 4-bit field overflow via sink-reported resync latency

What this pack contains

A deterministic, hardware-free reproduction of the register-encoding defect in hsw_activate_psr2() (sys/dev/drm/i915/intel_psr.c:410-443): the demo mirrors the kernel arithmetic exactly and shows that a sink reporting DP_SYNCHRONIZATION_LATENCY_IN_SINK = 0x0F (the maximum value allowed by DP_MAX_RESYNC_FRAME_COUNT_MASK, include/drm/drm_dp_helper.h:800) makes the driver write an EDP_PSR2_CTL value in which three independent 4-bit timing fields are corrupted by adjacent-field spill.

Build / run

./build.sh    # cc -O2 -o /tmp/psr2_reg_demo /tmp/psr2_reg_demo.c
./run.sh      # /tmp/psr2_reg_demo

(On the audit host the source is psr2_reg_demo.c; it was transferred to the DF guest, compiled with the guest's cc 8.3, and executed there β€” see build.log / run.log / env.txt.)

Expected output (decisive lines)

latency=14 (max legal)  EDP_PSR2_CTL=0xc00002ff -> IDLE_FRAME=15  FRAME_BEFORE_SU=15  TP2_TIME=2500us
latency=15 (SPILLS!)    EDP_PSR2_CTL=0xc0000310 -> IDLE_FRAME=0   FRAME_BEFORE_SU=1   TP2_TIME=50us

At latency=15 the kernel computes idle_frames = max(6, 15+1) = 16 and EDP_PSR2_FRAME_BEFORE_SU(16):

  • 16 << 0 (intel_psr.c:421) spills bit 4 into FRAME_BEFORE_SU β€” the IDLE_FRAME field reads back 0, silently dropping below the HW minimum of 6 frames the code explicitly tries to enforce (intel_psr.c:415-418).
  • 16 << 4 = 0x100 (intel_psr.c:430) spills bit 8 into TP2_TIME; OR'ed with the requested 2500us encoding (2<<8), the 2-bit TP2_TIME field becomes 3 = 50us β€” the opposite of the requested value.

Why no kernel-side run

The kernel manifestation requires an Intel display engine (HSW+) driving a PSR2-capable eDP sink that reports resync latency 0xF. The audit guest has virtio-gpu only (pciconf -l: vgapci0 ... chip=0x11111234), so i915 cannot bind; the write to EDP_PSR2_CTL (a device MMIO at 0x6f900) cannot be exercised there. The arithmetic is reproduced exactly; the MMIO effect is hardware-bound.

VERDICT.md
↓ download raw

VERDICT β€” DF-4450

Status: inconclusive (kernel-path hardware-blocked; arithmetic defect proven) Finding-level severity: Low Β· confidence: certain (math), hardware manifestation not exercisable on this guest

Root cause (path:line)

  • sys/dev/drm/i915/intel_psr.c:205-215 β€” intel_dp_get_sink_sync_latency() reads one sink-controlled DPCD byte and masks it with DP_MAX_RESYNC_FRAME_COUNT_MASK = 0xf (include/drm/drm_dp_helper.h:800), i.e. accepts 0..15 with no upper bound check for the PSR2 use.
  • sys/dev/drm/i915/intel_psr.c:418-421 β€” hsw_activate_psr2() computes idle_frames = max(6, vbt) then max(idle_frames, sink_sync_latency + 1) and writes val = idle_frames << EDP_PSR2_IDLE_FRAME_SHIFT.
  • sys/dev/drm/i915/intel_psr.c:430 β€” val |= EDP_PSR2_FRAME_BEFORE_SU(sink_sync_latency + 1).
  • sys/dev/drm/i915/i915_reg.h:4217-4222 β€” both EDP_PSR2_IDLE_FRAME_MASK (0xf, bits 3:0) and EDP_PSR2_FRAME_BEFORE_SU_MASK (0xf << 4, bits 7:4) are 4-bit fields; EDP_PSR2_TP2_TIME_MASK occupies bits 9:8.

With sink_sync_latency == 15 (a fully spec-legal DPCD value, attacker-chosen by a crafted sink β€” dongle/dock/panel firmware): 15 + 1 = 16 does not fit a 4-bit field, in two places, in the same register write.

Proof

psr2_reg_demo.c reproduces the kernel's value construction verbatim (build+run logs on the DF 6.5-DEVELOPMENT guest, cc 8.3):

latency=14 (max legal) EDP_PSR2_CTL=0xc00002ff -> IDLE_FRAME=15 FRAME_BEFORE_SU=15 TP2_TIME=2500us
latency=15 (SPILLS!)   EDP_PSR2_CTL=0xc0000310 -> IDLE_FRAME=0  FRAME_BEFORE_SU=1  TP2_TIME=50us

Three independent PSR2 timing fields end up wrong in a single MMIO write: IDLE_FRAME silently 0 (below the enforced minimum of 6), FRAME_BEFORE_SU 1 instead of 16, TP2_TIME 50us instead of the requested 2500us.

Impact assessment (honest)

  • No kernel memory-safety effect: EDP_PSR2_CTL is a device register; the spill corrupts display timing configuration only.
  • Practical ceiling: a crafted sink can destabilize PSR2 entry timing on GLK/CNL+/gen9 PSR2 platforms (link errors, blanking, flicker) β€” a local, hardware-assisted denial-of-service against display usability. PSR2 also requires sink_psr2_support (Y-coordinate + ALPM capable sink), further narrowing the trigger.
  • The PSR1 path is safe: EDP_PSR_CTL's idle-frame field is 8 bits wide (i915_reg.h:4151 area), and 16 fits.

Why the kernel path itself was not run

Guest hardware is virtio-gpu only (env.txt), so i915 never binds and EDP_PSR2_CTL (0x6f900) is not reachable. Building the demo on the guest and running it there (done) is the maximum achievable verification on this platform; the defect itself is arithmetic and reproducible by inspection.

Fix

fix.diff β€” refuse PSR2 in intel_psr2_config_valid() when sink_sync_latency + 1 overflows the 4-bit fields (i.e. > EDP_PSR2_IDLE_FRAME_MASK). One hunk, no behavior change for latency ≀ 14.

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

Cannot build the fixed kernel path on this guest: no Intel display hardware; the MMIO write under test never executes. fix.diff is a one-hunk guard in intel_psr2_config_valid() reviewed against the exact field widths (i915_reg.h:4217-4222).

fix.diff
↓ fix.diffper-fix-DF-4450

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log: 'latency=15 (SPILLS!) EDP_PSR2_CTL=0xc0000310 -> IDLE_FRAME=0 FRAME_BEFORE_SU=1 TP2_TIME=50us' vs legal max 'latency=14 -> 0xc00002ff IDLE_FRAME=15 FRAME_BEFORE_SU=15 TP2_TIME=2500us'; env.txt proves virtio-gpu-only guest; VERDICT.md has the full path:line trace

PoC changes

No kernel PoC seed existed; authored a standalone deterministic demo mirroring the exact kernel arithmetic and field definitions

Verified recommended fix

In intel_psr2_config_valid() reject PSR2 when dev_priv->psr.sink_sync_latency + 1 > EDP_PSR2_IDLE_FRAME_MASK (4-bit fields cannot hold 16)

Verdict

Arithmetic defect proven (demo compiled and run on the DF 6.5 guest): a sink reporting DP_SYNCHRONIZATION_LATENCY_IN_SINK=0xF makes hsw_activate_psr2() compute 15+1=16, overflowing the 4-bit EDP_PSR2_IDLE_FRAME and EDP_PSR2_FRAME_BEFORE_SU fields of EDP_PSR2_CTL and corrupting TP2_TIME as well (0xc0000310 -> IDLE_FRAME=0, FRAME_BEFORE_SU=1, TP2_TIME=50us). Kernel-side MMIO manifestation is hardware-bound and cannot be exercised: the guest has virtio-gpu only, i915 cannot bind. Impact ceiling is display-timing corruption on PSR2-capable platforms (local DoS via crafted sink); no kernel memory-safety effect. Mapped to 'inconclusive' (device absent) per orchestrator enum.