REPROBE destroy path leaves the per-output ACPI notify handler registered against freed memory (AcpiRemoveNotifyHandler(NULL) is a no-op)
Summary
The REPROBE handler NULLs vo->handle before rebinding, so vo_destroy calls AcpiRemoveNotifyHandler(NULL,...) which ACPICA rejects with AE_BAD_PARAMETER (ignored) - the installed handler stays registered on the real node with context=vo, and kfree(vo) runs. Later notifies dispatch on freed vo: UAF read + a 4-byte write-after-free (vo_brightness, value limited to 0..100). vo_bind also never removes the old handler (AE_ALREADY_EXISTS rejected). Destroy-with-NULL-handle reachability proven on guest by DF-4484's choreography. vo_matched-flag fix validated as part of DF-4484's diff.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4486 Β· 2 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | β | 3.1 KB | β raw | |
| verdict.json | β | 2.2 KB | view raw |
DF-4486 β stale per-output notify handler after REPROBE destroy
Root cause (code-certain, no runtime claim):
- sys/dev/acpica/acpi_video/acpi_video.c:385-395 β the VID_NOTIFY_REPROBE
(0x81) handler NULLs vo->handle for every output, rebinds those matched
by the namespace walk, and destroys the unmatched ones.
- sys/dev/acpica/acpi_video/acpi_video.c:627-631 β acpi_video_vo_destroy()
removes the per-output notify handler via
AcpiRemoveNotifyHandler(vo->handle, ACPI_DEVICE_NOTIFY, ...). For an
unmatched output vo->handle is NULL at this point.
- sys/contrib/dev/acpica/source/components/events/evxface.c:399-402 β
AcpiRemoveNotifyHandler() returns AE_BAD_PARAMETER for a NULL Device and
does nothing. The return value is ignored by the caller.
- Result: the handler installed at first bind
(acpi_video.c:611-613, AcpiInstallNotifyHandler(handle, ACPI_DEVICE_NOTIFY,
acpi_video_vo_notify_handler, vo)) stays registered on the REAL (old)
device node with context = vo, and kfree(vo, M_ACPIVIDEO) then runs
(acpi_video.c:654). Any later firmware notify delivered on that node runs
acpi_video_vo_notify_handler() with a freed vo:
- :679 if (vo->handle != handle) β UAF read of freed memory;
- :684-737 on brightness notifies β reads vo->vo_numlevels /
vo->vo_levels[i] from freed memory and WRITES
vo->vo_brightness = new_level (:737) into the freed chunk
(4-byte write-after-free, value limited to a listed level 0..100).
- Additionally acpi_video_vo_bind() (:594-615) never removes the old
handler before installing on the rebind handle; ACPICA rejects the second
install with AE_ALREADY_EXISTS (evxface.c:309-311) so the stale
registration survives even a successful rebind when the handle is unchanged.
Why "untested" (not runtime-verified): making the stale-handler dispatch
visible requires reallocating the freed M_ACPIVIDEO chunk with controlled
content (so vo->handle == handle passes) β M_ACPIVIDEO is only allocated by
this driver's acpi_video_vo_init(), so an in-guest demonstration would need
a second controllable allocation of that exact malloc type, which the driver
does not offer. The destroy-with-NULL-handle path itself is proven reachable
on the guest (DF-4484's REPROBE choreography destroys unmatched outputs the
same way; the panic trace in DF-4484/panic.txt shows vo_destroy running on
that path).
Fix: part of DF-4484's fix.diff β track reprobe matching with a
vo_matched flag instead of NULLing vo->handle (so vo_destroy always
removes the handler from the real node), and remove the old handler in
vo_bind before re-install.
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_testableCovered by DF-4484's fix.diff (same patch); not independently runtime-tested.
DF-4484/fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (static analysis with path:line); evxface.c:399-402 NULL-handle rejection
Verified recommended fix
Track reprobe matching with a vo_matched flag instead of NULLing vo->handle; remove the old notify handler in vo_bind before re-install
Verdict
Code-certain use-after-free of the notify-handler context: REPROBE (acpi_video.c:385-395) NULLs vo->handle before rebind, so acpi_video_vo_destroy removes the per-output handler via AcpiRemoveNotifyHandler(NULL,...) which returns AE_BAD_PARAMETER without doing anything (evxface.c:399-402); kfree(vo) then runs with the handler still registered on the real device node. Later notifies dispatch acpi_video_vo_notify_handler on freed vo: UAF read at :679 (vo->handle compare) and a 4-byte write-after-free at :737 (vo->vo_brightness, value 0..100) under brightness notifies. vo_bind also never removes the old handler before re-install (:611-613, AE_ALREADY_EXISTS per evxface.c:309-311). Not runtime-demonstrated: visibility requires reallocating the freed M_ACPIVIDEO chunk with controlled content (only vo_init allocates that type). Fixed by the same fix.diff as DF-4484 (vo_matched flag keeps handle valid at destroy).
No comments yet.