]> mj.ucw.cz Git - moe.git/blob - src/box.c
Imported first version.
[moe.git] / src / box.c
1 /*
2  *      A Simple Testing Sandbox
3  *
4  *      (c) 2001 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 <sys/wait.h>
19 #include <sys/user.h>
20 #include <sys/time.h>
21 #include <sys/ptrace.h>
22 #include <sys/signal.h>
23 #include <sys/sysinfo.h>
24 #include <sys/syscall.h>
25 #include <sys/resource.h>
26
27 #define NONRET __attribute__((noreturn))
28 #define UNUSED __attribute__((unused))
29
30 static int filter_syscalls;             /* 0=off, 1=liberal, 2=totalitarian */
31 static int timeout;
32 static int pass_environ;
33 static int use_wall_clock;
34 static int file_access;
35 static int verbose;
36 static int memory_limit;
37
38 static pid_t box_pid;
39 static int is_ptraced;
40 static volatile int timer_tick;
41 static time_t start_time;
42 static int ticks_per_sec;
43
44 #if defined(__GLIBC__) && __GLIBC__ == 2 && __GLIBC_MINOR__ > 0
45 /* glibc 2.1 or newer -> has lseek64 */
46 #define long_seek(f,o,w) lseek64(f,o,w)
47 #else
48 /* Touching clandestine places in glibc */
49 extern loff_t llseek(int fd, loff_t pos, int whence);
50 #define long_seek(f,o,w) llseek(f,o,w)
51 #endif
52
53 static void NONRET
54 box_exit(void)
55 {
56   if (box_pid > 0)
57     {
58       if (is_ptraced)
59         ptrace(PTRACE_KILL, box_pid);
60       kill(-box_pid, SIGKILL);
61       kill(box_pid, SIGKILL);
62     }
63   exit(1);
64 }
65
66 static void NONRET __attribute__((format(printf,1,2)))
67 die(char *msg, ...)
68 {
69   va_list args;
70   va_start(args, msg);
71   vfprintf(stderr, msg, args);
72   fputc('\n', stderr);
73   box_exit();
74 }
75
76 static void __attribute__((format(printf,1,2)))
77 log(char *msg, ...)
78 {
79   va_list args;
80   va_start(args, msg);
81   if (verbose)
82     {
83       vfprintf(stderr, msg, args);
84       fflush(stderr);
85     }
86   va_end(args);
87 }
88
89 static void
90 valid_filename(unsigned long addr)
91 {
92   char namebuf[4096], *p, *end;
93   static int mem_fd;
94
95   if (!file_access)
96     die("File access forbidden.");
97   if (file_access >= 9)
98     return;
99
100   if (!mem_fd)
101     {
102       sprintf(namebuf, "/proc/%d/mem", (int) box_pid);
103       mem_fd = open(namebuf, O_RDONLY);
104       if (mem_fd < 0)
105         die("open(%s): %m", namebuf);
106     }
107   p = end = namebuf;
108   do
109     {
110       if (p >= end)
111         {
112           int remains = PAGE_SIZE - (addr & (PAGE_SIZE-1));
113           int l = namebuf + sizeof(namebuf) - end;
114           if (l > remains)
115             l = remains;
116           if (!l)
117             die("Access to file with name too long.");
118           if (long_seek(mem_fd, addr, SEEK_SET) < 0)
119             die("long_seek(mem): %m");
120           remains = read(mem_fd, end, l);
121           if (remains < 0)
122             die("read(mem): %m");
123           if (!remains)
124             die("Access to file with name out of memory.");
125           end += l;
126           addr += l;
127         }
128     }
129   while (*p++);
130
131   log("[%s] ", namebuf);
132   if (file_access >= 3)
133     return;
134   if (!strchr(namebuf, '/') && strcmp(namebuf, ".."))
135     return;
136   if (file_access >= 2)
137     {
138       if ((!strncmp(namebuf, "/etc/", 5) ||
139            !strncmp(namebuf, "/lib/", 5) ||
140            !strncmp(namebuf, "/usr/lib/", 9))
141           && !strstr(namebuf, ".."))
142         return;
143       if (!strcmp(namebuf, "/dev/null") ||
144           !strcmp(namebuf, "/dev/zero"))
145         return;
146     }
147   die("Forbidden access to file `%s'.", namebuf);
148 }
149
150 static int
151 valid_syscall(struct user *u)
152 {
153   switch (u->regs.orig_eax)
154     {
155     case SYS_execve:
156       {
157         static int exec_counter;
158         return !exec_counter++;
159       }
160     case SYS_open:
161     case SYS_creat:
162     case SYS_unlink:
163     case SYS_oldstat:
164     case SYS_access:                    
165     case SYS_oldlstat:                  
166     case SYS_truncate:
167     case SYS_stat:
168     case SYS_lstat:
169     case SYS_truncate64:
170     case SYS_stat64:
171     case SYS_lstat64:
172       valid_filename(u->regs.ebx);
173       return 1;
174     case SYS_exit:
175     case SYS_read:
176     case SYS_write:
177     case SYS_close:
178     case SYS_lseek:
179     case SYS_getpid:
180     case SYS_getuid:
181     case SYS_oldfstat:
182     case SYS_dup:
183     case SYS_brk:
184     case SYS_getgid:
185     case SYS_geteuid:
186     case SYS_getegid:
187     case SYS_dup2:
188     case SYS_ftruncate:
189     case SYS_fstat:
190     case SYS_personality:
191     case SYS__llseek:
192     case SYS_readv:
193     case SYS_writev:
194     case SYS_getresuid:
195     case SYS_pread:
196     case SYS_pwrite:
197     case SYS_ftruncate64:
198     case SYS_fstat64:
199     case SYS_fcntl:
200     case SYS_mmap:
201     case SYS_munmap:
202     case SYS_ioctl:
203       return 1;
204     case SYS_time:
205     case SYS_alarm:
206     case SYS_pause:
207     case SYS_signal:
208     case SYS_fchmod:
209     case SYS_sigaction:
210     case SYS_sgetmask:
211     case SYS_ssetmask:
212     case SYS_sigsuspend:
213     case SYS_sigpending:
214     case SYS_getrlimit:
215     case SYS_getrusage:
216     case SYS_gettimeofday:
217     case SYS_select:
218     case SYS_readdir:
219     case SYS_setitimer:
220     case SYS_getitimer:
221     case SYS_sigreturn:
222     case SYS_mprotect:
223     case SYS_sigprocmask:
224     case SYS_getdents:
225     case SYS__newselect:
226     case SYS_fdatasync:
227     case SYS_mremap:
228     case SYS_poll:
229     case SYS_getcwd:
230     case SYS_nanosleep:
231     case SYS_rt_sigreturn:
232     case SYS_rt_sigaction:
233     case SYS_rt_sigprocmask:
234     case SYS_rt_sigpending:
235     case SYS_rt_sigtimedwait:
236     case SYS_rt_sigqueueinfo:
237     case SYS_rt_sigsuspend:
238     case SYS_mmap2:
239       return (filter_syscalls == 1);
240     default:
241       return 0;
242     }
243 }
244
245 static void
246 signal_alarm(int unused UNUSED)
247 {
248   /* Time limit checks are synchronous, so we only schedule them there. */
249   timer_tick = 1;
250   alarm(1);
251 }
252
253 static void
254 signal_int(int unused UNUSED)
255 {
256   /* Interrupts are fatal, so no synchronization requirements. */
257   die("Interrupted.");
258 }
259
260 static void
261 check_timeout(void)
262 {
263   int sec;
264
265   if (use_wall_clock)
266     sec = time(NULL) - start_time;
267   else
268     {
269       char buf[4096], *x;
270       int c, utime, stime;
271       static int proc_status_fd;
272       if (!proc_status_fd)
273         {
274           sprintf(buf, "/proc/%d/stat", (int) box_pid);
275           proc_status_fd = open(buf, O_RDONLY);
276           if (proc_status_fd < 0)
277             die("open(%s): %m", buf);
278         }
279       lseek(proc_status_fd, 0, SEEK_SET);
280       if ((c = read(proc_status_fd, buf, sizeof(buf)-1)) < 0)
281         die("read on /proc/$pid/stat: %m");
282       if (c >= (int) sizeof(buf) - 1)
283         die("/proc/$pid/stat too long");
284       buf[c] = 0;
285       x = buf;
286       while (*x && *x != ' ')
287         x++;
288       while (*x == ' ')
289         x++;
290       if (*x++ != '(')
291         die("proc syntax error 1");
292       while (*x && (*x != ')' || x[1] != ' '))
293         x++;
294       while (*x == ')' || *x == ' ')
295         x++;
296       if (sscanf(x, "%*c %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %d %d", &utime, &stime) != 2)
297         die("proc syntax error 2");
298       sec = (utime + stime)/ticks_per_sec;
299     }
300   if (verbose > 1)
301     fprintf(stderr, "[timecheck: %d seconds]\n", sec);
302   if (sec > timeout)
303     die("Time limit exceeded.");
304 }
305
306 static void
307 boxkeeper(void)
308 {
309   int syscall_count = 0;
310   struct sigaction sa;
311
312   is_ptraced = 1;
313   bzero(&sa, sizeof(sa));
314   sa.sa_handler = signal_int;
315   sigaction(SIGINT, &sa, NULL);
316   start_time = time(NULL);
317   ticks_per_sec = sysconf(_SC_CLK_TCK);
318   if (ticks_per_sec <= 0)
319     die("Invalid ticks_per_sec!");
320   if (timeout)
321     {
322       sa.sa_handler = signal_alarm;
323       sigaction(SIGALRM, &sa, NULL);
324       alarm(1);
325     }
326   for(;;)
327     {
328       struct rusage rus;
329       int stat;
330       pid_t p;
331       if (timer_tick)
332         {
333           check_timeout();
334           timer_tick = 0;
335         }
336       p = wait4(box_pid, &stat, WUNTRACED, &rus);
337       if (p < 0)
338         {
339           if (errno == EINTR)
340             continue;
341           die("wait4: %m");
342         }
343       if (p != box_pid)
344         die("wait4: unknown pid %d exited!", p);
345       if (WIFEXITED(stat))
346         {
347           struct timeval total;
348           int wall;
349           box_pid = 0;
350           if (WEXITSTATUS(stat))
351             die("Exited with error status %d.", WEXITSTATUS(stat));
352           timeradd(&rus.ru_utime, &rus.ru_stime, &total);
353           wall = time(NULL) - start_time;
354           if ((use_wall_clock ? wall : total.tv_sec) > timeout)
355             die("Timeout exceeded (after exit).");
356           fprintf(stderr, "OK (%d sec real, %d sec wall, %d syscalls)\n", (int) total.tv_sec, wall, syscall_count);
357           exit(0);
358         }
359       if (WIFSIGNALED(stat))
360         {
361           box_pid = 0;
362           die("Caught fatal signal %d.", WTERMSIG(stat));
363         }
364       if (WIFSTOPPED(stat))
365         {
366           int sig = WSTOPSIG(stat);
367           if (sig == SIGTRAP)
368             {
369               struct user u;
370               static int stop_count = -1;
371               if (ptrace(PTRACE_GETREGS, box_pid, NULL, &u) < 0)
372                 die("ptrace(PTRACE_GETREGS): %m");
373               stop_count++;
374               if (!stop_count)                  /* Traceme request */
375                 log(">> Traceme request caught\n");
376               else if (stop_count & 1)          /* Syscall entry */
377                 {
378                   log(">> Syscall %3ld (%08lx,%08lx,%08lx) ", u.regs.orig_eax, u.regs.ebx, u.regs.ecx, u.regs.edx);
379                   syscall_count++;
380                   if (!valid_syscall(&u))
381                     {
382                       /*
383                        * Unfortunately, PTRACE_KILL kills _after_ the syscall completes,
384                        * so we have to change it to something harmless (e.g., an undefined
385                        * syscall) and make the program continue.
386                        */
387                       unsigned int sys = u.regs.orig_eax;
388                       u.regs.orig_eax = 0xffffffff;
389                       if (ptrace(PTRACE_SETREGS, box_pid, NULL, &u) < 0)
390                         die("ptrace(PTRACE_SETREGS): %m");
391                       die("Forbidden syscall %d.", sys);
392                     }
393                 }
394               else                                      /* Syscall return */
395                 log("= %ld\n", u.regs.eax);
396               ptrace(PTRACE_SYSCALL, box_pid, 0, 0);
397             }
398           else if (sig != SIGSTOP && sig != SIGXCPU && sig != SIGXFSZ)
399             {
400               log(">> Signal %d\n", sig);
401               ptrace(PTRACE_SYSCALL, box_pid, 0, sig);
402             }
403           else
404             die("Received signal %d.", sig);
405         }
406       else
407         die("wait4: unknown status %x, giving up!", stat);
408     }
409 }
410
411 static void
412 box_inside(int argc, char **argv)
413 {
414   struct rlimit rl;
415   char *args[argc+1];
416   char *env[1] = { NULL };
417
418   memcpy(args, argv, argc * sizeof(char *));
419   args[argc] = NULL;
420   close(2);
421   dup(1);
422   setpgrp();
423   if (memory_limit)
424     {
425       rl.rlim_cur = rl.rlim_max = memory_limit * 1024;
426       if (setrlimit(RLIMIT_AS, &rl) < 0)
427         die("setrlimit: %m");
428     }
429   rl.rlim_cur = rl.rlim_max = 64;
430   if (setrlimit(RLIMIT_NOFILE, &rl) < 0)
431     die("setrlimit: %m");
432   if (filter_syscalls && ptrace(PTRACE_TRACEME) < 0)
433     die("ptrace(PTRACE_TRACEME): %m");
434   execve(args[0], args, (pass_environ ? environ : env));
435   die("execve(\"%s\"): %m", args[0]);
436 }
437
438 static void
439 usage(void)
440 {
441   fprintf(stderr, "Invalid arguments!\n");
442   printf("\
443 Usage: box [<options>] -- <command> <arguments>\n\
444 \n\
445 Options:\n\
446 -a <level>\tSet file access level (0=none, 1=cwd, 2=/etc,/lib,..., 3=whole fs, 9=no checks; needs -f)\n\
447 -e\t\tPass full environment of parent process\n\
448 -f\t\tFilter system calls (-ff=very restricted)\n\
449 -m <size>\tLimit address space to <size> KB\n\
450 -t <time>\tStop after <time> seconds\n\
451 -v\t\tBe verbose\n\
452 -w\t\tMeasure wall clock time instead of run time\n\
453 ");
454   exit(1);
455 }
456
457 int
458 main(int argc, char **argv)
459 {
460   int c;
461   uid_t uid;
462
463   while ((c = getopt(argc, argv, "a:efm:t:vw")) >= 0)
464     switch (c)
465       {
466       case 'a':
467         file_access = atol(optarg);
468         break;
469       case 'e':
470         pass_environ = 1;
471         break;
472       case 'f':
473         filter_syscalls++;
474         break;
475       case 'm':
476         memory_limit = atol(optarg);
477         break;
478       case 't':
479         timeout = atol(optarg);
480         break;
481       case 'v':
482         verbose++;
483         break;
484       case 'w':
485         use_wall_clock = 1;
486         break;
487       default:
488         usage();
489       }
490   if (optind >= argc)
491     usage();
492
493   uid = geteuid();
494   if (setreuid(uid, uid) < 0)
495     die("setreuid: %m");
496   box_pid = fork();
497   if (box_pid < 0)
498     die("fork: %m");
499   if (!box_pid)
500     box_inside(argc-optind, argv+optind);
501   else
502     boxkeeper();
503   die("Internal error: fell over edge of the world");
504 }