高级
安全沙箱

安全沙箱

Seele 评测系统的安全沙箱是一个被称为 Runj 的独立程序,使用 Go (opens in a new tab) 语言编写。评测系统在运行编译任务和执行任务时会启动 Runj,由它创建一个隔离环境,并在隔离环境中启动需要评测的程序。

Runj 确保程序运行在一个独立的环境中,仅具备有限的权限以完成执行,进而防止不同评测任务之间相互影响,以及恶意代码实施数据窃取、破坏系统的行为。为了进一步的安全性,Runj 具有尽可能低的权限需求,不使用 root 权限。此外,它不会对评测程序的执行效率造成显著的负面影响。

常见的在线评测系统使用的技术主要包括:ptracechrootseccomp 和容器技术等。Seele 选择基于 runc (opens in a new tab) 使用容器技术构建安全沙箱。

ptracechroot

ptrace 是 Linux 操作系统提供的一个系统调用,原本用于调试程序,可以控制程序的执行、监视资源占用等,因而被评测系统用来构建安全沙箱,防止程序执行不安全的系统调用。chroot 也是 Linux 内核提供的一个系统调用,可以修改一个进程的根目录。评测系统可以使用 chroot 来将程序的根目录设置到一个独立的文件夹中,使得程序无法访问系统的其它文件,进而实现环境隔离。

virusdefender 通过实验发现 (opens in a new tab)ptrace 会对程序的运行速度造成显著的负面影响:一个使用 C++ 语言编写的程序运行 900 万次 cincout,相比不使用 ptrace 沙箱的情况下,消耗的 CPU 时间增加了近 2 倍、实际运行时间增加了近 5 倍。因此,ptrace 技术可能导致评测系统错误地判断学生提交的代码超过用户设置的时间限制,并给出错误的评分,对课程和考试的公正性造成恶劣影响。

安全沙箱使用 chroot 时需要具备 root 权限,这带来了一定的安全风险。同时,chroot 的本质是修改内核中进程结构体的相关字段,并非为了安全沙箱而设计,不能完全阻止攻击。 恶意代码可能会利用这些潜在的攻击面取得 root 权限或者访问外部文件,实施数据窃取等行为。

seccomp

近年来,一些新的评测系统开始使用 Linux 内核提供的 seccomp 技术取代 ptrace 技术以限制程序进行的系统调用。seccomp 能够给程序设置一个由系统调用及其相关参数构成的规则列表,在这个列表中用户可以指定可以执行和不可执行的系统调用。相比 ptrace,它对程序的运行性能几乎没有负面影响。

不同程序的运行会产生不同的系统调用以及参数,因此这些评测系统一般都会给每种编程语言的运行环境专门设置一个 seccomp 的规则列表。维护这些规则列表本身需要耗费时间和努力,并且一旦规则出现纰漏可能会导致攻击面的出现,给评测系统带来安全风险。同时,若规则限制过当,学生提交的程序可能无法正常运行。同时,一些语言例如 Haskell 产生的程序在运行时产生的系统调用是难以完全列举的。seccomp 技术较差的泛用性和较高的配置复杂度,使得它难以应对本评测系统的需求。

容器技术

传统的虚拟化技术通过在物理服务器上使用虚拟机创建多个操作系统来实现环境隔离。以 Docker 为代表的软件提出了一种新的虚拟化形式:容器虚拟化。它们使用 Linux 容器相关技术为程序提供一个隔离的环境。容器虚拟化带来的功能非常切合安全沙箱的需求。以 Docker 为例,除了前文介绍的 seccomp 技术之外,它还主要使用了 Linux 内核提供的下列技术来构建容器:

命名空间

Linux 命名空间可以将系统的各种资源进行分隔,使得指定的程序只能看到访问的一部分资源,而无法访问其他资源。从这项技术被引入的 2006 年至今,人们已经向 Linux 内核中添加了许多种类的命名空间,对系统的不同资源进行分隔:

  • 用户命名空间(User namespaces)。隔离用户 ID 和相关权限。用户命名空间具有特殊的功能,下文会单独介绍。
  • Cgroup 命名空间(Cgroup namespaces)。隔离进程能在 cgroup 目录访问和配置的进程。
  • IPC 命名空间(IPC namespaces)。隔离进程对 SystemV IPC 和 POSIX 消息队列的访问。
  • 网络命名空间(Network namespaces)。隔离进程使用的网络栈、网络设备、端口等。
  • 挂载命名空间(Mount namespaces)。隔离进程访问的各个挂载点。
  • PID 命名空间(PID namespaces)。隔离进程的进程 ID 空间,这可以让位于不同的进程命名空间的进程拥有相同的 PID。
  • 时间命名空间(Time namespaces)。隔离进程得到的系统时钟时间。
  • UTS 命名空间(UTS namespaces)。隔离进程得到的 hostname。

cgroup

cgroup 技术能够限制程序的各种资源占用并在必要时终止超过限制的程序,以及收集程序的资源占用情况。Cgroup 目前分为 v1v2 两个版本,后者进行了较大规模的改进,并且目前大多数新版的 Linux 发行版已经默认使用 v2 版本,因此 Seele 使用了 v2 版本。Cgroup 通过不同的 controller 来实现不同种类资源的占用:

  • Cpu controller。控制进程使用的 Cpu 时间、优先级等,也可以收集进程运行消耗的 Cpu 时间,包括内核态时间与用户态时间。
  • Cpuset controller。控制进程处于哪些 Cpu 核心或节点,以及哪些 NUMA 节点。
  • Memory controller。控制进程在用户态、内核态以及 TCP 套接字中使用的内存,也可以收集进程运行消耗的内存量。
  • Pid controller。控制进程在达到数量限制之后无法再通过 fork()clone() 系统调用来产生新进程。

rlimit

Linux 提供的 rlimit 技术可以限制程序的某些资源占用,例如可以通过 RLIMIT_FSIZE 限制程序向文件描述符写入的数据总量、通过 RLIMIT_CORE 可以开启或关闭在程序宕机时系统收集 Core dump 信息的行为。

overlayfs

每个使用容器技术运行的进程都拥有一个隔离的文件系统,这个文件系统中含有进程需要的 Linux 系统中常见的各种文件和文件夹,例如 /proc/var 等文件夹、 bashls 等工具,以及 glibc 链接库。

总结

为了节约硬盘空间以及实现镜像分层存储,容器技术使用 Linux 提供的 overlayfs 为容器中的进程构建隔离的文件系统。overlayfs 可以将多个文件系统或文件夹合并为单个文件系统,让不同的容器共享同一个系统镜像。它们能够访问磁盘中相同的 glibc 链接库文件,也能够对各自的文件系统进行写入。overlayfs 会自动隔离这些写入,使得容器之间无法互相干扰,保证文件系统的隔离性。

容器技术相比前文介绍的 ptracechrootseccomp 技术在理论上具有更好的安全性。在命名空间技术的帮助下,我们可以对操作系统的各种关键资源进行隔离,让不同的程序处于隔离的环境中。例如,通过 IPC 命名空间的隔离,进程无法访问宿主系统上的其它进程的共享内存。又如,通过挂载命名空间的隔离,进程无法访问宿主系统的文件系统,也无法访问其他容器进程的文件系统。

容器技术相比前文介绍的技术也具有更好的泛用性。用户不需要再像使用 seccomp 技术那样,为每一种评测场景准备一套规则列表。容器技术为容器中的程序构建了一份独立的环境,即使程序包含恶意代码,它也只能访问到独立环境中的资源,难以突破到外部的评测系统的环境。