CVE-2020-14386: Linux kernel權(quán)限提升漏洞
Unit 42研究人員在Linux kernel源代碼中發(fā)現(xiàn)了一個(gè)內(nèi)存破壞漏洞,漏洞CVE編號為CVE-2020-14386。攻擊者利用該漏洞可以從特權(quán)用戶提權(quán)到Linux 系統(tǒng)的root用戶。
技術(shù)細(xì)節(jié)
該漏洞來源于net/packet/af_packet.c 文件的tpacket_rcv 函數(shù)中,是由于算術(shù)問題引發(fā)的內(nèi)存破壞。該漏洞是2008年7月引入的(commit 8913336),從2016年2月開始觸發(fā)內(nèi)存破壞(commit 58d19b19cd99),很多開發(fā)者都嘗試修復(fù)該漏洞,但提出的補(bǔ)丁都不足以預(yù)防內(nèi)存破壞。
為觸發(fā)該漏洞需要?jiǎng)?chuàng)建一個(gè)含有TPACKET_V2 ring緩存和 PACKET_RESERVE為特定值的原始包(AF_PACKET domain, SOCK_RAW type )。

headroom 是用戶指定大小的緩存,會(huì)在ring 緩存接收每個(gè)包的真實(shí)數(shù)據(jù)之前分配。該值可以通過setsockopt 系統(tǒng)調(diào)用在用戶空間來設(shè)置:
圖 1. Setsockopt設(shè)置 – PACKET_RESERVE
如圖 1所示,會(huì)檢查該值是否小于INT_MAX。該值是在補(bǔ)丁(https://lore.kernel.org/patchwork/patch/784412/)中新加的以防packet_set_ring 中最小幀大小計(jì)算溢出。然后回驗(yàn)證頁面是否是為接收或者傳輸?shù)膔ing緩存分配的。這么做的目的是預(yù)防tp_reserve 域和ring buffer之間的不連續(xù)。
在設(shè)置了tp_reserve 值后,就可以通過含有PACKET_RX_RING的setsockopt系統(tǒng)調(diào)用來觸發(fā)ring緩存的分配:
圖 2. From manual packet – PACKET_RX_RING option.
這是在packet_set_ring函數(shù)中實(shí)現(xiàn)的。在ring緩存分配之前,會(huì)有許多對從用戶空間接收的 tpacket_req結(jié)構(gòu)的檢查:
圖 3. packet_set_ring 函數(shù)中的安全檢查
從圖 3中可以看出,首先會(huì)計(jì)算最小的幀大小,然后與從用戶空間接收到的值進(jìn)行對比驗(yàn)證。檢查確保了在 tpacket 頭結(jié)構(gòu)的每個(gè)幀和tp_reserve 字節(jié)數(shù)之間的有空間。
在做完所有檢查之后,ring緩存本身就會(huì)通過 alloc_pg_vec調(diào)用來分配:
圖 4. packet_set_ring 函數(shù)中調(diào)用ring緩存分配函數(shù)
如上圖所示,block size(區(qū)塊大小)是由用戶空間控制的。alloc_pg_vec函數(shù)會(huì)分配 pg_vec數(shù)組,然后通過alloc_one_pg_vec_page 函數(shù)分配給每一個(gè)區(qū)塊鏈:
圖 5. alloc_pg_vec實(shí)現(xiàn)
alloc_one_pg_vec_page 函數(shù)會(huì)用 __get_free_pages 來分配區(qū)塊頁:
圖 6. alloc_one_pg_vec_page 實(shí)現(xiàn)
區(qū)塊分配后,pg_vec 數(shù)組就會(huì)保存在嵌入在 packet_sock結(jié)構(gòu)中的packet_ring_buffer結(jié)構(gòu)。
當(dāng)接口接收到包后,與tpacket_rcv函數(shù)綁定的socket、包數(shù)據(jù)、TPACKET 元數(shù)據(jù)都會(huì)寫入到ring緩存中。
漏洞
圖7是 tpacket_rcv 函數(shù)的實(shí)現(xiàn)。首先,會(huì)調(diào)用skb_network_offset 來提取接收到的包的網(wǎng)絡(luò)頭的偏移值到maclen中。在本例中,大小為14字節(jié),即以太網(wǎng)header的大小。之后,會(huì)根據(jù)TPACKET header、 maclen 和tp_reserve值來計(jì)算netoff。
但是計(jì)算的過程可能會(huì)溢出,因?yàn)?tp_reserve的類型是 unsigned int ,netoff的類型是unsigned short,而對tp_reserve 值的唯一限制是小于INT_MAX。
圖 7. tpacket_rcv中的算術(shù)計(jì)算
如圖 7所示,如果包中設(shè)置了PACKET_vnet_HDR ,就會(huì)加入sizeof(struct virtio_net_hdr) 。最后,以太網(wǎng)header的偏移量會(huì)計(jì)算會(huì)保存到macoff中。
如圖 8所示, virtio_net_hdr結(jié)構(gòu)會(huì)用 virtio_net_hdr_from_skb函數(shù)下入ring緩存中。 h.raw 指向ring 緩存中當(dāng)前空閑的幀。
圖 8. 調(diào)用tpacket_rcv中的 virtio_net_hdr_from_skb函數(shù)
研究人員設(shè)想有可能利用該溢出將netoff變成一個(gè)更小的值,所以macoff 可以接收一個(gè)大于block size的值,并嘗試寫入緩存中。
但是存在以下檢查,所以無法實(shí)現(xiàn):
圖 9. tpacket_rcv 函數(shù)中的另一個(gè)檢查
但是該檢查并不足以預(yù)防內(nèi)存破壞,因?yàn)槿匀豢梢酝ㄟ^溢出netoff 將macoff變成一個(gè)小一點(diǎn)的值。比如,將macoff變成小于10字節(jié)的 sizeof(struct virtio_net_hdr),然后用 virtio_net_hdr_from_skb 寫入緩存的邊界。
原語
通過控制macoff的值,就可以在控制的偏移量中初始化 virtio_net_hdr 結(jié)構(gòu)。 virtio_net_hdr_from_skb 函數(shù)會(huì)首先將整個(gè)struct 零化,然后根據(jù)skb結(jié)構(gòu)初始化結(jié)構(gòu)內(nèi)的所有域。
圖 10. virtio_net_hdr_from_skb 函數(shù)的實(shí)現(xiàn)
但可以設(shè)置skb 只讓零寫入結(jié)構(gòu)中。因此,就可以在__get_free_pages 分配中零化1-10個(gè)字節(jié)。無需任何堆操作技巧就可以立刻引發(fā)kernel 奔潰。
POC
觸發(fā)該漏洞的PoC代碼參見:https://www.openwall.com/lists/oss-security/2020/09/03/3
漏洞利用
漏洞利用過程參見:https://unit42.paloaltonetworks.com/cve-2020-14386/
補(bǔ)丁
研究人員提出的補(bǔ)丁參見:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=acf69c946233259ab4d64f8869d4037a198c7f06
圖 11. 研究人員提出的補(bǔ)丁
補(bǔ)丁的思想是如果將netoff 的類型從unsigned short 修改為unsigned int,就可以檢查是否會(huì)超過USHRT_MAX,如果超過的化就丟棄該包,以防進(jìn)一步利用。
總結(jié)
研究人員其實(shí)也很奇怪Linux kernel中至今還會(huì)存在如此簡單的算術(shù)安全問題,而且之前沒有被發(fā)現(xiàn)過。同時(shí),非特權(quán)的用戶空間也暴露出了本地權(quán)限提升的巨大攻擊面。