]> mj.ucw.cz Git - moe.git/blob - isolate/isolate.c
9ad969d790bda8fc73eb5afbfa80ea5ec3007346
[moe.git] / isolate / isolate.c
1 /*
2  *      A Process Isolator based in Linux Containers
3  *
4  *      (c) 2012 Martin Mares <mj@ucw.cz>
5  */
6
7 #define _GNU_SOURCE
8
9 #include "autoconf.h"
10
11 // FIXME: prune
12 #include <errno.h>
13 #include <stdio.h>
14 #include <fcntl.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <stdarg.h>
18 #include <stdint.h>
19 #include <unistd.h>
20 #include <getopt.h>
21 #include <time.h>
22 #include <sys/wait.h>
23 #include <sys/user.h>
24 #include <sys/time.h>
25 #include <sys/ptrace.h>
26 #include <sys/signal.h>
27 #include <sys/sysinfo.h>
28 #include <sys/resource.h>
29
30 #define NONRET __attribute__((noreturn))
31 #define UNUSED __attribute__((unused))
32 #define ARRAY_SIZE(a) (int)(sizeof(a)/sizeof(a[0]))
33
34 static int timeout;                     /* milliseconds */
35 static int wall_timeout;
36 static int extra_timeout;
37 static int pass_environ;
38 static int verbose;
39 static int memory_limit;
40 static int stack_limit;
41 static char *redir_stdin, *redir_stdout, *redir_stderr;
42 static char *set_cwd;
43
44 static pid_t box_pid;
45 static volatile int timer_tick;
46 static struct timeval start_time;
47 static int ticks_per_sec;
48 static int partial_line;
49
50 static int mem_peak_kb;
51 static int total_ms, wall_ms;
52
53 static void die(char *msg, ...) NONRET;
54 static void sample_mem_peak(void);
55
56 /*** Meta-files ***/
57
58 static FILE *metafile;
59
60 static void
61 meta_open(const char *name)
62 {
63   if (!strcmp(name, "-"))
64     {
65       metafile = stdout;
66       return;
67     }
68   metafile = fopen(name, "w");
69   if (!metafile)
70     die("Failed to open metafile '%s'",name);
71 }
72
73 static void
74 meta_close(void)
75 {
76   if (metafile && metafile != stdout)
77     fclose(metafile);
78 }
79
80 static void __attribute__((format(printf,1,2)))
81 meta_printf(const char *fmt, ...)
82 {
83   if (!metafile)
84     return;
85
86   va_list args;
87   va_start(args, fmt);
88   vfprintf(metafile, fmt, args);
89   va_end(args);
90 }
91
92 static void
93 final_stats(struct rusage *rus)
94 {
95   struct timeval total, now, wall;
96   timeradd(&rus->ru_utime, &rus->ru_stime, &total);
97   total_ms = total.tv_sec*1000 + total.tv_usec/1000;
98   gettimeofday(&now, NULL);
99   timersub(&now, &start_time, &wall);
100   wall_ms = wall.tv_sec*1000 + wall.tv_usec/1000;
101
102   meta_printf("time:%d.%03d\n", total_ms/1000, total_ms%1000);
103   meta_printf("time-wall:%d.%03d\n", wall_ms/1000, wall_ms%1000);
104   meta_printf("mem:%llu\n", (unsigned long long) mem_peak_kb * 1024);
105 }
106
107 /*** Messages and exits ***/
108
109 static void NONRET
110 box_exit(int rc)
111 {
112   if (box_pid > 0)
113     {
114       sample_mem_peak();
115       kill(-box_pid, SIGKILL);
116       kill(box_pid, SIGKILL);
117       meta_printf("killed:1\n");
118
119       struct rusage rus;
120       int p, stat;
121       do
122         p = wait4(box_pid, &stat, 0, &rus);
123       while (p < 0 && errno == EINTR);
124       if (p < 0)
125         fprintf(stderr, "UGH: Lost track of the process (%m)\n");
126       else
127         final_stats(&rus);
128     }
129   meta_close();
130   exit(rc);
131 }
132
133 static void
134 flush_line(void)
135 {
136   if (partial_line)
137     fputc('\n', stderr);
138   partial_line = 0;
139 }
140
141 /* Report an error of the sandbox itself */
142 static void NONRET __attribute__((format(printf,1,2)))
143 die(char *msg, ...)
144 {
145   va_list args;
146   va_start(args, msg);
147   flush_line();
148   char buf[1024];
149   vsnprintf(buf, sizeof(buf), msg, args);
150   meta_printf("status:XX\nmessage:%s\n", buf);
151   fputs(buf, stderr);
152   fputc('\n', stderr);
153   box_exit(2);
154 }
155
156 /* Report an error of the program inside the sandbox */
157 static void NONRET __attribute__((format(printf,1,2)))
158 err(char *msg, ...)
159 {
160   va_list args;
161   va_start(args, msg);
162   flush_line();
163   if (msg[0] && msg[1] && msg[2] == ':' && msg[3] == ' ')
164     {
165       meta_printf("status:%c%c\n", msg[0], msg[1]);
166       msg += 4;
167     }
168   char buf[1024];
169   vsnprintf(buf, sizeof(buf), msg, args);
170   meta_printf("message:%s\n", buf);
171   fputs(buf, stderr);
172   fputc('\n', stderr);
173   box_exit(1);
174 }
175
176 /* Write a message, but only if in verbose mode */
177 static void __attribute__((format(printf,1,2)))
178 msg(char *msg, ...)
179 {
180   va_list args;
181   va_start(args, msg);
182   if (verbose)
183     {
184       int len = strlen(msg);
185       if (len > 0)
186         partial_line = (msg[len-1] != '\n');
187       vfprintf(stderr, msg, args);
188       fflush(stderr);
189     }
190   va_end(args);
191 }
192
193 static void *
194 xmalloc(size_t size)
195 {
196   void *p = malloc(size);
197   if (!p)
198     die("Out of memory");
199   return p;
200 }
201
202 /*** Environment rules ***/
203
204 struct env_rule {
205   char *var;                    // Variable to match
206   char *val;                    // ""=clear, NULL=inherit
207   int var_len;
208   struct env_rule *next;
209 };
210
211 static struct env_rule *first_env_rule;
212 static struct env_rule **last_env_rule = &first_env_rule;
213
214 static struct env_rule default_env_rules[] = {
215   { "LIBC_FATAL_STDERR_", "1" }
216 };
217
218 static int
219 set_env_action(char *a0)
220 {
221   struct env_rule *r = xmalloc(sizeof(*r) + strlen(a0) + 1);
222   char *a = (char *)(r+1);
223   strcpy(a, a0);
224
225   char *sep = strchr(a, '=');
226   if (sep == a)
227     return 0;
228   r->var = a;
229   if (sep)
230     {
231       *sep++ = 0;
232       r->val = sep;
233     }
234   else
235     r->val = NULL;
236   *last_env_rule = r;
237   last_env_rule = &r->next;
238   r->next = NULL;
239   return 1;
240 }
241
242 static int
243 match_env_var(char *env_entry, struct env_rule *r)
244 {
245   if (strncmp(env_entry, r->var, r->var_len))
246     return 0;
247   return (env_entry[r->var_len] == '=');
248 }
249
250 static void
251 apply_env_rule(char **env, int *env_sizep, struct env_rule *r)
252 {
253   // First remove the variable if already set
254   int pos = 0;
255   while (pos < *env_sizep && !match_env_var(env[pos], r))
256     pos++;
257   if (pos < *env_sizep)
258     {
259       (*env_sizep)--;
260       env[pos] = env[*env_sizep];
261       env[*env_sizep] = NULL;
262     }
263
264   // What is the new value?
265   char *new;
266   if (r->val)
267     {
268       if (!r->val[0])
269         return;
270       new = xmalloc(r->var_len + 1 + strlen(r->val) + 1);
271       sprintf(new, "%s=%s", r->var, r->val);
272     }
273   else
274     {
275       pos = 0;
276       while (environ[pos] && !match_env_var(environ[pos], r))
277         pos++;
278       if (!(new = environ[pos]))
279         return;
280     }
281
282   // Add it at the end of the array
283   env[(*env_sizep)++] = new;
284   env[*env_sizep] = NULL;
285 }
286
287 static char **
288 setup_environment(void)
289 {
290   // Link built-in rules with user rules
291   for (int i=ARRAY_SIZE(default_env_rules)-1; i >= 0; i--)
292     {
293       default_env_rules[i].next = first_env_rule;
294       first_env_rule = &default_env_rules[i];
295     }
296
297   // Scan the original environment
298   char **orig_env = environ;
299   int orig_size = 0;
300   while (orig_env[orig_size])
301     orig_size++;
302
303   // For each rule, reserve one more slot and calculate length
304   int num_rules = 0;
305   for (struct env_rule *r = first_env_rule; r; r=r->next)
306     {
307       num_rules++;
308       r->var_len = strlen(r->var);
309     }
310
311   // Create a new environment
312   char **env = xmalloc((orig_size + num_rules + 1) * sizeof(char *));
313   int size;
314   if (pass_environ)
315     {
316       memcpy(env, environ, orig_size * sizeof(char *));
317       size = orig_size;
318     }
319   else
320     size = 0;
321   env[size] = NULL;
322
323   // Apply the rules one by one
324   for (struct env_rule *r = first_env_rule; r; r=r->next)
325     apply_env_rule(env, &size, r);
326
327   // Return the new env and pass some gossip
328   if (verbose > 1)
329     {
330       fprintf(stderr, "Passing environment:\n");
331       for (int i=0; env[i]; i++)
332         fprintf(stderr, "\t%s\n", env[i]);
333     }
334   return env;
335 }
336
337 /*** FIXME ***/
338
339 static void
340 signal_alarm(int unused UNUSED)
341 {
342   /* Time limit checks are synchronous, so we only schedule them there. */
343   timer_tick = 1;
344   alarm(1);
345 }
346
347 static void
348 signal_int(int unused UNUSED)
349 {
350   /* Interrupts are fatal, so no synchronization requirements. */
351   meta_printf("exitsig:%d\n", SIGINT);
352   err("SG: Interrupted");
353 }
354
355 #define PROC_BUF_SIZE 4096
356 static void
357 read_proc_file(char *buf, char *name, int *fdp)
358 {
359   int c;
360
361   if (!*fdp)
362     {
363       sprintf(buf, "/proc/%d/%s", (int) box_pid, name);
364       *fdp = open(buf, O_RDONLY);
365       if (*fdp < 0)
366         die("open(%s): %m", buf);
367     }
368   lseek(*fdp, 0, SEEK_SET);
369   if ((c = read(*fdp, buf, PROC_BUF_SIZE-1)) < 0)
370     die("read on /proc/$pid/%s: %m", name);
371   if (c >= PROC_BUF_SIZE-1)
372     die("/proc/$pid/%s too long", name);
373   buf[c] = 0;
374 }
375
376 static void
377 check_timeout(void)
378 {
379   if (wall_timeout)
380     {
381       struct timeval now, wall;
382       int wall_ms;
383       gettimeofday(&now, NULL);
384       timersub(&now, &start_time, &wall);
385       wall_ms = wall.tv_sec*1000 + wall.tv_usec/1000;
386       if (wall_ms > wall_timeout)
387         err("TO: Time limit exceeded (wall clock)");
388       if (verbose > 1)
389         fprintf(stderr, "[wall time check: %d msec]\n", wall_ms);
390     }
391   if (timeout)
392     {
393       char buf[PROC_BUF_SIZE], *x;
394       int utime, stime, ms;
395       static int proc_stat_fd;
396       read_proc_file(buf, "stat", &proc_stat_fd);
397       x = buf;
398       while (*x && *x != ' ')
399         x++;
400       while (*x == ' ')
401         x++;
402       if (*x++ != '(')
403         die("proc stat syntax error 1");
404       while (*x && (*x != ')' || x[1] != ' '))
405         x++;
406       while (*x == ')' || *x == ' ')
407         x++;
408       if (sscanf(x, "%*c %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %d %d", &utime, &stime) != 2)
409         die("proc stat syntax error 2");
410       ms = (utime + stime) * 1000 / ticks_per_sec;
411       if (verbose > 1)
412         fprintf(stderr, "[time check: %d msec]\n", ms);
413       if (ms > timeout && ms > extra_timeout)
414         err("TO: Time limit exceeded");
415     }
416 }
417
418 static void
419 sample_mem_peak(void)
420 {
421   /*
422    *  We want to find out the peak memory usage of the process, which is
423    *  maintained by the kernel, but unforunately it gets lost when the
424    *  process exits (it is not reported in struct rusage). Therefore we
425    *  have to sample it whenever we suspect that the process is about
426    *  to exit.
427    */
428   char buf[PROC_BUF_SIZE], *x;
429   static int proc_status_fd;
430   read_proc_file(buf, "status", &proc_status_fd);
431
432   x = buf;
433   while (*x)
434     {
435       char *key = x;
436       while (*x && *x != ':' && *x != '\n')
437         x++;
438       if (!*x || *x == '\n')
439         break;
440       *x++ = 0;
441       while (*x == ' ' || *x == '\t')
442         x++;
443
444       char *val = x;
445       while (*x && *x != '\n')
446         x++;
447       if (!*x)
448         break;
449       *x++ = 0;
450
451       if (!strcmp(key, "VmPeak"))
452         {
453           int peak = atoi(val);
454           if (peak > mem_peak_kb)
455             mem_peak_kb = peak;
456         }
457     }
458
459   if (verbose > 1)
460     msg("[mem-peak: %u KB]\n", mem_peak_kb);
461 }
462
463 static void
464 boxkeeper(void)
465 {
466   struct sigaction sa;
467
468   bzero(&sa, sizeof(sa));
469   sa.sa_handler = signal_int;
470   sigaction(SIGINT, &sa, NULL);
471
472   gettimeofday(&start_time, NULL);
473   ticks_per_sec = sysconf(_SC_CLK_TCK);
474   if (ticks_per_sec <= 0)
475     die("Invalid ticks_per_sec!");
476
477   if (timeout || wall_timeout)
478     {
479       sa.sa_handler = signal_alarm;
480       sigaction(SIGALRM, &sa, NULL);
481       alarm(1);
482     }
483
484   for(;;)
485     {
486       struct rusage rus;
487       int stat;
488       pid_t p;
489       if (timer_tick)
490         {
491           check_timeout();
492           timer_tick = 0;
493         }
494       p = wait4(box_pid, &stat, WUNTRACED, &rus);
495       if (p < 0)
496         {
497           if (errno == EINTR)
498             continue;
499           die("wait4: %m");
500         }
501       if (p != box_pid)
502         die("wait4: unknown pid %d exited!", p);
503       if (WIFEXITED(stat))
504         {
505           box_pid = 0;
506           final_stats(&rus);
507           if (WEXITSTATUS(stat))
508             {
509               // FIXME: Recognize internal errors during setup
510               meta_printf("exitcode:%d\n", WEXITSTATUS(stat));
511               err("RE: Exited with error status %d", WEXITSTATUS(stat));
512             }
513           if (timeout && total_ms > timeout)
514             err("TO: Time limit exceeded");
515           if (wall_timeout && wall_ms > wall_timeout)
516             err("TO: Time limit exceeded (wall clock)");
517           flush_line();
518           fprintf(stderr, "OK (%d.%03d sec real, %d.%03d sec wall, %d MB)\n",
519               total_ms/1000, total_ms%1000,
520               wall_ms/1000, wall_ms%1000,
521               (mem_peak_kb + 1023) / 1024);
522           box_exit(0);
523         }
524       if (WIFSIGNALED(stat))
525         {
526           box_pid = 0;
527           meta_printf("exitsig:%d\n", WTERMSIG(stat));
528           final_stats(&rus);
529           err("SG: Caught fatal signal %d", WTERMSIG(stat));
530         }
531       if (WIFSTOPPED(stat))
532         {
533           box_pid = 0;
534           meta_printf("exitsig:%d\n", WSTOPSIG(stat));
535           final_stats(&rus);
536           err("SG: Stopped by signal %d", WSTOPSIG(stat));
537         }
538       else
539         die("wait4: unknown status %x, giving up!", stat);
540     }
541 }
542
543 static void
544 box_inside(int argc, char **argv)
545 {
546   struct rlimit rl;
547   char *args[argc+1];
548
549   memcpy(args, argv, argc * sizeof(char *));
550   args[argc] = NULL;
551   if (set_cwd && chdir(set_cwd))
552     die("chdir: %m");
553   if (redir_stdin)
554     {
555       close(0);
556       if (open(redir_stdin, O_RDONLY) != 0)
557         die("open(\"%s\"): %m", redir_stdin);
558     }
559   if (redir_stdout)
560     {
561       close(1);
562       if (open(redir_stdout, O_WRONLY | O_CREAT | O_TRUNC, 0666) != 1)
563         die("open(\"%s\"): %m", redir_stdout);
564     }
565   if (redir_stderr)
566     {
567       close(2);
568       if (open(redir_stderr, O_WRONLY | O_CREAT | O_TRUNC, 0666) != 2)
569         die("open(\"%s\"): %m", redir_stderr);
570     }
571   else
572     dup2(1, 2);
573   setpgrp();
574
575   if (memory_limit)
576     {
577       rl.rlim_cur = rl.rlim_max = memory_limit * 1024;
578       if (setrlimit(RLIMIT_AS, &rl) < 0)
579         die("setrlimit(RLIMIT_AS): %m");
580     }
581
582   rl.rlim_cur = rl.rlim_max = (stack_limit ? (rlim_t)stack_limit * 1024 : RLIM_INFINITY);
583   if (setrlimit(RLIMIT_STACK, &rl) < 0)
584     die("setrlimit(RLIMIT_STACK): %m");
585
586   rl.rlim_cur = rl.rlim_max = 64;
587   if (setrlimit(RLIMIT_NOFILE, &rl) < 0)
588     die("setrlimit(RLIMIT_NOFILE): %m");
589
590   char **env = setup_environment();
591   execve(args[0], args, env);
592   die("execve(\"%s\"): %m", args[0]);
593 }
594
595 // FIXME: Prune (and also the getopt string)
596 static void
597 usage(void)
598 {
599   fprintf(stderr, "Invalid arguments!\n");
600   printf("\
601 Usage: box [<options>] -- <command> <arguments>\n\
602 \n\
603 Options:\n\
604 -a <level>\tSet file access level (0=none, 1=cwd, 2=/etc,/lib,..., 3=whole fs, 9=no checks; needs -f)\n\
605 -c <dir>\tChange directory to <dir> first\n\
606 -e\t\tInherit full environment of the parent process\n\
607 -E <var>\tInherit the environment variable <var> from the parent process\n\
608 -E <var>=<val>\tSet the environment variable <var> to <val>; unset it if <var> is empty\n\
609 -f\t\tFilter system calls (-ff=very restricted)\n\
610 -i <file>\tRedirect stdin from <file>\n\
611 -k <size>\tLimit stack size to <size> KB (default: 0=unlimited)\n\
612 -m <size>\tLimit address space to <size> KB\n\
613 -M <file>\tOutput process information to <file> (name:value)\n\
614 -o <file>\tRedirect stdout to <file>\n\
615 -p <path>\tPermit access to the specified path (or subtree if it ends with a `/')\n\
616 -p <path>=<act>\tDefine action for the specified path (<act>=yes/no)\n\
617 -r <file>\tRedirect stderr to <file>\n\
618 -s <sys>\tPermit the specified syscall (be careful)\n\
619 -s <sys>=<act>\tDefine action for the specified syscall (<act>=yes/no/file)\n\
620 -t <time>\tSet run time limit (seconds, fractions allowed)\n\
621 -T\t\tAllow syscalls for measuring run time\n\
622 -v\t\tBe verbose (use multiple times for even more verbosity)\n\
623 -w <time>\tSet wall clock time limit (seconds, fractions allowed)\n\
624 -x <time>\tSet extra timeout, before which a timing-out program is not yet killed,\n\
625 \t\tso that its real execution time is reported (seconds, fractions allowed)\n\
626 ");
627   exit(2);
628 }
629
630 int
631 main(int argc, char **argv)
632 {
633   int c;
634   uid_t uid;
635
636   while ((c = getopt(argc, argv, "a:c:eE:fi:k:m:M:o:p:r:s:t:Tvw:x:")) >= 0)
637     switch (c)
638       {
639       case 'c':
640         set_cwd = optarg;
641         break;
642       case 'e':
643         pass_environ = 1;
644         break;
645       case 'E':
646         if (!set_env_action(optarg))
647           usage();
648         break;
649       case 'k':
650         stack_limit = atol(optarg);
651         break;
652       case 'i':
653         redir_stdin = optarg;
654         break;
655       case 'm':
656         memory_limit = atol(optarg);
657         break;
658       case 'M':
659         meta_open(optarg);
660         break;
661       case 'o':
662         redir_stdout = optarg;
663         break;
664       case 'r':
665         redir_stderr = optarg;
666         break;
667       case 't':
668         timeout = 1000*atof(optarg);
669         break;
670       case 'v':
671         verbose++;
672         break;
673       case 'w':
674         wall_timeout = 1000*atof(optarg);
675         break;
676       case 'x':
677         extra_timeout = 1000*atof(optarg);
678         break;
679       default:
680         usage();
681       }
682   if (optind >= argc)
683     usage();
684
685   uid = geteuid();
686   if (setreuid(uid, uid) < 0)
687     die("setreuid: %m");
688   box_pid = fork();
689   if (box_pid < 0)
690     die("fork: %m");
691   if (!box_pid)
692     box_inside(argc-optind, argv+optind);
693   else
694     boxkeeper();
695   die("Internal error: fell over edge of the world");
696 }