作者:啟明星辰ADLab
原文鏈接:https://mp.weixin.qq.com/s/RoGHvNW2Y6dZOjgsBVVm5Q
01 漏洞詳情
近日,研究人員披露了一個Linux內核本地權限提升漏洞,發現在copy_page_to_iter_pipe和 push_pipe函數中,新分配的pipe_buffer結構體成員“flags”未被正確地初始化,可能包含舊值PIPE_BUF_FLAG_CAN_MERGE。攻擊者可利用此漏洞向由只讀文件支持的頁面緩存中的頁面寫入數據,從而提升權限。該漏洞編號為CVE-2022-0847,因漏洞類型和“DirtyCow”(臟牛)類似,亦稱為“DirtyPipe”。
02 相關系統調用實現
2.1 pipe系統調用實現
調用pipe()創建一個管道,返回兩個文件描述符,fd[1]為讀,fd[2]為寫。這里以linux-5.16.10內核代碼為例,調用到__do_pipe_flags()函數,該函數代碼實現如下:

首先調用create_pipe_files(),然后調用get_unused_fd_flags()分別獲取未使用的文件描述符fdr和fdw,并寫入到指針fd中。create_pipe_files()函數調用get_pipe_inode()函數獲取一個inode,并初始化相關數據結構。get_pipe_inode()函數又調用alloc_pipe_info()函數分配一個pipe_inode_info,該結構體是一個內核pipe結構體,用于管道的管理和操作。具體看下alloc_pipe_info()函數,該函數實現代碼如下:

首先初始化pipe_bufs為PIPE_DEF_BUFFERS,該值為16,然后分配pipe,接著判斷pipe_bufs*PAGE_SIZE的大小,pipe_bufs最大值為128,最小值為2。

然后開始分配pipe->bufs,正常一次性分配16個pipe_buffer,然后初始化pipe的相關成員,這里并不會初始化pipe_bufs中的pipe_buffer。piper_buffer結構體定義如下:

其中page用于存放數據,大小為一個頁面,ops為對應內存頁面操作集,成員flags為buffer類型。這16個pipe_buffer構成一個管道緩沖區的循環數組,pipe->head指向緩沖區生產點,pipe->tail指向消費點,在pipe的管理下,循環地用于數據的讀取和寫入。
當向管道中寫入數據時,會調用pipe_write()函數,該函數部分實現代碼如下:

首先從pipe->head開始,判斷pipe是否為滿的。不滿的情況下,拿出一個pipe_buffer,判斷page是否已分配,未分配隨即分配一個新page,然后初始化這個pipe_buffer相關成員,實現代碼如下:

行527,將buf->flags設置為PIPE_BUF_FLAG_CAN_MERGE,表示該buffer是可以合并的。最后調用copy_page_from_iter()函數將數據拷貝到新分配的page中。當從管道中讀取數據時,就是逆過程,其間并不改變既定buffer的頁面類型,不再贅述。
2.2 splice系統調用實現
splice是Linux 2.6.17新加入的系統調用,用于在兩個文件間移動數據,而無需內核態和用戶態的內存拷貝,但需要借助管道(pipe)實現。大概原理就是通過pipe buffer實現一組內核內存頁(pages of kernel memory)的引用計數指針(reference-countedpointers),數據拷貝過程中并不真正拷貝數據,而是創建一個新的指向內存頁的指針。也就是說拷貝過程實質是指針的拷貝,稱為零拷貝技術。
調用splice系統調用時,內核中會調用do_splice()函數,該函數實現代碼如下:

分三種情況,第一種為in/out均為pipe類型,第二種是in為pipe類型,第三種是out為pipe類型,這里我們分析第三種情況。調用spilce_file_tp_pipe()函數將數據寫入pipe中,具體會調用到generic_file_splice_read()函數,這里以linux-2.6.17內核版本為例,更容易理解零拷貝過程。該函數實現如下:

然后調用到__generic_file_splice_read()函數,該函數實現代碼如下:

映射到內存的頁面(structpage),其實就是每個file都有這么一個結構,將文件系統中這個file對應的數據與這個file對應的內存綁定到一起。然后定義一個splice_pipe_desc結構體,該結構體用于中轉file對應的內存頁。接下來就是將file對應的內存頁面整理放在spd中,過程比較復雜,略過。最后調用splice_to_pipe()函數操作pipe和spd,該函數實現關鍵代碼如下所示:

依次循環地從spd->pages中取出內存頁放在對應的buf->page中。可以看出這里僅僅是對內存頁面進行轉移,而沒有進行任何內存拷貝。
03 漏洞原理與補丁
3.1 漏洞原理
在linux-5.16.10內核中,調用splice()函數將數據寫入管道時,調用路徑如下所示:

比linux-2.6.17內核版本的復雜,最終會調用copy_page_to_iter_pipe()函數操作內存頁面,該函數實現代碼如下:

如前文所述,從pipe中取出buf,只是替換了ops,page,offset和len,并沒有修改buf->flags,因此該buffer所包含的頁面是可以合并的。當再次向管道中寫入數據時,因為pipe非初次使用,首先判斷要寫入的buffer類型,如果buf->flags為PIPE_BUF_FLAG_CAN_MERGE,行466,直接調用copy_page_from_iter()函數進行內存拷貝,而目的地址為buf->page,這個buf->page實際上就是來自file中對應的內存頁面。

3.2 補丁
該漏洞補丁在copy_page_to_iter_pipe()函數和push_pipe()函數中,將buf->flags置零。其中push_pipe()函數可在其他路徑中觸發,不再贅述。

04 利用分析
首先,調用pipe創建管道并通過寫讀操作將管道中的buffer類型設置為PIPE_BUF_FLAG_CAN_MERGE。

然后,將要覆蓋的文件通過splice寫入到pipe中,公開的利用中被覆蓋的文件為/usr/bin/pkexec,因為該程序具備suid能力。

觸發漏洞后,此時pipe中buf所包含的內存頁面均是指向/usr/bin/pkexec文件所屬的內存頁面,而且內存頁面都是可以合并的。最后再次調用write()函數將提權payload寫入pipe中,即寫入/usr/bin/pkexec文件中,然后運行/usr/bin/pkexec提升權限。
參考鏈接:
[1]https://dirtypipe.cm4all.com/
[2]https://haxx.in/files/dirtypipez.c
[3]https://lore.kernel.org/lkml/20220221100313.1504449-1-max.kellermann@ionos.com/
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1844/
暫無評論