# DF-3899 — Off-by-one bounds guard in `intel_get_shared_dpll_id()`

**File:** `sys/dev/drm/i915/intel_dpll_mgr.c:99-108`
**Severity:** Info (hardening / defense-in-depth — no reachable exploit path today)
**Status:** untested (Info-tier hardening finding; hardware-gated display path; no PoC
attempted per Phase V skip rule for Low/Info findings)

## What the code says

```c
enum intel_dpll_id
intel_get_shared_dpll_id(struct drm_i915_private *dev_priv,
			 struct intel_shared_dpll *pll)
{
	if (WARN_ON(pll < dev_priv->shared_dplls||
		    pll > &dev_priv->shared_dplls[dev_priv->num_shared_dpll]))
		return -1;

	return (enum intel_dpll_id) (pll - dev_priv->shared_dplls);
}
```

The guard uses `>` instead of `>=`: a pointer exactly equal to
`&shared_dplls[num_shared_dpll]` (the one-past-end element) passes the check and
the function returns `id == num_shared_dpll`. On ICL `num_shared_dpll == 7 ==
I915_NUM_PLLS` (`icl_plls[]`, intel_dpll_mgr.c:3206-3215), so the returned id is
a straight out-of-bounds index: callers feed it into register sub-field macros
and address macros, e.g.

* `skl_ddi_clock_get()`  — `DPLL_CTRL1_HDMI_MODE(pll_id)` /
  `DPLL_CTRL1_LINK_RATE_MASK(pll_id)` shifts (intel_ddi.c:1617-1625)
* `icl_ddi_clock_get()`  — `icl_calc_dp_combo_pll_link(dev_priv, pll_id)` →
  `ICL_DPLL_CFGCR0(pll_id)` (intel_ddi.c:1537-1543, intel_dpll_mgr.c:2564)
* `cnl_ddi_clock_get()`  — `CNL_DPLL_CFGCR0(pll_id)` (intel_ddi.c:1563-1565)

## Why it is not exploitable today (honest threat)

Every current caller passes a pointer that was either

* taken from inside the array (`intel_get_shared_dpll_by_id()` at
  intel_dpll_mgr.c:84-89; `intel_find_shared_dpll()` at :256/:276; the
  pre-allocated paths `ibx_get_dpll:437`, `bxt_get_dpll:1843`,
  `icl_get_dpll:2922`), or
* `pipe_config->shared_dpll`, which is only ever assigned from those in-array
  pointers or from range-`WARN_ON`-guarded register readbacks
  (intel_display.c:9386-9389, 9407-9427, 9464-9467, 9503).

A `NULL` pointer is caught by the first clause (`pll < shared_dplls`). The
boundary case (`shared_dplls + num_shared_dpll` exactly) can only arise from a
future caller bug — which is precisely the situation this guard exists to
catch, and it silently fails to. The sibling accessor
`intel_get_shared_dpll_by_id()` has no bounds check at all, so this WARN is the
only line of defense on the id-derivation path.

Upstream (Linux 5.x) later rewrote this function index-based with `>=`:

```c
	long pll_idx = pll - dev_priv->shared_dplls;
	if (WARN_ON(pll_idx < 0 || pll_idx >= ARRAY_SIZE(dev_priv->shared_dplls)))
		return -1;
```

## Fix

`fix.diff` in this directory (authored against the read-only `sys/` tree; never
applied there).

## Reproduce

Not runnable: the function is only reached on Intel display hardware during
modeset/clock-readout, and no existing caller supplies an out-of-range pointer
(that is the point of the guard — to protect against future callers). A QEMU
guest without an Intel GPU cannot instantiate `intel_shared_dpll_init()`
(`dev_priv->dpll_mgr` stays NULL and `intel_get_shared_dpll()` bails at
intel_dpll_mgr.c:3295). Verification is therefore source-level only.
