漏洞简介:
2023年12月19日,runc社区收到来自docker社区转发过来的安全通告,来自snyk的rmcnamara-snyk研究发现,由于runc由来已久的一个文件描述符泄露bug,攻击者可以通过容器的“工作目录”参数(.process.cwd
,对应的docker的参数为--workdir
),利用这个泄露的文件描述符,控制容器所在主机的整个文件系统;经过runc社区维护者的深入挖掘,来自SUSE和赛码网的工程师们发现,攻击者还可以通过容器启动参数(.process.args
,对应的docker参数为--entrypoint
),利用这个泄露的文件描述符,直接覆盖主机上的可执行文件,从而达到完全逃逸到主机的目的。目前这个漏洞的编号为:CVE-2024-21626。
这个漏洞的利用,其实和2019年的CVE-2019-5736容器逃逸漏洞类似,都是利用linux的伪文件系统/proc
进行攻击,容器运行时runc在启动真实的容器进程之前,其实是通过/proc/self/exe init
创建的一个进程(以下简称init进程),在设置完资源隔离和资源限制后,通过execve
系统调用来启动真正的容器进程。在容器进程真正启动之前,其实一直是runc在工作,runc进程其实是某个容器的第一个进程,所以runc进程本身是已经泄漏到容器空间当中的,这是CVE-2019-5736容器逃逸漏洞的根本原因。但是,既然runc在真正的容器进程启动之前一直在做工作,所以任何的疏忽大意,都有可能造成资源被泄漏到容器空间。不幸的是,这样的bug确实存在,在runc的某些代码重构过程中,不小心把两个主机文件描述符/sys/fs/cgroup泄漏到了init进程(并未泄漏到最终的容器空间),导致了本次逃逸的发生。这种文件描述符的泄漏对于runc本身的测试来说很难发现,因为他被init进程打开的其他文件描述符覆盖了。但是和docker或kubernetes结合在一起时,就增加了被发现的机会,因为他们打开了更多的文件描述符,从而导致runc泄漏的文件描述符没有被覆盖。
由于泄漏的文件描述符指向的是所在主机文件夹/sys/fs/cgroup,例如其对应的描述符是9,则linux伪文件系统/proc中的魔术链接(Magic Link)/proc/self/fd/9
对应的就是所在主机文件夹/sys/fs/cgroup
,而/proc/self/fd/9/..
对应的并不是/proc/self/fd
,而是所在主机文件夹/sys/fs
,假如将容器的工作目录设置为/proc/self/fd/9
,在容器启动后,攻击者可以通过cd ../../../..
逃逸到容器所在主机的根目录,如果容器是以root身份运行的,就可以为所欲为了。在接到安全通报后,赛码网和SUSE工程师第一时间提交了安全补丁,对这个漏洞进行修复。首先当然是要修复文件描述符泄漏的BUG(简称patch1),然后为了预防以后在再次出现类似的文件描述符泄漏后不至于造成容器逃逸,我们还提交了patch2, 对工作目录参数进行校验,linux的getcwd
调用会对是否是合法的工作目录进行判断。
这个漏洞封堵后,赛码网工程师继续深入挖掘,对于patch2,其实做的还不到位,因为如果未来还发生类似于patch1所封堵的文件泄漏bug,仅仅封堵工作目录这一个地方是不够的,execve
系统调用也可以对这个泄漏的文件描述符进行滥用。假设攻击者将容器的启动命令设置为/proc/self/fd/9/../../../bin/bash
,那么对于容器进程来说,/proc/self/exe
指向的就是所在主机上的可执行文件/bin/bash
,利用CVE-2019-5736的原理,对其进行覆盖。因此,为了避免未来的文件描述符泄漏带来类似的容器逃逸,runc维护者们在调用execve
启动真正的容器业务进程之前,对不必要的文件描述符进行显式关闭,这就可以做到万无一失了。当然,未来不出现文件描述符泄漏bug是我们应该追求的目标。
修复建议:
这是一个高危漏洞,10分制危害评分等级为8.6分,建议有条件的用户,将runc升级到v1.1.12(或通过上游升级),但是,可能需要做一些兼容性测试,如果确实无法对runc进行升级,则看能否在您所使用的runc版本上应用这些补丁后进行重新编译。如果都不行,则需要做到:
1、不下载运行来历不明的容器镜像;
2、启动容器或exec进入一个容器时,一定要显式声明工作目录和启动进程名;例如:docker run --workdir= --entrypoint= …
3、对于提供云服务的厂商,则一定要做校验用户提供的cwd和entrypoint参数,不提供linux伪文件系统/proc
的自定义挂载,不使用linux的伪文件系统作为cwd和entrypoint参数,例如不能使用/proc/
开头。但是这也不保险,因为对于entrypoint参数,我们可以使用Shebang来避开校验。您也可以使用(dmz)[https://github.com/opencontainers-sec/go-containersec]来进行临时的防护。当然,云服务厂商升级到1.1.12或者应用这些补丁代码是我们所强烈建议的。
4、如果版本升级实在跨度太大,可以在你现在跑的runc正式版本上,应用这个最小的补丁:https://github.com/lifubang/runc/pull/63
参考:
https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv
https://github.com/opencontainers/runc/releases/tag/v1.1.12
https://www.cve.org/CVERecord?id=CVE-2024-21626
dmz:赛码网为容器运行时社区runc提供的一个简化防护CVE-2019-5736的提案,目前已被接纳,但是应用生产还有一段路要走,请参考https://github.com/opencontainers/runc/pull/3983,另一个替换提案在论证中:https://github.com/opencontainers/runc/pull/4137#issuecomment-1905756274 。