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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3794 Β· 11 files
FileTypeDescriptionSize
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(&params->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

  1. debug.acpi.allow_method_calls=1 in /boot/loader.conf.local, reboot.
  2. cc -O -I/usr/src/sys -I/usr/src/sys/contrib/dev/acpica/source/include \ -o mcallleak mcallleak.c
  3. vmstat -m | grep acpica; ./mcallleak 100000; vmstat -m | grep acpica (and ./mcallctrl 20000 for the no-leak control).

Fix

See fix.diff: free args on the copyinstr failure path.

VERDICT.md
↓ download raw

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

fixed
baseline reproduced→ patch + rebuild →patched clean

With 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.
↓ fix.diffper-fix-DF-3794

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.