# DF-3913 VERDICT — intel_sdvo.c pass-2 audit + verification narrative

Reviewer: df-bsd-reviewer (GLM 5.3), pass 2, 2026-09-16
File: sys/dev/drm/i915/intel_sdvo.c (3,178 LOC)

## Bottom line

**inconclusive at runtime (hardware-gated); source-certain.** The leak at
intel_sdvo.c:2541-2544 / 2574-2577 / 2620-2623 / 2656-2659 is provable from
source: `intel_sdvo_connector_alloc()` (intel_sdvo.c:2481) kzallocs the
`intel_sdvo_connector_state` and installs it via
`__drm_atomic_helper_connector_reset()` (intel_sdvo.c:2487 →
drm_atomic_helper.c:3740, which merely assigns `connector->state`), and the
four failure blocks free only the connector. Nothing else can reach the
state pointer after the connector kfree, so the allocation leaks. The QEMU
audit guest has no Intel GPU (Bochs VGA only, `pciconf -l` chip 0x1234:1111
in run.log; kldstat shows no drm modules loaded), so the code path cannot be
executed there. Baseline and fix therefore cannot be behaviorally validated
on this guest: verdict `inconclusive`, impact `none` (nothing manifested),
fix_status `not_testable`. `fix.diff` is `git apply --check` clean against
the pristine `sys/` tree (verified; 4 hunks).

Severity Low: one-shot ~152-byte kmalloc leak, probe-time only, no UAF, no
attacker-controlled size/count, upstream-inherited (identical code in Linux
through the file's removal in v5.20).

## Exploit chain

None possible. The leaked memory is never referenced again (no UAF), never
read out (no info leak), and the trigger requires a hardware probe failure
(ENOMEM / connector IDA exhaustion, intel_sdvo.c:2438 →
drm_connector.c ida_simple_get(…,0,32)). Not reachable by an unprivileged
user at runtime.

## Full pass-2 candidate hunt (what was checked and killed)

Strategy items from the tasking, with citations — every memory-safety
candidate was killed by source evidence:

1. **Sideband response/argument sizes vs fixed arrays** — this vintage
   allocates `buf`/`msgs` from `args_len`
   (intel_sdvo.c:446-454,458-483); every call site passes a constant
   `sizeof()`, max 8 (`dtd->part1/part2`), which exactly matches the SDVO
   ARG_7..ARG_0 (0x00-0x07) and RETURN_0..7 (0x0a-0x11) register space
   (intel_sdvo_regs.h:108-123). All response buffers match their
   `response_len` exactly (BUILD_BUG_ONs at 642, 698, 781-782, 897, 1028,
   1659, 2766, 2956). No OOB.
2. **`tv_format_supported[val]` with user-controlled `val`**
   (intel_sdvo.c:2140) — killed: every ioctl path gates values through
   `drm_property_change_valid_get()` (legacy: drm_mode_object.c:436; atomic
   obj: drm_atomic_uapi.c:924-932; MODE_ATOMIC loop: drm_atomic_uapi.c:1347),
   and ENUM validation (drm_property.c:923-925) restricts `val` to the
   registered values {0..format_supported_num-1} < 19 = TV_FORMAT_NUM.
   Hence also `1 << conn_state->tv.mode` (1024, 1992) ≤ 1<<18.
3. **Device-controlled status indexing** (intel_sdvo.c:560-561) —
   `cmd_status_names[status]` guarded by
   `status <= SDVO_CMD_STATUS_SCALING_NOT_SUPP` (=6, intel_sdvo_regs.h:141);
   the array has 7 entries (intel_sdvo.c:427-435).
4. **TV format/scale tables** — `intel_sdvo_get_tv_modes`
   (2004-2014): 3-byte reply into zeroed u32, loop bounded by
   ARRAY_SIZE(sdvo_tv_modes)=19, higher device bits ignored;
   `intel_sdvo_tv_create_property` (2777-2791): bounded by TV_FORMAT_NUM;
   HDTV-only device format map ⇒ `format_supported_num==0` ⇒
   `drm_property_create(...,0)` returns a valid 0-value property
   (drm_property.c:103-107 skips alloc; NULL `values` never dereferenced,
   loop bound 0; no userspace value can validate ⇒ -EINVAL) — functional
   robustness only, no memory unsafety.
5. **Div-by-zero family** — only division is
   `dotclock /= pipe_config->pixel_multiplier`, guarded by
   `if (pipe_config->pixel_multiplier)` (intel_sdvo.c:1491-1492); other
   divisions are by constants (817-818, 1069, 3156-3157).
6. **Debug-log buffers/BUG_ON** (394-425, 512-586) — max ~70 of 256 bytes
   used (args ≤ 8 bytes ×3 chars + ≤40-char name); BUG_ONs unreachable.
7. **Type confusion of connector->state** (`container_of` into the larger
   `intel_sdvo_connector_state`) — killed: state is pre-installed by
   `intel_sdvo_connector_alloc` (2472-2491); `drm_connector_init` never
   allocates/replaces state (drm_connector.c, checked);
   `intel_sdvo_connector_funcs` has no `.reset` (2226-2236); duplicates use
   `kmemdup` of the correct size (2213-2224). The base-typed state can
   never exist for an SDVO connector.
8. **`sdvo->ddc_bus` mutation race** in `intel_sdvo_tmds_sink_detect`
   (1769-1786) — serialized: all detect/get_modes paths run under
   `connection_mutex` (drm_probe_helper.c:408).
9. **i2c lock protocol** — ddc proxy calls `__i2c_transfer`/unlocked
   sideband write only while holding the proxied parent-bus lock
   (intel_sdvo.c:2980-2983, 2997-3022); all other sideband traffic uses
   locked `i2c_transfer` (486-488). No unlocked access, no AB-BA.
   HPD `intel_sdvo_enable_hotplug` (1716-1722) can interleave write/read
   pairs with other CPUs at command granularity (bus still locked) — worst
   case a failed command retry; same as upstream; no kernel memory hazard.
10. **EDID pass-through** (b) — the ddc proxy is a 1:1 relay
    (2974-2995); parser robustness belongs to drm_edid.c (out of file).
11. **Uninitialized reads** — partial `read_response` fills are only
    consumed on full success (569-581 + return-false propagation at
    618-621, 1074-1076, 1454-1461); `args` memset (762); all
    `struct intel_sdvo_dtd` writers memset (802).
12. **NULL `sdvo_lvds_fixed_mode` derefs** — guarded by IS_LVDS at every
    site (768-771, 1145-1147, 1304-1306, 1646-1651); guaranteed non-NULL
    for LVDS connectors by the lvds_init err path (2674-2675).
13. **Probe/HPD ordering** (f) — `hpd_pin` assigned only after successful
    output_setup (3121-3126); pre-init `intel_sdvo_enable_hotplug` (2527)
    cannot dispatch to the encoder (dispatch requires hpd_pin), so no
    use-after-free of the freed encoder on the err path (3167-3175).
14. **Cross-file deps used here** — `intel_gmbus_get_adapter` WARNs and
    returns NULL for invalid pins (intel_i2c.c:891-894); VBT parse
    validates `crt_ddc_pin` (intel_bios.c:1505-1506). Safe.
15. **Known upstream CVEs** (g) — no CVE has ever been assigned against
    intel_sdvo.c; the classic fixed-array OOBs of the pre-2010 driver were
    fixed by the BUILD_BUG_ON/dynamic-alloc refactor this vintage already
    carries. Nothing carried.

The error-path leak (this finding) is the only survivor of the hunt.

## Fix validation

`fix.diff` authored post-verification against the read-only tree;
`git apply --check` passes (4 hunks, one per init site). Uses
`drm_atomic_helper_connector_destroy_state()` (drm_atomic_helper.c:3930,
already the driver's `.atomic_destroy_state` op at intel_sdvo.c:2234) —
for a freshly kzalloc'd, never-exposed state this is exactly `kfree(state)`.
Behavioral validation requires Intel SDVO hardware (apply faultinject.diff
+ fix.diff on real hardware; see README.md). Not testable on this guest ⇒
fix_status=not_testable.

## 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.
