]> mj.ucw.cz Git - moe.git/blob - isolate/isolate.c
6ce7b5b77e389135e9afdb0a3c346a349380ea2d
[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 #include <errno.h>
12 #include <stdio.h>
13 #include <fcntl.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <stdarg.h>
17 #include <stdint.h>
18 #include <unistd.h>
19 #include <getopt.h>
20 #include <sched.h>
21 #include <time.h>
22 #include <grp.h>
23 #include <sys/wait.h>
24 #include <sys/time.h>
25 #include <sys/signal.h>
26 #include <sys/resource.h>
27 #include <sys/mount.h>
28 #include <sys/stat.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 #define BOX_DIR CONFIG_ISOLATE_BOX_DIR
35 #define BOX_UID CONFIG_ISOLATE_BOX_UID
36 #define BOX_GID CONFIG_ISOLATE_BOX_GID
37
38 static int timeout;                     /* milliseconds */
39 static int wall_timeout;
40 static int extra_timeout;
41 static int pass_environ;
42 static int verbose;
43 static int memory_limit;
44 static int stack_limit;
45 static int max_processes = 1;
46 static char *redir_stdin, *redir_stdout, *redir_stderr;
47
48 static int cg_enable;
49 static int cg_memory_limit;
50 static int cg_timing;
51 static char *cg_root = "/sys/fs/cgroup";
52
53 static uid_t orig_uid;
54 static gid_t orig_gid;
55
56 static pid_t box_pid;
57 static int partial_line;
58 static char cleanup_cmd[256];
59
60 static struct timeval start_time;
61 static int ticks_per_sec;
62 static int total_ms, wall_ms;
63 static volatile sig_atomic_t timer_tick;
64
65 static int error_pipes[2];
66 static int write_errors_to_fd;
67 static int read_errors_from_fd;
68
69 static void die(char *msg, ...) NONRET;
70 static void cg_stats(void);
71 static int get_wall_time_ms(void);
72 static int get_run_time_ms(void);
73
74 /*** Meta-files ***/
75
76 static FILE *metafile;
77
78 static void
79 meta_open(const char *name)
80 {
81   if (!strcmp(name, "-"))
82     {
83       metafile = stdout;
84       return;
85     }
86   metafile = fopen(name, "w");
87   if (!metafile)
88     die("Failed to open metafile '%s'",name);
89 }
90
91 static void
92 meta_close(void)
93 {
94   if (metafile && metafile != stdout)
95     fclose(metafile);
96 }
97
98 static void __attribute__((format(printf,1,2)))
99 meta_printf(const char *fmt, ...)
100 {
101   if (!metafile)
102     return;
103
104   va_list args;
105   va_start(args, fmt);
106   vfprintf(metafile, fmt, args);
107   va_end(args);
108 }
109
110 static void
111 final_stats(struct rusage *rus)
112 {
113   total_ms = get_run_time_ms();
114   wall_ms = get_wall_time_ms();
115
116   meta_printf("time:%d.%03d\n", total_ms/1000, total_ms%1000);
117   meta_printf("time-wall:%d.%03d\n", wall_ms/1000, wall_ms%1000);
118   meta_printf("max-rss:%ld\n", rus->ru_maxrss);
119   meta_printf("csw-voluntary:%ld\n", rus->ru_nvcsw);
120   meta_printf("csw-forced:%ld\n", rus->ru_nivcsw);
121
122   cg_stats();
123 }
124
125 /*** Messages and exits ***/
126
127 static void
128 xsystem(const char *cmd)
129 {
130   int ret = system(cmd);
131   if (ret < 0)
132     die("system(\"%s\"): %m", cmd);
133   if (!WIFEXITED(ret) || WEXITSTATUS(ret))
134     die("system(\"%s\"): Exited with status %d", cmd, ret);
135 }
136
137 static void NONRET
138 box_exit(int rc)
139 {
140   if (box_pid > 0)
141     {
142       kill(-box_pid, SIGKILL);
143       kill(box_pid, SIGKILL);
144       meta_printf("killed:1\n");
145
146       struct rusage rus;
147       int p, stat;
148       do
149         p = wait4(box_pid, &stat, 0, &rus);
150       while (p < 0 && errno == EINTR);
151       if (p < 0)
152         fprintf(stderr, "UGH: Lost track of the process (%m)\n");
153       else
154         final_stats(&rus);
155     }
156
157   if (rc < 2 && cleanup_cmd[0])
158     xsystem(cleanup_cmd);
159
160   meta_close();
161   exit(rc);
162 }
163
164 static void
165 flush_line(void)
166 {
167   if (partial_line)
168     fputc('\n', stderr);
169   partial_line = 0;
170 }
171
172 /* Report an error of the sandbox itself */
173 static void NONRET __attribute__((format(printf,1,2)))
174 die(char *msg, ...)
175 {
176   va_list args;
177   va_start(args, msg);
178   char buf[1024];
179   int n = vsnprintf(buf, sizeof(buf), msg, args);
180
181   if (write_errors_to_fd)
182     {
183       // We are inside the box, have to use error pipe for error reporting.
184       // We hope that the whole error message fits in PIPE_BUF bytes.
185       write(write_errors_to_fd, buf, n);
186       exit(2);
187     }
188
189   // Otherwise, we in the box keeper process, so we report errors normally
190   flush_line();
191   meta_printf("status:XX\nmessage:%s\n", buf);
192   fputs(buf, stderr);
193   fputc('\n', stderr);
194   box_exit(2);
195 }
196
197 /* Report an error of the program inside the sandbox */
198 static void NONRET __attribute__((format(printf,1,2)))
199 err(char *msg, ...)
200 {
201   va_list args;
202   va_start(args, msg);
203   flush_line();
204   if (msg[0] && msg[1] && msg[2] == ':' && msg[3] == ' ')
205     {
206       meta_printf("status:%c%c\n", msg[0], msg[1]);
207       msg += 4;
208     }
209   char buf[1024];
210   vsnprintf(buf, sizeof(buf), msg, args);
211   meta_printf("message:%s\n", buf);
212   fputs(buf, stderr);
213   fputc('\n', stderr);
214   box_exit(1);
215 }
216
217 /* Write a message, but only if in verbose mode */
218 static void __attribute__((format(printf,1,2)))
219 msg(char *msg, ...)
220 {
221   va_list args;
222   va_start(args, msg);
223   if (verbose)
224     {
225       int len = strlen(msg);
226       if (len > 0)
227         partial_line = (msg[len-1] != '\n');
228       vfprintf(stderr, msg, args);
229       fflush(stderr);
230     }
231   va_end(args);
232 }
233
234 static void *
235 xmalloc(size_t size)
236 {
237   void *p = malloc(size);
238   if (!p)
239     die("Out of memory");
240   return p;
241 }
242
243 /*** Environment rules ***/
244
245 struct env_rule {
246   char *var;                    // Variable to match
247   char *val;                    // ""=clear, NULL=inherit
248   int var_len;
249   struct env_rule *next;
250 };
251
252 static struct env_rule *first_env_rule;
253 static struct env_rule **last_env_rule = &first_env_rule;
254
255 static struct env_rule default_env_rules[] = {
256   { "LIBC_FATAL_STDERR_", "1" }
257 };
258
259 static int
260 set_env_action(char *a0)
261 {
262   struct env_rule *r = xmalloc(sizeof(*r) + strlen(a0) + 1);
263   char *a = (char *)(r+1);
264   strcpy(a, a0);
265
266   char *sep = strchr(a, '=');
267   if (sep == a)
268     return 0;
269   r->var = a;
270   if (sep)
271     {
272       *sep++ = 0;
273       r->val = sep;
274     }
275   else
276     r->val = NULL;
277   *last_env_rule = r;
278   last_env_rule = &r->next;
279   r->next = NULL;
280   return 1;
281 }
282
283 static int
284 match_env_var(char *env_entry, struct env_rule *r)
285 {
286   if (strncmp(env_entry, r->var, r->var_len))
287     return 0;
288   return (env_entry[r->var_len] == '=');
289 }
290
291 static void
292 apply_env_rule(char **env, int *env_sizep, struct env_rule *r)
293 {
294   // First remove the variable if already set
295   int pos = 0;
296   while (pos < *env_sizep && !match_env_var(env[pos], r))
297     pos++;
298   if (pos < *env_sizep)
299     {
300       (*env_sizep)--;
301       env[pos] = env[*env_sizep];
302       env[*env_sizep] = NULL;
303     }
304
305   // What is the new value?
306   char *new;
307   if (r->val)
308     {
309       if (!r->val[0])
310         return;
311       new = xmalloc(r->var_len + 1 + strlen(r->val) + 1);
312       sprintf(new, "%s=%s", r->var, r->val);
313     }
314   else
315     {
316       pos = 0;
317       while (environ[pos] && !match_env_var(environ[pos], r))
318         pos++;
319       if (!(new = environ[pos]))
320         return;
321     }
322
323   // Add it at the end of the array
324   env[(*env_sizep)++] = new;
325   env[*env_sizep] = NULL;
326 }
327
328 static char **
329 setup_environment(void)
330 {
331   // Link built-in rules with user rules
332   for (int i=ARRAY_SIZE(default_env_rules)-1; i >= 0; i--)
333     {
334       default_env_rules[i].next = first_env_rule;
335       first_env_rule = &default_env_rules[i];
336     }
337
338   // Scan the original environment
339   char **orig_env = environ;
340   int orig_size = 0;
341   while (orig_env[orig_size])
342     orig_size++;
343
344   // For each rule, reserve one more slot and calculate length
345   int num_rules = 0;
346   for (struct env_rule *r = first_env_rule; r; r=r->next)
347     {
348       num_rules++;
349       r->var_len = strlen(r->var);
350     }
351
352   // Create a new environment
353   char **env = xmalloc((orig_size + num_rules + 1) * sizeof(char *));
354   int size;
355   if (pass_environ)
356     {
357       memcpy(env, environ, orig_size * sizeof(char *));
358       size = orig_size;
359     }
360   else
361     size = 0;
362   env[size] = NULL;
363
364   // Apply the rules one by one
365   for (struct env_rule *r = first_env_rule; r; r=r->next)
366     apply_env_rule(env, &size, r);
367
368   // Return the new env and pass some gossip
369   if (verbose > 1)
370     {
371       fprintf(stderr, "Passing environment:\n");
372       for (int i=0; env[i]; i++)
373         fprintf(stderr, "\t%s\n", env[i]);
374     }
375   return env;
376 }
377
378 /*** Control groups ***/
379
380 static char cg_path[256];
381
382 #define CG_BUFSIZE 1024
383
384 static int
385 cg_read(char *attr, char *buf)
386 {
387   int maybe = 0;
388   if (attr[0] == '?')
389     {
390       attr++;
391       maybe = 1;
392     }
393
394   char path[256];
395   snprintf(path, sizeof(path), "%s/%s", cg_path, attr);
396
397   int fd = open(path, O_RDONLY);
398   if (fd < 0)
399     {
400       if (maybe)
401         return 0;
402       die("Cannot read %s: %m", path);
403     }
404
405   int n = read(fd, buf, CG_BUFSIZE);
406   if (n < 0)
407     die("Cannot read %s: %m", path);
408   if (n >= CG_BUFSIZE - 1)
409     die("Attribute %s too long", path);
410   if (n > 0 && buf[n-1] == '\n')
411     n--;
412   buf[n] = 0;
413
414   if (verbose > 1)
415     msg("CG: Read %s = %s\n", attr, buf);
416
417   close(fd);
418   return 1;
419 }
420
421 static void __attribute__((format(printf,2,3)))
422 cg_write(char *attr, char *fmt, ...)
423 {
424   va_list args;
425   va_start(args, fmt);
426
427   char buf[CG_BUFSIZE];
428   int n = vsnprintf(buf, sizeof(buf), fmt, args);
429   if (n >= CG_BUFSIZE)
430     die("cg_writef: Value for attribute %s is too long", attr);
431
432   if (verbose > 1)
433     msg("CG: Write %s = %s", attr, buf);
434
435   char path[256];
436   snprintf(path, sizeof(path), "%s/%s", cg_path, attr);
437
438   int fd = open(path, O_WRONLY | O_TRUNC);
439   if (fd < 0)
440     die("Cannot write %s: %m", path);
441
442   int written = write(fd, buf, n);
443   if (written < 0)
444     die("Cannot set %s to %s: %m", path, buf);
445   if (written != n)
446     die("Short write to %s (%d out of %d bytes)", path, written, n);
447
448   close(fd);
449   va_end(args);
450 }
451
452 static void
453 cg_init(void)
454 {
455   if (!cg_enable)
456     return;
457
458   struct stat st;
459   if (stat(cg_root, &st) < 0 || !S_ISDIR(st.st_mode))
460     die("Control group filesystem at %s not mounted", cg_root);
461
462   snprintf(cg_path, sizeof(cg_path), "%s/box-%d", cg_root, BOX_UID);
463   msg("Using control group %s\n", cg_path);
464 }
465
466 static void
467 cg_prepare(void)
468 {
469   if (!cg_enable)
470     return;
471
472   struct stat st;
473   char buf[CG_BUFSIZE];
474
475   if (stat(cg_path, &st) >= 0 || errno != ENOENT)
476     {
477       msg("Control group %s already exists, trying to empty it.\n", cg_path);
478       if (rmdir(cg_path) < 0)
479         die("Failed to reset control group %s: %m", cg_path);
480     }
481
482   if (mkdir(cg_path, 0777) < 0)
483     die("Failed to create control group %s: %m", cg_path);
484
485   // If cpuset module is enabled, copy allowed cpus and memory nodes from parent group
486   if (cg_read("?../cpuset.cpus", buf))
487     cg_write("cpuset.cpus", "%s", buf);
488   if (cg_read("?../cpuset.mems", buf))
489     cg_write("cpuset.mems", "%s", buf);
490 }
491
492 static void
493 cg_enter(void)
494 {
495   if (!cg_enable)
496     return;
497
498   msg("Entering control group %s\n", cg_path);
499
500   struct stat st;
501   if (stat(cg_path, &st) < 0)
502     die("Control group %s does not exist: %m", cg_path);
503
504   if (cg_memory_limit)
505     {
506       cg_write("memory.limit_in_bytes", "%lld\n", (long long) cg_memory_limit << 10);
507       cg_write("memory.memsw.limit_in_bytes", "%lld\n", (long long) cg_memory_limit << 10);
508     }
509
510   if (cg_timing)
511     cg_write("cpuacct.usage", "0\n");
512
513   cg_write("tasks", "%d\n", (int) getpid());
514 }
515
516 static int
517 cg_get_run_time_ms(void)
518 {
519   if (!cg_enable)
520     return 0;
521
522   char buf[CG_BUFSIZE];
523   cg_read("cpuacct.usage", buf);
524   unsigned long long ns = atoll(buf);
525   return ns / 1000000;
526 }
527
528 static void
529 cg_stats(void)
530 {
531   if (!cg_enable)
532     return;
533
534   char buf[CG_BUFSIZE];
535
536   // Memory usage statistics
537   unsigned long long mem=0, memsw=0;
538   if (cg_read("?memory.max_usage_in_bytes", buf))
539     mem = atoll(buf);
540   if (cg_read("?memory.memsw.max_usage_in_bytes", buf))
541     {
542       memsw = atoll(buf);
543       if (memsw > mem)
544         mem = memsw;
545     }
546   if (mem)
547     meta_printf("cg-mem:%lld\n", mem >> 10);
548 }
549
550 static void
551 cg_remove(void)
552 {
553   char buf[CG_BUFSIZE];
554
555   if (!cg_enable)
556     return;
557
558   cg_read("tasks", buf);
559   if (buf[0])
560     die("Some tasks left in control group %s, failed to remove it", cg_path);
561
562   if (rmdir(cg_path) < 0)
563     die("Cannot remove control group %s: %m", cg_path);
564 }
565
566 /*** The keeper process ***/
567
568 static void
569 signal_alarm(int unused UNUSED)
570 {
571   /* Time limit checks are synchronous, so we only schedule them there. */
572   timer_tick = 1;
573   alarm(1);
574 }
575
576 static void
577 signal_int(int unused UNUSED)
578 {
579   /* Interrupts are fatal, so no synchronization requirements. */
580   meta_printf("exitsig:%d\n", SIGINT);
581   err("SG: Interrupted");
582 }
583
584 #define PROC_BUF_SIZE 4096
585 static void
586 read_proc_file(char *buf, char *name, int *fdp)
587 {
588   int c;
589
590   if (!*fdp)
591     {
592       sprintf(buf, "/proc/%d/%s", (int) box_pid, name);
593       *fdp = open(buf, O_RDONLY);
594       if (*fdp < 0)
595         die("open(%s): %m", buf);
596     }
597   lseek(*fdp, 0, SEEK_SET);
598   if ((c = read(*fdp, buf, PROC_BUF_SIZE-1)) < 0)
599     die("read on /proc/$pid/%s: %m", name);
600   if (c >= PROC_BUF_SIZE-1)
601     die("/proc/$pid/%s too long", name);
602   buf[c] = 0;
603 }
604
605 static int
606 get_wall_time_ms(void)
607 {
608   struct timeval now, wall;
609   gettimeofday(&now, NULL);
610   timersub(&now, &start_time, &wall);
611   return wall.tv_sec*1000 + wall.tv_usec/1000;
612 }
613
614 static int
615 get_run_time_ms(void)
616 {
617   if (cg_timing)
618     return cg_get_run_time_ms();
619
620   char buf[PROC_BUF_SIZE], *x;
621   int utime, stime;
622   static int proc_stat_fd;
623
624   read_proc_file(buf, "stat", &proc_stat_fd);
625   x = buf;
626   while (*x && *x != ' ')
627     x++;
628   while (*x == ' ')
629     x++;
630   if (*x++ != '(')
631     die("proc stat syntax error 1");
632   while (*x && (*x != ')' || x[1] != ' '))
633     x++;
634   while (*x == ')' || *x == ' ')
635     x++;
636   if (sscanf(x, "%*c %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %d %d", &utime, &stime) != 2)
637     die("proc stat syntax error 2");
638
639   return (utime + stime) * 1000 / ticks_per_sec;
640 }
641
642 static void
643 check_timeout(void)
644 {
645   if (wall_timeout)
646     {
647       int wall_ms = get_wall_time_ms();
648       if (wall_ms > wall_timeout)
649         err("TO: Time limit exceeded (wall clock)");
650       if (verbose > 1)
651         fprintf(stderr, "[wall time check: %d msec]\n", wall_ms);
652     }
653   if (timeout)
654     {
655       int ms = get_run_time_ms();
656       if (verbose > 1)
657         fprintf(stderr, "[time check: %d msec]\n", ms);
658       if (ms > timeout && ms > extra_timeout)
659         err("TO: Time limit exceeded");
660     }
661 }
662
663 static void
664 box_keeper(void)
665 {
666   read_errors_from_fd = error_pipes[0];
667   close(error_pipes[1]);
668
669   struct sigaction sa;
670   bzero(&sa, sizeof(sa));
671   sa.sa_handler = signal_int;
672   sigaction(SIGINT, &sa, NULL);
673
674   gettimeofday(&start_time, NULL);
675   ticks_per_sec = sysconf(_SC_CLK_TCK);
676   if (ticks_per_sec <= 0)
677     die("Invalid ticks_per_sec!");
678
679   if (timeout || wall_timeout)
680     {
681       sa.sa_handler = signal_alarm;
682       sigaction(SIGALRM, &sa, NULL);
683       alarm(1);
684     }
685
686   for(;;)
687     {
688       struct rusage rus;
689       int stat;
690       pid_t p;
691       if (timer_tick)
692         {
693           check_timeout();
694           timer_tick = 0;
695         }
696       p = wait4(box_pid, &stat, 0, &rus);
697       if (p < 0)
698         {
699           if (errno == EINTR)
700             continue;
701           die("wait4: %m");
702         }
703       if (p != box_pid)
704         die("wait4: unknown pid %d exited!", p);
705       box_pid = 0;
706
707       // Check error pipe if there is an internal error passed from inside the box
708       char interr[1024];
709       int n = read(read_errors_from_fd, interr, sizeof(interr) - 1);
710       if (n > 0)
711         {
712           interr[n] = 0;
713           die("%s", interr);
714         }
715
716       if (WIFEXITED(stat))
717         {
718           final_stats(&rus);
719           if (WEXITSTATUS(stat))
720             {
721               meta_printf("exitcode:%d\n", WEXITSTATUS(stat));
722               err("RE: Exited with error status %d", WEXITSTATUS(stat));
723             }
724           if (timeout && total_ms > timeout)
725             err("TO: Time limit exceeded");
726           if (wall_timeout && wall_ms > wall_timeout)
727             err("TO: Time limit exceeded (wall clock)");
728           flush_line();
729           fprintf(stderr, "OK (%d.%03d sec real, %d.%03d sec wall)\n",
730               total_ms/1000, total_ms%1000,
731               wall_ms/1000, wall_ms%1000);
732           box_exit(0);
733         }
734       else if (WIFSIGNALED(stat))
735         {
736           meta_printf("exitsig:%d\n", WTERMSIG(stat));
737           final_stats(&rus);
738           err("SG: Caught fatal signal %d", WTERMSIG(stat));
739         }
740       else if (WIFSTOPPED(stat))
741         {
742           meta_printf("exitsig:%d\n", WSTOPSIG(stat));
743           final_stats(&rus);
744           err("SG: Stopped by signal %d", WSTOPSIG(stat));
745         }
746       else
747         die("wait4: unknown status %x, giving up!", stat);
748     }
749 }
750
751 /*** The process running inside the box ***/
752
753 static void
754 setup_root(void)
755 {
756   if (mkdir("root", 0750) < 0 && errno != EEXIST)
757     die("mkdir('root'): %m");
758
759   if (mount("none", "root", "tmpfs", 0, "mode=755") < 0)
760     die("Cannot mount root ramdisk: %m");
761
762   static const char * const dirs[] = { "box", "/bin", "/lib", "/usr", "/dev" };
763   for (int i=0; i < ARRAY_SIZE(dirs); i++)
764     {
765       const char *d = dirs[i];
766       char buf[1024];
767       snprintf(buf, sizeof(buf), "root/%s", (d[0] == '/' ? d+1 : d));
768       msg("Binding %s on %s\n", d, buf);
769       if (mkdir(buf, 0755) < 0)
770         die("mkdir(%s): %m", buf);
771       if (mount(d, buf, "none", MS_BIND | MS_NOSUID | MS_NODEV, "") < 0)
772         die("Cannot bind %s on %s: %m", d, buf);
773     }
774
775   if (mkdir("root/proc", 0755) < 0)
776     die("Cannot create proc: %m");
777   if (mount("none", "root/proc", "proc", 0, "") < 0)
778     die("Cannot mount proc: %m");
779
780   if (chroot("root") < 0)
781     die("Chroot failed: %m");
782
783   if (chdir("root/box") < 0)
784     die("Cannot change current directory: %m");
785 }
786
787 static void
788 setup_credentials(void)
789 {
790   if (setresgid(BOX_GID, BOX_GID, BOX_GID) < 0)
791     die("setresgid: %m");
792   if (setgroups(0, NULL) < 0)
793     die("setgroups: %m");
794   if (setresuid(BOX_UID, BOX_UID, BOX_UID) < 0)
795     die("setresuid: %m");
796   setpgrp();
797 }
798
799 static void
800 setup_fds(void)
801 {
802   if (redir_stdin)
803     {
804       close(0);
805       if (open(redir_stdin, O_RDONLY) != 0)
806         die("open(\"%s\"): %m", redir_stdin);
807     }
808   if (redir_stdout)
809     {
810       close(1);
811       if (open(redir_stdout, O_WRONLY | O_CREAT | O_TRUNC, 0666) != 1)
812         die("open(\"%s\"): %m", redir_stdout);
813     }
814   if (redir_stderr)
815     {
816       close(2);
817       if (open(redir_stderr, O_WRONLY | O_CREAT | O_TRUNC, 0666) != 2)
818         die("open(\"%s\"): %m", redir_stderr);
819     }
820   else
821     dup2(1, 2);
822 }
823
824 static void
825 setup_rlim(const char *res_name, int res, rlim_t limit)
826 {
827   struct rlimit rl = { .rlim_cur = limit, .rlim_max = limit };
828   if (setrlimit(res, &rl) < 0)
829     die("setrlimit(%s, %jd)", res_name, (intmax_t) limit);
830 }
831
832 static void
833 setup_rlimits(void)
834 {
835 #define RLIM(res, val) setup_rlim("RLIMIT_" #res, RLIMIT_##res, val)
836
837   if (memory_limit)
838     RLIM(AS, memory_limit * 1024);
839
840   RLIM(STACK, (stack_limit ? (rlim_t)stack_limit * 1024 : RLIM_INFINITY));
841   RLIM(NOFILE, 64);
842   RLIM(MEMLOCK, 0);
843
844   if (max_processes)
845     RLIM(NPROC, max_processes);
846
847 #undef RLIM
848 }
849
850 static int
851 box_inside(void *arg)
852 {
853   char **args = arg;
854   write_errors_to_fd = error_pipes[1];
855   close(error_pipes[0]);
856
857   cg_enter();
858   setup_root();
859   setup_credentials();
860   setup_fds();
861   setup_rlimits();
862   char **env = setup_environment();
863
864   execve(args[0], args, env);
865   die("execve(\"%s\"): %m", args[0]);
866 }
867
868 /*** Commands ***/
869
870 static void
871 init(void)
872 {
873   msg("Preparing sandbox directory\n");
874   xsystem("rm -rf box");
875   if (mkdir("box", 0700) < 0)
876     die("Cannot create box: %m");
877   if (chown("box", orig_uid, orig_gid) < 0)
878     die("Cannot chown box: %m");
879
880   cg_prepare();
881 }
882
883 static void
884 cleanup(void)
885 {
886   struct stat st;
887   if (stat("box", &st) < 0 || !S_ISDIR(st.st_mode))
888     die("Box directory not found, there isn't anything to clean up");
889
890   msg("Deleting sandbox directory\n");
891   xsystem("rm -rf box");
892   cg_remove();
893 }
894
895 static void
896 run(char **argv)
897 {
898   struct stat st;
899   if (stat("box", &st) < 0 || !S_ISDIR(st.st_mode))
900     die("Box directory not found, did you run `isolate --init'?");
901
902   char cmd[256];
903   snprintf(cmd, sizeof(cmd), "chown -R %d.%d box", BOX_UID, BOX_GID);
904   xsystem(cmd);
905   snprintf(cleanup_cmd, sizeof(cleanup_cmd), "chown -R %d.%d box", orig_uid, orig_gid);
906
907   if (pipe(error_pipes) < 0)
908     die("pipe: %m");
909   for (int i=0; i<2; i++)
910     if (fcntl(error_pipes[i], F_SETFD, fcntl(error_pipes[i], F_GETFD) | FD_CLOEXEC) < 0 ||
911         fcntl(error_pipes[i], F_SETFL, fcntl(error_pipes[i], F_GETFL) | O_NONBLOCK) < 0)
912       die("fcntl on pipe: %m");
913
914   box_pid = clone(
915     box_inside,                 // Function to execute as the body of the new process
916     argv,                       // Pass our stack
917     SIGCHLD | CLONE_NEWIPC | CLONE_NEWNET | CLONE_NEWNS | CLONE_NEWPID,
918     argv);                      // Pass the arguments
919   if (box_pid < 0)
920     die("clone: %m");
921   if (!box_pid)
922     die("clone returned 0");
923   box_keeper();
924 }
925
926 static void
927 show_version(void)
928 {
929   // FIXME
930   printf("Process isolator 0.0\n");
931   printf("(c) 2012 Martin Mares <mj@ucw.cz>\n\n");
932   printf("Sandbox directory: %s\n", BOX_DIR);
933   printf("Sandbox credentials: uid=%u gid=%u\n", BOX_UID, BOX_GID);
934 }
935
936 /*** Options ***/
937
938 static void
939 usage(void)
940 {
941   fprintf(stderr, "Invalid arguments!\n");
942   printf("\
943 Usage: isolate [<options>] <command>\n\
944 \n\
945 Options:\n\
946 -c, --cg[=<parent>]\tPut process in a control group (optionally a sub-group of <parent>)\n\
947     --cg-mem=<size>\tLimit memory usage of the control group to <size> KB\n\
948     --cg-timing\t\tTime limits affects total run time of the control group\n\
949 -E, --env=<var>\tInherit the environment variable <var> from the parent process\n\
950 -E, --env=<var>=<val>\tSet the environment variable <var> to <val>; unset it if <var> is empty\n\
951 -x, --extra-time=<time>\tSet extra timeout, before which a timing-out program is not yet killed,\n\
952 \t\t\tso that its real execution time is reported (seconds, fractions allowed)\n\
953 -e, --full-env\t\tInherit full environment of the parent process\n\
954 -m, --mem=<size>\tLimit address space to <size> KB\n\
955 -M, --meta=<file>\tOutput process information to <file> (name:value)\n\
956 -k, --stack=<size>\tLimit stack size to <size> KB (default: 0=unlimited)\n\
957 -r, --stderr=<file>\tRedirect stderr to <file>\n\
958 -i, --stdin=<file>\tRedirect stdin from <file>\n\
959 -o, --stdout=<file>\tRedirect stdout to <file>\n\
960 -p, --processes[=<max>]\tEnable multiple processes (at most <max> of them); needs --cg\n\
961 -t, --time=<time>\tSet run time limit (seconds, fractions allowed)\n\
962 -v, --verbose\t\tBe verbose (use multiple times for even more verbosity)\n\
963 -w, --wall-time=<time>\tSet wall clock time limit (seconds, fractions allowed)\n\
964 \n\
965 Commands:\n\
966     --init\t\tInitialize sandbox (and its control group when --cg is used)\n\
967     --run -- <cmd> ...\tRun given command within sandbox\n\
968     --cleanup\t\tClean up sandbox\n\
969     --version\t\tDisplay program version and configuration\n\
970 ");
971   exit(2);
972 }
973
974 enum opt_code {
975   OPT_INIT = 256,
976   OPT_RUN,
977   OPT_CLEANUP,
978   OPT_VERSION,
979   OPT_CG_MEM,
980   OPT_CG_TIMING,
981 };
982
983 static const char short_opts[] = "c::eE:i:k:m:M:o:p::r:t:vw:x:";
984
985 static const struct option long_opts[] = {
986   { "cg",               2, NULL, 'c' },
987   { "cg-mem",           1, NULL, OPT_CG_MEM },
988   { "cg-timing",        0, NULL, OPT_CG_TIMING },
989   { "cleanup",          0, NULL, OPT_CLEANUP },
990   { "env",              1, NULL, 'E' },
991   { "extra-time",       1, NULL, 'x' },
992   { "full-env",         0, NULL, 'e' },
993   { "init",             0, NULL, OPT_INIT },
994   { "mem",              1, NULL, 'm' },
995   { "meta",             1, NULL, 'M' },
996   { "processes",        2, NULL, 'p' },
997   { "run",              0, NULL, OPT_RUN },
998   { "stack",            1, NULL, 'k' },
999   { "stderr",           1, NULL, 'r' },
1000   { "stdin",            1, NULL, 'i' },
1001   { "stdout",           1, NULL, 'o' },
1002   { "time",             1, NULL, 't' },
1003   { "verbose",          0, NULL, 'v' },
1004   { "version",          0, NULL, OPT_VERSION },
1005   { "wall-time",        1, NULL, 'w' },
1006   { NULL,               0, NULL, 0 }
1007 };
1008
1009 int
1010 main(int argc, char **argv)
1011 {
1012   int c;
1013   enum opt_code mode = 0;
1014
1015   while ((c = getopt_long(argc, argv, short_opts, long_opts, NULL)) >= 0)
1016     switch (c)
1017       {
1018       case 'c':
1019         if (optarg)
1020           cg_root = optarg;
1021         cg_enable = 1;
1022         break;
1023       case 'e':
1024         pass_environ = 1;
1025         break;
1026       case 'E':
1027         if (!set_env_action(optarg))
1028           usage();
1029         break;
1030       case 'k':
1031         stack_limit = atoi(optarg);
1032         break;
1033       case 'i':
1034         redir_stdin = optarg;
1035         break;
1036       case 'm':
1037         memory_limit = atoi(optarg);
1038         break;
1039       case 'M':
1040         meta_open(optarg);
1041         break;
1042       case 'o':
1043         redir_stdout = optarg;
1044         break;
1045       case 'p':
1046         if (optarg)
1047           max_processes = atoi(optarg);
1048         else
1049           max_processes = 0;
1050         break;
1051       case 'r':
1052         redir_stderr = optarg;
1053         break;
1054       case 't':
1055         timeout = 1000*atof(optarg);
1056         break;
1057       case 'v':
1058         verbose++;
1059         break;
1060       case 'w':
1061         wall_timeout = 1000*atof(optarg);
1062         break;
1063       case 'x':
1064         extra_timeout = 1000*atof(optarg);
1065         break;
1066       case OPT_INIT:
1067       case OPT_RUN:
1068       case OPT_CLEANUP:
1069       case OPT_VERSION:
1070         mode = c;
1071         break;
1072       case OPT_CG_MEM:
1073         cg_memory_limit = atoi(optarg);
1074         break;
1075       case OPT_CG_TIMING:
1076         cg_timing = 1;
1077         break;
1078       default:
1079         usage();
1080       }
1081
1082   if (!mode)
1083     usage();
1084   if (mode == OPT_VERSION)
1085     {
1086       show_version();
1087       return 0;
1088     }
1089
1090   if (geteuid())
1091     die("Must be started as root");
1092   orig_uid = getuid();
1093   orig_gid = getgid();
1094
1095   umask(022);
1096   if (chdir(BOX_DIR) < 0)
1097     die("chdir(%s): %m", BOX_DIR);
1098   cg_init();
1099
1100   switch (mode)
1101     {
1102     case OPT_INIT:
1103       if (optind < argc)
1104         usage();
1105       init();
1106       break;
1107     case OPT_RUN:
1108       if (optind >= argc)
1109         usage();
1110       run(argv+optind);
1111       break;
1112     case OPT_CLEANUP:
1113       if (optind < argc)
1114         usage();
1115       cleanup();
1116       break;
1117     default:
1118       die("Internal error: mode mismatch");
1119     }
1120   exit(0);
1121 }