最新消息:图 床

鏈表遊戲:CVE-2017-10661之完全利用

COOL IAM 155浏览 0评论

原文來自安全客,作者:huahuaisadog@360 Vulpecker Team
原文鏈接:https://www.anquanke.com/post/id/129468

最近在整理自己以前寫的一些Android內核漏洞利用的代碼,發現了一些新的思路。

CVE-2017-10661的利用是去年CORE TEAM在hitcon上分享過的:https://hitcon.org/2017/CMT/slide-files/d1_s3_r0.pdf。他們給出的利用是在有CAP_SYS_TIME這個capable權限下的利用方式,而普通用戶沒這個權限。最近整理到這裡的時候,想了想如何利用這個漏洞從0權限到root呢?沒想到竟然還能有一些收穫,分享一哈:

  • CVE-2017-10661簡單分析
  • CAP_SYS_TIME下的利用
  • pipe的TOCTTOU
  • 思考下鏈表操作與UAF
  • 0權限下的利用

CVE-2017-10661簡單分析

關於CVE-2017-10661的分析和SYS_TIME下的利用,CORE TEAM的ppt中已經有比較清晰的解釋。我這裡再簡單的用文字描述一遍吧。

這個漏洞存在於Linux內核代碼 fs/timerfd.c的timerfd_setup_cancel函數中:

static void timerfd_setup_cancel(struct timerfd_ctx *ctx, int flags)
{
    if ((ctx->clockid == CLOCK_REALTIME ||
         ctx->clockid == CLOCK_REALTIME_ALARM) &&
        (flags & TFD_TIMER_ABSTIME) && (flags & TFD_TIMER_CANCEL_ON_SET)) {
        if (!ctx->might_cancel) {      //[1][2]
            ctx->might_cancel = true;  //[3][4]
            spin_lock(&cancel_lock);
            list_add_rcu(&ctx->clist, &cancel_list); //[5][6]
            spin_unlock(&cancel_lock);
        }
    } else if (ctx->might_cancel) {
        timerfd_remove_cancel(ctx);
    }
}

這裡會有一個race condition:假設兩個線程同時對同一個ctx執行timerfd_setup_cancel操作,可能會出現這樣的情況(垂直方向為時間線):

Thread1                  Thread2

[1]檢查ctx->might_cancel,值為false

. [2]檢查ctx->might_cancel,值為false

[3]將ctx->might_cancel賦值為true

. [4]將ctx->might_cancel賦值為true

[5]將ctx加入到cancel_list中

. [6]將ctx再次加入到cancel_list中

所以,這裡其實是因為ctx->might_cancel是臨界資源,而這個函數對它的讀寫並沒有加鎖,雖然在if(!ctx->might_cancel)ctx->might_cancel的時間間隔很小,但是還是可以產生資源衝突的情況,也就導致了後面的問題:會對同一個節點執行兩次list_add_rcu操作,這是一個非常嚴重的問題。

首先cancel_list是一個帶頭結點的循環雙鏈表。list_add_rcu是一個頭插法加入節點的操作,所以第一次調用后,鏈表結構如圖:

TOCTTOU : time of check to time of use .寫程序的時候通常都會在使用前,對要使用的數據進行一個檢查。而這個檢查的時間點,和使用的時間點之間,其實是有空隙的。如果能在這個時間空隙里,做到對已經check的數據的更改,那麼就可能在use的時刻,使用到非法的數據。

pipe的readv / writev就是這樣一個典型。以readv為例,readv會在do_readv_writevrw_copy_check_uvector函數里對用戶態傳進來的所有iovector進行合法性檢查:

struct iovec {
    void *iov_base;
    size_t iov_len;
};
ssize_t rw_copy_check_uvector(int type, const struct iovec __user * uvector,
                  unsigned long nr_segs, unsigned long fast_segs,
                  struct iovec *fast_pointer,
                  struct iovec **ret_pointer)
{
    unsigned long seg;
    ssize_t ret;
    struct iovec *iov = fast_pointer;
    ...
    if (nr_segs > fast_segs) {
        iov = kmalloc(nr_segs*sizeof(struct iovec), GFP_KERNEL);  //[1]
        ...
    }
    if (copy_from_user(iov, uvector, nr_segs*sizeof(*uvector))) {
        ...
    }
    ...
    for (seg = 0; seg < nr_segs; seg++) {
        void __user *buf = iov[seg].iov_base;
        ssize_t len = (ssize_t)iov[seg].iov_len;
    ...
        if (type >= 0
            && unlikely(!access_ok(vrfy_dir(type), buf, len))) {  //[2]
            ret = -EFAULT;
            goto out;
        }
    ...
    }
}

可以看到這個檢查函數做了兩件事:

[1]如果iovector的個數比較多(大於8),就會kmalloc一段內存,然後將用戶態傳來的iovector拷貝進去。當然如果比較小,就直接把用戶態傳來的iovector放到棧上。

[2]對iovector進行合法性檢查,確保所有的iovecor的iov_base都是用戶態地址。

這裡也就是pipe的time of check。

在檢查通過之後,會去執行pipe_read函數,相信分析過CVE-2015-1805的朋友們都知道,pipe_read函數里對iovector的iov_base只會做是不是可寫地址的檢查,而不會做是不是用戶態地址的檢查,然後有數據就寫入。pipe_read函數往iovector的iov_base里寫入數據的時刻(__copy_to_user),就是pipe的time of use。

那麼這個check 和 use的間隙是多長呢?這取決於我們什麼時候往pipe的buffer里寫入數據。因為pipe_read默認是阻塞的,如果pipe的buffer里沒有數據,pipe_read就會一直被阻塞,直到我們調用writev往pipe的buffer寫數據。

所以,pipe的time of check to time of use這個間隔,可以由我們自己控制。

如果在這個時間間隔有辦法對iovector進行更改,那麼就可能往非法地址寫入數據:

转载请注明:IAMCOOL » 鏈表遊戲:CVE-2017-10661之完全利用

0 0 vote
Article Rating
Subscribe
Notify of
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x