如何设计一个安全沙盒
在 OJ 中,沙盒是必不可少的一环,服务器无法相信用户提交的代码,这可能导致很多安全问题。
总体上来说,沙盒需要做到两点:
- 对系统调用进行限制
- 对资源使用进行限制
系统调用限制
一般来说,系统调用的拦截有两种办法:
ptrace(2)
seccomp(2)
ptrace
这是一种开销较大的方法,拦截tracee
的系统调用的开始
和结束
,两次切换用户态和内核态。
GDB 的基本调试功能就是这么实现的,不过对于一个追求执行速度的沙盒来说,这个方法不可取。
seccomp
Secure computing,自 Kernel 2.6.12 引入,这是一种内核态下的过滤机制,最初限制程序只能用四个系统调用,用于运行低权限应用
1 | read,write,_exit,sigreturn |
要对所有系统调用进行拦截,要用到的是是Seccomp-BPF
,自 Kernel 3.5 引入,BPF最初是一种网络封包过滤代理,此后被广泛运用,包括这里。
由于其本质上是运行在内核的一个虚拟机,所以需要编写字节码,略微麻烦,所以有libseccomp去帮助人们方便快捷地编写规则
下面是使用libseccomp
的一个例子,实现了对execve
的禁用
1 |
|
接下来介绍一些对特定语言的设置
C/CPP
对于 C 和 CPP 这种较为底层的语言,通常使用白名单,对权限进行严格管控
Java/Python
对于带有虚拟机/解释器的语言,通常使用黑名单模式,避免出现错杀情况
一些特殊情况
有时候需要根据沙盒实现进行特殊限制,比如沙盒使用setitimer
时,alarm(0);
可以直接取消设定好的计时器,从而破坏沙盒功能
系统资源限制
时间
程序的时间可以分为两部分:
- CPU 时间
- 实际运行时间
由于操作系统的调度,程序主动sleep
,阻塞 I/O 等情况,程序的实际运行时间要大于 CPU 时间
setrlimit
这个调用可以实现对多种资源的限制,对于时间,它只能限制 CPU 时间
RLIMIT_CPU
这个只能精确到秒,可以用但是不建议
RLIMIT_RTTIME
可以精确到微妙,但是只能在特定的调度模式下使用,比较复杂,未使用
setitimer
此调用可以限制 CPU 时间和实际时间
ITIMER_REAL
decrements in real time, and delivers SIGALRM upon expiration.
ITIMER_VIRTUAL
decrements only when the process is executing, and delivers SIGVTALRM upon expiration.
ITIMER_PROF
decrements both when the process executes and when the system is executing on behalf of the process. Coupled with ITIMER_VIRTUAL, this timer is usually used to profile the time spent by the application in user and kernel space. SIGPROF is delivered upon expiration.
计算 CPU 时间时,一般不算入内核态时间,所以使用ITIMER_VIRTUAL
内存
这是一个很麻烦的地方,setrlimit
给了两个相关资源
RLIMIT_AS
虚拟内存大小
RLIMIT_RSS
Resident Set Size,常驻内存大小
一般来说,只能限制虚拟内存大小,RSS 不能作为可靠观测指标
一般情况下进程虚拟内存的大小都明显大于实际使用的物理内存,所以需要在限制时给出一个大于目标值的参数,如两倍大小的给定内存数
What is RSS and VSZ in Linux memory management - Stack Overflow
栈内存
使用setrlimit
,RLIMIT_STACK
可以这样限制,但是需要特别的方式进行判断,详见 man page
输出大小
这里是指The maximum size of files that the process may create.
所以,这里用来限制重定向 STDOUT 到文件后的输出
使用setrlimit
,RLIMIT_NPROC
线程数量
使用setrlimit
,RLIMIT_NPROC
其他一些细节
Guest 环境变量
由于沙盒是通过execve
实现的,所以给 Guest 传递什么变量就需要考虑一下
环境变量可以通过编译器提供的外部变量获取
1 |
|
通过在力扣等多个网站测试,只提供最基本的PATH
应该是 OK 的
1 | PATH=/usr/local/bin:/usr/bin:/bin |
Guest 的 argv[0]
根据标准,它应该是一个 可以代表程序的名字
所以这里完全可以自定义为guest