作者:1mperio@云鼎實驗室
原文鏈接:https://mp.weixin.qq.com/s/FQdNaLduPX4jZBKluOqFOA
0x01 背景
國外安全研究員champtar在日常使用中發現Kubernetes tmpfs掛載存在逃逸現象,研究后發現runC存在條件競爭漏洞,可以導致掛載逃逸。
關于條件競爭TOCTOU和一些linux文件基礎知識可見這篇文章《初探文件路徑條件競爭 - TOCTOU&CVE-2019-18276》。
CVE-2021-30465在Redteam的研究者視角中比較雞肋,因為需要K8S批量創建POD的權限。但在產品安全的視角恰恰相反,針對Caas(Container as a service)類產品,用戶/租戶擁有批量創建POD權限,利用掛載逃逸可打破租戶間隔離,同時讀取Host層面某些敏感數據,危害性極大。
0x02 RunC簡介
為了讓容器生態更加開放,Linux基金會發起OCI(Open Container Initiative),目標是標準化容器格式和運行時,其中一個重要產物就是CRI(Container Runtime Interface),抽象了容器運行時接口,使得上層調控容器更加便捷。containerd和runC都是其中代表產物,從dockerd中再剝離出containerd,向上提供rpc接口,再通過containerd去管理runC。containerd在初期也是直接對runC進行管理,但為了解決containerd進行升級等操作時會造成不可用的問題,containerd再拆出containerd-shim,獨立對接runC。containerd從Runtime、Distribution、Bundle維度提供容器全生命周期的管理能力,runC專注于Runtime。

0x03 容器設備掛載相關基礎知識
Namespace
Namespace是linux控制系統資源的抽象層,將不同的進程放置入不同的Namespace將獲得不同的資源視角,該項技術是容器實現的基礎。
linux提供8種不同的Namespace以提供不同維度的隔離能力,分別是:
- Cgroup
- IPC
- Network
- Mount
- PID
- Time
- User
- UTS
其中,Cgroup和Mount Namespace是最常接觸的,在容器掛載相關能力均通過Mount Namespace進行實現。Namespace的使用主要通過 clone 和 unshare 兩個方法實現,其中clone 創建新進程時,標志位為 CLONE_NEW* 將會創建新的Namespace并將子進程放入該Namespace,unshare 方法將調用進程放入不同的Namespace中。
Mount Namespace
Linux中有一個很核心的思想,那就是一切皆文件。在該思想下,Linux通過掛載對不同設備中的文件進行管理。在Linux中,每一個空目錄/文件都可以成為掛載點并設置相應的屬性。 在Mount Namespace下,處在當前Namespace中的進程只對當前Namespace中的掛載點可見,通過 /proc/[pid]/mounts 、 /proc/[pid]/mountinfo 和 /proc/[pid]/mountstats 提供不同維度的數據。每個task(在Linux中,不論是進程還是線程,在內核的視角都是一個task)都會指向一個Namesapce(存放在task→nsproxy中)。
struct nsproxy {
atomic_t count;
struct uts_namespace *uts_ns;
struct ipc_namespace *ipc_ns;
struct mnt_namespace *mnt_ns;
struct pid_namespace *pid_ns;
struct net *net_ns;
};
由于容器在啟動過程中,libcontainer會將一個新的Namespace并且將容器進程移入該Namespace下,所以不同的容器進程所指向的Namespace是不同的。
但Mount Namespace的引入也帶來了新的問題,由于Mount Namespace中的隔離性,當用戶需要掛載一個新的磁盤使所有Namespace可見時,就需要在所有的Namespace中都進行一次掛載,很麻煩,于是2.6.15中引入了共享子樹(Shared Subrees)。通過在不同掛載點設置不同的屬性,使掛載事件在不同的維度(peer group)進行傳播。目前支持以下四種傳播類型,其中 MS_SHARED 和 MS_SLAVE 比較常見。
- MS_SHARED
- MS_PRIVATE
- MS_SLAVE
- MS_UNBINDABLE
在MS_SLAVE傳播屬性的掛載點下,父掛載點(Master)的傳播事件可以接收,但子掛載點下(Slave)的掛載事件不再傳播,容器的Rootfs掛載即為該種類型,也就是說在容器中掛載的掛載動作是不影響宿主機的,保證了容器隔離。

0x04 漏洞分析
RunC漏洞掛載邏輯分析
checkout到修復commit(0ca91f44f1664da834bc61115a849b56d22f595f)的上一個版本commit(c01a56034f5ab0c1aa314377a499fe60a9c26b36)。
整體流程如下

分析
RunC通過命令 runc create + runc start 或 runc run 啟動一個容器, runc create 主要分為兩部分,一部分是準備容器進程的啟動參數,與真正實施容器 runc init 進程進行交互,保證容器初始化順利進行;另外一部分是執行克隆出的 runc init 進程,加入各種namespace并初始化容器進程的執行環境。本文以第二部分為切入點進行分析,從 libcontainer/standard_init_linux.go 的 linuxStandardInit.Init() 開始,在其中調用 prepareRootfs ,準備初始化rootfs并進行掛載。

在prepareRootfs 中,調用 prepareRoot 設置初始掛載點,并設置掛載標志位為 unix.MS_SLAVE | unix.MS_REC ,其后使用 mountToRootfs 對container.json中配置的掛載進行操作。

prepareRoot 中設置容器根目錄掛載標志位為 unix.MS_SLAVE | unix.MS_REC ,容器在初始的時候會通過鏡像中的容器標準包(bundle)掛載根文件系統(BaseFS),在這里runC默認將掛載點(Propagation Type)設置為slave。由于當前已經處于容器的mount namespace中,所以當前 \ 為容器根路徑。 rootfsParentMountPrivate 函數確保上一層的掛載點是 PRIVATE ,應該是出于防止逃逸的考慮。

在mountToRootfs 中,針對不同的設備類型存在不同的處理邏輯。

在tmpfs的處理邏輯中, configs.EXT_COPYUP默認為1。
首先準備 /tmp 目錄,在 prepareTmp 函數中將這個掛載點設置為 MS_PRIVATE ,再創建 runctmpdir 路徑,將目標路徑復制到 tmpDir 中,最后將 dest 路徑掛載到tmpDir 中,且Propagation Type設置為 MS_MOVE 。對于MS_MOVE ,官方說明如下:
If mountflags contains the flag MS_MOVE (available since Linux 2.4.18), then move a subtree: source specifies an existing mount point and target specifies the new location to which that mount point is to be relocated. The move is atomic: at no point is the subtree unmounted.
當此時的dest 為一個symlink時,subtree將覆蓋已存在掛載點。所以此處存在TOCTOU(Time-of-check to time-of-use),在 SecureJoin 函數執行時,dest 為正常路徑,當掛在發生時,dest 為symlink,導致逃逸發生。
結論
RunC為了防止在路徑組合中的路徑穿越漏洞,引入了filepath-securejoin作為符號鏈接過濾函數,但r在掛載時并未校驗掛載的實際目的路徑,從而導致存在TOCTOU條件競爭漏洞。
從securejoin的Readme中也可看出這一點。

之所以能夠成功逃逸的另一原因在于tmpfs中為了實現copy-up功能使用MS_MOVE作為掛載標志,根據runC作者的描述只有在tmpfs情況才能夠逃逸。
0x05 補丁分析
在補丁中,可以看出在tmpfs的掛載邏輯中,增加了 doTmpfsCopyUp 函數。
在其中使用 WithProcfd 函數防止TOCTOU漏洞的發生,所有的 securejoin.SecureJoin 移入WithProcfd進行統一處理。

WithProcfd 中使用 /proc/self/fd/ ,確保打開的文件是securejoin.SecureJoin 后的文件。

0x06 POC分析
漏洞作者給出的POC中給出了一個很精妙的構造,利用了這個看似很難利用的條件競爭漏洞。
首先,創建兩個公共tmpfs的掛載,名稱為test1、test2,在容器A中,將test1掛載到/test1路徑,test2掛載到/test2路徑,同時將/test2/test2指向/ 。在容器B中,將test1掛載到/test1路徑,test2掛載到/test1/mntx路徑和/test1/zzz路徑。
在容器A啟動后,將/test1/mnt-tmpx指向rootfs路徑,且交換mnt和mnt-tmpx,且rootfs/test2/test2指向/(K8S中,同一個pod下的rootfs在一個路徑,形如 /var/lib/kubelet/pods/$MY_POD_UID/volumes/kubernetes.io~empty-dir )。
所以當條件競爭掛載的時候,即容器B啟動時,掛載test2, mount('/','rootfs/test1/zzz') ,同時 MS_MOVE 標志位將原有該掛載點的subtree移至新掛載點下,造成逃逸發生。
0x07 總結
Linux在引入symlink的時候并不存在安全風險,但隨著時代的變遷(容器的引入),symlink確實在一定程度上確實容易造成容器逃逸的發生。Linux在嘗試在不同的角度去解決這個問題,但目前還沒有很完全的能夠解決此風險。這里不禁讓人想引用tk的一句話:
安全意識要有時代背景。
作者認為伴隨容器場景愈發復雜,安全研究的逐漸深入,非Linux內核漏洞導致的容器逃逸長期來看還會有一個增長的趨勢。
0x08 Reference
1.runc mount destinations can be swapped via symlink-exchange to cause mounts outside the rootfs (CVE-2021-30465)
https://blog.champtar.fr/runc-symlink-CVE-2021-30465/
2.About the Open Container Initiative
https://opencontainers.org/about/overview/
3.What is the relationship between containerd, OCI and runc?
https://gitlab.cncf.ci/containerd/containerd/blob/revert-1502-test-image/docs/index.md
4.Containerd Architecture
https://github.com/docker-archive/containerd/blob/master/design/architecture.md
5.namespaces(7) — Linux manual page
https://man7.org/linux/man-pages/man7/namespaces.7.html
6.clone(2) — Linux manual page
https://man7.org/linux/man-pages/man2/clone.2.html
7.unshare(2) — Linux manual page
https://man7.org/linux/man-pages/man2/unshare.2.html
8.Linux Namespace分析——mnt namespace的實現與應用
https://hustcat.github.io/namespace-implement-1/
9.Mount namespaces and shared subtrees
https://lwn.net/Articles/689856/
10.mount(2) — Linux manual page
https://man7.org/linux/man-pages/man2/mount.2.html
11.filepath-securejoin
https://github.com/cyphar/filepath-securejoin
12.tmpfs
https://en.wikipedia.org/wiki/Tmpfs
13.Overlay Filesystem
https://www.kernel.org/doc/html/latest/filesystems/overlayfs.html#non-directories
14.mount(2) — Linux manual page
https://man7.org/linux/man-pages/man2/mount.2.html
15.rootfs: add mount destination validation https://github.com/opencontainers/runc/commit/0ca91f44f1664da834bc61115a849b56d22f595f
16.Re: [PATCH 2/3] namei: implement AT_THIS_ROOT chroot-like path resolution
https://lwn.net/ml/linux-kernel/CAG48ez30WJhbsro2HOc_DR7V91M+hNFzBP5ogRMZaxbAORvqzg@mail.gmail.com/
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1674/
暫無評論