2019-08-02

开发OJ之沙箱 — 改用 golang

Why go?

原因很简单,因为 go 好用。 OJ 的 web 端是用的Laravel,而其他部分打算用的是 Go ,虽然 go 能便捷的与 C/C++ 集成在一起,但是并不似很方便,如果涉及到指针也很容易出错,所以就考虑将沙箱也一起用 go 修改,如果以后心血来潮,做 windows 的支持,相比 C/C++ 语言也会方便的多。

改进了什么

Go 语言 为不同的系统提供了不同的API,在 Linux 下 亦有提供 getrusage,setrlimit 这类的API。 新的也考虑引入 cgroup这个资源管理器,能很好的限制 CPU 时间片、内存使用量、IO 等系统资源的使用,但如果是在 docker 中使用则需要启动 docker 是增加参数允许和支持。也同时引入 Linux Namespace 用以做资源隔离,这样的话,在运行的时候在便不会出现程序间相互影响的情况,也同时可以隔离网络,防止利用 go mod 等类似工具编译时下载库。

开发的时候,使用 stress 工具来使用一定内存,测试内存使用量限制,在设置 cgroup 时,需要将该 cgroup 的 memory 的 swappiness 设置为0,否则内存一旦用尽,系统便会将旧申请的空间移入swap。关于程序的运行时间,可以用 go 语言中的上下文(context)来进行控制,精确度还是比较高的。

有了 cgroup 和 namespace 之后,几乎可以不用限制 syscall,依靠着这两工具,基本可以限制得很好,可以防止程序对系统的破坏,但是,还得考虑程序是恶意的,会尝试逃逸,所以还是得限制掉一些 syscall。可以直接参考 docker。

有什么问题吗

当然有!

内存使用量偏差

系统原有的 grusage 并不能获取正在运行的程序的内存使用大小,也不能获取由子程序内存申请的大小。例如 stress --vm-byte 256m --vm-keep --vm 2 --timeout 3s 这句脚本在 执行过程中形成3线程,最后用 grusage 收集信息的时候,内存的使用量 会是 6m 或者 256+m 左右,而实际只用量应该是在 512m 以上。(getrusage 模式参数 RUSAGE_CHILDREN),使用自己之前开发的sandbox 或者 QingDaoU 的 Judger 上都不能正常获取到内存的使用量。也与可能是我操作不对,欢迎评论交流。

既然引入了cgroup,cgroup 的内存子模块是有统计本进程和子进程的使用内存大小的,可以直接从那获取,但是使用 cgroup 需要 root 权限,如果不是以root运行,统计多进程的内存使用量就会出错。这里暂时没什么想法,也欢迎讨论。

seccomp load

go 的引入了协程这一概念,协程由go的调度器进行管理。docker 的 seccomp 是在容器启动前的启动线程通过exec调用自身,新启动的线程对自身进行限制,包括 新建namespace,cgroup,挂载文件,挂载网络,之后通过 exec 的相关组件执行命令,这么一个过程。(来源 自己动手写docker)

但是沙箱作为组件,是不应该通过调用本身("/proc/self/exe")来进行启动新的执行线程,这样就不是一个组件了,Golang 并没有提供 单纯的fork/vfrok/clone,提供的方法都是 fork + exec,(跟go语言设定有关,大部分多线程和多进程的场景都用协程来解决)。花了一天时间追了一下Go的实现源码,基本就是 clone/fork 后 父进程返回,子进程用dup2、exec等方法挂载输入输出和运行程序。

目前的计划是,参照着现有的 os/exec包以及相关的内容,自己写一个。基本就是照抄,只在必要的地方添加上自己的代码。

Other...

默默吐槽一下 go mod

代码呢

说了这么多,代码呢?

在 github 上,目前还没写完,也没开始放,目前(2019/09/29)还只是原型测试和功能测试,所有代码都是堆在一起。等觉得合适了,我再 push 上去。

-- EOF --

comments

如果无法加载 请将 disqus.com | disquscdn.com 加入代理