Uninitialized stack consumed as SKL DRAM channel info when one MAD_DIMM channel register is blank: ch0/ch1 rank/is_16gb_dimm/symmetric_memory steer watermark config
Summary
skl_dram_get_channels_info() declares ch0/ch1 uninitialized; the channel-info getter returns -EINVAL before writing any field when a channel's MAD_DIMM register is blank (empty DIMM slot). With exactly one populated channel (single-channel SKL/KBL memory), the caller consumes the failed channel's garbage in the rank compares/max, is_16gb_dimm, and the symmetric-memory test - feeding SKL watermark latency selection at every load. Not attacker-controlled; worst case wrong watermark latency -> display FIFO underruns/flicker on single-channel hardware. KMSAN-grade. Upstream later hardened with '= {}' initializers; one-line fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3901 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.9 KB | β raw | |
| VERDICT.md | β | 2.8 KB | β raw | |
| skl_dram_probe.c | β | 980 B | view raw | |
| build.sh | β | 92 B | view raw | |
| run.sh | β | 162 B | view raw | |
| build.log | β | 33 B | view raw | |
| run.log | β | 58 B | view raw | |
| env.txt | β | 332 B | view raw | |
| fix.diff | β | 492 B | view raw | |
| manifest.json | β | 782 B | view raw | |
| verdict.json | β | 2.4 KB | view raw |
DF-3901 β Uninitialized stack consumed as DRAM channel info when one MAD_DIMM channel is blank (SKL/KBL)
File: sys/dev/drm/i915/i915_drv.c
Class: uninitialized stack read (CWE-457) consumed into watermark config
Severity: Low (hardware-config-gated; no memory corruption, no info leak to userspace)
What
skl_dram_get_channels_info() (i915_drv.c:1216) declares channel structs
uninitialized:
struct dram_channel_info ch0, ch1; /* :1219 β NO initializer */
...
val_ch0 = I915_READ(SKL_MAD_DIMM_CH0_0_0_0_MCHBAR_MCMAIN);
ret = skl_dram_get_channel_info(&ch0, val_ch0); /* fills ch0 only on success */
...
val_ch1 = I915_READ(SKL_MAD_DIMM_CH1_0_0_0_MCHBAR_MCMAIN);
ret = skl_dram_get_channel_info(&ch1, val_ch1); /* fills ch1 only on success */
if (dram_info->num_channels == 0) return -EINVAL; /* both blank -> early out */
skl_dram_get_channel_info() (:1154) returns -EINVAL before assigning
anything when val == 0 or when both size fields are 0 (i.e. the DIMM slot
is empty β the MAD_DIMM register reads 0). If exactly one channel is
populated (single-channel memory, common budget SKL/KBL configs), the failed
channel's struct is left entirely uninitialized, yet the caller consumes it:
if (ch0.rank == I915_DRAM_RANK_SINGLE || /* :1243 β uninit if ch0 failed */
ch1.rank == I915_DRAM_RANK_SINGLE) /* :1244 β uninit if ch1 failed */
dram_info->rank = I915_DRAM_RANK_SINGLE;
else
dram_info->rank = max(ch0.rank, ch1.rank); /* :1247 β uninit max() */
dram_info->is_16gb_dimm = ch0.is_16gb_dimm || ch1.is_16gb_dimm; /* :1254 */
dev_priv->dram_info.symmetric_memory = /* :1256 */
intel_is_dram_symmetric(val_ch0, val_ch1, &ch0); /* reads ch0->s_info.* */
Stale stack bytes then steer dram_info->rank / is_16gb_dimm /
symmetric_memory, which feed intel_get_dram_info() (:1379) β SKL
watermark latency selection (skl_wm_*) β the wrong "16Gb DIMM" assumption
directly changes the level-0 watermark latency workaround decisions.
Upstream fixed exactly this by zero-initializing the channel structs
(struct dram_channel_info ch0 = {}, ch1 = {}; in later
drivers/gpu/drm/i915/display/intel_dram.c) plus reworking the rank logic.
Impact
Attacker-influence: essentially none (value comes from MMIO/stack garbage, not user input). Effect: wrong watermark programming β display FIFO underruns, flicker, blank screens on single-channel SKL/KBL machines; latent KMSAN-grade uninitialized-memory defect. DoS at worst, hardware-config-gated.
Reproduce
skl_dram_probe.c would issue I915_GETPARAM pre/post on such hardware;
the uninit consumption itself needs KMSAN-style instrumentation or a
single-channel SKL. Not runnable on this guest (no Intel GPU). Status:
inconclusive, code path certain.
Fix
Zero-initialize ch0/ch1 (and mirror upstream's early bail when a channel
read fails), see fix.diff.
DF-3901 VERDICT
Status: inconclusive (source-certain; hardware-gated, not runnable on this guest) reproduced: 0 Β· impact: none (worst case hw-gated display misconfig/DoS) Β· confidence: certain
Proof from source
skl_dram_get_channels_info()β i915_drv.c:1219:struct dram_channel_info ch0, ch1;β no initializer.skl_dram_get_channel_info()β i915_drv.c:1154-1202: returns-EINVALat :1160 (if (!val)) or :1165-1166 (tmp_l == 0 && tmp_s == 0) before writing any field of*ch. A blank MAD_DIMM channel register (empty DIMM slot on single-channel SKL/KBL) triggers exactly this.- Caller consumes the failed channel unconditionally after only checking
num_channels != 0(both-blank case): - :1243-1244ch0.rank/ch1.rankcompare - :1247max(ch0.rank, ch1.rank)βdram_info->rank- :1254ch0.is_16gb_dimm || ch1.is_16gb_dimmβdram_info->is_16gb_dimm- :1256intel_is_dram_symmetric(val_ch0, val_ch1, &ch0)(:1205-1213) readsch0->s_info.size/width/rank dram_info->rank/is_16gb_dimm/symmetric_memoryfeed SKL watermark latency logic (intel_pm.c skl_wm_* consumesdram_info->is_16gb_dimmfor the level-0 latency w/a) β wrong values = mis-programmed watermarks on the affected configs.
Reached from i915_driver_init_hw() β intel_get_dram_info() (:1547) on
every driver load on gen9 non-LP with one blank MAD_DIMM channel.
Upstream parity: later Linux (intel_dram.c) initializes ch0 = {}, ch1 = {}
and reworks this exact function β the = {} is the hardening this port lacks.
Why not live-verified
Guest has virtio VGA; the i915 path never executes. No /dev/dri. Recorded
inconclusive (same as DF-3634 for its guest-unreachable half).
Exploit chain
None. Not attacker-controllable; no corruption; no disclosure of the uninit bytes to userspace (they only steer internal watermark arithmetic).
Fix validation
Not applicable (no reproduced memory corruption). fix.diff zeroes the structs and bails out of the channel when the register is blank β mirrors upstream. fix_status: not_applicable.
kernel_refs
- sys/dev/drm/i915/i915_drv.c:1219 (uninit decl)
- sys/dev/drm/i915/i915_drv.c:1154-1166 (early -EINVAL before any field write)
- sys/dev/drm/i915/i915_drv.c:1243-1247 (rank consumption)
- sys/dev/drm/i915/i915_drv.c:1254-1258 (is_16gb_dimm / symmetric consumption)
- sys/dev/drm/i915/i915_drv.c:1397-1406 (intel_get_dram_info dispatch)
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_testablefix.diff adds the upstream '= {}' initializers (I915_DRAM_RANK_INVALID==0 so a blank channel then reads INVALID, is_16gb_dimm false); not build-validated on guest (i915 not buildable there)
fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (field-by-field proof); skl_dram_probe.c + run.log (guest: no render node)
PoC changes
authored fresh; probe is reachability-only since the uninit consumption is internal
Verified recommended fix
Zero-initialize ch0/ch1 in skl_dram_get_channels_info() (match upstream intel_dram.c '= {}')
Verdict
Source-certain uninitialized-stack consumption: skl_dram_get_channels_info() (i915_drv.c:1219) declares ch0/ch1 without initializers; skl_dram_get_channel_info() returns -EINVAL at :1160/:1165 before writing any field when a MAD_DIMM channel register is blank (empty DIMM slot); with exactly one populated channel the caller reads ch1.rank/ch0.rank (:1243-1247), is_16gb_dimm (:1254) and ch0.s_info.* via intel_is_dram_symmetric (:1256) from uninitialized stack, steering dram_info->rank/is_16gb_dimm/symmetric_memory used by SKL watermark latency selection at every driver load on gen9 non-LP. Not attacker-influenced, no corruption, no disclosure; worst case display underruns/blank on single-channel SKL/KBL. Upstream hardened this exact function later with '= {}' initializers (intel_dram.c). Guest cannot execute the i915 path (virtio VGA, no /dev/dri) -> inconclusive.
No comments yet.