原文來自安全客,作者: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_writev
的rw_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之完全利用