seccomp实现安全判题沙箱
需求分析
软件判题器需要一下几点
- 保证判题的正确性
- 能够并发进行判题
- 能够保证判题过程的安全性
- 能够限制判题过程的资源
- 能够根据编程语言进行动态定制
- 能够返回判题过程中使用的资源大小
需要的参数
入参 | 类型 | 描述 | 限制 |
---|---|---|---|
bin | string | 可执行文件 | 无限制 |
output-file | string | 输出重定向文件 | 无限制 |
input-file | string | 输入重定向文件 | 无限制 |
error-file | string | 错误重定向文件 | 无限制 |
num-thread | int | 最大可执行线程 | 不超过cpu核心数 |
max-memory | int | 最大内存使用(Byte) | 不超过系统内存的1/16 |
max-stack-size | int | 最大堆栈大小 | 无限制 |
max-output-size | int | 最大输出文件大小(KB) | 一般不超过100MB |
max-cpu-time | int | 最大CPU时间占用(ms) | 无限制 |
max-real-time | int | 包含系统调度的时间占用(ms) | 无限制 |
arguments | vector | 可执行文件的运行参数 | 长度<=255 |
env | vector | 执行程序时的环境变量 | 长度<=255 |
输出的结果
直接返回值结果
出参 | 类型 | 描述 |
---|---|---|
memory | int | 占用的内存(Byte) |
cpu-time | int | 占用的cpu时间(ms) |
real-time | int | 占用的真实时间(ms) |
output-file | string | 程序的输出结果 |
error-file | string | 出错的错误文件结果 |
exit-code | int | 程序执行结果的状态码 |
signal | int | 沙箱执行结果状态码 |
判题器实现
前期准备
# 安装seccomp开发环境支持
sudo apt-get install libseccomp-dev
创建限制与返回结果
按照判题器的要求创建资源限制,返回结果以及判题器的配置文件
判题的资源限制
// limit.h
struct Limit {
int max_real_time;
int max_cpu_time;
int max_memory;
int max_stack_size;
int max_output_size;
int num_thread;
};
判题最后返回的结果文件
// result.h
struct Result {
int cpu_time;
int real_time;
long memory;
int signal;
int exit_code;
};
判题器的配置文件
struct Config {
Limit limit;
std::string input_file;
std::string output_file;
std::string error_file;
};
创建判题器核心
rlimit
linux下对资源进行限制的函数是位于**<sys/resource>下的getrlimit和setrlimit**
#include <sys/resource.h>
int getrlimit(int resource, struct rlimit *rlptr);
int setrlimit(int resource, const struct rlimit *rlptr);
系统的资源由最初的0号进程获取,后续所有的子进程继承父进程的资源限制
每个子进程也可以调用setrlimit改变资源限制
rlimit的结构
struct rlimit {
rlim_t rlim_cur; //current limit
rlim_t rlim_max; //max limit value for "rlim_cur"
};
对应的resource参数可选如下:
RLIMIT_AS/RLIMIT_VMEM: 这两个资源表示的是同一个含义,都是只address space限制,可用内存用户地址空间最大长度,会影响到sbrk和mmap函数。
RLIMIT_STACK:栈的长度,默认一般是8K
RLIMIT_CORE:程序crash后生成的core dump文件的大小,如果为0将不生成对应的core文件。
RLIMIT_NOFILE:进程能够打开的最多文件数目,此限制会影响到sysconf的_SC_OPEN_MAX的返回值。
RLIMIT_NPROC:每个用户ID能够拥有的最大子进程数目,此限制会影响到sysconf的_SC_CHILD_MAX的返回值。
RLIMIT_NICE:对应进程的优先级nice值。
RLIMIT_SWAP:进程能够消耗的最大swap空间。
RLIMIT_CPU:CPU时间的最大值(秒单位),超过此限制后会发送SIGXCPU信号给进程。
RLIMIT_DATA:数据段的最大长度。默认为unlimited
RLIMIT_FSIZE:创建文件的最大字节长度。默认为ulimited
RLIMIT_MSGQUEUE:为posix消息队列可分配的最大存储字节数
RLIMIT_SIGPENDING:可排队的信号最大数量
RLIMIT_NPTS:可同时打开的伪终端数目
RLIMIT_RSS:最大可驻内存字节长度
RLIMIT_SBSIZE:单个用户所有套接字缓冲区的最大长度
RLIMIT_MEMLOCK:一个进程使用mlock能够锁定存储空间中的最大字节长度
如果设置resource的值为 RLIM_INFINITY 表示无限制
exit code
返回的状态码是0表示执行成功
对于完整的fork流程之后,会有一个退出的状态码,默认成功的状态码是0.其他的都是一些错误的状态码
在所有的状态码中1, 2, 126 – 165 和 255 是系统定义的有意义的状态码,用户定义的时候应当避免这些状态码
exit的函数段
void exit(int return_code)
如果执行exit()中的return_code大于255时,会对255取模
如果return_code是负数,返回值会做运算65536 + return_code * 256
一些特殊的常见的系统定义的状态码
- exit(1): It indicates abnormal termination of a program perhaps as a result a minor problem in the code.
- exit(2): It is similar to exit(1) but is displayed when the error occurred is a major one. This statement is rarely seen.
- exit(127): It indicates command not found.
- exit(132): It indicates that a program was aborted (received SIGILL), perhaps as a result of illegal instruction or that the binary is probably corrupt.
- exit(133): It indicates that a program was aborted (received SIGTRAP), perhaps as a result of dividing an integer by zero.
- exit(134): It indicates that a program was aborted (received SIGABRT), perhaps as a result of a failed assertion.
- exit(136): It indicates that a program was aborted (received SIGFPE), perhaps as a result of floating point exception or integer overflow.
- exit(137): It indicates that a program took up too much memory.
- exit(138): It indicates that a program was aborted (received SIGBUS), perhaps as a result of unaligned memory access.
- exit(139): It indicates Segmentation Fault which means that the program was trying to access a memory location not allocated to it. This mostly occurs while using pointers or trying to access an out-of-bounds array index.
- exit(158/152): It indicates that a program was aborted (received SIGXCPU), perhaps as a result of CPU time limit exceeded.
- exit(159/153): It indicates that a program was aborted (received SIGXFSZ), perhaps as a result of File size limit exceeded.
rule
对于seccomp的规则主要有两类
- 以c语言为代表的,无虚拟机运行的语言
- 以Java为代表的有虚拟机运行的语言
有虚拟机参与的语言,可以交由虚拟机来保证一部分的安全性
对于判题器核心有一点,对于不同的语言应当有不同的限制,下面列举了需要运行最少需要的权限
语言 | 权限 | 备注 |
---|---|---|
C/C++ | 见下 | 没有虚拟机,权限不需太多 |
JAVA | 见下 | 虚拟机运行必须需要的权限 |
GO | 同上JAVA | 虚拟机运行必须需要的权限 |
//大部分编程题运行需要的最小权限
int sys_call_whitelist[] = {SCMP_SYS(read), SCMP_SYS(fstat),
SCMP_SYS(mmap), SCMP_SYS(mprotect),
SCMP_SYS(munmap), SCMP_SYS(uname),
SCMP_SYS(arch_prctl), SCMP_SYS(brk),
SCMP_SYS(access), SCMP_SYS(exit_group),
SCMP_SYS(close), SCMP_SYS(readlink),
SCMP_SYS(sysinfo), SCMP_SYS(write),
SCMP_SYS(writev), SCMP_SYS(lseek),
SCMP_SYS(clock_gettime), SCMP_SYS(pread64)};
//JVM运行所需要的最小权限
int sys_call_whitelist[] = { SCMP_SYS(access),
SCMP_SYS(arch_prctl),
SCMP_SYS(brk),
SCMP_SYS(clock_getres),
SCMP_SYS(clone),
SCMP_SYS(close),
SCMP_SYS(connect),
SCMP_SYS(execve),
SCMP_SYS(exit_group),
SCMP_SYS(fchdir),
SCMP_SYS(fcntl),
SCMP_SYS(fstat),
SCMP_SYS(ftruncate),
SCMP_SYS(futex),
SCMP_SYS(getcwd),
SCMP_SYS(getdents),
SCMP_SYS(geteuid),
SCMP_SYS(getpid),
SCMP_SYS(gettid),
SCMP_SYS(getuid),
SCMP_SYS(kexec_load),
SCMP_SYS(kill),
SCMP_SYS(lseek),
SCMP_SYS(lstat),
SCMP_SYS(mkdir),
SCMP_SYS(mmap),
SCMP_SYS(mprotect),
SCMP_SYS(munmap),
SCMP_SYS(openat),
SCMP_SYS(prctl),
SCMP_SYS(pread64),
SCMP_SYS(prlimit64),
SCMP_SYS(pselect6),
SCMP_SYS(read),
SCMP_SYS(readlink),
SCMP_SYS(rt_sigaction),
SCMP_SYS(rt_sigprocmask),
SCMP_SYS(rt_sigreturn),
SCMP_SYS(sched_getaffinity),
SCMP_SYS(sched_yield),
SCMP_SYS(set_robust_list),
SCMP_SYS(set_tid_address),
SCMP_SYS(socket),
SCMP_SYS(stat),
SCMP_SYS(sysinfo),
SCMP_SYS(uname),
SCMP_SYS(unlink),
SCMP_SYS(write)};