【云安全系列】让Seccomp动起来­­SeccompNotify

admin 2022年11月9日13:27:59评论24 views字数 4867阅读16分13秒阅读模式
【云安全系列】让Seccomp动起来­­SeccompNotify

简介

【云安全系列】让Seccomp动起来­­SeccompNotify

根据我们上一篇《Seccomp -云安全syscall防护利器》的分析,我们可以知道 Seccomp-BPF 模式无论是在可扩展性上还是开发简易性上都得到了大幅的改善。尽管如此,Seccomp-BPF 却存在一个致命的缺陷,其对应的生效方式不适合动态的场景。具体来说,无论 syscall 最后是成功还是失败,一旦一个 Seccomp filter 成功加载到内核后,对应 syscall 的规则也就固定下来了。接下来不管在什么场景下,用户都无法再次根据具体的情况,去修改对应的 syscall 规则。这就意味着用户从一开始,就需要确定好对应进程所有的系统调用权限,一旦出现偏差,就需要重新修改、编译代码,这其实无形中增加了用户的试错成本。

SeccompNotify


为此,在 5.0(2019年3月4日)版本内核又加入了 seccomp-unotify 机制。相较于 Seccomp-BPF 模式下,系统调用的裁决需要由 filter 程序自己完成,seccomp-unotify 机制能够将裁决权转移给另一个用户态进程。为了方便理解,接下来我们将需要加载 Seccomp filter 程序的进程叫做 target,接收到 target 通知的进程叫做 supervisor。在这个模式下,supervisor 不仅可以对是否允许系统调用做出判断,它还可以替代 target 进程去执行这个系统调用的行为。这无疑再一次扩展了 Seccomp 的使用范围。


【云安全系列】让Seccomp动起来­­SeccompNotify


接下来我们通过一个简单的示例来了解 SeccompNotify 的具体流程,target 进程如下:


int main(int argc, char **argv){    ...    #创建filter    struct sock_filter filter[] = {        BPF_STMT(BPF_LD+BPF_W+BPF_ABS, (offsetof(struct seccomp_data, nr))),

BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_mkdir, 0, 1), BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_USER_NOTIF),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW), };

... #初始化notify fd notifyfd = syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, &prog); if (notifyfd < 0) { printf("fail to create seccomp: %sn", strerror(errno)); exit(-1); }

printf("tid: %d, notify fd: %dn", syscall(SYS_gettid), notifyfd); #触发mkdir fd = mkdir(argv[1], O_CREAT|O_RDWR); ...

return 0;}


在 target 进程内,我们首先定义了一个 socket filter, 这个 filter 允许除了 mkdir 之外的所有系统调用,mkdir 需要通过 Seccomp Notify 的方式由 supervisor 去判决是否允许执行。接着,通过 Seccomp 系统调用的方式来加载 filter,将对应的 tid 和 fd 打印出来。最后执行 mkdir 命令,触发 mkdir 系统调用,进程进入阻塞状态,等待内核返回。


对于 supervisor 程序:


static void supervisor_process_notifications(int notifyfd){    ...    #创建seccomp    if (syscall(SYS_seccomp, SECCOMP_GET_NOTIF_SIZES, 0, &sizes) == -1) {        ...   }

... #从notify df中轮询事件 if (ioctl(notifyfd, SECCOMP_IOCTL_NOTIF_RECV, req) == -1) { ... }

... # 判断mkdir的参数是否是 add if (strlen(path) == strlen("add") && strncmp(path, "add", strlen("add")) == 0) { printf("change name for add dirn"); if (access("change", 0) == -1){ mkdir(path, O_CREAT|O_RDWR); }   }    resp->error = 0; resp->flags = SECCOMP_USER_NOTIF_FLAG_CONTINUE;

resp->id = req->id; # 发送结果给内核 if (ioctl(notifyfd, SECCOMP_IOCTL_NOTIF_SEND, resp) == -1) { ... }}

int main(int argc, char **argv){ ... pid = atoi(argv[1]); targetfd = atoi(argv[2]); printf("PID: %d, TARGET FD: %dn", pid, targetfd); #获取target fd对应的pid fd pidfd = syscall(SYS_pidfd_open, pid, 0); assert(pidfd >= 0); printf("PIDFD: %dn", pidfd); #根据target fd获取监听的notify fd notifyfd = syscall(SYS_pidfd_getpid, pidfd, targetfd, 0); assert(notifyfd >= 0); printf("NOTIFY FD: %dn", notifyfd); #监视target进程 supervisor_process_notifications(notifyfd);

return 0;}


首先,supervisor 进程通过 pidfd_getpid 获取到 target 进程的 seccomp fd,并且从 fd 中不断轮询 target 内的 seccomp notify 事件。然后,再从 /proc/{pid}/mem 文件内获取 mkdir 系统调用所引用的参数,也就是创建的文件夹名称。supervisor 规定,如果创建文件夹的名称为 add,则会自动将该文件夹名称修改成 change,并且将结果返回给内核,从而达到约束 target 进程的目的。


执行 target 进程:


root@localhost:~/CLionProjects/seccomp# ./target /tmp/aatid: 8005, notify fd: 7


可以看到 target 进程进入了阻塞状态,并且对应的 tid 和 notify id 分别为 8005和7

,然后我们在另一个中断终端内执行 supervisor 进程:


root@localhost:~/CLionProjects/seccomp# ./supervisor 8005 7PID: 8005, TARGET FD: 7PIDFD: 7NOTIFY FD: 5Got notification for PID: 8005SYSCALL: 83open path: /tmp/aa


查看 /tmp 目录可以看到文件夹被成功创建了:


root@localhost:/tmp# ls -l /tmp/总用量 5124d--x------ 2 root root   4096 1014 15:47 aadrwxr-xr-x 2 root root   4096 1014 11:32 gproc-rw-r--r-- 1 root root     0 1014 11:32 heartalive.lock


但是如果我们将创建的文件夹名称修改为 add,情况又会是什么样呢?


root@localhost:~/CLionProjects/seccomp# ./target addtid: 8773, notify fd: 7


target 进程依旧是在阻塞状态,然后运行 supervisor:


root@localhost:~/CLionProjects/seccomp# ./supervisor 8773 7PID: 8773, TARGET FD: 7PIDFD: 7NOTIFY FD: 4Got notification for PID: 8773SYSCALL: 83open path: addchange name for add dir


通过日志可以看到,supervisor 捕获到 target 的文件夹名称为 add 后,执行了修改 add 文件夹的操作,遍历当前目录,查看是否成功修改:


root@localhost:~/CLionProjects/seccomp# ls -l总用量 24d--x------ 2 root root 4096 1014 15:57 changedrwxr-xr-x 5 root root 4096 1014 14:46 cmake-build-debug-rw-r--r-- 1 root root 131 1014 14:46 CMakeLists.txt-rw-r--r-- 1 root root 241 1014 14:52 hello.c-rw-r--r-- 1 root root 3188 1014 15:37 main.c-rw-r--r-- 1 root root 1560 1014 11:47 target.c


所以这里的话,supervisor 进程就通过截获了target进程的syscall,从而达到安全管控的目的了。

使用场景和注意事项


就目前而言,SeccompNotify 的运用场景主要有以下三种:

  1. supervisor 直接拒绝 target 的 syscall 请求。

  2. 在 supervisor 内执行其他的操作,并且向内核反馈对应的结果(也就是高特权进程替换低特权进程的操作)。

  3. 允许 target 执行对应 syscall。


    但需要注意的是第三种情况,如果执行的 syscall 携带了引用指针,那么就可能存在多个进程同时陷入阻塞状态,从而导致 race-condition的 情况。所以大家在使用的时候请务必小心谨慎。

总结


Seccomp 作为最早一批系统级安全防护机制,从最开始只支持4个 syscall 的保护,逐步发展到 300+syscall 管控,再到现在可以在用户态定义的动态管控。我们可以察觉系统安全正在逐步由小部分的内核开发维护人员,向着更加普遍且大众的开发者贴近。本文通过实际编码的方式,着重介绍了 SeccompNotify 在进程中通过将裁决移交给第三方进程,从而达到约束 target 进程的功能。除此之外,我们还概括了目前 SeccompNotify 主要使用的场景和注意事项,当然除了在进程中,SeccompNotify 在容器环境也能够实现动态生效的功能,而且还在不断的发展之中,深信服创新研究院云安全研究团队将对此持续探索

参考链接


  1. https://brauner.github.io/2020/07/23/seccomp-notify.html


  2. https://man7.org/linux/man-pages/man2/seccomp_unotify.2.html


  3. http://terenceli.github.io/%E6%8A%80%E6%9C%AF/2021/05/20/seccomp-user-notify


  4. https://blog.zhougy.top/2019/11/30/seccomp%E4%BB%8B%E7%BB%8D/


  5. https://cloud.tencent.com/developer/article/1801887


  6. https://www.kernel.org/doc/html/v5.17/userspace-api/seccomp_filter.html


  7. http://terenceli.github.io/%E6%8A%80%E6%9C%AF/2019/02/04/seccomp


  8. http://just4coding.com/2022/04/03/seccomp/#more

【云安全系列】让Seccomp动起来­­SeccompNotify


原文始发于微信公众号(深信服千里目安全技术中心):【云安全系列】让Seccomp动起来­­--SeccompNotify

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年11月9日13:27:59
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【云安全系列】让Seccomp动起来­­SeccompNotifyhttps://cn-sec.com/archives/1393129.html

发表评论

匿名网友 填写信息