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

intel_sdvo_connector_state leaked when intel_sdvo_connector_init() fails (all four SDVO connector init paths)

Summary

intel_sdvo_connector_alloc() kzallocs a struct intel_sdvo_connector_state and installs it as connector->state. When intel_sdvo_connector_init() (drm_connector_init wrapper) fails, all four init paths (dvi/tv/analog/lvds) free only the connector and leak the ~152-byte state, reachable only through the freed connector. Upstream-inherited: identical code in Linux through the file's removal in v5.20. One-shot leak per failed SDVO connector probe; trigger requires real Intel SDVO hardware plus a drm_connector_init failure at boot probe (core ENOMEM or connector-index IDA exhaustion). No UAF, no info leak, no unprivileged reachability. All other classes killed with citations: enum indices gated by ENUM validation, dynamic args/resp buffers all BUILD_BUG_ON-matched, TV tables bounded, ddc mutation serialized, no CVE ever assigned against this file and the pre-2010 fixed-array era is already fixed in this vintage.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3913 Β· 9 files
FileTypeDescriptionSize
README.md β€” 3.3 KB ↓ raw
VERDICT.md β€” 7.3 KB ↓ raw
faultinject.diff β€” 540 B view raw
probe.sh β€” 504 B view raw
fix.diff β€” 1.2 KB view raw
run.log β€” 249 B view raw
env.txt β€” 467 B view raw
manifest.json β€” 1.3 KB view raw
verdict.json β€” 4.4 KB view raw

DF-3913 β€” intel_sdvo_connector_state leaked on intel_sdvo_connector_init() failure (all four SDVO connector init paths)

File: sys/dev/drm/i915/intel_sdvo.c (pass 2, GLM 5.3 re-audit)

What the bug is

intel_sdvo_connector_alloc() (intel_sdvo.c:2472-2491) allocates two objects:

  1. struct intel_sdvo_connector (kzalloc, line 2477)
  2. struct intel_sdvo_connector_state (kzalloc, line 2481), immediately installed as connector->state via __drm_atomic_helper_connector_reset() (line 2487).

If intel_sdvo_connector_init() (which calls drm_connector_init()) fails, all four per-type init functions free only the connector and leak the state object:

  • intel_sdvo_dvi_init() β€” intel_sdvo.c:2541-2544
  • intel_sdvo_tv_init() β€” intel_sdvo.c:2574-2577
  • intel_sdvo_analog_init() β€” intel_sdvo.c:2620-2623
  • intel_sdvo_lvds_init() β€” intel_sdvo.c:2656-2659
    if (intel_sdvo_connector_init(intel_sdvo_connector, intel_sdvo) < 0) {
        kfree(intel_sdvo_connector);        /* state (line 2481) leaks */
        return false;
    }

The leaked allocation is sizeof(struct intel_sdvo_connector_state) (~120 bytes on amd64). The state pointer is reachable only through the freed connector, so it is unrecoverable. No UAF: the state is never touched again.

Trigger

Requires Intel SDVO hardware (i915G–Ironlake era, SDVOB/SDVOC port with a responding SDVO device on the GMBUS) and a failure of drm_connector_init() at that point: realistic causes are kzalloc failure inside the core, connector-index IDA exhaustion (ida_simple_get(...,0,32) β€” at most 32 connectors per device), or sysfs/kobject registration failure. Probe-time only; not user-triggerable at runtime.

Why it is Low

  • One-shot ~120-byte leak per failed probe, error path only.
  • No use-after-free, no info leak, no attacker-controlled size.
  • Upstream-inherited: identical code in Linux (through the file's removal in 5.20); worth fixing in DragonFly (and noting upstream) but not exploitable.

Reproduce on real hardware (fault injection)

Apply faultinject.diff to /usr/src/sys/dev/drm/i915/intel_sdvo.c on an Intel-SDVO-equipped DragonFly box, rebuild the kernel/module, boot:

dmesg | grep DF3913

Expected:

DF3913: simulating drm_connector_init failure for DVI device 0
DF3913: LEAKED intel_sdvo_connector_state (152 bytes) at ...   [buggy path]

With fix.diff also applied, the second line becomes:

DF3913: freed failing connector state ...                      [fixed path]

sizeof(struct intel_sdvo_connector_state) on the audited tree is 152 bytes (verify with the printout; struct layout depends on config).

On this audit guest (QEMU)

Not runnable: the guest has no Intel GPU (only QEMU Bochs VGA, chip 0x1234:1111 β€” see env.txt/run.log), so i915 never attaches and intel_sdvo_init() cannot execute. probe.sh records that gate. fix.diff is git apply --check-clean against the pristine tree.

Fix

fix.diff (git-apply-able, never applied to the read-only sys/ tree): free the installed state before freeing the connector at all four sites, using the same helper the driver already registers as .atomic_destroy_state (intel_sdvo.c:2234) β€” drm_atomic_helper_connector_destroy_state() (drm_atomic_helper.c:3930), which is a kfree(state) for a freshly-kzalloc'd, never-exposed state.

VERDICT.md
↓ download raw

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.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

fix.diff (4 hunks) is git apply --check clean against the pristine sys/ tree and uses drm_atomic_helper_connector_destroy_state() (drm_atomic_helper.c:3930), already the driver's .atomic_destroy_state op (intel_sdvo.c:2234); on a fresh kzalloc'd, never-exposed state it is exactly kfree(state). Behavioral validation requires Intel SDVO hardware (faultinject.diff + fix.diff on real HW: leak print disappears); not testable on the QEMU guest (no Intel GPU), so fix_status=not_testable.

findings/poc/DF-3913/fix.diff (git apply --check OK); VERDICT.md 'Fix validation' section
↓ fix.diffper-fix-DF-3913

Confirmed kernel references

Detail

Evidence (decisive lines)

README.md (leak path + hardware procedure); VERDICT.md (full pass-2 hunt incl. 15 killed candidate classes with path:line); faultinject.diff (forces the exact buggy block on real HW, git-apply --check clean); probe.sh + run.log + env.txt (guest hardware gate); fix.diff (4-hunk fix, git apply --check OK)

PoC changes

No PoC seed existed (first finding on this file). Authored faultinject.diff to force the exact failure block (git-apply-verified) and probe.sh to record the guest hardware gate; runtime demonstration requires Intel SDVO hardware absent from the QEMU guest.

Verified recommended fix

Free the installed connector state with drm_atomic_helper_connector_destroy_state() before kfree(intel_sdvo_connector) at all four intel_sdvo_connector_init() failure sites (see fix.diff).

Verdict

Source-certain error-path memory leak: intel_sdvo_connector_alloc() kzallocs an intel_sdvo_connector_state and installs it as connector->state (intel_sdvo.c:2481,2487), but when intel_sdvo_connector_init() fails, intel_sdvo_dvi_init/tv_init/analog_init/lvds_init (intel_sdvo.c:2541-2544, 2574-2577, 2620-2623, 2656-2659) kfree only the connector, leaking the ~152-byte state. One-shot, probe-time-only, requires drm_connector_init failure (ENOMEM/IDA exhaustion) on real Intel SDVO hardware; no UAF, no info leak, not user-triggerable. Runtime verification impossible on the audit guest: only PCI display device is QEMU Bochs VGA (chip 0x1234:1111, run.log), i915 never attaches, intel_sdvo_init() cannot execute - hence inconclusive rather than not_reproduced. Upstream-inherited (identical code in Linux through v5.20 removal).