关注了就能看到更多这么棒的文章哦~
Grabbing file descriptors with pidfd_getfd()
By Jonathan Corbet
January 9, 2020
原文来自:https://lwn.net/Articles/808997/
从user space对一组进程进行控制的需求越来越多,因此kernel增加了一些机制可以让一个进程操作另一个进程。不过拼图中还缺少一块,目前一个进程还是无法从另一个进程中复制已经打开的文件描述符。Sargun Dhillon的pidfd_getfd()系统调用patch set合入之后,kernel就可以拥有这个功能了。
现有的kernel里面已经支持某个进程打开另一个进程中已经处于打开状态的文件了。这是通过访问每个进程的/proc目录来实现的。不过,如果目标文件描述符是用于管道、套接字(socket)等等未能出现在/proc目录下的内容的话,这个方法就无法利用了。并且,采用这种方法会在file table里面创建新的条目,这个新条目并不是对应我们所关注进程中的文件描述符的。
如果我们的目标是希望直接修改那个特定的文件描述符的话,上述限制也就阻碍了我们想做的事情。这个patch set中举了一个例子来说明,就是其他用户试图把某个socket给bind到某个特权端口号的时候,我们试图使用seccomp来截获此bind申请。拥有响应特权的监管者进程可以先取得目标进程的socket对应的文件描述符,然后直接帮它进行bind操作(目标进程本身其实无权进行这个操作)。因为获取到的文件描述符跟原本哪个本质上来说是相同的,所以bind操作对目标进程也是可见的。
其实现在就是有办法从另一个进程提取文件描述符的。就是利用ptrace()来attach到目标进程,让它停止执行,然后注入代码让它可以跟管理员进程进行通信并通过SCM_RIGHTS格式来把文件描述符发送过去,然后继续运行原来代码。这个方案可行,可以就是不太优雅。它也会暂停目标进程的执行,这一点许多人不喜欢。
其实如果不希望暂停目标进程的话,在kernel还是很容易可以实现的。这个监管者进程只需要简单调用一下:
int pidfd_getfd(int pidfd, int targetfd, unsigned int flags);
pidfd参数就是用来指定目标进程的,它是在进程创建时获得的pidfd信息。需要获取的文件描述符是通过targetfd来指定的。一切正常的话,返回值是对应这个目标进程中特定文件的一个文件描述符需要。不过前提条件是调用此函数的进程要有权限对目标进程调用ptrace()。
flags这个参数目前没有用,必须传入0。后续会需要增加flags的含义。比如可以用一个flag标记来在目标进程这边在文件描述符被复制给调用此API的进程之后就关闭掉,这样就真正实现了从目标进程把文件描述符彻底接手过来的效果。还有一个预计可以加的flag是针对socket描述符的,在copy过程中移除cgroup数据。
这组patch set自从12月5日第一次发布之后经过了许多版迭代,变化很大。最早的一版是为ptrace()新增了一个PTRACE_GETFD命令。第3版切换成了针对pidfd的一个ioctl()操作。从首发之后的第15天就提出了第5版,使用了一个新增的系统调用来完成这个功能。目前最新版本是第9版。
一直一来大家对这个功能要达到的目标都没有什么异议。review中的意见都是针对实现方式的。目前来看Dhillon基本已经把所有可能的实现方案都尝试了一遍,不过其实还有人认为后续还需要用BPF来实现一版。如果没有这些意外的话,这个新增的系统调用应该能合并入5.6或者5.7版本中。
全文完
LWN文章遵循CC BY-SA 4.0许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注LWN深度文章以及开源社区的各种新近言论~