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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3901 Β· 11 files
FileTypeDescriptionSize
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.

VERDICT.md
↓ download raw

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

  1. skl_dram_get_channels_info() β€” i915_drv.c:1219: struct dram_channel_info ch0, ch1; β€” no initializer.
  2. skl_dram_get_channel_info() β€” i915_drv.c:1154-1202: returns -EINVAL at :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.
  3. Caller consumes the failed channel unconditionally after only checking num_channels != 0 (both-blank case): - :1243-1244 ch0.rank / ch1.rank compare - :1247 max(ch0.rank, ch1.rank) β†’ dram_info->rank - :1254 ch0.is_16gb_dimm || ch1.is_16gb_dimm β†’ dram_info->is_16gb_dimm - :1256 intel_is_dram_symmetric(val_ch0, val_ch1, &ch0) (:1205-1213) reads ch0->s_info.size/width/rank
  4. dram_info->rank/is_16gb_dimm/symmetric_memory feed SKL watermark latency logic (intel_pm.c skl_wm_* consumes dram_info->is_16gb_dimm for 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

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.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
↓ fix.diffper-fix-DF-3901

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.