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

vo destruction runs under the 'video' serial only while all vo sysctl handlers run under 'video_output' only β€” use-after-free race on vo/vo_levels with an unprivileged 'levels' reader

Summary

Two disjoint ACPI_SERIAL domains guard the same object across destroy vs handler execution and sysctl_ctx_free cannot stop an in-flight handler. A firmware REPROBE destroying an output concurrent with a sysctl read frees vo/vo_levels under the reader: the world-readable 'levels' oid runs sysctl_handle_opaque on freed memory (unpriv kernel-heap disclosure) and the brightness handler can write vo_brightness into freed memory. Microsecond-scale window per destroy event; missing mutual exclusion is plain in the source. Take-both-locks fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4488 Β· 2 files
FileTypeDescriptionSize
VERDICT.md β€” 2.7 KB ↓ raw
verdict.json β€” 1.9 KB view raw
VERDICT.md
↓ download raw

DF-4488 β€” destroy paths run under the video lock only while sysctl handlers run under video_output only (UAF race)

Root cause (lock-protocol analysis; no runtime claim): - Destroy paths that free struct acpi_video_output and its sysctl tree: - acpi_video_detach() :300-304 β€” takes ACPI_SERIAL(video) only. - REPROBE handler destroy loop :389-395 β€” takes ACPI_SERIAL(video) only. - Sysctl handlers dereference vo (arg1 of the oid): - acpi_video_vo_active_sysctl() :754 β€” takes video_output only. - acpi_video_vo_bright_sysctl() :774 β€” video_output only. - acpi_video_vo_presets_sysctl() :815 β€” video_output only. - acpi_video_vo_levels_sysctl() :858 β€” video_output only. None of them take the video serial; vo_destroy never takes video_output. sysctl_ctx_free() in vo_destroy unregisters the oids but cannot stop a handler that is already executing. - β‡’ a firmware REPROBE notify that destroys an unmatched output concurrent with a sysctl read frees vo (and vo_levels) under the reader. The levels oid is world-readable, so an UNPRIVILEGED sysctl hw.acpi.video.<out>.levels racing the destroy gets sysctl_handle_opaque(oidp, vo->vo_levels, ...) (:867-868) on freed memory β€” a kernel-heap disclosure to an unprivileged reader; the brightness handler can additionally write vo->vo_brightness (:800) into freed memory. - Also acpi_video_vo_active_sysctl() reads vo->handle BEFORE taking the lock (:751-753) β€” a TOCTOU on the pointer the REPROBE handler NULLs.

Why "untested"/speculative: the race window is the few instructions between handler entry and the opaque copy; triggering needs a firmware REPROBE that destroys an output racing a sysctl read β€” on the audit guest the SSDT choreography can fire the notify but only produces a single destroy event per output (destroyed outputs are not recreated), giving a ~Β΅s window. Not reliably demonstrable; the missing-lock overlap is, however, plain in the source (two different ACPI_SERIAL domains guarding the same object).

Fix: take ACPI_SERIAL_BEGIN(video) inside the sysctl handlers (or route vo_destroy through video_output as well) so destroy and handler execution are mutually exclusive.

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.

(No fix.diff for this item: documented recommendation only - see Fix above.)

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

Speculative race; not runtime-tested.

per-fix-DF-4488

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (lock analysis with path:line)

Verified recommended fix

Take ACPI_SERIAL_BEGIN(video) in the vo sysctl handlers (or video_output in vo_destroy) so destroy and handler execution are mutually exclusive

Verdict

Lock-domain mismatch: vo destroy paths (acpi_video_detach :300-304, REPROBE destroy loop :389-395) hold only the 'video' serial while all four vo sysctl handlers (:754,:774,:815,:858) hold only 'video_output'; sysctl_ctx_free cannot stop an in-flight handler. A firmware REPROBE destroying an output racing a sysctl read frees vo/vo_levels under the reader: the world-readable 'levels' oid then runs sysctl_handle_opaque on freed memory (unpriv kernel-heap disclosure) and the brightness handler can write vo->vo_brightness into freed memory (:800). vo_active_sysctl also reads vo->handle before locking (:751-753). Speculative: single microsecond-scale window per destroy event; not reliably demonstrable on the guest, but the missing mutual exclusion is plain in the source.