# 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)

- `sys/dev/drm/i915/intel_workarounds.c:787`
  `u32 slice = fls(sseu->slice_mask);`
  then `intel_workarounds.c:789`
  `u8 ss_mask = sseu->subslice_mask[slice];`

  `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.
