vo_get_brightness_levels leaks the _BCL ACPI evaluation buffer on every successful call (unbounded under firmware reprobe notifies)
Summary
The success return never frees bcl_buf.Pointer while only the failure paths reach the out: free. Every attach and every REPROBE rebind leaks one table-controlled eval buffer. VERIFIED on guest: 40 sysctl reads -> M_ACPICA 51->209 allocs (+257KB, ~78x3.3KB), levels stay correct. Fix validated: arena flat across the same loop.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4485 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.1 KB | β raw | |
| VERDICT.md | β | 2.1 KB | β raw | |
| run_df4485.sh | β | 748 B | view raw | |
| ssdt_dfb.asl | β | 3.1 KB | β download | |
| run.log | β | 781 B | view raw | |
| fix_run.log | β | 781 B | view raw | |
| verdict.json | β | 2.3 KB | view raw |
DF-4485 β vo_get_brightness_levels() leaks the _BCL ACPI evaluation buffer on every successful call
Impact
Low. Kernel memory leak in M_ACPICA (unbounded in the number of
firmware-initiated REPROBE notifies): each successful _BCL evaluation
during attach/rebind leaks the ACPI_BUFFER returned by AcpiEvaluateObject.
The leak size per event is controlled by the ACPI table (package size), so a
crafted table leaks arbitrarily large chunks per notify β kernel memory
exhaustion.
Where (sys/dev/acpica/acpi_video/acpi_video.c)
- :992-1003 β success path:
*levelp = levels; return (n);β returns WITHOUT freeingbcl_buf.Pointer(the ACPI_OBJECT package). Only the failure paths reach theout:label that frees it (:1005-1007). - Compare
vid_enum_outputs():960-963 which freesdod_buf.Pointeron every path including success.
Reproduction (guest-verified)
SSDT ssdt_dfb.asl: _BCL returns a 64-entry package (~3.3 KB eval buffer)
and always succeeds; _DCS fires Notify(VGT0, 0x81) (REPROBE) on every read
from the 2nd on, so each sysctl read of hw.acpi.video.lcd0.active rebinds
both outputs and leaks both _BCL eval buffers.
run_df4485.sh (log: run.log):
kldload acpi_video vmstat -m | grep acpica # baseline: 51 allocs, 10.2K in use sleep 10; vmstat -m ... # control: unchanged (51 / 10.2K) β idle churn = 0 40 Γ sysctl read lcd0.active # 39 REPROBEs Γ 2 outputs = 78 leaked buffers vmstat -m | grep acpica # after: 209 allocs, 267K in use
+158 allocations / +257 KB attributable to the reads; β3.3 KB per leaked
_BCL buffer, exactly the 64-entry package size. Guest stayed up, rebinds
kept working (levels still correct after the loop).
Verdict summary
reproduced β deterministic, measurable kernel memory growth under
firmware-controlled notify frequency (impact: memory-exhaustion DoS).
Attacker model: crafted ACPI table (boot/loader privilege) or firmware bug;
hence Low severity.
Fix
fix.diff (validated on the guest): free bcl_buf.Pointer on the success
path of vo_get_brightness_levels() before returning.
VERDICT β DF-4485 (vo_get_brightness_levels() leaks the _BCL eval buffer)
Status: reproduced. One guest boot with SSDT-B (ssdt_dfb.asl),
measured via the M_ACPICA malloc type in vmstat -m.
Chain
vo_get_brightness_levels(acpi_video.c:966-1009) evaluates_BCLintobcl_buf(ACPI_ALLOCATE_BUFFER). On success it copies the integers into a freshlevelsarray and returns at :1002-1003 WITHOUT freeingbcl_buf.Pointerβ only the failure paths reach theout:free at :1005-1007. (Comparevid_enum_outputs:960-963 which always frees.)- Every
acpi_video_vo_bindβ at attach and at every0x81REPROBE rebind β therefore leaks one eval buffer per output, sized by the ACPI table's_BCLpackage.
Measurement (run.log)
- baseline (after attach):
acpica 51 allocs, 10.2K in use - idle 10 s control:
acpica 51 allocs, 10.2Kβ background churn is zero - 40 sysctl reads of
lcd0.activeβ 39 REPROBE notifies β 39 rebinds Γ 2 outputs:acpica 209 allocs, 267K in use - Ξ = +158 allocations / +257 KB β β3.3 KB per leaked buffer β exactly the
64-entry
_BCLpackage this table returns. Levels stayed correct and the guest stayed up throughout (memory-exhaustion class, not a crash).
Ceiling
The notify frequency is firmware-controlled, and the per-event leak size is
table-controlled (_BCL package of N integers β ~48Β·N+overhead bytes), so a
crafted table can drain kernel memory arbitrarily fast. Requires
firmware/loader control (or buggy firmware reprobing) β Low severity.
Fix validation
fix.diff (shared with DF-4484) frees bcl_buf.Pointer on the success
path. Re-tested on the patched kernel with the identical 40-read loop: the
acpica arena stayed flat (see fix_run.log).
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
fixedPatched kernel: same 40-read REPROBE loop leaves the acpica malloc arena flat (no growth), levels stay correct.
fix.diff; fix_run.log (patched vmstat before/after identical)
Confirmed kernel references
Detail
Exploit chain
crafted SSDT _BCL with 64-entry package + per-read 0x81 reprobe notifies -> each rebind leaks the eval buffer -> measured unbounded M_ACPICA growth.
Evidence (decisive lines)
run.log (vmstat -m acpica baseline 51/10.2K, control 51/10.2K, after 209/267K; levels still valid)
PoC changes
SSDT-B written from scratch; 64-entry _BCL to make the leak measurable in vmstat -m.
Verified recommended fix
AcpiOsFree(bcl_buf.Pointer) on the success path of vo_get_brightness_levels before returning n
Verdict
Reproduced: vo_get_brightness_levels frees the _BCL ACPI eval buffer only on failure paths; the success return at acpi_video.c:1002-1003 leaks bcl_buf.Pointer on every attach/rebind. 39 firmware REPROBEs (SSDT-B, one per sysctl read) grew the M_ACPICA arena from 51 allocs/10.2K to 209 allocs/267K (+257KB, ~3.3KB per leaked 64-entry _BCL buffer) while a 10s idle control showed zero growth. Leak rate and per-event size are firmware/table-controlled -> kernel memory exhaustion class. Low severity (needs firmware notify control; crafted-table/boot privilege or buggy firmware).
No comments yet.