農林漁牧網

您現在的位置是:首頁 > 農業

Linux 2021 最新檔案系統層中的size_t-to-int 漏洞

2021-12-19由 飛魚在浪嶼 發表于 農業

路徑覆蓋檔案是什麼

更多網際網路精彩資訊、工作效率提升關注【飛魚在浪嶼】(日更新)

Linux 2021 最新檔案系統層中的size_t-to-int 漏洞

摘要

=========================== =========================================

一個Linux核心檔案系統層的size_t- to-int轉換漏洞:透過建立、掛載、刪除

總路徑長度超過1GB的深層目錄結構,非特權本地攻擊者可以將10位元組的字串“//deleted”寫入 vmalloc() 核心緩衝區的開頭下方,正好偏移量 -2GB-10B的位置。

利用了這種不受控制的越界寫入,並得了預設安裝的 Ubuntu 20。04 的完全 root 許可權,

Ubuntu 20。10、Ubuntu 21。04、Debian 11 和 Fedora 34 工作站;其他Linux 發行版當然容易受到攻擊,並且可能會被利用。

我們的漏洞利用需要大約 5GB 的記憶體和 1M 的 inode;

可從以下網址獲得:https : //www。qualys。com/research/security-advisories/ 詳細介紹

分析

===================================================================

Linux 核心的 seq_file 介面產生包含記錄序列的虛擬檔案(例如,/proc 中的許多檔案是

seq_files,而記錄通常是行)。每個記錄都必須適合一個seq_file 緩衝區,因此可以根據需要擴大它,方法是

在第 242 行將其大小加倍(seq_buf_alloc() 是 kvmalloc() 的簡單包裝器):

———————————————————————————————————— 168 ssize_t seq_read_iter(struct kiocb *iocb, struct iov_iter *iter) 169 { 170 struct seq_file *m = iocb->ki_filp->private_data; 。。。 205 /* grab buffer if we didn‘t have one */ 206 if (!m->buf) { 207 m->buf = seq_buf_alloc(m->size = PAGE_SIZE); 。。。 210 } 。。。 220 // get a non-empty record in the buffer 。。。 223 while (1) { 。。。 227 err = m->op->show(m, p); 。。。 236 if (!seq_has_overflowed(m)) // got it 237 goto Fill; 238 // need a bigger buffer 。。。 240 kvfree(m->buf); 。。。 242 m->buf = seq_buf_alloc(m->size <<= 1); 。。。 246 }————————————————————————————————————

這個大小乘法本身並不是一個漏洞,因為m->size 是一個 size_t(一個無符號的 64 位整數,在 x86_64 上),並且系統會在這個乘法溢位整數 m->size之前就耗盡記憶體。

不幸的是,這個 size_t 也被傳遞給 一個度量用 int型別作為引數的函式(有符號的 32 位整數)而不是 size_t 的函式。例如,show_mountinfo() 函式(在第 227 行呼叫以格式化

/proc/self/mountinfo 中的記錄)呼叫 seq_dentry()(在第 150 行),它

呼叫 dentry_path()(在第 530 行),它呼叫prepend()(在第 387 行):

————————————————————————————————————135 static int show_mountinfo(struct seq_file *m, struct vfsmount *mnt)136 {。。。150 seq_dentry(m, mnt->mnt_root, “ \t\n\\”);———————————————————————————————————— 523 int seq_dentry(struct seq_file *m, struct dentry *dentry, const char *esc) 524 { 525 char *buf; 526 size_t size = seq_get_buf(m, &buf); 。。。 529 if (size) { 530 char *p = dentry_path(dentry, buf, size);————————————————————————————————————380 char *dentry_path(struct dentry *dentry, char *buf, int buflen)381 {382 char *p = NULL;。。。385 if (d_unlinked(dentry)) {386 p = buf + buflen;387 if (prepend(&p, &buflen, “//deleted”, 10) != 0)———————————————————————————————————— 11 static int prepend(char **buffer, int *buflen, const char *str, int namelen) 12 { 13 *buflen -= namelen; 14 if (*buflen < 0) 15 return -ENAMETOOLONG; 16 *buffer -= namelen; 17 memcpy(*buffer, str, namelen);————————————————————————————————————

結果,如果一個無特權的本地攻擊者建立、掛載和刪除總路徑長度超過 1GB 的深層目錄結構,如果攻擊者 呼叫open() 和 read()函式操作/proc/self/mountinfo,則:

- 在 seq_read_iter() 中,使用vmalloc() 分配了2GB 緩衝區(第242行),並

呼叫 show_mountinfo()(第 227 行);

- 在 show_mountinfo() 中,使用空的 2GB 緩衝區呼叫 seq_dentry()(第 150 行);

- 在 seq_dentry() 中,呼叫dentry_path() 攜帶引數2GB 大小(第 530 行);

- 在 dentry_path() 中,因此 int buflen 為負數 (INT_MIN,-2GB),p 指向 vmalloc()分配的 緩衝區下方 -2GB 的負偏移量(第 386 行),並呼叫 prepend()(第 387 行);

- 在 prepend() 中,*buflen 減去10 個位元組並變成一個大但是是正的整數(第 13 行),*buffer 減少 10 位元組,指向vmalloc()ated 緩衝區下方 -2GB-10B 的偏移量(第 16 行),

寫入 10 位元組大小的字串“//deleted”(第 17 行)。

漏洞利用概述

===================================================================

1/ mkdir () 一個總路徑長度超過 1GB的深層目錄結構(大約 1M 個巢狀目錄),我們將其繫結掛載到非特權使用者名稱空間中,然後 rmdir() 將其掛載。

2/ 建立一個執行緒,它 vmalloc() 訪問一個小的 eBPF 程式(透過BPF_PROG_LOAD),然後我們阻止這個執行緒(透過 userfaultfd 或 FUSE),在核心進行 JIT 編譯之前,eBPF 程式已經透過核心 eBPF 驗證程式的驗證

3/ 我們在我們的非特權使用者名稱空間中 open() /proc/self/mountinfo ,並開始 read() 我們繫結掛載目錄的長路徑,從而將字串“//deleted”寫入正好 -2GB 的偏移量-10B 低於

vmalloc() 化緩衝區的開頭。

4/ 安排這個“//deleted”字串覆蓋經過驗證的eBPF程式的一條指令(從而使

核心eBPF驗證器的安全檢查無效),並將這個不受控制的越界寫入轉化為資訊暴露,並進入有限但受控的越界寫入。

5/透過重用 Manfred Paul 漂亮的 btf 和map_push_elem 技術,將這個有限的越界寫入轉換為核心記憶體的任意讀寫:https ://www。thezdi。com/blog/2020/4/8 /cve-2020-8835-linux-kernel-privilege-escalation-via-improper-ebpf-program-verification

6/ 使用這個任意讀取來定位核心記憶體中的 modprobe_path[] 緩衝區,並使用任意寫入來替換

這個緩衝區的內容(預設為“/sbin/modprobe”)和我們自己的可執行檔案的路徑,從而獲得完整的 root 許可權。

漏洞利用細節

====================================================================

a/ 建立了一個總路徑長度超過1GB的目錄:理論上,需要建立超過1GB/256B=4M的巢狀目錄(NAME_MAX 為 255);實際上,show_mountinfo() 將我們長目錄中的每個 ’\\‘ 字元替換為 4 位元組字串“\\134”,因此我們只需要建立 1M 巢狀目錄。

b/ 我們填補了所有大的 vmalloc 漏洞:

在幾個非特權使用者名稱空間中繫結掛載 (MS_BIND)長目錄的各個部分,並

透過 read()ing /proc/self/mountinfo 來 vmalloc() 分配 大 seq_file 緩衝區。

例如,我們在漏洞利用中 vmalloc() 佔用了 768MB 的大緩衝區。

c/ vmalloc() 使用了兩個 1GB 緩衝區和一個 2GB 緩衝區(透過在三個不同的使用者名稱空間中繫結掛載我們的長目錄,並透過 read()ing

/proc/self/mountinfo),檢查“//deleted “ 確實寫入在 2GB 緩衝區開頭下方的 -2GB-10B 偏移量(即,第一個 1GB 緩衝區開頭上方的 8182B——“XXX”是保護頁):

”//deleted“ | 4KB v 1GB 4KB 1GB 4KB 2GB——-|——-|——-+——————-|——-|————————-|——-|————————-| 。。。 |XXX| seq_file buffer |XXX| seq_file buffer |XXX| seq_file buffer |——-|——-|——-+——————-|——-|————————-|——-|————————-| | | | | \——<——<——<——<——<——<——<——/ 8182B -2GB-10B

d/ 填寫所有小的 vmalloc 漏洞:vmalloc()透過傳送 () 大量 NETLINK_USERSOCK 訊息來消耗各種小套接字緩衝區。例如,在漏洞利用中 vmalloc() 佔用了 256MB 的小緩衝區。

e/ 建立了 1024 個使用者空間執行緒;每個執行緒開始將 eBPF程式載入到核心​中,但是(透過 userfaultfd 或 FUSE)阻塞了核心空間中的每個執行緒(在第 2101 行),在 eBPF 程式

實際上被 vmalloc() 化之前(在第 2162 行):

————————————————————————————————————2076 static int bpf_prog_load(union bpf_attr *attr, union bpf_attr __user *uattr)2077 {。。。。2100 /* copy eBPF program license from user space */2101 if (strncpy_from_user(license, u64_to_user_ptr(attr->license),。。。。2161 /* plain bpf_prog allocation */2162 prog = bpf_prog_alloc(bpf_prog_size(attr->insn_cnt), GFP_USER);————————————————————————————————————

f / vfree第一個 1GB seq_file 緩衝區(其中“//deleted”被越界寫入),立即解除對所有 1024 個執行緒的阻塞;eBPF 程式被 vmalloc() 放入剛剛 vfree() 的 1GB 孔中:

4KB 1GB 4KB 1GB 4KB 2GB——-|——-|————————-|——-|————————-|——-|————————-| 。。。 |XXX| eBPF programs |XXX| seq_file buffer |XXX| seq_file buffer |——-|——-|————————-|——-|————————-|——-|————————-|

g/ 接下來,(再次透過 userfaultfd 或 FUSE)阻止我們的一個執行緒(在第 12795 行)在核心 eBPF驗證程式。驗證其 eBPF 程式之後但在核心對其進行 JIT 編譯之前:

——- —————————————————————————— ————————- 12640 int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, 12641 union bpf_attr __user *uattr) 12642 { 。。。。。12795 print_verification_stats(環境);—————————————————————————— ————————————

h/ 最後,用越界的“//deleted”字串覆蓋了這個 eBPF 程式的指令(再次透過我們的 2GB seq_file 緩衝區),因此使核心 eBPF 驗證器的安全檢查無效:

”//deleted“ | 4KB v 1GB 4KB 1GB 4KB 2GB——-|——-|——-+——————-|——-|————————-|——-|————————-| 。。。 |XXX| eBPF programs |XXX| seq_file buffer |XXX| seq_file buffer |——-|——-|——-+——————-|——-|————————-|——-|————————-| | | | | \——<——<——<——<——<——<——<——/ 8182B -2GB-10B

首先,變換這種不受控制的 eBPF 程式損壞為資訊洩露。第一個未損壞的 eBPF 程式被

核心 eBPF 驗證器認為是安全的、

Manfred Paul 的 btf和 map_push_elem 技術,將這種有限的越界讀寫轉換為核心記憶體的任意讀寫:

- 透過任意核心讀取,我們定位符號“__request_module”,從而找到函式__request_module(),反彙編這個函式,

並從

“if (!modprobe_path[0])”指令中提取 modprobe_path[] 的地址。

- 使用任意核心寫入,我們用我們自己的可執行檔案的路徑覆蓋modprobe_path[](預設為“/sbin/modprobe”)的內容,並呼叫 request_module()(透過建立一個 netlink 套接字),

它執行 modprobe_path,就是我們自己的可執行檔案。

緩解措施

========================== ==========================================

重要說明:以下緩解措施僅阻止我們特定的漏洞利用(但可能存在其他漏洞利用技術);至徹底修復這個漏洞,核心必須打補丁。

- 將 /proc/sys/kernel/unprivileged_userns_clone 設定為 0,以防止攻擊者在使用者名稱空間中掛載長目錄。但是,攻擊者可能會透過 FUSE 掛載一個長目錄;還沒有完全探索這種可能性,因為無意中在 systemd 中偶然發現了CVE-2021-33910:如果攻擊者 FUSE-mount 一個長目錄(超過 8MB),那麼 systemd 會耗盡其堆疊,崩潰,從而導致整個操作崩潰系統(核心恐慌)。

- 將 /proc/sys/kernel/unprivileged_bpf_disabled 設定為 1,以防止攻擊者將 eBPF 程式載入到核心中。然而攻擊者可能會損壞其他 vmalloc() 化物件(例如,執行緒堆疊),但尚未調查這種可能性。

Linux 2021 最新檔案系統層中的size_t-to-int 漏洞