Linux內核權限提升漏洞“DirtyPipe”(CVE-2022-0847)分析

發布時間 2022-03-14

漏洞詳情


近日,研究人員披露了一個Linux內核本地權限提升漏洞,發現在copy_page_to_iter_pipe和 push_pipe函數中,新分配的pipe_buffer結構體成員“flags”未被正確地初始化,可能包含舊值PIPE_BUF_FLAG_CAN_MERGE。攻擊者可利用此漏洞向由只讀文件支持的頁面緩存中的頁面寫入數據,從而提升權限。該漏洞編號為CVE-2022-0847,因漏洞類型和“DirtyCow”(臟牛)類似,亦稱為“DirtyPipe”。



相關系統調用實現


2.1 pipe系統調用實現


調用pipe()創建一個管道,返回兩個文件描述符,fd[1]為讀,fd[2]為寫。這里以linux-5.16.10內核代碼為例,調用到__do_pipe_flags()函數,該函數代碼實現如下:


代碼文件.png


首先調用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()函數,該函數實現代碼如下:


代碼文件.png


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


代碼文件.png

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


代碼文件.png


代碼文件.png


代碼文件.png


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


代碼文件.png


代碼文件.png

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


代碼文件.png


依次循環地從spd->pages中取出內存頁放在對應的buf->page中??梢钥闯鲞@里僅僅是對內存頁面進行轉移,而沒有進行任何內存拷貝。


漏洞原理與補丁


3.1 漏洞原理

在linux-5.16.10內核中,調用splice()函數將數據寫入管道時,調用路徑如下所示:


代碼文件.png


如前文所述,從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中對應的內存頁面。


代碼文件.png


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


代碼文件.png


利用分析


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


代碼文件.png


觸發漏洞后,此時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/