Linux內核AF_VSOCK套接字條件競爭漏洞(CVE-2021-26708)分析

發布時間 2021-03-10

漏洞背景


近期,國外安全研究人員在oss-security上披露了一個AF_VSOCK套接字條件競爭高危漏洞CVE-2021-26708(CNVD-2021-10822、CNNVD-202102-529)。根據披露細節,該漏洞是由于錯誤加鎖導致,可以在低權限下觸發并自動加載易受攻擊驅動模塊創建AF_VSOCK套接字,進而導致本地權限提升。該漏洞補丁已經合并到Linux內核主線中。


VSOCK介紹和架構


VSOCK介紹


VM套接字最早是由Vmware開發并提交到Linux內核主線中。VM套接字允許虛擬機與虛擬機管理程序之間進行通信。虛擬機和主機上的用戶級應用程序都可以使用VM 套接字API,從而促進guest虛擬機與其host之間的快速有效通信。該機制提供了一個vsock套接字地址系列及其vmci傳輸,旨在與接口級別的UDP和TCP兼容。VSOCK機制隨即得到Linux社區的響應,Redhat在VSOCK中為vsock添加了virtio傳輸,QEMU/KVM虛擬機管理提供支持,Microsoft添加了HyperV傳輸。


VSOCK架構


VM套接字與其他套接字類型類似,例如Berkeley UNIX套接字接口。VM套接字模塊支持面向連接的流套接字(例如TCP)和無連接數據報套接字(例如UDP)。VM套接字協議系列定義為“AF_VSOCK”,并且套接字操作分為SOCK_DGRAM和SOCK_STREAM。如下圖所示:


1.png


VSOCK支持socket API。AF_SOCK地址簇包含兩個要素:CID和port。CID為ContextIdentifier,上下文標識符;port為端口。TCP/IP應用程序幾乎不需要更改就可以適配,每一個地址表示為。還有一層為transport,VSOCK transport用于實現guest和host之間通信的數據通道。如下圖所示:


2.png


Transport根據傳輸方向分為兩種(以SOCK_STREAM類型為例),一種為G2H transport,表示guest到host的傳輸類型,運行在guest中。另一種為H2G transport,表示host到guest的傳輸類型。以QEMU/KVM傳輸為例,如下圖所示:


3.png


該傳輸提供套接字層接口的驅動分為兩個部分:一個是運行在guest中的virtio-transport,用于配合guest進行數據傳輸;另一個是運行在host中的vhost-transport,用于配合host進行數據傳輸。VSOCK transport還提供多傳輸通道模式,該功能是為了支持嵌套虛擬機中的VSOCK功能。如下圖所示:


4.png


支持L1虛擬機同時加載H2G和G2H兩個傳輸通道,此時L1虛擬機即是host也是guest,通過H2G傳輸通道和L2嵌套虛擬機通信,通過G2H傳輸通道和L0 host通信。VSOCK transport還支持本地環回傳輸通道模式,不需要有虛擬機。如下圖所示:


5.png


該模式用于測試和調試,由vsock-loopback提供支持,并對地址簇中的CID進行了分類,包含兩種類型:一種是VMADDR_CID_LOCAL,表示本地環回;一種為VMADDR_CID_HOST,表示H2G傳輸通道加載,G2H傳輸通道未加載。

 

漏洞分析與觸發過程


漏洞分析


該漏洞觸發原因是錯誤加鎖導致條件競爭,根據補丁可知,存在多處錯誤加鎖,這里以vsock_stream_setsockopt()函數補丁為例,如下圖所示:


6.png


補丁很簡潔,將第1564行代碼移動到第1571行,中間就隔著第1569行代碼:lock_sock(sk)。加鎖前,vsk->transport已經賦值到transport變量中,這里產生了一個引用,然后才進行lock_sock(sk)將sk鎖定。但是vsk->transport會在多處被調用甚至被釋放,這就有可能通過條件競爭造成Use After Free。


觸發過程


首先找到修改或釋放vsk->transport的調用路徑,來看關鍵函數vsock_assign_transport()的實現。對于多傳輸模式,該函數用于根據不同CID分配不同的傳輸通道。實現代碼如下圖所示:


7.png


根據sk->sk_type分為SOCK_DGRAM和SOCK_STREAM,在SOCK_STREAM中,分為三種傳輸通道。這里可以通過將CID設置為本地環回模式,得到transport_local傳輸通道。接下來如下圖所示:


8.png


如果vsk->transport不為空,則進入if語句。先判斷vsk->transport是否等于new_transport,如果等于直接返回,在觸發過程中,要保證能走到vsock_deassign_transport()函數,該函數是析構函數,用于釋放transport。如下代碼所示:


9.png


行411,調用vsk->transport->destruct(),要明確使用transport類型,前文已經確定使用transport_local。Transport_local為全局變量,會在vsock_core_register()函數中被初始化。該函數被調用情況如下圖所示:


10.png


*_init()函數用來初始化transport的回調函數,根據第二部分介紹,vhost_vsock_init()、virtio_vsock_init()和vsock_loopback_init()函數為QEMU/KVM環境下的支持函數。我們發現transport->destruct()函數的最后實現都是同一個函數。如下圖所示:


11.png


該destruct()函數釋放vsk->trans,如下圖所示:


12.png


而vsk->trans指針是指向transport的。結構體vsock_sock定義如下所示:


13.png


最終可以構造一個釋放transport的函數路徑為:vsock_stream_connect-> vsock_assign_transport->virtio_transport_destrcut。


找到了釋放路徑,下一步找使用路徑,virtio_transport_notify_buffer_size()函數會使用transport。如下圖所示:


14.png


第492行,通過vsk->trans獲取指向transport的指針,第497行,解引用vvs指針,對vvs->buf_alloc進行賦值。而調用virtio_transport_notify_buffer_size()函數最終會被vsock_stream_setsockopt()函數調用。最終可以構造一個使用transport的函數路徑為:vsock_stream_setsockopt-> vsock_update_buffer_size->virtio_transport_notify_buffer_size。


接下來就是營造一個搶鎖的條件競爭環境,很明顯必須是connect()系統調用先搶到鎖對transport進行釋放,然后再調用setsockopt()才能觸發漏洞。有開發人員提出使用userfaultfd機制先將lock_sock鎖定,然后在去釋放鎖,進行條件競爭。漏洞觸發過程如下圖所示:


15.png


藍框中是connect()調用過程,最后調用virtio_transport_destruct()函數釋放vsk->trans。紅框中是setsockopt()調用過程,調用virtio_transport_notify_buffer_size()函數使用vvs,該值是0xffff888107a74500,在0xffff888107a74500+0x28處會寫入4字節。


參考鏈接:

[1]https://github.com/torvalds/linux/commit/d021c344051af91f42c5ba9fdedc176740cbd238

[2]https://static.sched.com/hosted_files/devconfcz2020a/b1/DevConf.CZ_2020_vsock_v1.1.pdf

[3]https://github.com/jordan9001/vsock_poc

[4]https://terenceli.github.io/%E6%8A%80%E6%9C%AF/2020/04/18/vsock-internals


啟明星辰積極防御實驗室(ADLab)


ADLab成立于1999年,是中國安全行業最早成立的攻防技術研究實驗室之一,微軟MAPP計劃核心成員,“黑雀攻擊”概念首推者。截止目前,ADLab已通過CVE累計發布安全漏洞近1100個,通過 CNVD/CNNVD累計發布安全漏洞1000余個,持續保持國際網絡安全領域一流水準。實驗室研究方向涵蓋操作系統與應用系統安全研究、智能終端安全研究、物聯網智能設備安全研究、Web安全研究、工控系統安全研究、云安全研究。研究成果應用于產品核心技術研究、國家重點科技項目攻關、專業安全服務等。


微信圖片_20210310102858.jpg