Show More
Commit Description:
[grader] import script now support raw with testruns...
Commit Description:
[grader] import script now support raw with testruns git-svn-id: http://theory.cpe.ku.ac.th/grader/judge/trunk/scripts@267 6386c4cd-e34a-4fa8-8920-d93eb39b512e
File last commit:
Show/Diff file:
Action:
std-script/box.cc | 684 lines | 15.5 KiB | text/x-c | CppLexer |
/*
* A Simple Testing Sandbox
*
* (c) 2001--2004 Martin Mares <mj@ucw.cz>
*/
#define _LARGEFILE64_SOURCE
//#define _GNU_SOURCE
#include <errno.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <getopt.h>
#include <time.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <sys/time.h>
#include <sys/ptrace.h>
#include <sys/signal.h>
#include <sys/sysinfo.h>
#include <sys/syscall.h>
#include <sys/resource.h>
#define NONRET __attribute__((noreturn))
#define UNUSED __attribute__((unused))
static int filter_syscalls; /* 0=off, 1=liberal, 2=totalitarian */
static double timeout;
static int pass_environ;
static int use_wall_clock;
static int file_access;
static int verbose;
static int memory_limit;
static int allow_times;
static char *redir_stdin, *redir_stdout;
static char *set_cwd;
static pid_t box_pid;
static int is_ptraced;
static volatile int timer_tick;
static time_t start_time;
static int ticks_per_sec;
static int page_size;
#if defined(__GLIBC__) && __GLIBC__ == 2 && __GLIBC_MINOR__ > 0
/* glibc 2.1 or newer -> has lseek64 */
#define long_seek(f,o,w) lseek64(f,o,w)
#else
/* Touching clandestine places in glibc */
extern loff_t llseek(int fd, loff_t pos, int whence);
#define long_seek(f,o,w) llseek(f,o,w)
#endif
int max_mem_used = 0;
void print_running_stat(double wall_time,
double user_time,
double system_time,
int mem_usage)
{
fprintf(stderr,"%.4lfr%.4lfu%.4lfs%dm\n",
wall_time, user_time, system_time, mem_usage);
}
static void NONRET
box_exit(void)
{
if (box_pid > 0) {
if (is_ptraced)
ptrace(PTRACE_KILL, box_pid);
kill(-box_pid, SIGKILL);
kill(box_pid, SIGKILL);
}
struct timeval total;
int wall;
struct rusage rus;
int stat;
pid_t p;
// wait so that we can get information
p = wait4(box_pid, &stat, WUNTRACED, &rus);
if (p < 0) {
fprintf(stderr,"wait4: error\n");
print_running_stat(0,0,0,max_mem_used);
} else if (p != box_pid) {
fprintf(stderr,"wait4: unknown pid %d exited!\n", p);
print_running_stat(0,0,0,max_mem_used);
} else {
if (!WIFEXITED(stat))
fprintf(stderr,"wait4: unknown status\n");
struct timeval total;
int wall;
wall = time(NULL) - start_time;
timeradd(&rus.ru_utime, &rus.ru_stime, &total);
print_running_stat((double)wall,
(double) rus.ru_utime.tv_sec +
((double) rus.ru_utime.tv_usec/1000000.0),
(double) rus.ru_stime.tv_sec +
((double) rus.ru_stime.tv_usec/1000000.0),
max_mem_used);
}
exit(1);
}
static void NONRET __attribute__((format(printf,1,2)))
die(char *msg, ...)
{
va_list args;
va_start(args, msg);
vfprintf(stderr, msg, args);
fputc('\n', stderr);
box_exit();
}
static void __attribute__((format(printf,1,2)))
log(char *msg, ...)
{
va_list args;
va_start(args, msg);
if (verbose)
{
vfprintf(stderr, msg, args);
fflush(stderr);
}
va_end(args);
}
static void
valid_filename(unsigned long addr)
{
char namebuf[4096], *p, *end;
static int mem_fd;
if (!file_access)
die("File access forbidden.");
if (file_access >= 9)
return;
if (!mem_fd)
{
sprintf(namebuf, "/proc/%d/mem", (int) box_pid);
mem_fd = open(namebuf, O_RDONLY);
if (mem_fd < 0)
die("open(%s): %m", namebuf);
}
p = end = namebuf;
do
{
if (p >= end)
{
int remains = PAGE_SIZE - (addr & (PAGE_SIZE-1));
int l = namebuf + sizeof(namebuf) - end;
if (l > remains)
l = remains;
if (!l)
die("Access to file with name too long.");
if (long_seek(mem_fd, addr, SEEK_SET) < 0)
die("long_seek(mem): %m");
remains = read(mem_fd, end, l);
if (remains < 0)
die("read(mem): %m");
if (!remains)
die("Access to file with name out of memory.");
end += l;
addr += l;
}
}
while (*p++);
log("[%s] ", namebuf);
if (file_access >= 3)
return;
if (!strchr(namebuf, '/') && strcmp(namebuf, ".."))
return;
if (file_access >= 2)
{
if ((!strncmp(namebuf, "/etc/", 5) ||
!strncmp(namebuf, "/lib/", 5) ||
!strncmp(namebuf, "/usr/lib/", 9))
&& !strstr(namebuf, ".."))
return;
if (!strcmp(namebuf, "/dev/null") ||
!strcmp(namebuf, "/dev/zero") ||
!strcmp(namebuf, "/proc/meminfo") ||
!strcmp(namebuf, "/proc/self/stat") ||
!strncmp(namebuf, "/usr/share/zoneinfo/", 20))
return;
}
die("Forbidden access to file `%s'.", namebuf);
}
static int
valid_syscall(struct user *u)
{
switch (u->regs.orig_eax)
{
case __NR_execve:
{
static int exec_counter;
return !exec_counter++;
}
case __NR_open:
case __NR_creat:
case __NR_unlink:
case __NR_oldstat:
case __NR_access:
case __NR_oldlstat:
case __NR_truncate:
case __NR_stat:
case __NR_lstat:
case __NR_truncate64:
case __NR_stat64:
case __NR_lstat64:
valid_filename(u->regs.ebx);
return 1;
case __NR_exit:
case __NR_read:
case __NR_write:
case __NR_close:
case __NR_lseek:
case __NR_getpid:
case __NR_getuid:
case __NR_oldfstat:
case __NR_dup:
case __NR_brk:
case __NR_getgid:
case __NR_geteuid:
case __NR_getegid:
case __NR_dup2:
case __NR_ftruncate:
case __NR_fstat:
case __NR_personality:
case __NR__llseek:
case __NR_readv:
case __NR_writev:
case __NR_getresuid:
#ifdef __NR_pread64
case __NR_pread64:
case __NR_pwrite64:
#else
case __NR_pread:
case __NR_pwrite:
#endif
case __NR_ftruncate64:
case __NR_fstat64:
case __NR_fcntl:
case __NR_fcntl64:
case __NR_mmap:
case __NR_munmap:
case __NR_ioctl:
case __NR_uname:
case 252:
case 243:
// added for free pascal
case __NR_ugetrlimit:
case __NR_readlink:
return 1;
// case __NR_time:
case __NR_alarm:
// case __NR_pause:
case __NR_signal:
case __NR_fchmod:
case __NR_sigaction:
case __NR_sgetmask:
case __NR_ssetmask:
case __NR_sigsuspend:
case __NR_sigpending:
case __NR_getrlimit:
case __NR_getrusage:
case __NR_gettimeofday:
case __NR_select:
case __NR_readdir:
case __NR_setitimer:
case __NR_getitimer:
case __NR_sigreturn:
case __NR_mprotect:
case __NR_sigprocmask:
case __NR_getdents:
case __NR_getdents64:
case __NR__newselect:
case __NR_fdatasync:
case __NR_mremap:
case __NR_poll:
case __NR_getcwd:
case __NR_nanosleep:
case __NR_rt_sigreturn:
case __NR_rt_sigaction:
case __NR_rt_sigprocmask:
case __NR_rt_sigpending:
case __NR_rt_sigtimedwait:
case __NR_rt_sigqueueinfo:
case __NR_rt_sigsuspend:
case __NR_mmap2:
case __NR__sysctl:
return (filter_syscalls == 1);
case __NR_times:
case __NR_time:
return allow_times;
case __NR_kill:
if (u->regs.ebx == box_pid)
die("Commited suicide by signal %d.", (int)u->regs.ecx);
return 0;
default:
return 0;
}
}
static void
signal_alarm(int unused UNUSED)
{
/* Time limit checks are synchronous, so we only schedule them there. */
timer_tick = 1;
//NOTE: do not use alarm, changed to setitimer for precision
// alarm(1);
}
static void
signal_int(int unused UNUSED)
{
/* Interrupts are fatal, so no synchronization requirements. */
die("Interrupted.");
}
static void
check_timeout(void)
{
double sec;
if (use_wall_clock)
sec = (double)(time(NULL) - start_time);
else
{
char buf[4096], *x;
int c, utime, stime;
static int proc_status_fd;
if (!proc_status_fd)
{
sprintf(buf, "/proc/%d/stat", (int) box_pid);
proc_status_fd = open(buf, O_RDONLY);
if (proc_status_fd < 0)
die("open(%s): %m", buf);
}
lseek(proc_status_fd, 0, SEEK_SET);
if ((c = read(proc_status_fd, buf, sizeof(buf)-1)) < 0)
die("read on /proc/$pid/stat: %m");
if (c >= (int) sizeof(buf) - 1)
die("/proc/$pid/stat too long");
buf[c] = 0;
x = buf;
while (*x && *x != ' ')
x++;
while (*x == ' ')
x++;
if (*x++ != '(')
die("proc syntax error 1");
while (*x && (*x != ')' || x[1] != ' '))
x++;
while (*x == ')' || *x == ' ')
x++;
if (sscanf(x, "%*c %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %d %d", &utime, &stime) != 2)
die("proc syntax error 2");
//printf("%s - %d\n",x,ticks_per_sec);
sec = ((double)(utime + stime))/(double)ticks_per_sec;
}
if (verbose > 1)
fprintf(stderr, "[timecheck: %d seconds]\n", sec);
if (sec > timeout) {
die("Time limit exceeded.",sec,timeout);
}
}
static void
check_memory_usage()
{
char proc_fname[100];
sprintf(proc_fname,"/proc/%d/statm",box_pid);
//printf("proc fname: %s\n",proc_fname);
FILE *fp = fopen(proc_fname,"r");
if(fp!=NULL) {
char line[1000];
fgets(line,999,fp);
//printf("%s\n",line);
int m;
if(sscanf(line,"%d",&m)==1) {
m = (m*page_size+1023)/1024;
if(m>max_mem_used)
max_mem_used = m;
}
fclose(fp);
}
}
static void
boxkeeper(void)
{
int syscall_count = 0;
struct sigaction sa;
is_ptraced = 1;
bzero(&sa, sizeof(sa));
sa.sa_handler = signal_int;
sigaction(SIGINT, &sa, NULL);
start_time = time(NULL);
ticks_per_sec = sysconf(_SC_CLK_TCK);
page_size = getpagesize();
if (ticks_per_sec <= 0)
die("Invalid ticks_per_sec!");
check_memory_usage();
sa.sa_handler = signal_alarm;
sigaction(SIGALRM, &sa, NULL);
//alarm(1);
struct itimerval val;
val.it_interval.tv_sec = 0;
val.it_interval.tv_usec = 50000;
val.it_value.tv_sec = 0;
val.it_value.tv_usec = 50000;
setitimer(ITIMER_REAL,&val,NULL);
/*
--- add alarm handler no matter what..
if (timeout)
{
sa.sa_handler = signal_alarm;
sigaction(SIGALRM, &sa, NULL);
alarm(1);
}
*/
for(;;)
{
struct rusage rus;
int stat;
pid_t p;
if (timer_tick)
{
check_timeout();
check_memory_usage();
timer_tick = 0;
}
p = wait4(box_pid, &stat, WUNTRACED, &rus);
if (p < 0)
{
if (errno == EINTR)
continue;
die("wait4: %m");
}
if (p != box_pid)
die("wait4: unknown pid %d exited!", p);
if (WIFEXITED(stat))
{
struct timeval total;
int wall;
wall = time(NULL) - start_time;
timeradd(&rus.ru_utime, &rus.ru_stime, &total);
box_pid = 0;
if (WEXITSTATUS(stat))
fprintf(stderr,"Exited with error status %d.\n", WEXITSTATUS(stat));
else if ((use_wall_clock ?
wall :
(double) total.tv_sec +
((double) total.tv_usec/1000000.0)) > timeout)
fprintf(stderr,"Time limit exceeded.\n");
else
// report OK and statistics
fprintf(stderr,"OK\n");
print_running_stat((double) wall,
(double) rus.ru_utime.tv_sec +
((double) rus.ru_utime.tv_usec/1000000.0),
(double) rus.ru_stime.tv_sec +
((double) rus.ru_stime.tv_usec/1000000.0),
max_mem_used);
/*
(%.4lf sec real (%d), %d sec wall, %d syscalls, %d kb)\n",
(double) total.tv_sec + ((double)total.tv_usec / 1000000.0),
(int) total.tv_usec,
wall,
syscall_count,
max_mem_used);
*/
exit(0);
}
if (WIFSIGNALED(stat))
{
box_pid = 0;
fprintf(stderr,"Caught fatal signal %d.\n", WTERMSIG(stat));
struct timeval total;
int wall;
wall = time(NULL) - start_time;
timeradd(&rus.ru_utime, &rus.ru_stime, &total);
print_running_stat((double) wall,
(double) rus.ru_utime.tv_sec +
((double) rus.ru_utime.tv_usec/1000000.0),
(double) rus.ru_stime.tv_sec +
((double) rus.ru_stime.tv_usec/1000000.0),
max_mem_used);
exit(0);
}
if (WIFSTOPPED(stat))
{
int sig = WSTOPSIG(stat);
if (sig == SIGTRAP)
{
struct user u;
static int stop_count = -1;
if (ptrace(PTRACE_GETREGS, box_pid, NULL, &u) < 0)
die("ptrace(PTRACE_GETREGS): %m");
stop_count++;
if (!stop_count) /* Traceme request */
log(">> Traceme request caught\n");
else if (stop_count & 1) /* Syscall entry */
{
log(">> Syscall %3ld (%08lx,%08lx,%08lx) ", u.regs.orig_eax, u.regs.ebx, u.regs.ecx, u.regs.edx);
syscall_count++;
if (!valid_syscall(&u))
{
/*
* Unfortunately, PTRACE_KILL kills _after_ the syscall completes,
* so we have to change it to something harmless (e.g., an undefined
* syscall) and make the program continue.
*/
unsigned int sys = u.regs.orig_eax;
u.regs.orig_eax = 0xffffffff;
if (ptrace(PTRACE_SETREGS, box_pid, NULL, &u) < 0)
die("ptrace(PTRACE_SETREGS): %m");
die("Forbidden syscall %d.", sys);
}
}
else /* Syscall return */
log("= %ld\n", u.regs.eax);
ptrace(PTRACE_SYSCALL, box_pid, 0, 0);
}
else if (sig != SIGSTOP && sig != SIGXCPU && sig != SIGXFSZ)
{
log(">> Signal %d\n", sig);
ptrace(PTRACE_SYSCALL, box_pid, 0, sig);
}
else
die("Received signal %d.", sig);
}
else
die("wait4: unknown status %x, giving up!", stat);
}
}
static void
box_inside(int argc, char **argv)
{
struct rlimit rl;
char *args[argc+1];
char *env[1] = { NULL };
memcpy(args, argv, argc * sizeof(char *));
args[argc] = NULL;
if (set_cwd && chdir(set_cwd))
die("chdir: %m");
if (redir_stdin)
{
close(0);
if (open(redir_stdin, O_RDONLY) != 0)
die("open(\"%s\"): %m", redir_stdin);
}
if (redir_stdout)
{
close(1);
if (open(redir_stdout, O_WRONLY | O_CREAT | O_TRUNC, 0666) != 1)
die("open(\"%s\"): %m", redir_stdout);
}
dup2(1, 2);
setpgrp();
if (memory_limit)
{
rl.rlim_cur = rl.rlim_max = memory_limit * 1024;
if (setrlimit(RLIMIT_AS, &rl) < 0)
die("setrlimit: %m");
}
rl.rlim_cur = rl.rlim_max = 64;
if (setrlimit(RLIMIT_NOFILE, &rl) < 0)
die("setrlimit: %m");
if (filter_syscalls && ptrace(PTRACE_TRACEME) < 0)
die("ptrace(PTRACE_TRACEME): %m");
execve(args[0], args, (pass_environ ? environ : env));
die("execve(\"%s\"): %m", args[0]);
}
static void
usage(void)
{
fprintf(stderr, "Invalid arguments!\n");
printf("\
Usage: box [<options>] -- <command> <arguments>\n\
\n\
Options:\n\
-a <level>\tSet file access level (0=none, 1=cwd, 2=/etc,/lib,..., 3=whole fs, 9=no checks; needs -f)\n\
-c <dir>\tChange directory to <dir> first\n\
-e\t\tPass full environment of parent process\n\
-f\t\tFilter system calls (-ff=very restricted)\n\
-i <file>\tRedirect stdin from <file>\n\
-m <size>\tLimit address space to <size> KB\n\
-o <file>\tRedirect stdout to <file>\n\
-t <time>\tStop after <time> seconds\n\
-T\t\tAllow syscalls for measuring run time\n\
-v\t\tBe verbose\n\
-w\t\tMeasure wall clock time instead of run time\n\
");
exit(1);
}
int
main(int argc, char **argv)
{
int c;
uid_t uid;
while ((c = getopt(argc, argv, "a:c:efi:m:o:t:Tvw")) >= 0)
switch (c)
{
case 'a':
file_access = atol(optarg);
break;
case 'c':
set_cwd = optarg;
break;
case 'e':
pass_environ = 1;
break;
case 'f':
filter_syscalls++;
break;
case 'i':
redir_stdin = optarg;
break;
case 'm':
memory_limit = atol(optarg);
break;
case 'o':
redir_stdout = optarg;
break;
case 't':
timeout = atof(optarg);
break;
case 'T':
allow_times++;
break;
case 'v':
verbose++;
break;
case 'w':
use_wall_clock = 1;
break;
default:
usage();
}
if (optind >= argc)
usage();
uid = geteuid();
if (setreuid(uid, uid) < 0)
die("setreuid: %m");
box_pid = fork();
if (box_pid < 0)
die("fork: %m");
if (!box_pid)
box_inside(argc-optind, argv+optind);
else
boxkeeper();
die("Internal error: fell over edge of the world");
}