vbt_sdvo_panel_type consumed as an unvalidated element index into the VBT BDB_SDVO_PANEL_DTDS table (dvo_timing + index) β root-set OOB read incl. negative offsets
Summary
The consumer only special-cases -2/-1 and uses the raw int as a 36-byte element offset from the VBT section base (mapped BIOS/heap copy) - large values panic, moderate values feed OOB data into the panel mode. Root tunable + SDVO-panel hardware required (unreachable on guest); upstream-precedented clamp fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5136 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.0 KB | β raw | |
| VERDICT.md | β | 2.0 KB | β raw | |
| run.sh | β | 203 B | view raw | |
| env.txt | β | 480 B | view raw | |
| fix.diff | β | 391 B | view raw | |
| verdict.json | β | 2.4 KB | view raw |
DF-5136 β vbt_sdvo_panel_type is used as an unvalidated array index into
the VBT SDVO panel-timing table (root-set OOB read)
sys/dev/drm/i915/i915_params.c:68-71 registers vbt_sdvo_panel_type as a
fully user-of-loader-controlled int tunable (TUNABLE_INT("drm.i915.vbt_sdvo_panel_type",
...) plus the macro-generated KBUILD_MODNAME.vbt_sdvo_panel_type name),
documented as "-2=ignore, -1=auto [default], index in VBT BIOS table" β but
neither the param file nor the consumer range-checks the value.
The consumer, parse_sdvo_panel_data() (sys/dev/drm/i915/intel_bios.c:349-387):
index = i915_modparams.vbt_sdvo_panel_type; /* :357 */
if (index == -2) return; /* :358 */
if (index == -1) index = sdvo_lvds_options->panel_type; /* VBT 4-bit field */
dvo_timing = find_section(bdb, BDB_SDVO_PANEL_DTDS);
if (!dvo_timing) return;
...
fill_detail_timing_data(panel_fixed_mode, dvo_timing + index); /* :381 */
Any value other than exactly -1/-2 (e.g. -3, or 100000, or INT_MAX) is used
directly as a pointer offset into the mapped/copied VBT blob:
dvo_timing + index with index an int scaled by sizeof(struct
lvds_dvo_timing) β an out-of-bounds read of arbitrary offset from the
section base (negative indexes read before the section). The garbage
lvvs_dvo_timing is then copied into panel_fixed_mode and consumed as a
display mode by the LVDS/SDVO paths (and later vbt.panel_type-driven
consumers use panel_type * 4 shifts on VBT data). A huge index faults the
mapping β kernel panic; a moderate index yields OOB kernel heap/BIOS-memory
read that flows into mode computation and DRM debug logs.
Preconditions: root sets drm.i915.vbt_sdvo_panel_type=<n> (or the mangled
KBUILD_MODNAME.vbt_sdvo_panel_type) in loader.conf/kenv before i915
attaches, on a machine whose VBT actually has a BDB_SDVO_PANEL_DTDS section
(old SDVO/LVDS platforms). No runtime sysctl exists for this param on this
port, so the trigger is boot/loader-time and root-only β hence severity Low
(input-validation gap / hardening), reported because the identical gap was
deemed worth fixing upstream (Linux later clamps panel_type).
Status
Not executed on the audit guest: the QEMU std-VGA machine has no Intel GPU,
no OpRegion/VBT, and i915 never attaches, so parse_sdvo_panel_data()
cannot be reached there (intel_bios_init only runs on real hardware). The
defect is established by source: the value flows from the tunable to
dvo_timing + index with no bound other than the two exact magic values.
Fix
Clamp after the -2/-1 handling:
--- sys/dev/drm/i915/intel_bios.c
+++ sys/dev/drm/i915/intel_bios.c
@@ -371,6 +371,10 @@ parse_sdvo_panel_data(struct drm_i915_private *dev_priv,
index = sdvo_lvds_options->panel_type;
}
+ if (index < 0 || index > 0xf) {
+ DRM_DEBUG_KMS("Invalid VBT SDVO panel type %d\n", index);
+ return;
+ }
+
dvo_timing = find_section(bdb, BDB_SDVO_PANEL_DTDS);
(panel_type is a 4-bit field per the VBT spec; the BDB_SDVO_PANEL_DTDS table it indexes holds 16 entries on every generation that ships it.)
DF-5136 β VERDICT: untested (no Intel hardware on the audit guest); defect established by source
Why not executed
The audit guest is QEMU std-VGA: i915.ko loads but never attaches (no Intel PCI ID), so intel_bios_init()/parse_sdvo_panel_data() never run and no VBT blob exists to index. Status "untested" per the strict status vocabulary; this is a hardware-precondition gap, not a refutation.
Source proof (path:line)
- sys/dev/drm/i915/i915_params.c:68 β TUNABLE_INT("drm.i915.vbt_sdvo_panel_type", &i915_modparams.vbt_sdvo_panel_type): boot/loader-time int fully under the configurator's control; no validation anywhere (the param file's only job).
- sys/dev/drm/i915/intel_bios.c:357 β index = i915_modparams.vbt_sdvo_panel_type.
- :358/:363 β only the exact magic values -2 (ignore) and -1 (auto, replace with the VBT's own 4-bit panel_type, itself checked <= 0xf at :229) are special-cased.
- :373-381 β dvo_timing = find_section(bdb, BDB_SDVO_PANEL_DTDS); fill_detail_timing_data(panel_fixed_mode, dvo_timing + index) β raw unclamped int as element index (struct lvds_dvo_timing is 36 bytes: pointer travels 36*index bytes from the section base; negative index goes below the section).
Outcome space: large |index| -> unmapped-address kernel fault (panic); moderate index -> OOB read of BIOS memory / kmemdup'd VBT heap blob whose contents propagate into dev_priv->vbt.sdvo_lvds_vbt_mode (mode used by intel_lvds/intel_sdvo connector setup and printed via drm_mode_debug_printmodeline at :386).
Severity Low: root-only boot tunable + old-SDVO-hardware precondition, but a clear missing-bounds defect with an upstream-precedented fix.
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_testableNot testable on this guest (no Intel GPU); the one-line clamp is trivially correct against the source trace.
fix.diff (unified diff against sys/dev/drm/i915/intel_bios.c).
Confirmed kernel references
Detail
Exploit chain
root loader.conf/kenv drm.i915.vbt_sdvo_panel_type=
Evidence (decisive lines)
README.md/VERDICT.md: full source trace with line cites; run.sh documents why no trigger exists on the guest.
PoC changes
No seed; analysis-only pack. fix.diff authored from source (clamp index to 0..0xf after the -1/-2 handling).
Verified recommended fix
Clamp index to 0..0xf after the -2/-1 handling in parse_sdvo_panel_data (intel_bios.c:371), see fix.diff.
Verdict
vbt_sdvo_panel_type is registered as a raw int tunable (i915_params.c:68) and consumed at intel_bios.c:357-381 as an element index into the VBT BDB_SDVO_PANEL_DTDS section with only the exact magic values -2/-1 special-cased β any other value (negative or huge) drives dvo_timing + index out of bounds (36 bytes/element) in the mapped VBT blob, then into fill_detail_timing_data and the SDVO/LVDS mode. Not executed: the audit guest has no Intel GPU, i915 never attaches, and intel_bios_init()/parse_sdvo_panel_data are unreachable there β a hardware-precondition gap, not a refutation. Root-set boot tunable + old-SDVO-VBT precondition => Low.
No comments yet.