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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4485 Β· 7 files
FileTypeDescriptionSize
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 freeing bcl_buf.Pointer (the ACPI_OBJECT package). Only the failure paths reach the out: label that frees it (:1005-1007).
  • Compare vid_enum_outputs() :960-963 which frees dod_buf.Pointer on 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.md
↓ download raw

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

  1. vo_get_brightness_levels (acpi_video.c:966-1009) evaluates _BCL into bcl_buf (ACPI_ALLOCATE_BUFFER). On success it copies the integers into a fresh levels array and returns at :1002-1003 WITHOUT freeing bcl_buf.Pointer β€” only the failure paths reach the out: free at :1005-1007. (Compare vid_enum_outputs :960-963 which always frees.)
  2. Every acpi_video_vo_bind β€” at attach and at every 0x81 REPROBE rebind β€” therefore leaks one eval buffer per output, sized by the ACPI table's _BCL package.

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 _BCL package 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

fixed
baseline reproduced→ patch + rebuild →patched clean

Patched 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)
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

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).