一、漏洞詳情
近日,研究人員披露了一個Linux核心本地許可權提升漏洞,發現在copy_page_to_iter_pipe和 push_pipe函式中,新分配的pipe_buffer結構體成員“flags”未被正確地初始化,可能包含舊值PIPE_BUF_FLAG_CAN_MERGE。攻擊者可利用此漏洞向由只讀檔案支援的頁面快取中的頁面寫入資料,從而提升許可權。該漏洞編號為CVE-2022-0847,因漏洞型別和“DirtyCow”(髒牛)類似,亦稱為“DirtyPipe”。
二、相關係統呼叫實現
(一)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的頁面型別,不再贅述。
(二)splice系統呼叫實現
splice是Linux 2.6.17新加入的系統呼叫,用於在兩個檔案間移動資料,而無需核心態和使用者態的記憶體複製,但需要藉助管道(pipe)實現。大概原理就是透過pipe buffer實現一組核心記憶體頁(pages of kernel memory)的引用計數指標(reference-counted pointers),資料複製過程中並不真正複製資料,而是建立一個新的指向記憶體頁的指標。也就是說複製過程實質是指標的複製,稱為零複製技術。
呼叫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()函式,該函式實現程式碼如下:

首先獲取in->f_mapping,該結構體是用於管理檔案(struct inode)對映到記憶體的頁面(struct page)的,其實就是每個file都有這麼一個結構,將檔案系統中這個file對應的資料與這個file對應的記憶體繫結到一起。然後定義一個splice_pipe_desc結構體,該結構體用於中轉file對應的記憶體頁。接下來就是將file對應的記憶體頁面整理放在spd中,過程比較複雜,略過。最後呼叫splice_to_pipe()函式操作pipe和spd,該函式實現關鍵程式碼如下所示:

依次迴圈地從spd->pages中取出記憶體頁放在對應的buf->page中。可以看出這裡僅僅是對記憶體頁面進行轉移,而沒有進行任何記憶體複製。
三、漏洞原理與補丁
(一)漏洞原理
在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中對應的記憶體頁面。

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

四、利用分析
首先,呼叫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/
啟明星辰積極防禦實驗室(ADLab)
ADLab成立於1999年,是中國安全行業最早成立的攻防技術研究實驗室之一,微軟MAPP計劃核心成員,“黑雀攻擊”概念首推者。截止目前,ADLab已透過CVE累計釋出安全漏洞近1100個,透過 CNVD/CNNVD累計釋出安全漏洞2000餘個,持續保持國際網路安全領域一流水準。實驗室研究方向涵蓋作業系統與應用系統安全研究、移動智慧終端安全研究、物聯網智慧裝置安全研究、Web安全研究、工控系統安全研究、雲安全研究。研究成果應用於產品核心技術研究、國家重點科技專案攻關、專業安全服務等。
