作者:啟明星辰ADLab
原文鏈接:https://mp.weixin.qq.com/s/WMFkPJOd29yOiGoC92QFJA

一、漏洞背景

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

二、VSOCK介紹和架構

2.1 VSOCK介紹

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

2.2 VSOCK架構

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

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

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

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

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

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

三、漏洞分析與觸發過程

3.1漏洞分析

該漏洞觸發原因是錯誤加鎖導致條件競爭,根據補丁可知,存在多處錯誤加鎖,這里以

vsock_stream_setsockopt()函數補丁為例,如下圖所示:

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

3.2觸發過程

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

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

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

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

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

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

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

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

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

第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鎖定,然后在去釋放鎖,進行條件競爭。漏洞觸發過程如下圖所示:

藍框中是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


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1504/