摘要:在年月底时,我写了一篇文章发布之际。为何有存在前面已经基本介绍了相关背景,并且也基本明确了就是在正式发布之前的最后一个版本,那为什么会出现呢我们首先要介绍今年的一个提权漏洞。
在 18 年 11 月底时,我写了一篇文章 《runc 1.0-rc6 发布之际》 。如果你还不了解 runc 是什么,以及如何使用它,请参考我那篇文章。本文中,不再对其概念和用法等进行说明。
在 runc 1.0-rc6 发布之时,给版本的别名为 "For Real This Time",当时我们原定计划是发布 1.0 的,但是作为基础依赖软件,我们认为当时的版本还有几个问题:
不够规范;
发布周期不明确;
为了给相关的 runtime 足够的时间进行修正/升级,以及规范版本生命周期等,最终决定了发布 runc 1.0-rc6。
为何有 runc 1.0-rc7 存在前面已经基本介绍了相关背景,并且也基本明确了 rc6 就是在 1.0 正式发布之前的最后一个版本,那 rc7 为什么会出现呢?
CVE-2019-5736我们首先要介绍今年 runc 的一个提权漏洞 CVE-2019-5736 。
2019 年 2 月 11 日在 oss-security 邮件组正式批露该漏洞,攻击者可以利用恶意容器覆盖主机上的 runc 文件,从而达到攻击的目的;(具体的攻击方式此处略过),注意不要轻易使用来源不可信的镜像创建容器便可有效避免被攻击的可能。
简单补充下可能被攻击的方式:
运行恶意的 Docker 镜像
在主机上执行 docker exec 进入容器内
关于容器安全或者容器的运行机制,其实涉及的点很多,我在去年的一次线上分享 《基于 GitLab 的 CI 实践》 有提到过 Linux Security Modules(LSM)等相关的内容,对容器安全感兴趣的朋友可以对 LSM 多了解下。
不过本文主要看的是 runc 如何修复该漏洞的,以及后续产生的影响。
修复方式// 对 memfd_create 系统调用做了个封装 省略部分代码 #if !defined(SYS_memfd_create) && defined(__NR_memfd_create) # define SYS_memfd_create __NR_memfd_create #endif #ifdef SYS_memfd_create # define HAVE_MEMFD_CREATE # ifndef MFD_CLOEXEC # define MFD_CLOEXEC 0x0001U # define MFD_ALLOW_SEALING 0x0002U # endif int memfd_create(const char *name, unsigned int flags) { return syscall(SYS_memfd_create, name, flags); } // 一个简单的只读缓存区 static char *read_file(char *path, size_t *length) { int fd; char buf[4096], *copy = NULL; if (!length) return NULL; fd = open(path, O_RDONLY | O_CLOEXEC); if (fd < 0) return NULL; *length = 0; for (;;) { int n; n = read(fd, buf, sizeof(buf)); if (n < 0) goto error; if (!n) break; copy = must_realloc(copy, (*length + n) * sizeof(*copy)); memcpy(copy + *length, buf, n); *length += n; } close(fd); return copy; error: close(fd); free(copy); return NULL; } // 将复制后的 fd 重赋值/执行 static int clone_binary(void) { int binfd, memfd; ssize_t sent = 0; #ifdef HAVE_MEMFD_CREATE memfd = memfd_create(RUNC_MEMFD_COMMENT, MFD_CLOEXEC | MFD_ALLOW_SEALING); #else memfd = open("/tmp", O_TMPFILE | O_EXCL | O_RDWR | O_CLOEXEC, 0711); #endif if (memfd < 0) return -ENOTRECOVERABLE; binfd = open("/proc/self/exe", O_RDONLY | O_CLOEXEC); if (binfd < 0) goto error; sent = sendfile(memfd, binfd, NULL, RUNC_SENDFILE_MAX); close(binfd); if (sent < 0) goto error; #ifdef HAVE_MEMFD_CREATE int err = fcntl(memfd, F_ADD_SEALS, RUNC_MEMFD_SEALS); if (err < 0) goto error; #else int newfd; char *fdpath = NULL; if (asprintf(&fdpath, "/proc/self/fd/%d", memfd) < 0) goto error; newfd = open(fdpath, O_RDONLY | O_CLOEXEC); free(fdpath); if (newfd < 0) goto error; close(memfd); memfd = newfd; #endif return memfd; error: close(memfd); return -EIO; } int ensure_cloned_binary(void) { int execfd; char **argv = NULL, **envp = NULL; int cloned = is_self_cloned(); if (cloned > 0 || cloned == -ENOTRECOVERABLE) return cloned; if (fetchve(&argv, &envp) < 0) return -EINVAL; execfd = clone_binary(); if (execfd < 0) return -EIO; fexecve(execfd, argv, envp); return -ENOEXEC; }
省略掉了部分代码,完整代码可直接参考 runc 代码仓库 。
整个的修复逻辑我在上面的代码中加了备注,总结来讲其实就是:
创建了一个只存在于内存中的 memfd ;
将原本的 runc 拷贝至这个 memfd ;
在进入 namespace 前,通过这个 memfd 重新执行 runc ; (这是为了确保之后即使被攻击/替换也操作的还是内存中的这个只读的 runc)
经过以上的操作,就基本修复了 CVE-2019-5736 。
影响 内核相关在上面讲完修复方式后,我们来看下会产生哪些影响。
涉及到了系统调用 memfd_create(2) 和 fcntl(2)
增加了系统调用,那自然就要看内核是否支持了。实际上,这些函数是在 2015 年 2 月(距这次修复整整 4 年,也挺有趣)被加入到 Linux 3.17 内核中的。
换句话说就是 凡是在此内核版本之前的系统,均无法正常使用该功能,对我们的影响就是,如果你在此版本内核之前的机器上使用了包含上述修复代码的 runc 或构建在其之上的 containerd、 Docker 等都无法正常工作 。
以 Docker 举例:安装 docker-ce-18.09.2 或 docker-ce-18.06.3 可避免受 CVE-2019-5736 影响,但如果内核版本较低,在运行容器时可能会有如下情况出现: (不同版本/内核可能出现其他情况)
[tao@moelove ~]# docker run --rm my-registry/os/debian echo Hello docker: Error response from daemon: OCI runtime create failed: container_linux.go:344: starting container process caused "process_linux.go:293: copying bootstrap data to pipe caused "write init-p: broken pipe"": unknown.
解决办法
升级内核;这是最直接的办法,而且使用一个新版本的内核也能省去很多不必要的麻烦:)
rancher 提供了一个 runc-cve 的 patch,可兼容部分 3.x 内核的系统(我没有测试过)
如果你不升级 runc/containerd/Docker 等版本的话,那建议你 1. 将 runc 可执行程序放到只读文件系统上,可避免被覆盖;2. 启动容器时,启用 SELinux; 3. 在容器内使用低权限用户或者采用映射的方式,但保证用户对主机上的 runc 程序无写权限。
注意:
memfd_create 等相关系统调用,也被加入到了 Debian 3.16 和 Ubuntu 14.04 updates 中,当然也被反向移植到了 CentOS 7.3 内核 3.10.0-514 版本之后。 (Red Hat 给 CentOS 7.x 的 3.10 内核上反向移植了很多特性)
内存相关从上面的说明中,也很容易可以看到, 内存的使用上会有所增加,不过之后已做了修复。这里不再进行展开。
其他偶尔可能触发一些内核 bug 之类的(总之建议升级 :)
等待 rc8 发布上面已经介绍了 1.0-rc7 出现的主要原因 CVE-2019-5736;当然这个版本中也有一些新特性和一些 bugfix 不过不是本文的主要内容,不再赘述。
值得一提的是这次的版本命名:runc 1.0-rc7 -- "The Eleventh Hour" 后面这个别名其实来自于一部英剧,感兴趣也可以去看看。
至于下个版本是不是会是 1.0 正式版呢?目前来看应该不是,有极大可能会发布 runc 1.0-rc8 做一些 bugfix,让我们拭目以待。
可以通过下面二维码订阅我的文章公众号【MoeLove】
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/32970.html
摘要:在年月底时,我写了一篇文章发布之际。为何有存在前面已经基本介绍了相关背景,并且也基本明确了就是在正式发布之前的最后一个版本,那为什么会出现呢我们首先要介绍今年的一个提权漏洞。 在 18 年 11 月底时,我写了一篇文章 《runc 1.0-rc6 发布之际》 。如果你还不了解 runc 是什么,以及如何使用它,请参考我那篇文章。本文中,不再对其概念和用法等进行说明。 在 runc 1....
摘要:生态周报内容主要包含我所接触到的生态相关的每周值得推荐的一些信息。在发现异常后官方团队迅速采取行动并保护网站免受攻击。期待能早日解决相关问题,并迎来的正式发布。这些功能适用于,,,,,,,和编写的应用程序等,并将在下周放出技术预览版本。 「K8S 生态周报」内容主要包含我所接触到的 K8S 生态相关的每周值得推荐的一些信息。欢迎订阅知乎专栏「k8s生态」。 Docker Hub 用户隐...
摘要:生态周报内容主要包含我所接触到的生态相关的每周值得推荐的一些信息。在发现异常后官方团队迅速采取行动并保护网站免受攻击。期待能早日解决相关问题,并迎来的正式发布。这些功能适用于,,,,,,,和编写的应用程序等,并将在下周放出技术预览版本。 「K8S 生态周报」内容主要包含我所接触到的 K8S 生态相关的每周值得推荐的一些信息。欢迎订阅知乎专栏「k8s生态」。 Docker Hub 用户隐...
摘要:生态周报内容主要包含我所接触到的生态相关的每周值得推荐的一些信息。欢迎订阅知乎专栏生态。正式发布是一个用于本地搭建环境的工具,使用方法可参考使用搭建本地环境。其他特性请阅读正式发布是一个使用来为构建的工具,现在是的项目。 「K8S 生态周报」内容主要包含我所接触到的 K8S 生态相关的每周值得推荐的一些信息。欢迎订阅知乎专栏「k8s生态」。 Kubernetes 1.14 正式发布 1...
摘要:生态周报内容主要包含我所接触到的生态相关的每周值得推荐的一些信息。欢迎订阅知乎专栏生态。正式发布是一个用于本地搭建环境的工具,使用方法可参考使用搭建本地环境。其他特性请阅读正式发布是一个使用来为构建的工具,现在是的项目。 「K8S 生态周报」内容主要包含我所接触到的 K8S 生态相关的每周值得推荐的一些信息。欢迎订阅知乎专栏「k8s生态」。 Kubernetes 1.14 正式发布 1...
阅读 1103·2021-11-16 11:45
阅读 3123·2021-10-13 09:40
阅读 713·2019-08-26 13:45
阅读 1187·2019-08-26 13:32
阅读 2166·2019-08-26 13:23
阅读 910·2019-08-26 12:16
阅读 2823·2019-08-26 11:37
阅读 1747·2019-08-26 10:32