整个流程下来其实并没有太多的亮点,甚至说是这个问题为何到如今才被发现才是令人惊讶的,因为问题的核心就在于一个abstract namespace unix socket
,不管是问题的原因还是出现都在于此。那就从这个abstract namespace unix socket
开始谈起。
这是一种IPC
方式,调用方式和普通的套接字一样使用的socket()
,只不过domain
选取的是AF_UNIX
用于本地通信。而类型则有三种:
- 基于文件
- socketpair创建的匿名对
- abstract namespace,linux特有
其中第二种以前有讨论过,主要应用与父子进程通信因此不谈,所以这次可以直接看一下第一种和第三种,或者说为什么要有第三种?
基于文件的unix socket
的工作模式如下:
服务端:创建socket—绑定文件—监听—接受客户端连接—接收/发送数据—关闭
客户端:创建socket—绑定文件—连接—发送/接收数据—关闭
可以直接从
man unix
看到demo
和网络套接字相比,socket
从ip + 端口
变成了一个socket文件
,这也就是说这种工作模式高度依赖于文件系统,那自然问题就出来了:
- 使用
socket
的进程必须具备对应路径的读写权限 - 关闭通讯时,创建的
socket
文件并不会被自动删除,需要在代码中单独增加删除逻辑,例如unlink
或是remove()
- 倘若
scoket文件
被意外删除则会导致不可控的问题,最典型的就是client
认为server
已经关闭 - 文件冲突问题
上述的问题其实主要都是因为依赖于文件系统而导致的,那么倘若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
的实际实现上可以分为containerd
和containerd-shim
,而前者正是通过后者来管理到一个个具体的容器,后者则是作为容器的管理接口而存在。然而就是这儿的实现上却出了问题,那就是containerd
和containerd-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
从而造成逃逸。
放文件夹里了,因为有其他事且我自身的环境升级了,就没测试过能不能用,但是思路大概是这个思路
没什么可讨论的,官方老老实实的从abstract
改回了基于socket文件
的,可以看Merge pull request from GHSA-36xw-fx78-c5r4