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

Uninitialized stack array consumed as eDP link rates when DP_SUPPORTED_LINK_RATES DPCD read fails

Summary

intel_edp_init_dpcd() ignores the return value of drm_dp_dpcd_read() into __le16 sink_rates[8]; on a failed/partial read (sink-controlled) the uninitialized stack contents are le16-decoded, multiplied and stored into intel_dp->sink_rates[] with num_sink_rates up to 8. Writes are bounded and values are filtered by intersection with kernel-known source rates, so no OOB and no userland disclosure - but attacker-triggerable uninitialized kernel memory is used as control data (nondeterministic link-rate selection, spurious training failure), and the uninit read is UB under sanitizers. A flaky or hostile eDP panel whose DPCD read of the supported-rates register times out after advertising edp_dpcd[0] >= DP_EDP_14. Fix: memset on short read.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3627 Β· 4 files
FileTypeDescriptionSize
README.md β€” 2.5 KB ↓ raw
fix.diff β€” 758 B view raw
verdict.json β€” 2.3 KB view raw
manifest.json β€” 686 B view raw

DF-3627 β€” Uninitialized stack array consumed as eDP link rates when DPCD read fails

  • File: sys/dev/drm/i915/intel_dp.c:3947-3969 (intel_edp_init_dpcd)
  • Class: CWE-457 (use of uninitialized memory), sink-triggered
  • Severity: Low (no OOB, no userland leak; garbage control data only)
  • Status: inconclusive on the audit guest (virtio-only; eDP path needs an internal panel on Intel hardware). Source-level certain.

What happens

    if (intel_dp->edp_dpcd[0] >= DP_EDP_14) {
        __le16 sink_rates[DP_MAX_SUPPORTED_RATES];   /* 8 x u16, UNINITIALIZED */
        int i;

        drm_dp_dpcd_read(&intel_dp->aux, DP_SUPPORTED_LINK_RATES,
                sink_rates, sizeof(sink_rates));   /* return value IGNORED */

        for (i = 0; i < ARRAY_SIZE(sink_rates); i++) {
            int val = le16_to_cpu(sink_rates[i]);
            if (val == 0)
                break;
            intel_dp->sink_rates[i] = (val * 200) / 10;
        }
        intel_dp->num_sink_rates = i;
    }

intel_edp_init_dpcd() runs at eDP connector init / detect. edp_dpcd[0] >= DP_EDP_14 is itself sink-supplied. If the AUX read of DP_SUPPORTED_LINK_RATES fails (short read, timeout, defer β€” a misbehaving or malicious panel controls this), the stack array is left untouched and its garbage contents are interpreted as link rates:

  • garbage flows into intel_dp->sink_rates[0..7] and num_sink_rates (≀ 8, writes bounded by DP_MAX_SUPPORTED_RATES β€” no OOB write);
  • intel_dp_set_common_rates() intersects them with source rates (intersect_rates, bounded, WARN_ON(k >= DP_MAX_SUPPORTED_RATES)) so only values that exactly match a known-good source rate survive; otherwise num_common_rates == 0 falls back to 162000 (intel_dp.c:536-539).

So the practical effect is uninitialized kernel stack used as control data: nondeterministic link-rate selection / spurious training failures, plus MSan-detectable UB. No information disclosure path exists β€” the values are only ever compared against kernel constants (intel_dp_print_rates logs only common rates, which are source constants).

The same unchecked read exists in upstream Linux of this vintage (inherited).

Fix

Check the read result and zero-initialize the array (see fix.diff).

Runtime verification

Not possible on this guest: the eDP/intel_edp_init_dpcd path requires Intel display hardware with an internal panel; the guest's GPU is virtio (chip=0x1111:1234) and i915 never attaches. Marked inconclusive per the honest-status policy; the code ships in /boot/kernel/i915.ko.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

fix.diff zeroes sink_rates when the DPCD read does not return the full 16 bytes; not build-validated on this guest (target path needs Intel eDP hardware).

findings/poc/DF-3627/fix.diff
↓ fix.diffper-fix-DF-3627

Confirmed kernel references

Detail

Evidence (decisive lines)

README.md (code excerpt + data-flow + bounds analysis); fix.diff (memset-on-failed-read). Decisive lines: intel_dp.c:3949 (uninit decl), 3952-3953 (unchecked read), 3955-3969 (garbage consumed).

PoC changes

No seed PoC; pure source-analysis finding with a hardening fix.

Verified recommended fix

Treat a failed DP_SUPPORTED_LINK_RATES read as 'no rates' (memset/zero) in intel_edp_init_dpcd.

Verdict

Source-level certain, runtime inconclusive: intel_edp_init_dpcd() (sys/dev/drm/i915/intel_dp.c:3948-3969) ignores the return value of drm_dp_dpcd_read() into the uninitialized __le16 sink_rates[8] stack array; on read failure (sink-controlled: a flaky/hostile eDP panel) garbage stack data is converted into intel_dp->sink_rates[] / num_sink_rates control data. Writes are bounded by DP_MAX_SUPPORTED_RATES (no OOB) and values are subsequently filtered by intersection with kernel-known source rates (no userspace disclosure), so the impact ceiling is nondeterministic link-rate selection and spurious training failures plus MSan-visible UB. Not runnable on this guest: the eDP path requires Intel display hardware with an internal panel; guest GPU is virtio (0x1111:1234) and i915 never attaches. Code ships in /boot/kernel/i915.ko.