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

wa_init_mcr uses 1-based fls() to index the 0-based subslice_mask[] and (via intel_calculate_mcr_s_ss_select) programs invalid MCR slice steering on gen10/11

Summary

slice = fls(sseu->slice_mask) (1-based) then subslice_mask[slice] (0-based u8[8]): every single-slice gen10/11 part (all ICL) reads slice 1's mask instead of enabled slice 0's, and the same 1-based values are programmed into GEN8_MCR_SELECTOR - steering MCR reads to a disabled slice/subslice (the code's own comment requires an enabled pair). 0-based field semantics proven in-tree by read_subslice_reg callers. No user input reaches it (boot fuse data); OOB needs fls==8, impossible (GEN_MAX_SLICES=6). Impact is indeterminate hangcheck INSTDONE reads. ffs()-1 fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4416 Β· 5 files
FileTypeDescriptionSize
VERDICT.md β€” 4.0 KB ↓ raw
verdict.json β€” 2.4 KB view raw
fix.diff β€” 859 B view raw
manifest.json β€” 662 B view raw
README.md β€” 900 B ↓ raw

DF-4416 β€” i915: fls() off-by-one in MCR slice selection (wa_init_mcr + intel_calculate_mcr_s_ss_select)

Build / Run / Expected

Not runnable in this environment. Requires Ice Lake / Cannon Lake graphics hardware. On such hardware: apply fix.diff, rebuild kernel, and after init dump GEN8_MCR_SELECTOR (0xFD0): baseline steers slice=1 on single-slice parts (slice_mask==0x1); patched steers slice=0. See VERDICT.md for the full line-cited analysis (0-based field semantics proven via for_each_instdone_slice_subslice, intel_ringbuffer.h:103-109).

Artifacts

VERDICT.md Β· verdict.json Β· fix.diff Β· manifest.json

VERDICT.md
↓ download raw

DF-4416 β€” i915 MCR slice selection uses 1-based fls() to index/steer 0-based slice fields

Verdict (short)

Status: untested β€” no Intel gen10/gen11 (CNL/ICL) GPU exists in the audit QEMU guest, so the trigger path (cnl_gt_workarounds_init / icl_gt_workarounds_init) cannot execute there. The defect itself is proven by source analysis with in-tree proof of the 0-based field semantics. Severity Low (register-steering correctness bug; no memory-safety impact on reachable hardware).

Root cause (path:line)

fls() returns the 1-based position of the MSB (fls(0x1)==1). sseu->subslice_mask[] (sys/dev/drm/i915/intel_device_info.h:121, u8 subslice_mask[GEN_MAX_SUBSLICES /* 8 */]) is a 0-based per-slice array. With slice_mask == 0x1 (single-slice part β€” every ICL, and the only case the is_power_of_2() guard admits on real hardware) the code reads subslice_mask[1], i.e. slice 1's mask, not the enabled slice 0's.

Index range analysis: fls(u8) ∈ [1..8]; array bound is 8, so an actual OOB read requires slice_mask >= 0x80 (8 slices) β€” impossible: GEN_MAX_SLICES=6 (intel_device_info.h:116) and no real gen10/11 part fuses more than 3. The bug is therefore wrong-slice, not out-of-bounds, on real hardware.

  • The same 1-based values are then programmed into the MCR steering selector: intel_engine_cs.c:769-770 (intel_calculate_mcr_s_ss_select), whose return value intel_workarounds.c:819-822 writes into GEN8_MCR_SELECTOR as a GT workaround on gen10/11. GEN8_MCR_SLICE(slice)/GEN11_MCR_SLICE(slice) (i915_reg.h:2442,2446) take a 0-based slice id β€” proven by read_subslice_reg (intel_engine_cs.c:785,798-804) whose callers pass 0-based counters from for_each_instdone_slice_subslice (intel_ringbuffer.h:103-109).

Net effect on single-slice ICL (slice_mask==0x1): MCR selector steered to slice 1 (does not exist) instead of slice 0, violating the requirement stated in the function's own comment (intel_workarounds.c:808-817: "needs to be programmed to point to any enabled s/ss pair"). Mis-steered MCR reads return indeterminate data β€” used by INSTDONE reads (hang detection) and the WaProgramMgsrForL3BankSpecificMmioReads WARN check (intel_workarounds.c:799).

Why it is only Low

  • No attacker-controlled input reaches the computation: sseu->slice_mask is derived at boot from hardware fuse registers (intel_device_info.c).
  • No kernel-memory disclosure: the wrong value goes to MMIO, not to userspace.
  • Worst realistic impact: invalid MCR steering on gen10/11 β†’ wrong per-slice MMIO read values β†’ misbehaving hangcheck / spurious or missed WARN. This is a correctness bug (upstream later switched this code to ffs(mask) - 1).

Fix

findings/poc/DF-4416/fix.diff β€” use 0-based indices in both wa_init_mcr (this file) and intel_calculate_mcr_s_ss_select (intel_engine_cs.c, companion instance feeding the write at intel_workarounds.c:822).

Reproduce (on hardware with an ICL/CNL GPU)

  1. Build a kernel with drm debug (CONFIG_DRM_I915_DEBUG_GEM equivalent) and boot on Intel Ice Lake graphics.
  2. dmesg | grep -i mcr / observe wa_init_mcr WARN behavior, and dump GEN8_MCR_SELECTOR (offset 0xFD0) after init β€” bits 30:27 will show slice=1 although only slice 0 is enabled (slice_mask=0x1 in dev_priv->info.sseu).
  3. With the patch, the selector shows slice 0.

Not runnable in this audit environment (QEMU guest has no Intel GPU); status stays untested.

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 validation not performed: no triggering hardware (Intel gen10/11 GPU / 82541 NIC) exists in the audit QEMU guest; fix.diff authored from line-accurate source analysis only.

Fix validation not performed: no triggering hardware (Intel gen10/11 GPU / 82541 NIC) exists in the audit QEMU guest; fix.diff authored from line-accurate source analysis only.
↓ fix.diffper-fix-DF-4416

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (full trace); intel_workarounds.c:781-800,819-822; intel_engine_cs.c:765-782,785; intel_device_info.h:116-121; i915_reg.h:2442-2449

PoC changes

No runnable PoC authored (hardware-dependent path); source-trace verification only.

Verified recommended fix

Use ffs(sseu->slice_mask)-1 for the array index and 0-based values for the MCR selector (see fix.diff).

Verdict

wa_init_mcr (intel_workarounds.c:787) indexes the 0-based sseu->subslice_mask[] with the 1-based fls(slice_mask) result; the same 1-based values from intel_calculate_mcr_s_ss_select (intel_engine_cs.c:769-770) are programmed into GEN8/11_MCR_SELECTOR via intel_workarounds.c:819-822. On single-slice gen10/11 parts (slice_mask==0x1, all ICL) this reads slice 1's subslice mask and steers MCR packets to the nonexistent slice 1, contradicting the enabled-pair requirement in the function's own comment. Out-of-bounds requires 8 fused slices (fls==8 vs array[8]) which no hardware has (GEN_MAX_SLICES=6), so impact is wrong MCR steering / bogus WARN at :799, not memory corruption. Untested: guest has no Intel GPU; 0-based semantics proven in-tree by read_subslice_reg callers (intel_ringbuffer.h:103-109).