]> mj.ucw.cz Git - moe.git/blob - isolate/isolate.c
16ea06e10a3ebb6c0616790cb3fa982c57bbc842
[moe.git] / isolate / isolate.c
1 /*
2  *      A Process Isolator based on Linux Containers
3  *
4  *      (c) 2012 Martin Mares <mj@ucw.cz>
5  *      (c) 2012 Bernard Blackham <bernard@blackham.com.au>
6  */
7
8 #define _GNU_SOURCE
9
10 #include "autoconf.h"
11
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 <sched.h>
22 #include <time.h>
23 #include <grp.h>
24 #include <mntent.h>
25 #include <limits.h>
26 #include <sys/wait.h>
27 #include <sys/time.h>
28 #include <sys/signal.h>
29 #include <sys/resource.h>
30 #include <sys/mount.h>
31 #include <sys/stat.h>
32 #include <sys/quota.h>
33 #include <sys/vfs.h>
34
35 #define NONRET __attribute__((noreturn))
36 #define UNUSED __attribute__((unused))
37 #define ARRAY_SIZE(a) (int)(sizeof(a)/sizeof(a[0]))
38
39 static int timeout;                     /* milliseconds */
40 static int wall_timeout;
41 static int extra_timeout;
42 static int pass_environ;
43 static int verbose;
44 static int memory_limit;
45 static int stack_limit;
46 static int block_quota;
47 static int inode_quota;
48 static int max_processes = 1;
49 static char *redir_stdin, *redir_stdout, *redir_stderr;
50
51 static int cg_enable;
52 static int cg_memory_limit;
53 static int cg_timing;
54
55 static int box_id;
56 static char box_dir[1024];
57 static pid_t box_pid;
58
59 static uid_t box_uid;
60 static gid_t box_gid;
61 static uid_t orig_uid;
62 static gid_t orig_gid;
63
64 static int partial_line;
65 static char cleanup_cmd[256];
66
67 static struct timeval start_time;
68 static int ticks_per_sec;
69 static int total_ms, wall_ms;
70 static volatile sig_atomic_t timer_tick;
71
72 static int error_pipes[2];
73 static int write_errors_to_fd;
74 static int read_errors_from_fd;
75
76 static void die(char *msg, ...) NONRET;
77 static void cg_stats(void);
78 static int get_wall_time_ms(void);
79 static int get_run_time_ms(struct rusage *rus);
80
81 /*** Meta-files ***/
82
83 static FILE *metafile;
84
85 static void
86 meta_open(const char *name)
87 {
88   if (!strcmp(name, "-"))
89     {
90       metafile = stdout;
91       return;
92     }
93   metafile = fopen(name, "w");
94   if (!metafile)
95     die("Failed to open metafile '%s'",name);
96 }
97
98 static void
99 meta_close(void)
100 {
101   if (metafile && metafile != stdout)
102     fclose(metafile);
103 }
104
105 static void __attribute__((format(printf,1,2)))
106 meta_printf(const char *fmt, ...)
107 {
108   if (!metafile)
109     return;
110
111   va_list args;
112   va_start(args, fmt);
113   vfprintf(metafile, fmt, args);
114   va_end(args);
115 }
116
117 static void
118 final_stats(struct rusage *rus)
119 {
120   total_ms = get_run_time_ms(rus);
121   wall_ms = get_wall_time_ms();
122
123   meta_printf("time:%d.%03d\n", total_ms/1000, total_ms%1000);
124   meta_printf("time-wall:%d.%03d\n", wall_ms/1000, wall_ms%1000);
125   meta_printf("max-rss:%ld\n", rus->ru_maxrss);
126   meta_printf("csw-voluntary:%ld\n", rus->ru_nvcsw);
127   meta_printf("csw-forced:%ld\n", rus->ru_nivcsw);
128
129   cg_stats();
130 }
131
132 /*** Messages and exits ***/
133
134 static void
135 xsystem(const char *cmd)
136 {
137   int ret = system(cmd);
138   if (ret < 0)
139     die("system(\"%s\"): %m", cmd);
140   if (!WIFEXITED(ret) || WEXITSTATUS(ret))
141     die("system(\"%s\"): Exited with status %d", cmd, ret);
142 }
143
144 static void NONRET
145 box_exit(int rc)
146 {
147   if (box_pid > 0)
148     {
149       kill(-box_pid, SIGKILL);
150       kill(box_pid, SIGKILL);
151       meta_printf("killed:1\n");
152
153       struct rusage rus;
154       int p, stat;
155       do
156         p = wait4(box_pid, &stat, 0, &rus);
157       while (p < 0 && errno == EINTR);
158       if (p < 0)
159         fprintf(stderr, "UGH: Lost track of the process (%m)\n");
160       else
161         final_stats(&rus);
162     }
163
164   if (rc < 2 && cleanup_cmd[0])
165     xsystem(cleanup_cmd);
166
167   meta_close();
168   exit(rc);
169 }
170
171 static void
172 flush_line(void)
173 {
174   if (partial_line)
175     fputc('\n', stderr);
176   partial_line = 0;
177 }
178
179 /* Report an error of the sandbox itself */
180 static void NONRET __attribute__((format(printf,1,2)))
181 die(char *msg, ...)
182 {
183   va_list args;
184   va_start(args, msg);
185   char buf[1024];
186   int n = vsnprintf(buf, sizeof(buf), msg, args);
187
188   if (write_errors_to_fd)
189     {
190       // We are inside the box, have to use error pipe for error reporting.
191       // We hope that the whole error message fits in PIPE_BUF bytes.
192       write(write_errors_to_fd, buf, n);
193       exit(2);
194     }
195
196   // Otherwise, we in the box keeper process, so we report errors normally
197   flush_line();
198   meta_printf("status:XX\nmessage:%s\n", buf);
199   fputs(buf, stderr);
200   fputc('\n', stderr);
201   box_exit(2);
202 }
203
204 /* Report an error of the program inside the sandbox */
205 static void NONRET __attribute__((format(printf,1,2)))
206 err(char *msg, ...)
207 {
208   va_list args;
209   va_start(args, msg);
210   flush_line();
211   if (msg[0] && msg[1] && msg[2] == ':' && msg[3] == ' ')
212     {
213       meta_printf("status:%c%c\n", msg[0], msg[1]);
214       msg += 4;
215     }
216   char buf[1024];
217   vsnprintf(buf, sizeof(buf), msg, args);
218   meta_printf("message:%s\n", buf);
219   fputs(buf, stderr);
220   fputc('\n', stderr);
221   box_exit(1);
222 }
223
224 /* Write a message, but only if in verbose mode */
225 static void __attribute__((format(printf,1,2)))
226 msg(char *msg, ...)
227 {
228   va_list args;
229   va_start(args, msg);
230   if (verbose)
231     {
232       int len = strlen(msg);
233       if (len > 0)
234         partial_line = (msg[len-1] != '\n');
235       vfprintf(stderr, msg, args);
236       fflush(stderr);
237     }
238   va_end(args);
239 }
240
241 /*** Utility functions ***/
242
243 static void *
244 xmalloc(size_t size)
245 {
246   void *p = malloc(size);
247   if (!p)
248     die("Out of memory");
249   return p;
250 }
251
252 static char *
253 xstrdup(char *str)
254 {
255   char *p = strdup(str);
256   if (!p)
257     die("Out of memory");
258   return p;
259 }
260
261 static int dir_exists(char *path)
262 {
263   struct stat st;
264   return (stat(path, &st) >= 0 && S_ISDIR(st.st_mode));
265 }
266
267 /*** Environment rules ***/
268
269 struct env_rule {
270   char *var;                    // Variable to match
271   char *val;                    // ""=clear, NULL=inherit
272   int var_len;
273   struct env_rule *next;
274 };
275
276 static struct env_rule *first_env_rule;
277 static struct env_rule **last_env_rule = &first_env_rule;
278
279 static struct env_rule default_env_rules[] = {
280   { "LIBC_FATAL_STDERR_", "1" }
281 };
282
283 static int
284 set_env_action(char *a0)
285 {
286   struct env_rule *r = xmalloc(sizeof(*r) + strlen(a0) + 1);
287   char *a = (char *)(r+1);
288   strcpy(a, a0);
289
290   char *sep = strchr(a, '=');
291   if (sep == a)
292     return 0;
293   r->var = a;
294   if (sep)
295     {
296       *sep++ = 0;
297       r->val = sep;
298     }
299   else
300     r->val = NULL;
301   *last_env_rule = r;
302   last_env_rule = &r->next;
303   r->next = NULL;
304   return 1;
305 }
306
307 static int
308 match_env_var(char *env_entry, struct env_rule *r)
309 {
310   if (strncmp(env_entry, r->var, r->var_len))
311     return 0;
312   return (env_entry[r->var_len] == '=');
313 }
314
315 static void
316 apply_env_rule(char **env, int *env_sizep, struct env_rule *r)
317 {
318   // First remove the variable if already set
319   int pos = 0;
320   while (pos < *env_sizep && !match_env_var(env[pos], r))
321     pos++;
322   if (pos < *env_sizep)
323     {
324       (*env_sizep)--;
325       env[pos] = env[*env_sizep];
326       env[*env_sizep] = NULL;
327     }
328
329   // What is the new value?
330   char *new;
331   if (r->val)
332     {
333       if (!r->val[0])
334         return;
335       new = xmalloc(r->var_len + 1 + strlen(r->val) + 1);
336       sprintf(new, "%s=%s", r->var, r->val);
337     }
338   else
339     {
340       pos = 0;
341       while (environ[pos] && !match_env_var(environ[pos], r))
342         pos++;
343       if (!(new = environ[pos]))
344         return;
345     }
346
347   // Add it at the end of the array
348   env[(*env_sizep)++] = new;
349   env[*env_sizep] = NULL;
350 }
351
352 static char **
353 setup_environment(void)
354 {
355   // Link built-in rules with user rules
356   for (int i=ARRAY_SIZE(default_env_rules)-1; i >= 0; i--)
357     {
358       default_env_rules[i].next = first_env_rule;
359       first_env_rule = &default_env_rules[i];
360     }
361
362   // Scan the original environment
363   char **orig_env = environ;
364   int orig_size = 0;
365   while (orig_env[orig_size])
366     orig_size++;
367
368   // For each rule, reserve one more slot and calculate length
369   int num_rules = 0;
370   for (struct env_rule *r = first_env_rule; r; r=r->next)
371     {
372       num_rules++;
373       r->var_len = strlen(r->var);
374     }
375
376   // Create a new environment
377   char **env = xmalloc((orig_size + num_rules + 1) * sizeof(char *));
378   int size;
379   if (pass_environ)
380     {
381       memcpy(env, environ, orig_size * sizeof(char *));
382       size = orig_size;
383     }
384   else
385     size = 0;
386   env[size] = NULL;
387
388   // Apply the rules one by one
389   for (struct env_rule *r = first_env_rule; r; r=r->next)
390     apply_env_rule(env, &size, r);
391
392   // Return the new env and pass some gossip
393   if (verbose > 1)
394     {
395       fprintf(stderr, "Passing environment:\n");
396       for (int i=0; env[i]; i++)
397         fprintf(stderr, "\t%s\n", env[i]);
398     }
399   return env;
400 }
401
402 /*** Directory rules ***/
403
404 struct dir_rule {
405   char *inside;                 // A relative path
406   char *outside;                // This can be an absolute path or a relative path starting with "./"
407   unsigned int flags;           // DIR_FLAG_xxx
408   struct dir_rule *next;
409 };
410
411 enum dir_rule_flags {
412   DIR_FLAG_RW = 1,
413   DIR_FLAG_NOEXEC = 2,
414   DIR_FLAG_FS = 4,
415   DIR_FLAG_MAYBE = 8,
416   DIR_FLAG_DEV = 16,
417 };
418
419 static const char * const dir_flag_names[] = { "rw", "noexec", "fs", "maybe", "dev" };
420
421 static struct dir_rule *first_dir_rule;
422 static struct dir_rule **last_dir_rule = &first_dir_rule;
423
424 static int add_dir_rule(char *in, char *out, unsigned int flags)
425 {
426   // Make sure that "in" is relative
427   while (in[0] == '/')
428     in++;
429   if (!*in)
430     return 0;
431
432   // Check "out"
433   if (flags & DIR_FLAG_FS)
434     {
435       if (!out || out[0] == '/')
436         return 0;
437     }
438   else
439     {
440       if (out && out[0] != '/' && strncmp(out, "./", 2))
441         return 0;
442     }
443
444   // Override an existing rule
445   struct dir_rule *r;
446   for (r = first_dir_rule; r; r = r->next)
447     if (!strcmp(r->inside, in))
448       break;
449
450   // Add a new rule
451   if (!r)
452     {
453       r = xmalloc(sizeof(*r));
454       r->inside = in;
455       *last_dir_rule = r;
456       last_dir_rule = &r->next;
457       r->next = NULL;
458     }
459   r->outside = out;
460   r->flags = flags;
461   return 1;
462 }
463
464 static unsigned int parse_dir_option(char *opt)
465 {
466   for (unsigned int i = 0; i < ARRAY_SIZE(dir_flag_names); i++)
467     if (!strcmp(opt, dir_flag_names[i]))
468       return 1U << i;
469   die("Unknown directory option %s", opt);
470 }
471
472 static int set_dir_action(char *arg)
473 {
474   arg = xstrdup(arg);
475
476   char *colon = strchr(arg, ':');
477   unsigned int flags = 0;
478   while (colon)
479     {
480       *colon++ = 0;
481       char *next = strchr(colon, ':');
482       if (next)
483         *next = 0;
484       flags |= parse_dir_option(colon);
485       colon = next;
486     }
487
488   char *eq = strchr(arg, '=');
489   if (eq)
490     {
491       *eq++ = 0;
492       return add_dir_rule(arg, (*eq ? eq : NULL), flags);
493     }
494   else
495     {
496       char *out = xmalloc(1 + strlen(arg) + 1);
497       sprintf(out, "/%s", arg);
498       return add_dir_rule(arg, out, flags);
499     }
500 }
501
502 static void init_dir_rules(void)
503 {
504   set_dir_action("box=./box:rw");
505   set_dir_action("bin");
506   set_dir_action("dev:dev");
507   set_dir_action("lib");
508   set_dir_action("lib64:maybe");
509   set_dir_action("proc=proc:fs");
510   set_dir_action("usr");
511 }
512
513 static void make_dir(char *path)
514 {
515   char *sep = (path[0] == '/' ? path+1 : path);
516
517   for (;;)
518     {
519       sep = strchr(sep, '/');
520       if (sep)
521         *sep = 0;
522
523       if (!dir_exists(path) && mkdir(path, 0777) < 0)
524         die("Cannot create directory %s: %m\n", path);
525
526       if (!sep)
527         return;
528       *sep++ = '/';
529     }
530 }
531
532 static void apply_dir_rules(void)
533 {
534   for (struct dir_rule *r = first_dir_rule; r; r=r->next)
535     {
536       char *in = r->inside;
537       char *out = r->outside;
538       if (!out)
539         {
540           msg("Not binding anything on %s\n", r->inside);
541           continue;
542         }
543
544       if ((r->flags & DIR_FLAG_MAYBE) && !dir_exists(out))
545         {
546           msg("Not binding %s on %s (does not exist)\n", out, r->inside);
547           continue;
548         }
549
550       char root_in[1024];
551       snprintf(root_in, sizeof(root_in), "root/%s", in);
552       make_dir(root_in);
553
554       unsigned long mount_flags = 0;
555       if (!(r->flags & DIR_FLAG_RW))
556         mount_flags |= MS_RDONLY;
557       if (r->flags & DIR_FLAG_NOEXEC)
558         mount_flags |= MS_NOEXEC;
559       if (!(r->flags & DIR_FLAG_DEV))
560         mount_flags |= MS_NODEV;
561
562       if (r->flags & DIR_FLAG_FS)
563         {
564           msg("Mounting %s on %s (flags %lx)\n", out, in, mount_flags);
565           if (mount("none", root_in, out, mount_flags, "") < 0)
566             die("Cannot mount %s on %s: %m", out, in);
567         }
568       else
569         {
570           mount_flags |= MS_BIND | MS_NOSUID;
571           msg("Binding %s on %s (flags %lx)\n", out, in, mount_flags);
572           // Most mount flags need remount to work
573           if (mount(out, root_in, "none", mount_flags, "") < 0 ||
574               mount(out, root_in, "none", MS_REMOUNT | mount_flags, "") < 0)
575             die("Cannot mount %s on %s: %m", out, in);
576         }
577     }
578 }
579
580 /*** Control groups ***/
581
582 static char cg_path[256];
583
584 #define CG_BUFSIZE 1024
585
586 static int
587 cg_read(char *attr, char *buf)
588 {
589   int maybe = 0;
590   if (attr[0] == '?')
591     {
592       attr++;
593       maybe = 1;
594     }
595
596   char path[256];
597   snprintf(path, sizeof(path), "%s/%s", cg_path, attr);
598
599   int fd = open(path, O_RDONLY);
600   if (fd < 0)
601     {
602       if (maybe)
603         return 0;
604       die("Cannot read %s: %m", path);
605     }
606
607   int n = read(fd, buf, CG_BUFSIZE);
608   if (n < 0)
609     die("Cannot read %s: %m", path);
610   if (n >= CG_BUFSIZE - 1)
611     die("Attribute %s too long", path);
612   if (n > 0 && buf[n-1] == '\n')
613     n--;
614   buf[n] = 0;
615
616   if (verbose > 1)
617     msg("CG: Read %s = %s\n", attr, buf);
618
619   close(fd);
620   return 1;
621 }
622
623 static void __attribute__((format(printf,2,3)))
624 cg_write(char *attr, char *fmt, ...)
625 {
626   va_list args;
627   va_start(args, fmt);
628
629   char buf[CG_BUFSIZE];
630   int n = vsnprintf(buf, sizeof(buf), fmt, args);
631   if (n >= CG_BUFSIZE)
632     die("cg_writef: Value for attribute %s is too long", attr);
633
634   if (verbose > 1)
635     msg("CG: Write %s = %s", attr, buf);
636
637   char path[256];
638   snprintf(path, sizeof(path), "%s/%s", cg_path, attr);
639
640   int fd = open(path, O_WRONLY | O_TRUNC);
641   if (fd < 0)
642     die("Cannot write %s: %m", path);
643
644   int written = write(fd, buf, n);
645   if (written < 0)
646     die("Cannot set %s to %s: %m", path, buf);
647   if (written != n)
648     die("Short write to %s (%d out of %d bytes)", path, written, n);
649
650   close(fd);
651   va_end(args);
652 }
653
654 static void
655 cg_init(void)
656 {
657   if (!cg_enable)
658     return;
659
660   char *cg_root = CONFIG_ISOLATE_CGROUP_ROOT;
661   if (!dir_exists(cg_root))
662     die("Control group filesystem at %s not mounted", cg_root);
663
664   snprintf(cg_path, sizeof(cg_path), "%s/box-%d", cg_root, box_id);
665   msg("Using control group %s\n", cg_path);
666 }
667
668 static void
669 cg_prepare(void)
670 {
671   if (!cg_enable)
672     return;
673
674   struct stat st;
675   char buf[CG_BUFSIZE];
676
677   if (stat(cg_path, &st) >= 0 || errno != ENOENT)
678     {
679       msg("Control group %s already exists, trying to empty it.\n", cg_path);
680       if (rmdir(cg_path) < 0)
681         die("Failed to reset control group %s: %m", cg_path);
682     }
683
684   if (mkdir(cg_path, 0777) < 0)
685     die("Failed to create control group %s: %m", cg_path);
686
687   // If cpuset module is enabled, copy allowed cpus and memory nodes from parent group
688   if (cg_read("?../cpuset.cpus", buf))
689     cg_write("cpuset.cpus", "%s", buf);
690   if (cg_read("?../cpuset.mems", buf))
691     cg_write("cpuset.mems", "%s", buf);
692 }
693
694 static void
695 cg_enter(void)
696 {
697   if (!cg_enable)
698     return;
699
700   msg("Entering control group %s\n", cg_path);
701
702   struct stat st;
703   if (stat(cg_path, &st) < 0)
704     die("Control group %s does not exist: %m", cg_path);
705
706   if (cg_memory_limit)
707     {
708       cg_write("memory.limit_in_bytes", "%lld\n", (long long) cg_memory_limit << 10);
709       cg_write("memory.memsw.limit_in_bytes", "%lld\n", (long long) cg_memory_limit << 10);
710     }
711
712   if (cg_timing)
713     cg_write("cpuacct.usage", "0\n");
714
715   cg_write("tasks", "%d\n", (int) getpid());
716 }
717
718 static int
719 cg_get_run_time_ms(void)
720 {
721   if (!cg_enable)
722     return 0;
723
724   char buf[CG_BUFSIZE];
725   cg_read("cpuacct.usage", buf);
726   unsigned long long ns = atoll(buf);
727   return ns / 1000000;
728 }
729
730 static void
731 cg_stats(void)
732 {
733   if (!cg_enable)
734     return;
735
736   char buf[CG_BUFSIZE];
737
738   // Memory usage statistics
739   unsigned long long mem=0, memsw=0;
740   if (cg_read("?memory.max_usage_in_bytes", buf))
741     mem = atoll(buf);
742   if (cg_read("?memory.memsw.max_usage_in_bytes", buf))
743     {
744       memsw = atoll(buf);
745       if (memsw > mem)
746         mem = memsw;
747     }
748   if (mem)
749     meta_printf("cg-mem:%lld\n", mem >> 10);
750 }
751
752 static void
753 cg_remove(void)
754 {
755   char buf[CG_BUFSIZE];
756
757   if (!cg_enable)
758     return;
759
760   cg_read("tasks", buf);
761   if (buf[0])
762     die("Some tasks left in control group %s, failed to remove it", cg_path);
763
764   if (rmdir(cg_path) < 0)
765     die("Cannot remove control group %s: %m", cg_path);
766 }
767
768 /*** Disk quotas ***/
769
770 static int
771 path_begins_with(char *path, char *with)
772 {
773   while (*with)
774     if (*path++ != *with++)
775       return 0;
776   return (!*with || *with == '/');
777 }
778
779 static char *
780 find_device(char *path)
781 {
782   FILE *f = setmntent("/proc/mounts", "r");
783   if (!f)
784     die("Cannot open /proc/mounts: %m");
785
786   struct mntent *me;
787   int best_len = 0;
788   char *best_dev = NULL;
789   while (me = getmntent(f))
790     {
791       if (!path_begins_with(me->mnt_fsname, "/dev"))
792         continue;
793       if (path_begins_with(path, me->mnt_dir))
794         {
795           int len = strlen(me->mnt_dir);
796           if (len > best_len)
797             {
798               best_len = len;
799               free(best_dev);
800               best_dev = xstrdup(me->mnt_fsname);
801             }
802         }
803     }
804   endmntent(f);
805   return best_dev;
806 }
807
808 static void
809 set_quota(void)
810 {
811   if (!block_quota)
812     return;
813
814   char cwd[PATH_MAX];
815   if (!getcwd(cwd, sizeof(cwd)))
816     die("getcwd: %m");
817
818   char *dev = find_device(cwd);
819   if (!dev)
820     die("Cannot identify filesystem which contains %s", cwd);
821   msg("Quota: Mapped path %s to a filesystem on %s\n", cwd, dev);
822
823   // Sanity check
824   struct stat dev_st, cwd_st;
825   if (stat(dev, &dev_st) < 0)
826     die("Cannot identify block device %s: %m", dev);
827   if (!S_ISBLK(dev_st.st_mode))
828     die("Expected that %s is a block device", dev);
829   if (stat(".", &cwd_st) < 0)
830     die("Cannot stat cwd: %m");
831   if (cwd_st.st_dev != dev_st.st_rdev)
832     die("Identified %s as a filesystem on %s, but it is obviously false", cwd, dev);
833
834   struct dqblk dq = {
835     .dqb_bhardlimit = block_quota,
836     .dqb_bsoftlimit = block_quota,
837     .dqb_ihardlimit = inode_quota,
838     .dqb_isoftlimit = inode_quota,
839     .dqb_valid = QIF_LIMITS,
840   };
841   if (quotactl(QCMD(Q_SETQUOTA, USRQUOTA), dev, box_uid, (caddr_t) &dq) < 0)
842     die("Cannot set disk quota: %m");
843   msg("Quota: Set block quota %d and inode quota %d\n", block_quota, inode_quota);
844
845   free(dev);
846 }
847
848 /*** The keeper process ***/
849
850 static void
851 signal_alarm(int unused UNUSED)
852 {
853   /* Time limit checks are synchronous, so we only schedule them there. */
854   timer_tick = 1;
855   alarm(1);
856 }
857
858 static void
859 signal_int(int unused UNUSED)
860 {
861   /* Interrupts are fatal, so no synchronization requirements. */
862   meta_printf("exitsig:%d\n", SIGINT);
863   err("SG: Interrupted");
864 }
865
866 #define PROC_BUF_SIZE 4096
867 static void
868 read_proc_file(char *buf, char *name, int *fdp)
869 {
870   int c;
871
872   if (!*fdp)
873     {
874       sprintf(buf, "/proc/%d/%s", (int) box_pid, name);
875       *fdp = open(buf, O_RDONLY);
876       if (*fdp < 0)
877         die("open(%s): %m", buf);
878     }
879   lseek(*fdp, 0, SEEK_SET);
880   if ((c = read(*fdp, buf, PROC_BUF_SIZE-1)) < 0)
881     die("read on /proc/$pid/%s: %m", name);
882   if (c >= PROC_BUF_SIZE-1)
883     die("/proc/$pid/%s too long", name);
884   buf[c] = 0;
885 }
886
887 static int
888 get_wall_time_ms(void)
889 {
890   struct timeval now, wall;
891   gettimeofday(&now, NULL);
892   timersub(&now, &start_time, &wall);
893   return wall.tv_sec*1000 + wall.tv_usec/1000;
894 }
895
896 static int
897 get_run_time_ms(struct rusage *rus)
898 {
899   if (cg_timing)
900     return cg_get_run_time_ms();
901
902   if (rus)
903     {
904       struct timeval total;
905       timeradd(&rus->ru_utime, &rus->ru_stime, &total);
906       return total.tv_sec*1000 + total.tv_usec/1000;
907     }
908
909   char buf[PROC_BUF_SIZE], *x;
910   int utime, stime;
911   static int proc_stat_fd;
912
913   read_proc_file(buf, "stat", &proc_stat_fd);
914   x = buf;
915   while (*x && *x != ' ')
916     x++;
917   while (*x == ' ')
918     x++;
919   if (*x++ != '(')
920     die("proc stat syntax error 1");
921   while (*x && (*x != ')' || x[1] != ' '))
922     x++;
923   while (*x == ')' || *x == ' ')
924     x++;
925   if (sscanf(x, "%*c %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %d %d", &utime, &stime) != 2)
926     die("proc stat syntax error 2");
927
928   return (utime + stime) * 1000 / ticks_per_sec;
929 }
930
931 static void
932 check_timeout(void)
933 {
934   if (wall_timeout)
935     {
936       int wall_ms = get_wall_time_ms();
937       if (wall_ms > wall_timeout)
938         err("TO: Time limit exceeded (wall clock)");
939       if (verbose > 1)
940         fprintf(stderr, "[wall time check: %d msec]\n", wall_ms);
941     }
942   if (timeout)
943     {
944       int ms = get_run_time_ms(NULL);
945       if (verbose > 1)
946         fprintf(stderr, "[time check: %d msec]\n", ms);
947       if (ms > timeout && ms > extra_timeout)
948         err("TO: Time limit exceeded");
949     }
950 }
951
952 static void
953 box_keeper(void)
954 {
955   read_errors_from_fd = error_pipes[0];
956   close(error_pipes[1]);
957
958   struct sigaction sa;
959   bzero(&sa, sizeof(sa));
960   sa.sa_handler = signal_int;
961   sigaction(SIGINT, &sa, NULL);
962
963   gettimeofday(&start_time, NULL);
964   ticks_per_sec = sysconf(_SC_CLK_TCK);
965   if (ticks_per_sec <= 0)
966     die("Invalid ticks_per_sec!");
967
968   if (timeout || wall_timeout)
969     {
970       sa.sa_handler = signal_alarm;
971       sigaction(SIGALRM, &sa, NULL);
972       alarm(1);
973     }
974
975   for(;;)
976     {
977       struct rusage rus;
978       int stat;
979       pid_t p;
980       if (timer_tick)
981         {
982           check_timeout();
983           timer_tick = 0;
984         }
985       p = wait4(box_pid, &stat, 0, &rus);
986       if (p < 0)
987         {
988           if (errno == EINTR)
989             continue;
990           die("wait4: %m");
991         }
992       if (p != box_pid)
993         die("wait4: unknown pid %d exited!", p);
994       box_pid = 0;
995
996       // Check error pipe if there is an internal error passed from inside the box
997       char interr[1024];
998       int n = read(read_errors_from_fd, interr, sizeof(interr) - 1);
999       if (n > 0)
1000         {
1001           interr[n] = 0;
1002           die("%s", interr);
1003         }
1004
1005       if (WIFEXITED(stat))
1006         {
1007           final_stats(&rus);
1008           if (WEXITSTATUS(stat))
1009             {
1010               meta_printf("exitcode:%d\n", WEXITSTATUS(stat));
1011               err("RE: Exited with error status %d", WEXITSTATUS(stat));
1012             }
1013           if (timeout && total_ms > timeout)
1014             err("TO: Time limit exceeded");
1015           if (wall_timeout && wall_ms > wall_timeout)
1016             err("TO: Time limit exceeded (wall clock)");
1017           flush_line();
1018           fprintf(stderr, "OK (%d.%03d sec real, %d.%03d sec wall)\n",
1019               total_ms/1000, total_ms%1000,
1020               wall_ms/1000, wall_ms%1000);
1021           box_exit(0);
1022         }
1023       else if (WIFSIGNALED(stat))
1024         {
1025           meta_printf("exitsig:%d\n", WTERMSIG(stat));
1026           final_stats(&rus);
1027           err("SG: Caught fatal signal %d", WTERMSIG(stat));
1028         }
1029       else if (WIFSTOPPED(stat))
1030         {
1031           meta_printf("exitsig:%d\n", WSTOPSIG(stat));
1032           final_stats(&rus);
1033           err("SG: Stopped by signal %d", WSTOPSIG(stat));
1034         }
1035       else
1036         die("wait4: unknown status %x, giving up!", stat);
1037     }
1038 }
1039
1040 /*** The process running inside the box ***/
1041
1042 static void
1043 setup_root(void)
1044 {
1045   if (mkdir("root", 0750) < 0 && errno != EEXIST)
1046     die("mkdir('root'): %m");
1047
1048   if (mount("none", "root", "tmpfs", 0, "mode=755") < 0)
1049     die("Cannot mount root ramdisk: %m");
1050
1051   apply_dir_rules();
1052
1053   if (chroot("root") < 0)
1054     die("Chroot failed: %m");
1055
1056   if (chdir("root/box") < 0)
1057     die("Cannot change current directory: %m");
1058 }
1059
1060 static void
1061 setup_credentials(void)
1062 {
1063   if (setresgid(box_gid, box_gid, box_gid) < 0)
1064     die("setresgid: %m");
1065   if (setgroups(0, NULL) < 0)
1066     die("setgroups: %m");
1067   if (setresuid(box_uid, box_uid, box_uid) < 0)
1068     die("setresuid: %m");
1069   setpgrp();
1070 }
1071
1072 static void
1073 setup_fds(void)
1074 {
1075   if (redir_stdin)
1076     {
1077       close(0);
1078       if (open(redir_stdin, O_RDONLY) != 0)
1079         die("open(\"%s\"): %m", redir_stdin);
1080     }
1081   if (redir_stdout)
1082     {
1083       close(1);
1084       if (open(redir_stdout, O_WRONLY | O_CREAT | O_TRUNC, 0666) != 1)
1085         die("open(\"%s\"): %m", redir_stdout);
1086     }
1087   if (redir_stderr)
1088     {
1089       close(2);
1090       if (open(redir_stderr, O_WRONLY | O_CREAT | O_TRUNC, 0666) != 2)
1091         die("open(\"%s\"): %m", redir_stderr);
1092     }
1093   else
1094     dup2(1, 2);
1095 }
1096
1097 static void
1098 setup_rlim(const char *res_name, int res, rlim_t limit)
1099 {
1100   struct rlimit rl = { .rlim_cur = limit, .rlim_max = limit };
1101   if (setrlimit(res, &rl) < 0)
1102     die("setrlimit(%s, %jd)", res_name, (intmax_t) limit);
1103 }
1104
1105 static void
1106 setup_rlimits(void)
1107 {
1108 #define RLIM(res, val) setup_rlim("RLIMIT_" #res, RLIMIT_##res, val)
1109
1110   if (memory_limit)
1111     RLIM(AS, memory_limit * 1024);
1112
1113   RLIM(STACK, (stack_limit ? (rlim_t)stack_limit * 1024 : RLIM_INFINITY));
1114   RLIM(NOFILE, 64);
1115   RLIM(MEMLOCK, 0);
1116
1117   if (max_processes)
1118     RLIM(NPROC, max_processes);
1119
1120 #undef RLIM
1121 }
1122
1123 static int
1124 box_inside(void *arg)
1125 {
1126   char **args = arg;
1127   write_errors_to_fd = error_pipes[1];
1128   close(error_pipes[0]);
1129
1130   cg_enter();
1131   setup_root();
1132   setup_credentials();
1133   setup_fds();
1134   setup_rlimits();
1135   char **env = setup_environment();
1136
1137   execve(args[0], args, env);
1138   die("execve(\"%s\"): %m", args[0]);
1139 }
1140
1141 static void
1142 box_init(void)
1143 {
1144   if (box_id < 0 || box_id >= CONFIG_ISOLATE_NUM_BOXES)
1145     die("Sandbox ID out of range (allowed: 0-%d)", CONFIG_ISOLATE_NUM_BOXES-1);
1146   box_uid = CONFIG_ISOLATE_FIRST_UID + box_id;
1147   box_gid = CONFIG_ISOLATE_FIRST_GID + box_id;
1148
1149   snprintf(box_dir, sizeof(box_dir), "%s/%d", CONFIG_ISOLATE_BOX_DIR, box_id);
1150   make_dir(box_dir);
1151   if (chdir(box_dir) < 0)
1152     die("chdir(%s): %m", box_dir);
1153 }
1154
1155 /*** Commands ***/
1156
1157 static void
1158 init(void)
1159 {
1160   msg("Preparing sandbox directory\n");
1161   xsystem("rm -rf box");
1162   if (mkdir("box", 0700) < 0)
1163     die("Cannot create box: %m");
1164   if (chown("box", orig_uid, orig_gid) < 0)
1165     die("Cannot chown box: %m");
1166
1167   cg_prepare();
1168   set_quota();
1169
1170   puts(box_dir);
1171 }
1172
1173 static void
1174 cleanup(void)
1175 {
1176   if (!dir_exists("box"))
1177     die("Box directory not found, there isn't anything to clean up");
1178
1179   msg("Deleting sandbox directory\n");
1180   xsystem("rm -rf *");
1181   if (rmdir(box_dir) < 0)
1182     die("Cannot remove %s: %m", box_dir);
1183   cg_remove();
1184 }
1185
1186 static void
1187 run(char **argv)
1188 {
1189   if (!dir_exists("box"))
1190     die("Box directory not found, did you run `isolate --init'?");
1191
1192   char cmd[256];
1193   snprintf(cmd, sizeof(cmd), "chown -R %d.%d box", box_uid, box_gid);
1194   xsystem(cmd);
1195   snprintf(cleanup_cmd, sizeof(cleanup_cmd), "chown -R %d.%d box", orig_uid, orig_gid);
1196
1197   if (pipe(error_pipes) < 0)
1198     die("pipe: %m");
1199   for (int i=0; i<2; i++)
1200     if (fcntl(error_pipes[i], F_SETFD, fcntl(error_pipes[i], F_GETFD) | FD_CLOEXEC) < 0 ||
1201         fcntl(error_pipes[i], F_SETFL, fcntl(error_pipes[i], F_GETFL) | O_NONBLOCK) < 0)
1202       die("fcntl on pipe: %m");
1203
1204   box_pid = clone(
1205     box_inside,                 // Function to execute as the body of the new process
1206     argv,                       // Pass our stack
1207     SIGCHLD | CLONE_NEWIPC | CLONE_NEWNET | CLONE_NEWNS | CLONE_NEWPID,
1208     argv);                      // Pass the arguments
1209   if (box_pid < 0)
1210     die("clone: %m");
1211   if (!box_pid)
1212     die("clone returned 0");
1213   box_keeper();
1214 }
1215
1216 static void
1217 show_version(void)
1218 {
1219   printf("Process isolator 1.0\n");
1220   printf("(c) 2012 Martin Mares and Bernard Blackham\n");
1221   printf("\nCompile-time configuration:\n");
1222   printf("Sandbox directory: %s\n", CONFIG_ISOLATE_BOX_DIR);
1223   printf("Sandbox credentials: uid=%u-%u gid=%u-%u\n",
1224     CONFIG_ISOLATE_FIRST_UID,
1225     CONFIG_ISOLATE_FIRST_UID + CONFIG_ISOLATE_NUM_BOXES - 1,
1226     CONFIG_ISOLATE_FIRST_GID,
1227     CONFIG_ISOLATE_FIRST_GID + CONFIG_ISOLATE_NUM_BOXES - 1);
1228 }
1229
1230 /*** Options ***/
1231
1232 static void __attribute__((format(printf,1,2)))
1233 usage(const char *msg, ...)
1234 {
1235   if (msg != NULL)
1236     {
1237       va_list args;
1238       va_start(args, msg);
1239       vfprintf(stderr, msg, args);
1240       va_end(args);
1241     }
1242   printf("\
1243 Usage: isolate [<options>] <command>\n\
1244 \n\
1245 Options:\n\
1246 -b, --box-id=<id>\tWhen multiple sandboxes are used in parallel, each must get a unique ID\n\
1247 -c, --cg[=<parent>]\tPut process in a control group (optionally a sub-group of <parent>)\n\
1248     --cg-mem=<size>\tLimit memory usage of the control group to <size> KB\n\
1249     --cg-timing\t\tTime limits affects total run time of the control group\n\
1250 -d, --dir=<dir>\t\tMake a directory <dir> visible inside the sandbox\n\
1251     --dir=<in>=<out>\tMake a directory <out> outside visible as <in> inside\n\
1252     --dir=<in>=\t\tDelete a previously defined directory rule (even a default one)\n\
1253     --dir=...:<opt>\tSpecify options for a rule:\n\
1254 \t\t\t\tdev\tAllow access to special files\n\
1255 \t\t\t\tfs\tMount a filesystem (e.g., --dir=/proc:proc:fs)\n\
1256 \t\t\t\tmaybe\tSkip the rule if <out> does not exist\n\
1257 \t\t\t\tnoexec\tDo not allow execution of binaries\n\
1258 \t\t\t\trw\tAllow read-write access\n\
1259 -E, --env=<var>\t\tInherit the environment variable <var> from the parent process\n\
1260 -E, --env=<var>=<val>\tSet the environment variable <var> to <val>; unset it if <var> is empty\n\
1261 -x, --extra-time=<time>\tSet extra timeout, before which a timing-out program is not yet killed,\n\
1262 \t\t\tso that its real execution time is reported (seconds, fractions allowed)\n\
1263 -e, --full-env\t\tInherit full environment of the parent process\n\
1264 -m, --mem=<size>\tLimit address space to <size> KB\n\
1265 -M, --meta=<file>\tOutput process information to <file> (name:value)\n\
1266 -q, --quota=<blk>,<ino>\tSet disk quota to <blk> blocks and <ino> inodes\n\
1267 -k, --stack=<size>\tLimit stack size to <size> KB (default: 0=unlimited)\n\
1268 -r, --stderr=<file>\tRedirect stderr to <file>\n\
1269 -i, --stdin=<file>\tRedirect stdin from <file>\n\
1270 -o, --stdout=<file>\tRedirect stdout to <file>\n\
1271 -p, --processes[=<max>]\tEnable multiple processes (at most <max> of them); needs --cg\n\
1272 -t, --time=<time>\tSet run time limit (seconds, fractions allowed)\n\
1273 -v, --verbose\t\tBe verbose (use multiple times for even more verbosity)\n\
1274 -w, --wall-time=<time>\tSet wall clock time limit (seconds, fractions allowed)\n\
1275 \n\
1276 Commands:\n\
1277     --init\t\tInitialize sandbox (and its control group when --cg is used)\n\
1278     --run -- <cmd> ...\tRun given command within sandbox\n\
1279     --cleanup\t\tClean up sandbox\n\
1280     --version\t\tDisplay program version and configuration\n\
1281 ");
1282   exit(2);
1283 }
1284
1285 enum opt_code {
1286   OPT_INIT = 256,
1287   OPT_RUN,
1288   OPT_CLEANUP,
1289   OPT_VERSION,
1290   OPT_CG_MEM,
1291   OPT_CG_TIMING,
1292 };
1293
1294 static const char short_opts[] = "b:c:d:eE:i:k:m:M:o:p::q:r:t:vw:x:";
1295
1296 static const struct option long_opts[] = {
1297   { "box-id",           1, NULL, 'b' },
1298   { "cg",               1, NULL, 'c' },
1299   { "cg-mem",           1, NULL, OPT_CG_MEM },
1300   { "cg-timing",        0, NULL, OPT_CG_TIMING },
1301   { "cleanup",          0, NULL, OPT_CLEANUP },
1302   { "dir",              1, NULL, 'd' },
1303   { "env",              1, NULL, 'E' },
1304   { "extra-time",       1, NULL, 'x' },
1305   { "full-env",         0, NULL, 'e' },
1306   { "init",             0, NULL, OPT_INIT },
1307   { "mem",              1, NULL, 'm' },
1308   { "meta",             1, NULL, 'M' },
1309   { "processes",        2, NULL, 'p' },
1310   { "quota",            1, NULL, 'q' },
1311   { "run",              0, NULL, OPT_RUN },
1312   { "stack",            1, NULL, 'k' },
1313   { "stderr",           1, NULL, 'r' },
1314   { "stdin",            1, NULL, 'i' },
1315   { "stdout",           1, NULL, 'o' },
1316   { "time",             1, NULL, 't' },
1317   { "verbose",          0, NULL, 'v' },
1318   { "version",          0, NULL, OPT_VERSION },
1319   { "wall-time",        1, NULL, 'w' },
1320   { NULL,               0, NULL, 0 }
1321 };
1322
1323 int
1324 main(int argc, char **argv)
1325 {
1326   int c;
1327   char *sep;
1328   enum opt_code mode = 0;
1329
1330   init_dir_rules();
1331
1332   while ((c = getopt_long(argc, argv, short_opts, long_opts, NULL)) >= 0)
1333     switch (c)
1334       {
1335       case 'b':
1336         box_id = atoi(optarg);
1337         break;
1338       case 'c':
1339         cg_enable = 1;
1340         break;
1341       case 'd':
1342         if (!set_dir_action(optarg))
1343           usage("Invalid directory specified: %s\n", optarg);
1344         break;
1345       case 'e':
1346         pass_environ = 1;
1347         break;
1348       case 'E':
1349         if (!set_env_action(optarg))
1350           usage("Invalid environment specified: %s\n", optarg);
1351         break;
1352       case 'k':
1353         stack_limit = atoi(optarg);
1354         break;
1355       case 'i':
1356         redir_stdin = optarg;
1357         break;
1358       case 'm':
1359         memory_limit = atoi(optarg);
1360         break;
1361       case 'M':
1362         meta_open(optarg);
1363         break;
1364       case 'o':
1365         redir_stdout = optarg;
1366         break;
1367       case 'p':
1368         if (optarg)
1369           max_processes = atoi(optarg);
1370         else
1371           max_processes = 0;
1372         break;
1373       case 'q':
1374         sep = strchr(optarg, ',');
1375         if (!sep)
1376           usage("Invalid quota specified: %s\n", optarg);
1377         block_quota = atoi(optarg);
1378         inode_quota = atoi(sep+1);
1379         break;
1380       case 'r':
1381         redir_stderr = optarg;
1382         break;
1383       case 't':
1384         timeout = 1000*atof(optarg);
1385         break;
1386       case 'v':
1387         verbose++;
1388         break;
1389       case 'w':
1390         wall_timeout = 1000*atof(optarg);
1391         break;
1392       case 'x':
1393         extra_timeout = 1000*atof(optarg);
1394         break;
1395       case OPT_INIT:
1396       case OPT_RUN:
1397       case OPT_CLEANUP:
1398       case OPT_VERSION:
1399         mode = c;
1400         break;
1401       case OPT_CG_MEM:
1402         cg_memory_limit = atoi(optarg);
1403         break;
1404       case OPT_CG_TIMING:
1405         cg_timing = 1;
1406         break;
1407       default:
1408         usage(NULL);
1409       }
1410
1411   if (!mode)
1412     usage("Please specify an isolate command (e.g. --init, --run).\n");
1413   if (mode == OPT_VERSION)
1414     {
1415       show_version();
1416       return 0;
1417     }
1418
1419   if (geteuid())
1420     die("Must be started as root");
1421   orig_uid = getuid();
1422   orig_gid = getgid();
1423
1424   umask(022);
1425   box_init();
1426   cg_init();
1427
1428   switch (mode)
1429     {
1430     case OPT_INIT:
1431       if (optind < argc)
1432         usage("--init mode takes no parameters\n");
1433       init();
1434       break;
1435     case OPT_RUN:
1436       if (optind >= argc)
1437         usage("--run mode requires a command to run\n");
1438       run(argv+optind);
1439       break;
1440     case OPT_CLEANUP:
1441       if (optind < argc)
1442         usage("--cleanup mode takes no parameters\n");
1443       cleanup();
1444       break;
1445     default:
1446       die("Internal error: mode mismatch");
1447     }
1448   exit(0);
1449 }