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)
PoC verification
Evidence pack
findings/poc/DF-4488 Β· 2 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | β | 2.7 KB | β raw | |
| verdict.json | β | 1.9 KB | view 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_testableSpeculative race; not runtime-tested.
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.
No comments yet.