mirror of
https://github.com/QingdaoU/Judger.git
synced 2025-01-16 01:13:25 +00:00
296 lines
11 KiB
C
296 lines
11 KiB
C
#define _GNU_SOURCE
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#ifndef __APPLE__
|
|
#include <seccomp.h>
|
|
#else
|
|
#warning "###### This judger can not work under OSX, installation is only for dev dependencies! #####"
|
|
#endif
|
|
#include <signal.h>
|
|
#include <errno.h>
|
|
#include <pwd.h>
|
|
#include <sched.h>
|
|
#include <grp.h>
|
|
#include <sys/time.h>
|
|
#include <sys/resource.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/types.h>
|
|
|
|
#include "runner.h"
|
|
#include "logger.h"
|
|
|
|
#define STACK_SIZE (2 * 1024 * 1024)
|
|
|
|
|
|
int set_timer(int sec, int ms, int is_cpu_time) {
|
|
struct itimerval time_val;
|
|
time_val.it_interval.tv_sec = time_val.it_interval.tv_usec = 0;
|
|
time_val.it_value.tv_sec = sec;
|
|
time_val.it_value.tv_usec = ms * 1000;
|
|
if (setitimer(is_cpu_time ? ITIMER_VIRTUAL : ITIMER_REAL, &time_val, NULL)) {
|
|
return SETITIMER_FAILED;
|
|
}
|
|
return SUCCESS;
|
|
}
|
|
|
|
|
|
int child_process(void *clone_args){
|
|
FILE *log_fp = ((struct clone_args *)clone_args)->log_fp;
|
|
struct config *config = ((struct clone_args *)clone_args)->config;
|
|
FILE *in_file = NULL, *out_file = NULL, *err_file = NULL;
|
|
struct rlimit memory_limit, cpu_time_rlimit;
|
|
gid_t group_list[] = {config->gid};
|
|
#ifndef __APPLE__
|
|
int i;
|
|
int syscalls_whitelist[] = {SCMP_SYS(read), SCMP_SYS(fstat),
|
|
SCMP_SYS(mmap), SCMP_SYS(mprotect),
|
|
SCMP_SYS(munmap), SCMP_SYS(open),
|
|
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(lseek)};
|
|
int syscalls_whitelist_length = sizeof(syscalls_whitelist) / sizeof(int);
|
|
scmp_filter_ctx ctx = NULL;
|
|
#endif
|
|
// child process
|
|
// On success, these system calls return 0.
|
|
// On error, -1 is returned, and errno is set appropriately.
|
|
if (config->max_memory != MEMORY_UNLIMITED) {
|
|
memory_limit.rlim_cur = memory_limit.rlim_max = (rlim_t) (config->max_memory) * 2;
|
|
if (setrlimit(RLIMIT_AS, &memory_limit) == -1) {
|
|
LOG_FATAL(log_fp, "setrlimit memory failed, errno: %d", errno);
|
|
ERROR(log_fp, SETRLIMIT_FAILED);
|
|
}
|
|
}
|
|
if (config->max_cpu_time != CPU_TIME_UNLIMITED) {
|
|
// cpu time
|
|
if (set_timer(config->max_cpu_time / 1000, config->max_cpu_time % 1000, 1) != SUCCESS) {
|
|
LOG_FATAL(log_fp, "set cpu time timer failed");
|
|
ERROR(log_fp, SETITIMER_FAILED);
|
|
}
|
|
// real time
|
|
if (set_timer(config->max_real_time / 1000, config->max_real_time % 1000, 0) != SUCCESS) {
|
|
LOG_FATAL(log_fp, "set real time timer failed");
|
|
ERROR(log_fp, SETITIMER_FAILED);
|
|
}
|
|
|
|
// child process can not inherit timeout rules from parent process defined by setitimer, so we use setrlimit to
|
|
// control child process max running time
|
|
cpu_time_rlimit.rlim_cur = cpu_time_rlimit.rlim_max = (config->max_cpu_time + 1000) / 1000;
|
|
if (setrlimit(RLIMIT_CPU, &cpu_time_rlimit) == -1) {
|
|
LOG_FATAL(log_fp, "setrlimit cpu time failed, errno: %d", errno);
|
|
ERROR(log_fp, SETRLIMIT_FAILED);
|
|
}
|
|
}
|
|
|
|
// read stdin from in file
|
|
// On success, these system calls return the new descriptor.
|
|
// On error, -1 is returned, and errno is set appropriately.
|
|
if (config->in_file != NULL) {
|
|
if ((in_file = fopen(config->in_file, "r")) == NULL) {
|
|
LOG_FATAL(log_fp, "failed to open stdin redirect file");
|
|
ERROR(log_fp, DUP2_FAILED);
|
|
}
|
|
if (dup2(fileno(in_file), fileno(stdin)) == -1) {
|
|
LOG_FATAL(log_fp, "dup2 stdin failed, errno: %d", errno);
|
|
ERROR(log_fp, DUP2_FAILED);
|
|
}
|
|
}
|
|
// write stdout to out file
|
|
if (config->out_file != NULL) {
|
|
if ((out_file = fopen(config->out_file, "w")) == NULL) {
|
|
LOG_FATAL(log_fp, "failed to open stdout redirect file");
|
|
ERROR(log_fp, DUP2_FAILED);
|
|
}
|
|
if (dup2(fileno(out_file), fileno(stdout)) == -1) {
|
|
LOG_FATAL(log_fp, "dup2 stdout failed, errno: %d", errno);
|
|
ERROR(log_fp, DUP2_FAILED);
|
|
}
|
|
}
|
|
// write stderr to err file
|
|
if (config->err_file != NULL) {
|
|
// if err_file and out_file are the same path, we use out_file pointer as err_file pointer, to avoid conflict
|
|
if (strcmp(config->out_file, config->err_file) == 0) {
|
|
err_file = out_file;
|
|
}
|
|
else {
|
|
if ((err_file = fopen(config->err_file, "w")) == NULL) {
|
|
LOG_FATAL(log_fp, "failed to open stderr redirect file");
|
|
ERROR(log_fp, DUP2_FAILED);
|
|
}
|
|
}
|
|
if (dup2(fileno(err_file), fileno(stderr)) == -1) {
|
|
LOG_FATAL(log_fp, "dup2 stdout failed, errno: %d", errno);
|
|
ERROR(log_fp, DUP2_FAILED);
|
|
}
|
|
}
|
|
if (config->gid != -1 && (setgid(config->gid) == -1 || setgroups(sizeof(group_list) / sizeof(gid_t), group_list) == -1)) {
|
|
LOG_FATAL(log_fp, "setgid failed, errno: %d", errno);
|
|
ERROR(log_fp, SET_GID_FAILED);
|
|
}
|
|
if (config->uid != -1 && setuid(config->uid) == -1) {
|
|
LOG_FATAL(log_fp, "setuid failed, errno: %d", errno);
|
|
ERROR(log_fp, SET_UID_FAILED);
|
|
}
|
|
#ifndef __APPLE__
|
|
if (config->use_sandbox != 0) {
|
|
// load seccomp rules
|
|
ctx = seccomp_init(SCMP_ACT_KILL);
|
|
if (!ctx) {
|
|
LOG_FATAL(log_fp, "init seccomp failed");
|
|
ERROR(log_fp, LOAD_SECCOMP_FAILED);
|
|
}
|
|
for (i = 0; i < syscalls_whitelist_length; i++) {
|
|
if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, syscalls_whitelist[i], 0) != 0) {
|
|
LOG_FATAL(log_fp, "load syscall white list failed");
|
|
ERROR(log_fp, LOAD_SECCOMP_FAILED);
|
|
}
|
|
}
|
|
// add extra rule for execve
|
|
if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(execve), 1, SCMP_A0(SCMP_CMP_EQ, (scmp_datum_t)(config->path))) != 0) {
|
|
LOG_FATAL(log_fp, "load execve rule failed");
|
|
ERROR(log_fp, LOAD_SECCOMP_FAILED);
|
|
}
|
|
// only fd 0 1 2 are allowed
|
|
if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 1, SCMP_A0(SCMP_CMP_LE, 2)) != 0) {
|
|
LOG_FATAL(log_fp, "load dup2 rule failed");
|
|
ERROR(log_fp, LOAD_SECCOMP_FAILED);
|
|
}
|
|
if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(writev), 1, SCMP_A0(SCMP_CMP_LE, 2)) != 0) {
|
|
LOG_FATAL(log_fp, "load dup2 rule failed");
|
|
ERROR(log_fp, LOAD_SECCOMP_FAILED);
|
|
}
|
|
if (seccomp_load(ctx) != 0) {
|
|
LOG_FATAL(log_fp, "seccomp load failed");
|
|
ERROR(log_fp, LOAD_SECCOMP_FAILED);
|
|
}
|
|
seccomp_release(ctx);
|
|
}
|
|
#endif
|
|
execve(config->path, config->args, config->env);
|
|
LOG_FATAL(log_fp, "execve failed, errno: %d", errno);
|
|
ERROR(log_fp, EXCEVE_FAILED);
|
|
return 1;
|
|
}
|
|
|
|
|
|
void run(struct config *config, struct result *result) {
|
|
int status;
|
|
struct rusage resource_usage;
|
|
struct timeval start, end;
|
|
|
|
int signal, pid;
|
|
FILE *log_fp = NULL;
|
|
char *stack = NULL;
|
|
struct clone_args clone_args;
|
|
|
|
log_fp = log_open(config->log_path);
|
|
if(log_fp == NULL){
|
|
result->flag = SYSTEM_ERROR;
|
|
return;
|
|
}
|
|
|
|
gettimeofday(&start, NULL);
|
|
|
|
if(config->max_memory < 1 && config->max_memory != MEMORY_UNLIMITED) {
|
|
LOG_FATAL(log_fp, "max_memory must > 1 or unlimited");
|
|
result->flag = SYSTEM_ERROR;
|
|
log_close(log_fp);
|
|
return;
|
|
}
|
|
if(config->max_cpu_time < 1 && config->max_cpu_time != CPU_TIME_UNLIMITED) {
|
|
LOG_FATAL(log_fp, "max_cpu_time must > 1 or unlimited");
|
|
result->flag = SYSTEM_ERROR;
|
|
log_close(log_fp);
|
|
return;
|
|
}
|
|
if((stack = malloc(STACK_SIZE)) == NULL) {
|
|
LOG_FATAL(log_fp, "malloc stack failed");
|
|
result->flag = SYSTEM_ERROR;
|
|
log_close(log_fp);
|
|
return;
|
|
}
|
|
|
|
clone_args.config = config;
|
|
clone_args.log_fp = log_fp;
|
|
pid = clone(child_process, stack + STACK_SIZE, SIGCHLD, (void *)(&clone_args));
|
|
|
|
if (pid < 0) {
|
|
LOG_FATAL(log_fp, "fork failed, errno: %d", errno);
|
|
result->flag = SYSTEM_ERROR;
|
|
log_close(log_fp);
|
|
return;
|
|
}
|
|
else {
|
|
// parent process
|
|
|
|
// on success, returns the process ID of the child whose state has changed;
|
|
// On error, -1 is returned.
|
|
if (wait4(pid, &status, 0, &resource_usage) == -1) {
|
|
LOG_FATAL(log_fp, "wait4 failed");
|
|
result->flag = SYSTEM_ERROR;
|
|
log_close(log_fp);
|
|
return;
|
|
}
|
|
LOG_DEBUG(log_fp, "exit status: %d", WEXITSTATUS(status));
|
|
result->exit_status = WEXITSTATUS(status);
|
|
result->cpu_time = (int) (resource_usage.ru_utime.tv_sec * 1000 +
|
|
resource_usage.ru_utime.tv_usec / 1000 +
|
|
resource_usage.ru_stime.tv_sec * 1000 +
|
|
resource_usage.ru_stime.tv_usec / 1000);
|
|
// avoid 0 ms
|
|
if(result->cpu_time == 0) {
|
|
result->cpu_time = 1;
|
|
}
|
|
|
|
// osx: ru_maxrss the maximum resident set size utilized (in bytes).
|
|
// linux: ru_maxrss (since Linux 2.6.32)This is the maximum resident set size used (in kilobytes).
|
|
// For RUSAGE_CHILDREN, this is the resident set size of the largest child,
|
|
// not the maximum resident set size of the processtree.
|
|
result->memory = resource_usage.ru_maxrss * 1024;
|
|
|
|
result->signal = 0;
|
|
result->flag = SUCCESS;
|
|
|
|
if (WIFSIGNALED(status) != 0) {
|
|
signal = WTERMSIG(status);
|
|
LOG_DEBUG(log_fp, "signal: %d", signal);
|
|
result->signal = signal;
|
|
if (signal == SIGALRM) {
|
|
result->flag = REAL_TIME_LIMIT_EXCEEDED;
|
|
}
|
|
else if (signal == SIGVTALRM) {
|
|
result->flag = CPU_TIME_LIMIT_EXCEEDED;
|
|
}
|
|
else if (signal == SIGSEGV) {
|
|
if (config->max_memory != MEMORY_UNLIMITED && result->memory > config->max_memory) {
|
|
result->flag = MEMORY_LIMIT_EXCEEDED;
|
|
}
|
|
else {
|
|
result->flag = RUNTIME_ERROR;
|
|
}
|
|
}
|
|
// Child process error
|
|
else if (signal == SIGUSR1){
|
|
result->flag = SYSTEM_ERROR;
|
|
}
|
|
else {
|
|
result->flag = RUNTIME_ERROR;
|
|
}
|
|
}
|
|
else {
|
|
if (config->max_memory != MEMORY_UNLIMITED && result->memory > config->max_memory) {
|
|
result->flag = MEMORY_LIMIT_EXCEEDED;
|
|
}
|
|
if (WEXITSTATUS(status) != 0) {
|
|
result->flag = RUNTIME_ERROR;
|
|
}
|
|
}
|
|
gettimeofday(&end, NULL);
|
|
result->real_time = (int) (end.tv_sec * 1000 + end.tv_usec / 1000 - start.tv_sec * 1000 - start.tv_usec / 1000);
|
|
log_close(log_fp);
|
|
}
|
|
}
|