OJ 沙箱

最近想要实现一个简单的 OJ,于是就研究了各种 OJ 沙箱的实现.这里按照实现使用的机制分成几类介绍.

Attack Surface

因为要运行用户提交的程序,可能有多种方式攻击 OJ 系统:

ptrace

ptrace 是一个 syscall,可以用于追踪子进程的系统调用.由于会导致严重的性能问题,而且并不是现在的主流判题沙箱实现使用的机制,这里省略更多的细节.

Linux seccomp

QDUOJ 的文档

Linux namespaces & cgroups

在 Linux 中,namespaces 用于给不同的进程对系统资源的不同视野;而 cgroups 用于限制和统计进程使用的资源.这也是 Linux 容器技术背后的内核机制.最直接的讲解请看相关的 man page.需要注意的是,因为 cgroups v2 曾经缺失统计历史最大内存用量的功能,现有的系统(包括 SYZOJHydroJudge)选择让用户禁用 unified hierarchy.

因此,一个沙箱可以这样实现:

  1. 在准备阶段,设置 rootfs 的 bind mount,并挂载限制了大小的 tmpfs;
  2. 在运行程序的阶段,先在相关的控制器中新建 cgroup,再用 clone syscall 创建具有新 network namespace, mount namespace, pid namespace 的子进程.在这个子进程中,将自身加入到 cgroup 中,然后 fork/exec 子进程.在 namespace 外的进程可以待子进程结束后在 cgroup 的相关文件中读取资源占用信息.
  3. 最后的清理阶段中,准备阶段的 rootfs bind mount 和 tmpfs 会被 umount 掉.

之前提到的 SYZOJ 和 HydroJudge 都用的是这种机制,而 IOI 中使用的 isolate 也有使用这些机制的模式.

我写了一套 rootless containters 机制的判题沙箱,在 lrw04/jury

OpenBSD pledge/unveil

据我所知,目前没有使用 OpenBSD 的 OJ.然而,OpenBSD 的两个 syscall 对沙箱作者来说十分诱人:pledgeunveil.我以前尝试过用 unveil 来限制程序的可见范围,但发现如果不在 unveil 的策略上下功夫的话动态链接的程序,以及不少需要读取运行库的编程语言都会因为读取不到运行时库而不能运行.在仔细阅读 man page 之后又觉得可能还是可能的.然而,据我所知 OpenBSD 没有提供特别的统计和限制资源占用的工具.

microvms

firecracker 是一种轻量化的虚拟机,比传统的虚拟机启动时间更短.同样,据我所知,目前没有使用 microvms 的 OJ.