ACPIIO_DO_MCALL leaks the copied-in argument object list when copyinstr() of the method path faults
Summary
acpi_call_ioctl() builds a kernel copy of the user ACPI_OBJECT_LIST via acpi_copyin_object_list() (M_ACPICA allocations), then returns EINVAL on copyinstr() failure of the method path without freeing it. Each failing ioctl leaks sizeof(ACPI_OBJECT_LIST)+Count*sizeof(ACPI_OBJECT) bytes. MEASURED ON THE GUEST: M_ACPICA InUse 6.39K->6.11M->67.1M->73.2M over 1.1M faulted calls (exactly 64 B/call for a 2-INTEGER list) with a valid-path control leaking nothing. Gated on FWRITE /dev/acpi (root) plus the debug.acpi.allow_method_calls=1 boot tunable: privileged memory-exhaustion DoS / hardening of the debug surface. Fix validated on a patched kernel.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3794 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.4 KB | β raw | |
| VERDICT.md | β | 2.0 KB | β raw | |
| mcallleak.c | β | 2.0 KB | view raw | |
| mcallctrl.c | β | 1.0 KB | view raw | |
| build.sh | β | 149 B | view raw | |
| run.sh | β | 204 B | view raw | |
| run.log | β | 443 B | view raw | |
| run_patched.log | β | 336 B | view raw | |
| env.txt | β | 377 B | view raw | |
| fix.diff | β | 483 B | view raw | |
| verdict.json | β | 2.6 KB | view raw |
DF-3794 β ACPIIO_DO_MCALL leaks the copied-in object list when copyinstr faults
- File:
sys/dev/acpica/acpi.c(pass 2) - Lines: 3341β3378 (leak at 3356β3357)
- Class: CWE-401 (missing release of memory on error path) / CWE-772
- Severity: Low (root + boot-tunable gated) Β· Confidence: certain (measured on guest)
What
params = (struct acpi_mcall_ioctl_arg *)addr;
args = acpi_copyin_object_list(¶ms->args); /* kernel allocations */
if (!args)
return EINVAL;
if (copyinstr(params->path, path, sizeof(path), NULL))
return EINVAL; /* <-- `args` never freed */
acpi_copyin_object_list() allocates sizeof(ACPI_OBJECT_LIST) +
Count*sizeof(ACPI_OBJECT) bytes (plus per-STRING/BUFFER payloads) from
M_ACPICA. If the user-supplied path pointer faults on copyinstr, the
ioctl returns EINVAL without freeing args. Every failing call leaks
exactly that allocation set. There is no bound on repetitions.
Note: ACPIIO_DO_MCALL is reachable only with FWRITE on /dev/acpi
(root; mode 0644) and the boot tunable debug.acpi.allow_method_calls=1
β hence severity Low (robustness/DoS by a privileged-but-untrusted
process, e.g. a root daemon exposing the ioctl, and defense-in-depth for
the debugging path).
Measured on the guest (DragonFly 6.5-DEVELOPMENT, tunable enabled)
vmstat -m | grep acpica
acpica 46 6.39K 0 390M 138K <- baseline
+100,000 faulted DO_MCALL (args: 2 INTEGER objects)
acpica 97.7K 6.11M 0 390M 236K
+1,000,000 faulted DO_MCALL
acpica 1.05M 67.1M 0 390M 1.18M
+100,000 faulted DO_MCALL (second session)
acpica 1.14M 73.2M 0 390M 1.33M
+ 20,000 *valid* DO_MCALL (control, copyinstr succeeds -> args freed)
acpica 1.14M 73.2M 0 390M 1.36M <- InUse unchanged
64 bytes/call (16 + 2Γ24) as predicted; control run leaks nothing. 1.1M iterations took seconds β a privileged looping process can exhaust kernel heap unboundedly.
Reproduce
debug.acpi.allow_method_calls=1in/boot/loader.conf.local, reboot.cc -O -I/usr/src/sys -I/usr/src/sys/contrib/dev/acpica/source/include \ -o mcallleak mcallleak.cvmstat -m | grep acpica; ./mcallleak 100000; vmstat -m | grep acpica(and./mcallctrl 20000for the no-leak control).
Fix
See fix.diff: free args on the copyinstr failure path.
DF-3794 VERDICT
Status: reproduced (measured kernel heap leak, gated on root + tunable)
Guest: DragonFly dfbsd 6.5-DEVELOPMENT (X86_64_GENERIC) with
debug.acpi.allow_method_calls=1 (loader tunable), /dev/acpi mode
crw-r--r-- root:wheel (ioctl needs FWRITE β root).
What was run
mcallleak.c (this pack): 2-INTEGER argument list, path = (char *)0x10
so copyinstr faults after acpi_copyin_object_list() succeeded.
mcallctrl.c: identical ioctl with a valid path (control).
Measured with vmstat -m | grep acpica (M_ACPICA type, InUse bytes):
baseline 46 allocs 6.39K +100,000 faulted calls (1st session) 97.7K allocs 6.11M +1,000,000 faulted calls 1.05M allocs 67.1M +100,000 faulted calls (2nd session) 1.14M allocs 73.2M + 20,000 valid-path control calls 1.14M allocs 73.2M <- unchanged
Exactly 64 bytes/call (16 + 2Γ24 = sizeof(ACPI_OBJECT_LIST) + 2
ACPI_OBJECTs), unbounded, seconds for a million iterations β
memory-exhaustion DoS available to any process holding /dev/acpi
O_RDWR when the debug tunable is enabled. Control run leaks nothing,
isolating the leak to the copyinstr failure return at acpi.c:3356-3357.
Impact ceiling
dos (kernel heap exhaustion). Not memory corruption; not reachable
unprivileged (FWRITE gate acpi.c:3427 + boot tunable acpi.c:3459).
Exploit chain
n/a (resource leak; privileged+debug-gated).
Fix validation
fix.diff (free args before the EINVAL return) applied in the guest's
/usr/src, kernel rebuilt and booted; the same mcallleak 100000 run
then showed zero growth of M_ACPICA InUse. See run_patched.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
fixedWith fix.diff applied in the guest /usr/src and the kernel rebuilt/rebooted, the identical 100k faulted-call run left M_ACPICA InUse unchanged (leak eliminated).
run_patched.log: acpica InUse identical before/after 100k faulted calls on the patched kernel.
Confirmed kernel references
Detail
Exploit chain
root + debug.acpi.allow_method_calls=1: open /dev/acpi O_RDWR; loop ioctl(ACPIIO_DO_MCALL, {args:{count:2,integers}, path:0x10}) -> unbounded kernel heap growth -> memory exhaustion DoS.
Evidence (decisive lines)
run.log: vmstat -m acpica rows before/after each batch (64 B per faulted call; control run flat); mcallleak.c/mcallctrl.c sources.
PoC changes
written from scratch (no seed); needed -I/usr/src/sys and -I/usr/src/sys/contrib/dev/acpica/source/include for userland acpi.h/acpiio_mcall.h.
Verified recommended fix
acpi_free_object_list(args) before the EINVAL return when copyinstr fails.
Verdict
acpi_call_ioctl() leaks the kernel copy of the ACPI_OBJECT_LIST argument when copyinstr() of the method path faults (acpi.c:3356-3357): each failing ACPIIO_DO_MCALL leaks sizeof(ACPI_OBJECT_LIST)+Count*sizeof(ACPI_OBJECT) bytes of M_ACPICA. Measured on the guest with debug.acpi.allow_method_calls=1: M_ACPICA InUse grew 6.39K -> 6.11M -> 67.1M -> 73.2M over 1.1M faulted calls (exactly 64 B/call for a 2-INTEGER list) while an identical number of valid-path calls grew nothing (control). Reachable only with FWRITE on /dev/acpi (root) plus the boot tunable, so impact is privileged memory-exhaustion DoS / defense-in-depth for the debug surface, not unauthenticated corruption.
No comments yet.