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

Missing !conn_state->crtc guard in intel_panel_set_backlight: unprivileged hw.backlight_level sysctl write NULL-derefs to_intel_crtc(NULL)->pipe in vlv_set_backlight (VLV/CHV)

Summary

intel_panel_set_backlight is the only live caller-fed path into panel->backlight.set() without a conn_state->crtc check - its ACPI sibling carries exactly that guard with a comment documenting crtc-less states. On DragonFly the 'any time' caller is the DFly-specific sysctl hw.backlight_level (CTLFLAG_RW|CTLFLAG_ANYBODY) writable by any local user. On VLV/CHV the set hook executes to_intel_crtc(NULL)->pipe - container_of NULL member read -> page-fault panic. Preconditions: panel->backlight.enabled seeded true from the BIOS PWM-enable bit while the connector's atomic state has no crtc. The read side is fully defended; the set-side asymmetry is a porting oversight. Guard-mirror fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4161 Β· 7 files
FileTypeDescriptionSize
README.md β€” 2.7 KB ↓ raw
VERDICT.md β€” 5.4 KB ↓ raw
trigger.sh β€” 1.1 KB view raw
fix.diff β€” 1.1 KB view raw
env.txt β€” 926 B view raw
manifest.json β€” 1.2 KB view raw
verdict.json β€” 3.7 KB view raw

DF-4161 β€” missing !conn_state->crtc guard in intel_panel_set_backlight β†’ vlv_set_backlight NULL deref via unprivileged hw.backlight_level sysctl

File: sys/dev/drm/i915/intel_panel.c (pass 2)

What

intel_panel_set_backlight() (intel_panel.c:1148-1170) is the only live caller-fed entry into panel->backlight.set() that does NOT check conn_state->crtc. Its ACPI sibling intel_panel_set_backlight_acpi() (:620-654) carries exactly that guard (:634) with the comment "Lack of crtc may occur during driver init because connection_mutex isn't held across the entire backlight setup + modeset readout, and the BIOS can issue the requests at any time." On DragonFly the "request at any time" caller is the DFly-specific sysctl hw.backlight_level (sysctl_backlight_handler, :1811-1841, CTLFLAG_RW | CTLFLAG_ANYBODY β€” writable by ANY local user).

On VLV/CHV (Valleyview/Cherryview, non-DSI connectors) the selected .set hook is vlv_set_backlight() (:577-586):

enum i915_pipe pipe = to_intel_crtc(conn_state->crtc)->pipe;   /* :581 β€” NULL deref */

while the read side is defended (intel_get_pipe_from_connector returns INVALID_PIPE for a NULL crtc, intel_display.c:14158-14168, and _vlv_get_backlight WARN_ONs and returns 0, :497-503). No other .set implementation derefs conn_state->crtc, which is why the guard's absence only bites VLV/CHV.

Preconditions (hardware state)

  • VLV/CHV i915 with LVDS/eDP panel, pm_method = register PWM (not DSI-pwm).
  • panel->backlight.enabled == true with connector->base.state->crtc == NULL. enabled is seeded from the BIOS handoff at setup (panel->backlight.enabled = ctl2 & BLM_PWM_ENABLE, :1662) β€” independent of any DRM atomic commit. A panel the BIOS left lit that was never (or is currently not) bound to a CRTC in the connector state satisfies both. present is true after any successful setup (:1875).
  • The sysctl write must change the value (val != user_level), be >= 0 and <= max_brightness (:1834) β€” trivially arranged by any local user.

Trigger (to run on VLV/CHV hardware)

./trigger.sh

expects hw.backlight_level to exist; writes a fresh value as an unprivileged user. On a vulnerable kernel the expected result is a kernel page fault at to_intel_crtc(NULL)->pipe inside vlv_set_backlight (near-NULL read) β†’ panic.

Status on this audit guest

Hardware-gated: the QEMU guest has virtio VGA only (no 0x8086 display device, hw.drm/hw.backlight_level unknown oids β€” see env.txt). Per the DF-4022 family convention the finding is source-certain / runtime-inconclusive.

Fix

See fix.diff: mirror the _acpi guard in intel_panel_set_backlight() and add a defensive WARN_ON(!conn_state->crtc) in vlv_set_backlight().

VERDICT.md
↓ download raw

DF-4161 β€” VERDICT

Status: inconclusive (source-level certain, runtime hardware-gated). Expected impact if hardware present: panic β€” local unprivileged DoS on VLV/CHV.

Reproduced?

No. The audit guest has no Intel GPU (virtio VGA only; sysctl hw.drm β†’ unknown oid; zero 0x8086 PCI devices β€” env.txt), so intel_panel_setup_backlight() / sysctl_backlight_handler() are never reached. The deref chain is proven at source level end-to-end:

The chain (path:line)

  1. Unprivileged entry β€” intel_panel_setup_backlight() registers hw.backlight_level as CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY (intel_panel.c:1884-1889) with sysctl_backlight_handler as the callback. ANY local user may write it (this is the same unprivileged entry DF-1411 analyzed for its modeset-UAF; this finding is the different, stable-state NULL-crtc defect).

  2. Handler validation is insufficient for VLV β€” sysctl_backlight_handler (:1811-1841) captures conn_state = connector->base.state (:1815), reads max_brightness under backlight_lock, accepts any val with 0 <= val <= max_brightness (:1834), then takes connection_mutex and calls intel_panel_set_backlight(conn_state, val, max_brightness) (:1836). The connection_mutex does NOT imply the connector state has a crtc β€” mutex β‰  crtc.

  3. Missing guard β€” intel_panel_set_backlight() (:1148-1170) checks only panel->backlight.present (:1156). Contrast intel_panel_set_backlight_acpi() (:620-654) which checks !panel->backlight.present || !conn_state->crtc (:634) with the explicit comment that crtc-less requests occur in practice. The static function is the porting leftover of the upstream hook that carries the same guard; the guard was dropped in this tree.

  4. enabled gate passes β€” panel->backlight.enabled was seeded at setup from the BIOS PWM-enable bit (vlv_setup_backlight, :1643-1662: panel->backlight.enabled = ctl2 & BLM_PWM_ENABLE) and is independent of any atomic commit; it stays true when the panel was lit at handoff and the connector never got a CRTC (or a readout left it unassigned).

  5. Deref β€” panel->backlight.set(conn_state, level) dispatches to vlv_set_backlight() which executes to_intel_crtc(conn_state->crtc)->pipe (:581). to_intel_crtc(NULL) is a container_of around NULL; reading the pipe member at its small struct offset faults β†’ kernel panic.

The read path is defended end-to-end (intel_get_pipe_from_connector returns INVALID_PIPE for crtc==NULL, intel_display.c:14158-14168; _vlv_get_backlight WARN_ONs, intel_panel.c:497-503) β€” the asymmetry confirms the set path is an oversight, not a design decision.

Exploit chain

Not a corruption primitive: deterministic near-NULL read β†’ panic. Unprivileged local DoS on VLV/CHV laptops in the crtc-less+enabled state. No route to uid0.

Why not false-positive

  • The deref instruction sequence is unconditional in vlv_set_backlight.
  • The sibling _acpi variant carries the exact guard with a comment describing real-world occurrences of the state.
  • DF-1411 (modeset-race UAF on the same sysctl) is a separate defect and was excluded from this analysis.

Fix validation

fix.diff authored against the read-only tree (guard + WARN_ON in vlv_set_backlight). Not runtime-testable on this guest (no i915 hardware); git apply --check clean.

Negative results this pass (intel_panel.c, classes hunted and killed)

  • other controller setups: cnp_setup_backlight hardcodes controller=0 (intel_panel.c:1719) β€” no VBT index; lpt/pch/i9xx/i965 use no controller field; bxt VBT 0-15 controller is DF-1412 (excluded, not re-filed).
  • intel_backlight_device sysfs registration is dead code on DFly (#if 0, :1265-1281); intel_backlight_device_unregister likewise (:1288-1295).
  • PWM divider math: every *_hz_to_pwm is only reached via get_backlight_max_vbt() which forces pwm_freq_hz != 0 (:1446-1453); pwm_freq_hz * mul (u16 * ≀128) and clock fit u32/int β€” no div0, no overflow.
  • DSI PWM path: DFly's pwm_get() stub returns NULL (linux/pwm.h:68-72) which passes the IS_ERR() check (:1754), but the follow-up pwm_config(NULL,...) stub returns -EINVAL (:34-39 of the same header) so pwm_setup_backlight fails closed (:1768-1775). No NULL-pwm escape.
  • scaling arithmetic: scale() clamps inputs and uses u64 intermediates (:389-408); min can never exceed max (get_backlight_min_vbt clamps to [0..64] then scales into [0..max], :1482-1489); register writes are masked RMW (& ~BACKLIGHT_DUTY_CYCLE_MASK), so no adjacent-field bleed; i9xx combination level * 0xfe ≀ 32767255254 fits u32 (:561).
  • sysctl node lifetime: nodes live in the DRM device sysctl ctx, which drm_device_detach() frees via drm_sysctl_cleanup() BEFORE driver/connector teardown (drm_dragonfly.c:250-256 β†’ sysctl_ctx_free, drm_sysctl.c:109-117) β€” no sysctl-after-connector-free window at detach.
  • vlv_disable_backlight's crtc deref (:717) is only reached from encoder disable paths where old_conn_state always carries the crtc being disabled.

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 (guard at :1156 + WARN_ON at vlv_set_backlight) applies clean via git apply --check; cannot be runtime-validated without VLV/CHV hardware.

findings/poc/DF-4161/fix.diff
↓ fix.diffper-fix-DF-4161

Confirmed kernel references

Detail

Exploit chain

On VLV/CHV hardware: any local user writes hw.backlight_level (value != current, 0<=val<=max) while the BIOS-lit panel's connector state has no crtc -> near-NULL read -> deterministic panic. DoS only; no write primitive, no uid0 route.

Evidence (decisive lines)

VERDICT.md (path:line chain + pass-2 negative results); env.txt (hardware absence + md5 cfe495b2207583b24f9c7c5714b38389 == audited tree); trigger.sh (exact unprivileged command); fix.diff (guard + WARN_ON, git apply --check clean)

PoC changes

No seed PoC. trigger.sh authored this pass (documents the unprivileged sysctl command + skip condition for guests).

Verified recommended fix

Mirror the _acpi guard: add 'if (!conn_state->crtc) return;' to intel_panel_set_backlight (+WARN_ON belt-and-braces in vlv_set_backlight), see fix.diff.

Verdict

Source-level certain deref chain, runtime hardware-gated (inconclusive per family convention). sysctl_backlight_handler (intel_panel.c:1811-1841) accepts writes from ANY local user (CTLFLAG_RW|CTLFLAG_ANYBODY, registered at :1884-1889) and calls intel_panel_set_backlight(conn_state, val, max) at :1836. intel_panel_set_backlight (:1148-1170) checks ONLY panel->backlight.present (:1156) -- it is missing the '!conn_state->crtc' guard its ACPI sibling intel_panel_set_backlight_acpi carries at :634 with a comment documenting that crtc-less states occur in practice. With panel->backlight.enabled seeded true from the BIOS handoff (vlv_setup_backlight :1662, enabled = ctl2 & BLM_PWM_ENABLE, independent of any atomic commit) and a connector state carrying no crtc, the dispatch reaches vlv_set_backlight (:577) which executes to_intel_crtc(conn_state->crtc)->pipe at :581 -> container_of(NULL) member read -> page-fault panic. Read side is fully defended (intel_get_pipe_from_connector returns INVALID_PIPE, intel_display.c:14158-14168; _vlv_get_backlight WARN_ONs, :497-503) -- the set-side asymmetry is the oversight. VLV/CHV (non-DSI) only: no other .set hook derefs conn_state->crtc. Guest: virtio VGA only, zero 0x8086 PCI devices, hw.drm/hw.backlight_level unknown oids (env.txt) -> not runtime-testable. Distinct from DF-1411 (modeset-race UAF on the same sysctl) and DF-1412 (bxt VBT controller OOB MMIO); neither re-filed.