计算机系统的各种硬件资源是有限的,在现代多任务操作系统上同时运行的多个进程都需要访问这些资源,为了更好的管理这些资源进程是不允许直接操作的,所有对这些资源的访问都必须有操作系统控制。也就是说操作系统是使用这些资源的唯一入口,而这个入口就是操作系统提供的系统调用(System Call)【简写syscall】。Linux 提供的系统调用种类十分丰富,不乏一些危险的调用,例如vm86,这是我觉得最危险的系统调用之一,当然也有一些十分普遍的调用,比如read,write。
沙箱需要保护系统的安全,所以需要沙箱能控制所有系统调用,或者提供一种方式,让操作系统完成限制。 Linux 提供了 ptrace 和 seccomp 两个方式。
ptrace 简介
ptrace 应该是最常见的 syscall 拦截函数,可以 拦截并修改 程序运行中的所有系统调用,strace 利用 ptrace 实现的, 原有的OJ沙箱也是利用 ptrace 进行判定的,预先维护了一张 okcalls 的白名单,利用 ptrace 获取当前调用,如果在白名单内允许则放行, 但这样效率一看就不是很高。
seccomp 简介
seccomp 是 2005 年发布的,用以运行不安全的程序,能够限制系统调用类别以及参数,docker, QEMU 等很多Linux上的东西都是通过seccomp来保证安全的。由于是预先载入规则,交由系统来判定,所以效率会高一些。这也是沙箱选用的方法
白名单 or 黑名单
偷懒选择黑名单, 如果是在 docker 里运行,也可以选择黑名单,docker 只提供 370+ 个系统调用。但是如果是要运行在系统之上,那么一定要白名单机制。现在是使用黑名单和白名单混合的机制,之后可以全部使用白名单机制
限制名单
详细的限制 可以 在 Sandbox - Boxjan Git 里看到。这里只会阐述 禁止/放开 某些syscall的原因。
strict source
使用白名单模式,仅提供基本文件(IO)操作(read write lseek等),内存操作(mmap mremap等),进程操作(主要为信号拦截及退出),获取必要的系统信息这些 syscall。 这个模式是可以用来运行 C、C++、Java等大部分编译型语言。
为了阻止用户的程序(新建)写入文件,选择了限制open() openat(),使用seccomp判断若 open 和 openat 的第二参数中含有 O_WRONLY 或 O_RDWR 则会杀掉程序。
gentle source
使用黑名单模式,禁止了网络(socket),大部分 set 开头的syscall, 修改文件(权限)操作,以及禁止了fork/vfork , 但并未限制 clone, 这是考虑到有部分语言底层需要多线程进行操作(例如 ruby)。
为了阻止用户的程序(新建)写入文件,选择了限制open() openat(),使用seccomp判断若 open 和 openat 的第二参数中不含有 O_WRONLY 或 O_RDWR 则允许程序运行,否则杀掉进程。
compile source
使用黑名单模式,禁止了网络,部分set开头syscall,用于编译型语言编译可执行程序时的syscall限制,限制网络的原因是防止类似于go等语言通过网络获取代码进而进行编译。
总结
OJ 沙箱的大部分设计思路就写得差不多了,分成了几篇文章,可以通过 Tag: SandBox 查看这部分的文章。这部分代码暂时放在 Boxjan Git 上,之后会迁移到 Github上,如果有任何问题欢迎提issue或在博客下评论。
-- EOF --
comments