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

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