马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
【HUSTOJ 判题机源码解读系列04】判题机常见技能选择方案
从本文开始,我们就需要准备开始解读 HUSTOJ 中最核心的 judge_client.cc 文件的前置知识了。
作为真正判题的模块,我们需要明白其实现原理,使用了哪些技能,不过既然本文标题为“判题机常见技能选择方案”,应是不范围于 HUSTOJ 选择的技能方案,还应该先容一些其他的技能方案,扩展一下知识的广度也是好的。
1. 判题机的职责
在了解常见的技能方案之前,我们在这里先明确一下判题机最核心的职责,我将其分别两个重要部门:
- 沙箱部门:提供一个受限的执行环境,负责编译和运行用户代码,并收集标准输出。沙箱的关键在于安全性,包括但不限于,防止恶意代码破坏服务器、限定 CPU 和内存占用、拦截危险的体系调用等等。
- 评测部门:判断用户代码的执行结果是否正确,包括检查算法是否符合预期、是否超出时间或内存限定,以及标准输出格式是否合规等。
别的,有些博客和教程会把判题任务调度也归入判题机的范畴,但我更倾向于把它作为上层服务,让判题机专注于代码执行和评测。如许不但逻辑更清晰,也更利于体系扩展和维护。
评测部门现实上就是重要做了两件事,一是假如用户代码有执行有异常,需要确定异常是什么,并返回异常信息。二是假如没有异常,那就将用户标准输出文件和标准答案文件举行比对,检察输出是否一致。
这两件事实践起来都非常简朴。判题机真正的难点是怎样实现一个安全、高效的代码沙箱。
2. 沙箱部门实现技能方案
2.1 基于 ptrace + setrlimit + chroot 实现(HUSTOJ 的技能方案)
在 Linux 中,ptrace 和 setrlimit 分别用于进程控制和资源限定,而 chroot 则可以对文件体系举行隔离。
2.1.1 使用 ptrace 拦截危险的体系调用
利用 ptrace 允许父进程监督和控制子进程的机制,我们可以拦截危险的体系调用。在 Linux 下,每个体系调用都有一个唯一的编号(如 open、close 可能对应 2 和 3,详细编号取决于 CPU 架构)。当进程执行体系调用时,这个编号会存放在 rax 寄存器(对于 x86_64 架构)中。
现在我们就可以实现:
- 拦截体系调用:在子进程执行 syscall 时暂停它,并读取 rax 寄存器中的体系调用编号。
- 比对白名单:维护一个允许调用的体系调用列表,假如 rax 中的编号不在白名单内,我们就可以判断这个进程想要执行恶意代码,就直接终止该进程。
如许,我们就能有用拦截危险的体系调用,如 execve()(执行新进程)、fork()(创建子进程)等,克制代码逃逸或破坏服务器。然而,ptrace 的开销较大,因为它需要在每次体系调用发生时暂停进程,并在用户态与内核态之间频繁切换,十分影响运行效率。
2.1.2 使用 setrlimit 实现资源控制
使用 setrlimit 可以直接用于限定进程 CPU、内存、磁盘等,详细实现如下:
- 限定 CPU 运行时间(防止死循环):setrlimit(RLIMIT_CPU, time_limit);
- 限定最大内存使用(防止 OOM):setrlimit(RLIMIT_AS, memory_limit);
- 限定最大文件大小(防止恶意生成大文件):setrlimit(RLIMIT_FSIZE, max_file_size);
2.1.3 使用 chroot 隔离文件体系
chroot 允许改变进程的根目录视图,让进程只能访问特定的文件体系。比方,OJ 评测机可以使用 chroot("/sandbox"),让代码只能访问 /sandbox 目录,而无法访问 /etc/passwd 等体系文件,从而提供一定程度的文件隔离。
但是 chroot 其实是有进程逃逸的风险的。假如进程拥有 CAP_SYS_CHROOT 权限,大概可以或许执行某些体系调用(如 pivot_root),就可能绕过 chroot 限定。
2.2 基于 seccomp + cgroup + namespace + pivot_root 实现
2.2.1 实现 seccomp 实现拦截危险的体系调用
seccomp 允许进程以白名单模式运行,只允许执行特定的体系调用,其他所有体系调用都会被拒绝(SCMP_ACT_KILL)大概返回错误(SCMP_ACT_ERRNO)。
并且 seccomp 直接在内核级过滤 syscall,不会像 ptrace 那样需要执行进程暂停 & 用户态切换,手动判断体系调用是否合规,导致进程运行缓慢。
2.2.2 使用 cgroup 实现资源控制
cgroup 是 Linux 提供的基于文件体系的进程资源控制机制,可以对一组进程举行CPU、内存、I/O、进程数等资源的限定。
通过向对应的文件写入配置:
- echo 50000 > /sys/fs/cgroup/cpu/oj/cpu.cfs_quota_us
复制代码 可以有用的限定限定进程组使用的体系资源,并且相对于 setrlimit 只能限定单个进程来说,cgroup 可以或许限定一整个进程组,并且数据更准确。
2.2.3 使用 namespace + pivot_root 隔离进程运行环境
Linux 提供的 namespace 机制允许进程在隔离的环境中运行,使其看不到宿主机的其他进程和资源。在此根本上,通过 bind mount 和pivot_root隔离进程运行环境的文件体系,可以或许进一步简直保用户步伐不会影响到服务器。
3. 使用 docker、podman 等容器软件(HUSTOJ 也支持)
除了自己实现一个代码沙箱,我们还可以使用 Docker、Podman 如许的容器软件来作为代码沙箱。现实上,Docker 和 Podman 的底层也是基于 Cgroup、Namespace 等 Linux 内核技能来实现进程隔离和资源控制的。
选择这种方式的最大优点就是 “简朴”。这些开源容器软件已经封装好了底层逻辑,我们无需手动实现复杂的 进程管理、文件体系隔离、资源限定 等机制,直接使用现成的容器功能就行。而且,Docker 和 Podman 颠末恒久的社区维护,安全性、稳定性和兼容性都非常优秀。
但缺点在于灵活性较低。由于 Docker 和 Podman 并非专门为代码沙箱设计,而是通用的容器运行时,无法针对 OJ 评测机、在线编译环境等特别需求举行深度优化。别的,通用容器的额外封装层会更消耗性能,性能可能比不上为 OJ 定制的沙箱。
4. 总结
本文我们先容了实当代码沙箱的常见的几种方案,而 HUSTOJ 使用的 ptrace + setrlimit 的方案已经足够满足一个 OJ 判题机的所需,其优点是简朴,缺点是效率有问题。
下期仍和本期一样,属于技能先容类型,重要内容就是先容 ptrace 怎样使用,了解了 ptrace 怎么使用之后再开始分析 judge_client.cc 的详细实现。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |