Skip to content

Latest commit

 

History

History
77 lines (51 loc) · 5.07 KB

从CVE-2020-15257认识到的问题.md

File metadata and controls

77 lines (51 loc) · 5.07 KB

问题的原因

整个流程下来其实并没有太多的亮点,甚至说是这个问题为何到如今才被发现才是令人惊讶的,因为问题的核心就在于一个abstract namespace unix socket,不管是问题的原因还是出现都在于此。那就从这个abstract namespace unix socket开始谈起。

Unix Socket

这是一种IPC方式,调用方式和普通的套接字一样使用的socket(),只不过domain选取的是AF_UNIX用于本地通信。而类型则有三种:

  1. 基于文件
  2. socketpair创建的匿名对
  3. abstract namespace,linux特有

其中第二种以前有讨论过,主要应用与父子进程通信因此不谈,所以这次可以直接看一下第一种和第三种,或者说为什么要有第三种?

基于文件的unix socket的工作模式如下:

服务端:创建socket—绑定文件—监听—接受客户端连接—接收/发送数据—关闭
客户端:创建socket—绑定文件—连接—发送/接收数据—关闭

可以直接从man unix看到demo

和网络套接字相比,socketip + 端口变成了一个socket文件,这也就是说这种工作模式高度依赖于文件系统,那自然问题就出来了:

  1. 使用socket的进程必须具备对应路径的读写权限
  2. 关闭通讯时,创建的socket文件并不会被自动删除,需要在代码中单独增加删除逻辑,例如unlink或是remove()
  3. 倘若scoket文件被意外删除则会导致不可控的问题,最典型的就是client认为server已经关闭
  4. 文件冲突问题

上述的问题其实主要都是因为依赖于文件系统而导致的,那么倘若unix socket本身可以脱离依赖的话,是否就能解决这些问题?在这样的前提下abstract namespace无疑是一个非常棒的解决问题的方式。简单来说就是这种方式采用了abstract namespace也就是说在相同的namespace下维护了一个虚拟的文件系统,而其中的socket文件也会在断开连接后自动删除,并且该文件在常规文件系统中没有对应且无法看到。 继续来看demo

#define SOCKET_NAME "@9Lq7BNBnBycd6nxy.unix"


server.c:
           name.sun_family = AF_UNIX;
           strcpy(name.sun_path, SOCKET_NAME);
           name.sun_path[0] = 0;
           int server_len = strlen(SOCKET_NAME) + offsetof(struct sockaddr_un, sun_path);
           ret = bind(connection_socket, (const struct sockaddr *) &name,
                      server_len);


client.c:
           addr.sun_family = AF_UNIX;
           strcpy(addr.sun_path, SOCKET_NAME);
           addr.sun_path[0] = 0;
           int server_len = strlen(SOCKET_NAME) + offsetof(struct sockaddr_un,sun_path);
           ret = connect(data_socket, (const struct sockaddr *) &addr,
                          server_len);

综上所述之前的问题也就不存在了,然而随着容器化的普及,新的问题也随之而来。

容器构建中的问题

就以市场占有率最高的docker来说,自从其变化了自身的架构后,就每每在架构上出现问题这次也不例外,标题中的cve-2020-15257就是出现在其中的组件containerd上。以容器单元来说其创建和运行是依托的runc,但是需要针对一个个容器进行管理和调度,就在其上提供了一层组件对上提供gRpc调用,对下则提供对于容器的管理在,这一层就是containerd

containerd的实际实现上可以分为containerdcontainerd-shim,而前者正是通过后者来管理到一个个具体的容器,后者则是作为容器的管理接口而存在。然而就是这儿的实现上却出了问题,那就是containerdcontainerd-shim的通信依靠的就是abstract namespace unix socket,且会基于这个socket建立gRpc服务的底层逻辑,而其ACL也只有简单的UID/GID判断,用简单的话语来说只要在相同的namespace中,一个UID/GID能通过ACL的进程就能通过abstract namespace unix socket主动连接containerd-shim然后调用其管理容器。

也许还会幸免需要有相同的namespace才行,然而当容器的启动使用了host网络,即代表宿主机与容器并没有隔离网络,那么这一点自然也就符合了,因此当容器内的root执行程序时就能够直接控制到宿主机上的containerd-shim从而造成逃逸。

demo

放文件夹里了,因为有其他事且我自身的环境升级了,就没测试过能不能用,但是思路大概是这个思路

官方修复

没什么可讨论的,官方老老实实的从abstract改回了基于socket文件的,可以看Merge pull request from GHSA-36xw-fx78-c5r4

参考文档