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

vlv_dsi_init !fixed_mode error path kfrees a registered drm_connector without drm_connector_cleanup β€” dangling entry on mode_config.connector_list and in the mode-object IDR

Summary

The !fixed_mode branch jumps to err AFTER drm_connector_init registered the connector in the mode-object IDR and on the global connector list. The err label runs encoder_cleanup + kfrees but never connector_cleanup - leaving a dangling pointer on the global list and a live stale id in the IDR. Post-trigger, unprivileged GETRESOURCES/GETCONNECTOR walk/resolve the freed connector (UAF), and drm_mode_config_cleanup at unload does connector_put on freed memory (guaranteed unload crash). Trigger is a drm_mode_duplicate OOM during attach. Dedicated err_connector label with connector_cleanup fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4206 Β· 5 files
FileTypeDescriptionSize
README.md β€” 3.8 KB ↓ raw
VERDICT.md β€” 2.8 KB ↓ raw
verdict.json β€” 2.7 KB view raw
fix.diff β€” 728 B view raw
manifest.json β€” 916 B view raw

DF-4206 β€” vlv_dsi_init !fixed_mode error path kfrees a registered connector β†’ dangling entry in mode_config list/IDR

File: sys/dev/drm/i915/vlv_dsi.c (pass 2) Severity: Low | Confidence: certain | CWE-416 (use after free) with CWE-401 (leak of connector->name / probed modes)

Build / Run

No build or run: Phase V skipped per contract (Low severity β€” the trigger is a drm_mode_duplicate allocation failure, i.e. kernel OOM at exactly this point in DSI init; the audit guest has no Intel GPU). The defect is provable from source.

The bug (path:line)

Execution order in vlv_dsi_init():

  1. vlv_dsi.c:1839 drm_connector_init(dev, connector, ...) β€” registers the connector in the mode-object IDR (__drm_mode_object_add, drm_connector.c:208) and links it into dev->mode_config.connector_list (drm_connector.c:258).
  2. vlv_dsi.c:1842 drm_connector_helper_add.
  3. vlv_dsi.c:1848 intel_connector_attach_encoder.
  4. vlv_dsi.c:1851 intel_dsi_vbt_get_modes() β€” duplicates vbt.lfp_lvds_vbt_mode (non-NULL is guaranteed on this path because intel_dsi_vbt_init() already dereferenced it, intel_dsi_vbt.c:544) and adds it as PREFERRED.
  5. vlv_dsi.c:1852-1857 loop picks the PREFERRED mode into fixed_mode via a second drm_mode_duplicate.
  6. vlv_dsi.c:1860-1863 β€” if fixed_mode == NULL (either duplicate failed: kernel OOM), goto err.
  7. err label vlv_dsi.c:1875-1878: drm_encoder_cleanup(&intel_encoder->base); kfree(intel_dsi); kfree(intel_connector); β€” drm_connector_cleanup() is never called.

Consequences after step 7 (i915 driver init continues β€” a failed output does not abort the driver):

  • dev->mode_config.connector_list still contains &connector->head which now points into freed memory (connector is embedded in the freed intel_connector). Every walker dereferences freed memory:
  • DRM_IOCTL_MODE_GETRESOURCES (unprivileged) builds the connector id list from that list;
  • DRM_IOCTL_MODE_GETCONNECTOR with the stale id resolves via drm_mode_object_find() β†’ the IDR still holds the freed connector's id (only drm_connector_cleanup β†’ drm_mode_object_unregister removes it, drm_connector.c:405) β†’ returns the freed pointer β†’ UAF read/write;
  • drm_mode_config_cleanup() at unload does drm_connector_put() on each list entry β†’ refcount write to freed memory β†’ UAF write, guaranteed crash on unload.
  • connector->name (kasprintf, drm_connector.c:236-239) and any probed modes leak ( drm_connector_cleanup would have freed both, drm_connector.c:392-396,406).

Trigger

drm_mode_duplicate β†’ drm_mode_create β†’ kzalloc failure: i.e. kernel memory exhaustion during DSI connector init (step 4 or step 5). All other goto err sites (:1811 host init, :1818 vbt init) are before drm_connector_init and are correctly handled.

Distinct from known findings

  • DF-1446 covers the leak of dsi_hosts[port] (host + mipi_dsi_device) on the same err: label and on encoder teardown. This finding is a different object class (the drm_connector) and a different impact (use-after-free of globally-linked state vs. a pure allocation leak).

Fix

Dedicated err_connector label calling drm_connector_cleanup(connector) before the encoder cleanup β€” see fix.diff. drm_connector_cleanup (drm_connector.c:375-422) removes probed modes, unregisters the IDR object, frees the name, and list_dels from connector_list, so the dangling state is fully unwound.

Threat model / impact ceiling

Trigger is OOM-gated (not attacker-chosen timing without memory-pressure influence). After the trigger, ordinary unprivileged DRM ioctls walk/use the freed connector β€” a genuine UAF surface β€” but the object is a small driver-owned struct; practical exploitation is constrained. Treated as Low.

VERDICT.md
↓ download raw

DF-4206 VERDICT

Status: untested (Phase V skipped β€” Low severity, OOM-gated trigger, no Intel GPU on the audit guest). Bug existence: certain, proven from source.

Reproduced?

Not on the guest: the QEMU audit VM has no Intel GPU (i915 never attaches, vlv_dsi_init unreachable), and the trigger requires a kzalloc failure inside drm_mode_duplicate during DSI connector init on real hardware. Source-level trace is complete and unambiguous (below), so bug existence is rated certain; runtime impact untested.

The chain, with citations

  • Registration: drm_connector_init puts the connector in the mode-object IDR (drm_connector.c:208 __drm_mode_object_add) and on dev->mode_config.connector_list (drm_connector.c:258 list_add_tail).
  • Faulting flow: vlv_dsi.c:1839 (init) β†’ :1848 (attach encoder) β†’ :1851 (intel_dsi_vbt_get_modes, adds PREFERRED mode) β†’ :1852-1857 (second duplicate into fixed_mode) β†’ :1860-1863 goto err when the duplicate fails β†’ :1875-1878 kfree(intel_dsi); kfree(intel_connector) with no drm_connector_cleanup.
  • Post-free use (unprivileged): MODE_GETRESOURCES walks connector_list; MODE_GETCONNECTOR resolves the stale IDR id (drm_mode_object_find); drm_mode_config_cleanup at unload does drm_connector_put on the freed entry (refcount write to freed memory).
  • Secondary leaks on the same path: connector->name (drm_connector.c:236) and probed modes (drm_connector.c:392-396 would have freed them).

Impact ceiling

Use-after-free of a driver-owned intel_connector reachable through plain DRM ioctls after the OOM-gated trigger fires during attach; guaranteed crash at driver unload (drm_connector_put on freed memory). No escalation chain developed: the trigger cannot be scheduled by an unprivileged user, and the freed object is small driver state. Low severity.

Fix validation

Not performed on the guest (no hardware). By inspection, the err_connector label calling drm_connector_cleanup(connector) exactly undoes steps taken after :1839: IDR unregister (:405), name free (:406), probed-modes free (:392-393), list_del (:409) β€” leaving the earlier err sites (:1811, :1818) untouched and correct.

Relationship to DF-1446

DF-1446 (same label) is the dsi_hosts host/device leak β€” a CWE-401 resource leak. DF-4206 is the connector lifetime defect β€” CWE-416. Different object, different impact, complementary fix (fix.diff adds a label; DF-1446's fix adds dsi_hosts frees).

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

No kernel-build fix validation performed: Low-severity OOM-gated finding, guest lacks Intel GPU; fix.diff is a two-hunk label addition whose correctness is checkable by inspection against drm_connector_cleanup (drm_connector.c:375-422).

fix.diff (err_connector label + rerouted goto); not built or applied to sys/.
↓ fix.diffper-fix-DF-4206

Confirmed kernel references

Detail

Evidence (decisive lines)

README.md and VERDICT.md in this pack; decisive lines: vlv_dsi.c:1839 (drm_connector_init), :1848 (attach), :1851-1857 (mode dup), :1860-1863 (goto err), :1875-1878 (kfree without cleanup); drm_connector.c:208 (IDR add), :258 (list_add_tail), :375-422 (what cleanup should have done).

PoC changes

No PoC authored: trigger is a kzalloc failure inside drm_mode_duplicate during DSI init on real hardware; audit guest has no Intel GPU. Static proof of path only.

Verified recommended fix

Add an err_connector label calling drm_connector_cleanup(connector) before the existing err cleanup, and route the !fixed_mode branch to it (fix.diff).

Verdict

Bug existence certain from source trace: when the PREFERRED-mode drm_mode_duplicate fails (kernel OOM) after drm_connector_init already registered the connector (drm_connector.c:208 IDR, :258 connector_list), vlv_dsi_init jumps to err (vlv_dsi.c:1860-1863 -> :1875-1878) and kfrees intel_connector without drm_connector_cleanup, leaving a dangling pointer on dev->mode_config.connector_list and a live id in the mode-object IDR. Post-trigger, unprivileged MODE_GETRESOURCES/MODE_GETCONNECTOR and drm_mode_config_cleanup at unload walk/put the freed connector (UAF read/write; guaranteed unload crash). Not verified on the guest: no Intel GPU on the audit VM and the trigger is OOM-gated, so Phase V is skipped per contract (Low severity). Distinct from DF-1446 (dsi_hosts leak on the same label - different object class and impact).