Description:
add box.c git-svn-id: http://theory.cpe.ku.ac.th/grader/cli/trunk/scripts@17 6386c4cd-e34a-4fa8-8920-d93eb39b512e
Commit status:
[Not Reviewed]
References:
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r5:db4eafda35d6 - - 2 files changed: 557 inserted, 0 deleted

This diff has been collapsed as it changes many lines, (557 lines changed) Show them Hide them
@@ -0,0 +1,557
1 + /*
2 + * A Simple Testing Sandbox
3 + *
4 + * (c) 2001--2004 Martin Mares <mj@ucw.cz>
5 + */
6 +
7 + #define _LARGEFILE64_SOURCE
8 + #define _GNU_SOURCE
9 +
10 + #include <errno.h>
11 + #include <stdio.h>
12 + #include <fcntl.h>
13 + #include <stdlib.h>
14 + #include <string.h>
15 + #include <stdarg.h>
16 + #include <unistd.h>
17 + #include <getopt.h>
18 + #include <time.h>
19 + #include <sys/wait.h>
20 + #include <sys/user.h>
21 + #include <sys/time.h>
22 + #include <sys/ptrace.h>
23 + #include <sys/signal.h>
24 + #include <sys/sysinfo.h>
25 + #include <sys/syscall.h>
26 + #include <sys/resource.h>
27 +
28 + #define NONRET __attribute__((noreturn))
29 + #define UNUSED __attribute__((unused))
30 +
31 + static int filter_syscalls; /* 0=off, 1=liberal, 2=totalitarian */
32 + static int timeout;
33 + static int pass_environ;
34 + static int use_wall_clock;
35 + static int file_access;
36 + static int verbose;
37 + static int memory_limit;
38 + static int allow_times;
39 + static char *redir_stdin, *redir_stdout;
40 + static char *set_cwd;
41 +
42 + static pid_t box_pid;
43 + static int is_ptraced;
44 + static volatile int timer_tick;
45 + static time_t start_time;
46 + static int ticks_per_sec;
47 +
48 + #if defined(__GLIBC__) && __GLIBC__ == 2 && __GLIBC_MINOR__ > 0
49 + /* glibc 2.1 or newer -> has lseek64 */
50 + #define long_seek(f,o,w) lseek64(f,o,w)
51 + #else
52 + /* Touching clandestine places in glibc */
53 + extern loff_t llseek(int fd, loff_t pos, int whence);
54 + #define long_seek(f,o,w) llseek(f,o,w)
55 + #endif
56 +
57 + static void NONRET
58 + box_exit(void)
59 + {
60 + if (box_pid > 0)
61 + {
62 + if (is_ptraced)
63 + ptrace(PTRACE_KILL, box_pid);
64 + kill(-box_pid, SIGKILL);
65 + kill(box_pid, SIGKILL);
66 + }
67 + exit(1);
68 + }
69 +
70 + static void NONRET __attribute__((format(printf,1,2)))
71 + die(char *msg, ...)
72 + {
73 + va_list args;
74 + va_start(args, msg);
75 + vfprintf(stderr, msg, args);
76 + fputc('\n', stderr);
77 + box_exit();
78 + }
79 +
80 + static void __attribute__((format(printf,1,2)))
81 + log(char *msg, ...)
82 + {
83 + va_list args;
84 + va_start(args, msg);
85 + if (verbose)
86 + {
87 + vfprintf(stderr, msg, args);
88 + fflush(stderr);
89 + }
90 + va_end(args);
91 + }
92 +
93 + static void
94 + valid_filename(unsigned long addr)
95 + {
96 + char namebuf[4096], *p, *end;
97 + static int mem_fd;
98 +
99 + if (!file_access)
100 + die("File access forbidden.");
101 + if (file_access >= 9)
102 + return;
103 +
104 + if (!mem_fd)
105 + {
106 + sprintf(namebuf, "/proc/%d/mem", (int) box_pid);
107 + mem_fd = open(namebuf, O_RDONLY);
108 + if (mem_fd < 0)
109 + die("open(%s): %m", namebuf);
110 + }
111 + p = end = namebuf;
112 + do
113 + {
114 + if (p >= end)
115 + {
116 + int remains = PAGE_SIZE - (addr & (PAGE_SIZE-1));
117 + int l = namebuf + sizeof(namebuf) - end;
118 + if (l > remains)
119 + l = remains;
120 + if (!l)
121 + die("Access to file with name too long.");
122 + if (long_seek(mem_fd, addr, SEEK_SET) < 0)
123 + die("long_seek(mem): %m");
124 + remains = read(mem_fd, end, l);
125 + if (remains < 0)
126 + die("read(mem): %m");
127 + if (!remains)
128 + die("Access to file with name out of memory.");
129 + end += l;
130 + addr += l;
131 + }
132 + }
133 + while (*p++);
134 +
135 + log("[%s] ", namebuf);
136 + if (file_access >= 3)
137 + return;
138 + if (!strchr(namebuf, '/') && strcmp(namebuf, ".."))
139 + return;
140 + if (file_access >= 2)
141 + {
142 + if ((!strncmp(namebuf, "/etc/", 5) ||
143 + !strncmp(namebuf, "/lib/", 5) ||
144 + !strncmp(namebuf, "/usr/lib/", 9))
145 + && !strstr(namebuf, ".."))
146 + return;
147 + if (!strcmp(namebuf, "/dev/null") ||
148 + !strcmp(namebuf, "/dev/zero") ||
149 + !strcmp(namebuf, "/proc/meminfo") ||
150 + !strcmp(namebuf, "/proc/self/stat") ||
151 + !strncmp(namebuf, "/usr/share/zoneinfo/", 20))
152 + return;
153 + }
154 + die("Forbidden access to file `%s'.", namebuf);
155 + }
156 +
157 + static int
158 + valid_syscall(struct user *u)
159 + {
160 + switch (u->regs.orig_eax)
161 + {
162 + case __NR_execve:
163 + {
164 + static int exec_counter;
165 + return !exec_counter++;
166 + }
167 + case __NR_open:
168 + case __NR_creat:
169 + case __NR_unlink:
170 + case __NR_oldstat:
171 + case __NR_access:
172 + case __NR_oldlstat:
173 + case __NR_truncate:
174 + case __NR_stat:
175 + case __NR_lstat:
176 + case __NR_truncate64:
177 + case __NR_stat64:
178 + case __NR_lstat64:
179 + valid_filename(u->regs.ebx);
180 + return 1;
181 + case __NR_exit:
182 + case __NR_read:
183 + case __NR_write:
184 + case __NR_close:
185 + case __NR_lseek:
186 + case __NR_getpid:
187 + case __NR_getuid:
188 + case __NR_oldfstat:
189 + case __NR_dup:
190 + case __NR_brk:
191 + case __NR_getgid:
192 + case __NR_geteuid:
193 + case __NR_getegid:
194 + case __NR_dup2:
195 + case __NR_ftruncate:
196 + case __NR_fstat:
197 + case __NR_personality:
198 + case __NR__llseek:
199 + case __NR_readv:
200 + case __NR_writev:
201 + case __NR_getresuid:
202 + #ifdef __NR_pread64
203 + case __NR_pread64:
204 + case __NR_pwrite64:
205 + #else
206 + case __NR_pread:
207 + case __NR_pwrite:
208 + #endif
209 + case __NR_ftruncate64:
210 + case __NR_fstat64:
211 + case __NR_fcntl:
212 + case __NR_fcntl64:
213 + case __NR_mmap:
214 + case __NR_munmap:
215 + case __NR_ioctl:
216 + case __NR_uname:
217 + case 252:
218 + case 243:
219 + return 1;
220 + // case __NR_time:
221 + case __NR_alarm:
222 + // case __NR_pause:
223 + case __NR_signal:
224 + case __NR_fchmod:
225 + case __NR_sigaction:
226 + case __NR_sgetmask:
227 + case __NR_ssetmask:
228 + case __NR_sigsuspend:
229 + case __NR_sigpending:
230 + case __NR_getrlimit:
231 + case __NR_getrusage:
232 + case __NR_gettimeofday:
233 + case __NR_select:
234 + case __NR_readdir:
235 + case __NR_setitimer:
236 + case __NR_getitimer:
237 + case __NR_sigreturn:
238 + case __NR_mprotect:
239 + case __NR_sigprocmask:
240 + case __NR_getdents:
241 + case __NR_getdents64:
242 + case __NR__newselect:
243 + case __NR_fdatasync:
244 + case __NR_mremap:
245 + case __NR_poll:
246 + case __NR_getcwd:
247 + case __NR_nanosleep:
248 + case __NR_rt_sigreturn:
249 + case __NR_rt_sigaction:
250 + case __NR_rt_sigprocmask:
251 + case __NR_rt_sigpending:
252 + case __NR_rt_sigtimedwait:
253 + case __NR_rt_sigqueueinfo:
254 + case __NR_rt_sigsuspend:
255 + case __NR_mmap2:
256 + case __NR__sysctl:
257 + return (filter_syscalls == 1);
258 + case __NR_times:
259 + return allow_times;
260 + case __NR_kill:
261 + if (u->regs.ebx == box_pid)
262 + die("Commited suicide by signal %d.", (int)u->regs.ecx);
263 + return 0;
264 + default:
265 + return 0;
266 + }
267 + }
268 +
269 + static void
270 + signal_alarm(int unused UNUSED)
271 + {
272 + /* Time limit checks are synchronous, so we only schedule them there. */
273 + timer_tick = 1;
274 + alarm(1);
275 + }
276 +
277 + static void
278 + signal_int(int unused UNUSED)
279 + {
280 + /* Interrupts are fatal, so no synchronization requirements. */
281 + die("Interrupted.");
282 + }
283 +
284 + static void
285 + check_timeout(void)
286 + {
287 + int sec;
288 +
289 + if (use_wall_clock)
290 + sec = time(NULL) - start_time;
291 + else
292 + {
293 + char buf[4096], *x;
294 + int c, utime, stime;
295 + static int proc_status_fd;
296 + if (!proc_status_fd)
297 + {
298 + sprintf(buf, "/proc/%d/stat", (int) box_pid);
299 + proc_status_fd = open(buf, O_RDONLY);
300 + if (proc_status_fd < 0)
301 + die("open(%s): %m", buf);
302 + }
303 + lseek(proc_status_fd, 0, SEEK_SET);
304 + if ((c = read(proc_status_fd, buf, sizeof(buf)-1)) < 0)
305 + die("read on /proc/$pid/stat: %m");
306 + if (c >= (int) sizeof(buf) - 1)
307 + die("/proc/$pid/stat too long");
308 + buf[c] = 0;
309 + x = buf;
310 + while (*x && *x != ' ')
311 + x++;
312 + while (*x == ' ')
313 + x++;
314 + if (*x++ != '(')
315 + die("proc syntax error 1");
316 + while (*x && (*x != ')' || x[1] != ' '))
317 + x++;
318 + while (*x == ')' || *x == ' ')
319 + x++;
320 + if (sscanf(x, "%*c %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %d %d", &utime, &stime) != 2)
321 + die("proc syntax error 2");
322 + sec = (utime + stime)/ticks_per_sec;
323 + }
324 + if (verbose > 1)
325 + fprintf(stderr, "[timecheck: %d seconds]\n", sec);
326 + if (sec > timeout)
327 + die("Time limit exceeded.");
328 + }
329 +
330 + static void
331 + boxkeeper(void)
332 + {
333 + int syscall_count = 0;
334 + struct sigaction sa;
335 +
336 + is_ptraced = 1;
337 + bzero(&sa, sizeof(sa));
338 + sa.sa_handler = signal_int;
339 + sigaction(SIGINT, &sa, NULL);
340 + start_time = time(NULL);
341 + ticks_per_sec = sysconf(_SC_CLK_TCK);
342 + if (ticks_per_sec <= 0)
343 + die("Invalid ticks_per_sec!");
344 + if (timeout)
345 + {
346 + sa.sa_handler = signal_alarm;
347 + sigaction(SIGALRM, &sa, NULL);
348 + alarm(1);
349 + }
350 + for(;;)
351 + {
352 + struct rusage rus;
353 + int stat;
354 + pid_t p;
355 + if (timer_tick)
356 + {
357 + check_timeout();
358 + timer_tick = 0;
359 + }
360 + p = wait4(box_pid, &stat, WUNTRACED, &rus);
361 + if (p < 0)
362 + {
363 + if (errno == EINTR)
364 + continue;
365 + die("wait4: %m");
366 + }
367 + if (p != box_pid)
368 + die("wait4: unknown pid %d exited!", p);
369 + if (WIFEXITED(stat))
370 + {
371 + struct timeval total;
372 + int wall;
373 + box_pid = 0;
374 + if (WEXITSTATUS(stat))
375 + die("Exited with error status %d.", WEXITSTATUS(stat));
376 + timeradd(&rus.ru_utime, &rus.ru_stime, &total);
377 + wall = time(NULL) - start_time;
378 + if ((use_wall_clock ? wall : total.tv_sec) > timeout)
379 + die("Time limit exceeded (after exit).");
380 + fprintf(stderr, "OK (%d sec real, %d sec wall, %d syscalls)\n", (int) total.tv_sec, wall, syscall_count);
381 + exit(0);
382 + }
383 + if (WIFSIGNALED(stat))
384 + {
385 + box_pid = 0;
386 + die("Caught fatal signal %d.", WTERMSIG(stat));
387 + }
388 + if (WIFSTOPPED(stat))
389 + {
390 + int sig = WSTOPSIG(stat);
391 + if (sig == SIGTRAP)
392 + {
393 + struct user u;
394 + static int stop_count = -1;
395 + if (ptrace(PTRACE_GETREGS, box_pid, NULL, &u) < 0)
396 + die("ptrace(PTRACE_GETREGS): %m");
397 + stop_count++;
398 + if (!stop_count) /* Traceme request */
399 + log(">> Traceme request caught\n");
400 + else if (stop_count & 1) /* Syscall entry */
401 + {
402 + log(">> Syscall %3ld (%08lx,%08lx,%08lx) ", u.regs.orig_eax, u.regs.ebx, u.regs.ecx, u.regs.edx);
403 + syscall_count++;
404 + if (!valid_syscall(&u))
405 + {
406 + /*
407 + * Unfortunately, PTRACE_KILL kills _after_ the syscall completes,
408 + * so we have to change it to something harmless (e.g., an undefined
409 + * syscall) and make the program continue.
410 + */
411 + unsigned int sys = u.regs.orig_eax;
412 + u.regs.orig_eax = 0xffffffff;
413 + if (ptrace(PTRACE_SETREGS, box_pid, NULL, &u) < 0)
414 + die("ptrace(PTRACE_SETREGS): %m");
415 + die("Forbidden syscall %d.", sys);
416 + }
417 + }
418 + else /* Syscall return */
419 + log("= %ld\n", u.regs.eax);
420 + ptrace(PTRACE_SYSCALL, box_pid, 0, 0);
421 + }
422 + else if (sig != SIGSTOP && sig != SIGXCPU && sig != SIGXFSZ)
423 + {
424 + log(">> Signal %d\n", sig);
425 + ptrace(PTRACE_SYSCALL, box_pid, 0, sig);
426 + }
427 + else
428 + die("Received signal %d.", sig);
429 + }
430 + else
431 + die("wait4: unknown status %x, giving up!", stat);
432 + }
433 + }
434 +
435 + static void
436 + box_inside(int argc, char **argv)
437 + {
438 + struct rlimit rl;
439 + char *args[argc+1];
440 + char *env[1] = { NULL };
441 +
442 + memcpy(args, argv, argc * sizeof(char *));
443 + args[argc] = NULL;
444 + if (set_cwd && chdir(set_cwd))
445 + die("chdir: %m");
446 + if (redir_stdin)
447 + {
448 + close(0);
449 + if (open(redir_stdin, O_RDONLY) != 0)
450 + die("open(\"%s\"): %m", redir_stdin);
451 + }
452 + if (redir_stdout)
453 + {
454 + close(1);
455 + if (open(redir_stdout, O_WRONLY | O_CREAT | O_TRUNC, 0666) != 1)
456 + die("open(\"%s\"): %m", redir_stdout);
457 + }
458 + dup2(1, 2);
459 + setpgrp();
460 + if (memory_limit)
461 + {
462 + rl.rlim_cur = rl.rlim_max = memory_limit * 1024;
463 + if (setrlimit(RLIMIT_AS, &rl) < 0)
464 + die("setrlimit: %m");
465 + }
466 + rl.rlim_cur = rl.rlim_max = 64;
467 + if (setrlimit(RLIMIT_NOFILE, &rl) < 0)
468 + die("setrlimit: %m");
469 + if (filter_syscalls && ptrace(PTRACE_TRACEME) < 0)
470 + die("ptrace(PTRACE_TRACEME): %m");
471 + execve(args[0], args, (pass_environ ? environ : env));
472 + die("execve(\"%s\"): %m", args[0]);
473 + }
474 +
475 + static void
476 + usage(void)
477 + {
478 + fprintf(stderr, "Invalid arguments!\n");
479 + printf("\
480 + Usage: box [<options>] -- <command> <arguments>\n\
481 + \n\
482 + Options:\n\
483 + -a <level>\tSet file access level (0=none, 1=cwd, 2=/etc,/lib,..., 3=whole fs, 9=no checks; needs -f)\n\
484 + -c <dir>\tChange directory to <dir> first\n\
485 + -e\t\tPass full environment of parent process\n\
486 + -f\t\tFilter system calls (-ff=very restricted)\n\
487 + -i <file>\tRedirect stdin from <file>\n\
488 + -m <size>\tLimit address space to <size> KB\n\
489 + -o <file>\tRedirect stdout to <file>\n\
490 + -t <time>\tStop after <time> seconds\n\
491 + -T\t\tAllow syscalls for measuring run time\n\
492 + -v\t\tBe verbose\n\
493 + -w\t\tMeasure wall clock time instead of run time\n\
494 + ");
495 + exit(1);
496 + }
497 +
498 + int
499 + main(int argc, char **argv)
500 + {
501 + int c;
502 + uid_t uid;
503 +
504 + while ((c = getopt(argc, argv, "a:c:efi:m:o:t:Tvw")) >= 0)
505 + switch (c)
506 + {
507 + case 'a':
508 + file_access = atol(optarg);
509 + break;
510 + case 'c':
511 + set_cwd = optarg;
512 + break;
513 + case 'e':
514 + pass_environ = 1;
515 + break;
516 + case 'f':
517 + filter_syscalls++;
518 + break;
519 + case 'i':
520 + redir_stdin = optarg;
521 + break;
522 + case 'm':
523 + memory_limit = atol(optarg);
524 + break;
525 + case 'o':
526 + redir_stdout = optarg;
527 + break;
528 + case 't':
529 + timeout = atol(optarg);
530 + break;
531 + case 'T':
532 + allow_times++;
533 + break;
534 + case 'v':
535 + verbose++;
536 + break;
537 + case 'w':
538 + use_wall_clock = 1;
539 + break;
540 + default:
541 + usage();
542 + }
543 + if (optind >= argc)
544 + usage();
545 +
546 + uid = geteuid();
547 + if (setreuid(uid, uid) < 0)
548 + die("setreuid: %m");
549 + box_pid = fork();
550 + if (box_pid < 0)
551 + die("fork: %m");
552 + if (!box_pid)
553 + box_inside(argc-optind, argv+optind);
554 + else
555 + boxkeeper();
556 + die("Internal error: fell over edge of the world");
557 + }
deleted file
binary diff hidden
You need to be logged in to leave comments. Login now