- 論壇徽章:
- 0
|
參考文檔:
http://lwn.net/Articles/268783/
http://www.avertlabs.com/researc ... l-vmsplice-exploit/
http://lwn.net/Articles/271688/
溢出程序在http://www.milw0rm.com/exploits/5092,存在該問(wèn)題的內(nèi)核版本是2.6.17 – 2.6.24.1。
首先分析一下造成溢出的原因:
Vmsplice的作用是將一個(gè)文件描述符(必須是一個(gè)pipe)和一段內(nèi)存連接起來(lái)。這個(gè)功能的實(shí)現(xiàn)是通過(guò)fs/splice.c的do_vmsplice()function來(lái)實(shí)現(xiàn),在該function種,定義了兩個(gè)數(shù)組:
struct page *pages[PIPE_BUFFERS];
struct partial_page partial[PIPE_BUFFERS];
PIPE_BUFFERS的值在存在溢出問(wèn)題的版本中的是定義為16。這兩個(gè)函數(shù)都傳遞到了 get_iovec_page_array()這個(gè)function中。
以2.6.22.14版本的源代碼為例,看看在fs/splice.c的1565行開始的get_iovec_page_array函數(shù)。
在該函數(shù)中我們看到:
error = get_user(len, &iov->iov_len);
if (unlikely(!len))
break;
在這里僅僅判斷l(xiāng)en是正數(shù)就ok,而len是可以通過(guò)用戶控制的。
npages = (off + len + PAGE_SIZE - 1) >> PAGE_SHIFT;
if (npages > PIPE_BUFFERS - buffers)
npages = PIPE_BUFFERS – buffers;
error = get_user_pages(current, current->mm,
(unsigned long) base, npages, 0, 0,&pages[buffers], NULL);
npages的值是通過(guò)len計(jì)算得出,那么我們將len值設(shè)為UINT32_MAX的話,那么計(jì)算off+len+PAGE_SIZE的結(jié)果就會(huì)導(dǎo)致整型數(shù)包裹(integer wrap),那么npages的將會(huì)是0,這是unexpected的。我們現(xiàn)在來(lái)分析get_user_pages在得到了unexpected的npages值后,會(huì)有什么樣的結(jié)果。get_user_pages是用來(lái)將用戶空間中頁(yè)(pages)映射(pin)入內(nèi)存,并且得到他們頁(yè)結(jié)構(gòu)(struct page)的指針。然而在get_user_pages()函數(shù)內(nèi)部,處理頁(yè)面時(shí)使用的do{}while()的結(jié)尾處是:
len--;
} while (len && start < vma->vm_end);
如果len的值是0的話(正如我們期望的那樣),那么這個(gè)循環(huán)將會(huì)至少循環(huán)一次,將len值減為-1后,繼續(xù)在頁(yè)面中錯(cuò)誤地執(zhí)行,直到執(zhí)行到?jīng)]有有效的mapping的地址后,指針將會(huì)停止并返回。但是在這時(shí),他或許已經(jīng)在儲(chǔ)了比他當(dāng)時(shí)所分配的內(nèi)存空間更多的內(nèi)容,到其page數(shù)組中。也就是說(shuō)在這種情況下get_user_pages()將會(huì)溢出pages數(shù)組,寫了不僅僅是PIPE_BUFFERS(16)個(gè)指針到數(shù)組中。然而真正被溢出程序所利用數(shù)組是partial數(shù)組。
在do_vmsplice()中定義的partial數(shù)組同樣被傳遞給了get_iovec_page_array()。在partial數(shù)組中描述了需要寫入到管道中的頁(yè)面的其他的部分。 在get_user_pages()返回后,緊跟著一個(gè)循環(huán)語(yǔ)句:
for (i = 0; i < error; i++) {
const int plen = min_t(size_t, len, PAGE_SIZE - off);
partial[buffers].offset = off;
partial[buffers].len = plen;
off = 0;
len -= plen;
buffers++;
}
在這種情況下,因?yàn)樗许?yè)面都被寫入,被計(jì)算的偏移量(offset)將會(huì)是zero,并且長(zhǎng)度(length)值是PAGE_SIZE(4096).而從get_user_pages()的返回值error,將會(huì)是在被溢出的情況下 被mapped的pages頁(yè)的數(shù)量:46。那么實(shí)際上partial數(shù)組的同樣是被定義為16個(gè)元素,因此上邊的這個(gè)循環(huán)同樣會(huì)導(dǎo)致溢出的發(fā)生。
這兩個(gè)數(shù)組都是在vmsplice_to_page()中聲明的。在內(nèi)存分配中partial數(shù)組將會(huì)放在pages的下邊,因此一點(diǎn)partial數(shù)組被overflow,那么這個(gè)循環(huán)將會(huì)同樣溢出放在上邊的pages數(shù)組。因此pages數(shù)組的內(nèi)容將會(huì)被改寫為0,而不是先前的指向pages結(jié)構(gòu)的指針。
當(dāng)這些完成后,控制權(quán)返回到vmsplice_to_page()-溢出并不足以覆蓋返回地址。針對(duì)splice_to_pipe()的調(diào)用目前看來(lái)要結(jié)束了,但是一些有趣的事情發(fā)生了。在這個(gè)function的開始,有一個(gè)test:
if (!pipe->readers) {
send_sig(SIGPIPE, current, 0);
if (!ret)
ret = -EPIPE;
break;
}
如果我們看攻擊代碼的話,我們會(huì)看到
if (pipe(pi) < 0) die("pipe", errno);
close(pi[0]);
在調(diào)用vmsplice()之前,已經(jīng)將pipe的讀取端關(guān)閉了。因此splice_to_pipe將會(huì)立即退出,然而在退出時(shí),將會(huì)執(zhí)行如下操作:
while (page_nr < spd_pages)
page_cache_release(spd->pages[page_nr++]);
我們知道get_user_pages()函數(shù)的調(diào)用將會(huì)lock內(nèi)存中的相關(guān)頁(yè),以便允許內(nèi)核對(duì)其進(jìn)行訪問(wèn);上邊這兩行是一段清理代碼用來(lái)返回并unlock先前鎖住而目前不再使用的pages。但是在我們這個(gè)例子中,pages數(shù)組的內(nèi)容已經(jīng)被改寫為0。那么接下來(lái)發(fā)生的事情,將會(huì)是內(nèi)核欺騙(kernel oops),因?yàn)閜ages數(shù)組中填充的內(nèi)容并不是合法的地址。溢出代碼通過(guò)一些小方法,比如使用一些特定的mmap()調(diào)用,將會(huì)在內(nèi)存地址的底部構(gòu)造任意的內(nèi)容。
當(dāng)運(yùn)行在內(nèi)核模式,直接去取指向用戶空間的指針的值盡管可能會(huì)造成很多問(wèn)題,但是確實(shí)可以被忍受的。如果地址是有效的并且相關(guān)也駐留內(nèi)存當(dāng)中,那么直接的取值也是能夠成功的。因此當(dāng)kernel開始工作在他以為是指向struct page空間的指針的內(nèi)存時(shí),并沒(méi)有得到任何的錯(cuò)誤提示;而是得到了通過(guò)exploit程序所構(gòu)造的數(shù)據(jù)內(nèi)容。
kernle 一般情況下將每頁(yè)page看為個(gè)體。但是在有些時(shí)候,或有多個(gè)page組成的集合,被稱為”compound pages”.這種情況發(fā)生在一段被kernel所需要的連續(xù)的空間的大小大于一個(gè)page的大小時(shí);當(dāng)這種調(diào)用發(fā)生時(shí),一組compound pages被傳遞給調(diào)用者。比較特別的地方是,他們?cè)诒会尫艜r(shí),是會(huì)被拆分開,因此就會(huì)有拆分的動(dòng)作發(fā)生。因此compound pages會(huì)有一個(gè)普通pages所沒(méi)有的屬性:當(dāng)pages被釋放時(shí),會(huì)調(diào)用destructor。
我們來(lái)看一下攻擊程序中是如何設(shè)置low-memory page structures的:
pages[0]->flags = 1 << PG_compound;
pages[0]->private = (unsigned long) pages[0];
pages[0]->count = 1;
pages[1]->lru.next = (long) kernel_code;
當(dāng)內(nèi)核在用戶空間的0位置開始尋找page structure時(shí),將會(huì)發(fā)現(xiàn)該page structure是組compound page. destructor(存放在第2個(gè)page 結(jié)構(gòu)中的lru.next)所指向的是一段先前在exploit代碼中定義的kernel_code()。因?yàn)閏ount被設(shè)置為1,因此執(zhí)行page_cache_release()(會(huì)將count值減1)將會(huì)得出沒(méi)有剩余的指針了,而這段page看起來(lái)像一段compound page,destructor將會(huì)被調(diào)用。這時(shí),存放在kernel_code位置的任意代碼就可以在內(nèi)核狀態(tài)運(yùn)行。 |
|