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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5136 Β· 6 files
FileTypeDescriptionSize
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.)

VERDICT.md
↓ download raw

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)

  1. 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).
  2. sys/dev/drm/i915/intel_bios.c:357 β€” index = i915_modparams.vbt_sdvo_panel_type.
  3. :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.
  4. :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_testable
baseline no→ patch + rebuild →patched clean

Not 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).
↓ fix.diffper-fix-DF-5136

Confirmed kernel references

Detail

Exploit chain

root loader.conf/kenv drm.i915.vbt_sdvo_panel_type= (n not in {-2,-1}) -> i915 attach on hardware with BDB_SDVO_PANEL_DTDS -> parse_sdvo_panel_data -> fill_detail_timing_data(dvo_timing + n) -> OOB read (panic on large n; garbage mode/possible kernel-log disclosure on moderate n).

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.