标签:
最近在研究基于linux的OJ系统,然后想自己写一系列文章记录自己这段时间的学习成果。
首先,从原理上讲,OJ功能实现并不难,最主要解决的是安全性问题。总结一下,而安全性方面问题主要是用户可能提交恶意不友好的代码。关于如何过滤这些不安全的代码,我从网上收集整理了许多资料,大体上思路如下:
先说错误的做法:
1.所有的字符串过滤都是不靠谱儿的,坑人坑自己,C语言强大的宏几乎没有绕不过的字符串过滤,而且误伤也是很常见的,比如,你在程序里要是不小心定义一个叫做fork的变量,那么你的程序别指望可以AC了,因为字符串过滤会把你的fork想成创建子进程的那个fork,而这是不被允许的。
2.手工审计头文件,去掉某些头文件或者注释掉一部分是辛苦的且无用的;做了这样的工作之后你就几乎不再想去升级编译器及头文件了,更可怕的是这个工作需要你对语言,编译器,连接器有一定程度的了解,而我认为拥有足够了解的人都会明白这样做是毫无道理可言的:就算没有头文件,没有了函数原型,调用系统调用的方法还是有一大把,而且也都不是很麻烦。
再说说准备工作:
1.熟悉你的目标系统(windows or linux):必须要了解这个平台下的原生系统调用API是怎么使用的(不然你怎么屏蔽),最好可以了解到汇编层面。必须要了解这个平台下的用户系统,权限控制,资源限制。最好了解一下进程跟踪,调试,监控工具或者系统调用,例如Linux下的ptrace。最好要了解目标系统提供的各种沙盒限制功能。
2.了解你的编程语言及工具链:必须要了解你的目标语言的特性,及其在一般的OI/ACM比赛中的规定和限制。必须要了解你的工具链的功能及各种参数。
3.拥有足够的编程功底,对于这样的小的程序,应严格杜绝缓冲区溢出之类的BUG。
最后说说我的做法:我的目标平台是Linux,目标语言是C/C++,Java,Python。
1.操作系统层面:
a.时间资源的限制。
内存:我使用了rlimit进行控制,同时也是方便在运行结束后获得内存使用情况的数据,不过有一个缺点就是如果声明了超大的空间但是从未访问过就会不被统计进来,但是观察到很多ACM或者OI比赛也都是这样处理的,所有这也不算是一个问题。
时间:首先同样也是使用rlimit进行CPU时间控制,注意它只能控制CPU时间,不能控制实际运行时间,所以像是sleep或者IO阻塞之类的情况是没有办法的,所以还在额外添加了一个alarm来进行实际的限制。按照大部分比赛的管理,最终统计的时间是 CPU 时间。
文件句柄:同样可以通过 rlimit 来实现,以保证程序不要打开太多文件。不过其实文件这一块问题是比较多的,如果可行的话最好还是使用 stdio 然后管道重定向,完全禁止程序的文件 IO 操作。
b.访问控制:
利用低权限用户nobody ,将程序限制在指定目录中运行。由于是比赛程序,使用的动态链接库很有限,所以直接静态编译,从而使得运行目录中连 .so 都不需要。
进行必要的权限控制,例如将输入文件和程序文件本身设置为程序的运行用户只读不可写。
c.权限控制:
监控程序使用 root 权限运行, 完成必要准备后 fork 并切换为受限用户(比如 nobody )来运行程序。
rlimit 设置的都是 hard limit ,非 root 无法修改。
正确设置运行用户之后,nobody 受限用户是无法逃出的。
d.系统调用控制:
上面这些(尤其是第一步)是有很大问题,就算不是 root ,也还能做到很多事情。且不说 fork 之类的,光是那个
alarm ,就可以很轻松的把计时器取消了或者干脆主动接收这个信号。所以最根本的还是需要使用 ptrace
之类的调试器附着上程序,监控所有的系统调用,进行白名单 + 计数器(比如 exec 和 open
)过滤。这一步其实是最麻烦的(不同平台的系统调用号不一样,我们使用的是 strace 项目里头整理的调用号)。
e.更进一步:
如果你对操作系统更熟悉,那么还有一些更有趣的事情可以做。比如 Linux 下的 seccomp 功能(seccomp - Wikipedia , Chrome Linux 版就在沙盒中使用了这个技术 ),尤其是后期加入了 seccomp-bpf 之后变得更加易用。还比如 SELinux 也可以作为 defend-by-depth 的一环。另外, cgroup 其实也可以用得上。
2.编译层面:
a.很多编译工具都提供了强大的参数控制,允许你进行包括禁用内嵌 ASM 、限制连接路径之类的一些操作。通读一遍 manpage 肯定会有帮助的。
b.算法竞赛的程序推荐静态编译,之后控制起来少了动态链接库会方便许多。
c.小 心编译期间的一些“高级功能”,比如 C 的 include 其实是有很多巧妙的用法,试试看在 Linux 下 #include "/dev/random" 或者 #include "/dev/tty" 之类的(这两个东西会把网络上不少二流 OJ 直接卡死……)。
d.不要使用 root 用户编译,越复杂的程序越容易有 bug ,万一哪天出个编译器的 0day ……
e.考虑给编译过程同样进行时间、资源限制以作为额外防护手段。
3.架构层面:
a.运行在虚拟机/容器中
b.快照
c.心跳检测
标签:
原文地址:http://www.cnblogs.com/hsp-77-abc/p/hsp-1615433864_OJ_001.html