1 /* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2 * Use of this source code is governed by a BSD-style license that can be
3 * found in the LICENSE file.
4 */
5
6 #include <dlfcn.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <unistd.h>
11
12 #include "libminijail.h"
13 #include "libsyscalls.h"
14
15 #include "elfparse.h"
16 #include "util.h"
17
18 #define IDMAP_LEN 32U
19
set_user(struct minijail * j,const char * arg)20 static void set_user(struct minijail *j, const char *arg)
21 {
22 char *end = NULL;
23 int uid = strtod(arg, &end);
24 if (!*end && *arg) {
25 minijail_change_uid(j, uid);
26 return;
27 }
28
29 if (minijail_change_user(j, arg)) {
30 fprintf(stderr, "Bad user: '%s'\n", arg);
31 exit(1);
32 }
33 }
34
set_group(struct minijail * j,const char * arg)35 static void set_group(struct minijail *j, const char *arg)
36 {
37 char *end = NULL;
38 int gid = strtod(arg, &end);
39 if (!*end && *arg) {
40 minijail_change_gid(j, gid);
41 return;
42 }
43
44 if (minijail_change_group(j, arg)) {
45 fprintf(stderr, "Bad group: '%s'\n", arg);
46 exit(1);
47 }
48 }
49
use_caps(struct minijail * j,const char * arg)50 static void use_caps(struct minijail *j, const char *arg)
51 {
52 uint64_t caps;
53 char *end = NULL;
54 caps = strtoull(arg, &end, 16);
55 if (*end) {
56 fprintf(stderr, "Invalid cap set: '%s'\n", arg);
57 exit(1);
58 }
59 minijail_use_caps(j, caps);
60 }
61
add_binding(struct minijail * j,char * arg)62 static void add_binding(struct minijail *j, char *arg)
63 {
64 char *src = strtok(arg, ",");
65 char *dest = strtok(NULL, ",");
66 char *flags = strtok(NULL, ",");
67 if (!src || !dest) {
68 fprintf(stderr, "Bad binding: %s %s\n", src, dest);
69 exit(1);
70 }
71 if (minijail_bind(j, src, dest, flags ? atoi(flags) : 0)) {
72 fprintf(stderr, "minijail_bind failed.\n");
73 exit(1);
74 }
75 }
76
add_mount(struct minijail * j,char * arg)77 static void add_mount(struct minijail *j, char *arg)
78 {
79 char *src = strtok(arg, ",");
80 char *dest = strtok(NULL, ",");
81 char *type = strtok(NULL, ",");
82 char *flags = strtok(NULL, ",");
83 char *data = strtok(NULL, ",");
84 if (!src || !dest || !type) {
85 fprintf(stderr, "Bad mount: %s %s %s\n", src, dest, type);
86 exit(1);
87 }
88 if (minijail_mount_with_data(j, src, dest, type,
89 flags ? strtoul(flags, NULL, 16) : 0,
90 data)) {
91 fprintf(stderr, "minijail_mount failed.\n");
92 exit(1);
93 }
94 }
95
build_idmap(id_t id,id_t lowerid)96 static char *build_idmap(id_t id, id_t lowerid)
97 {
98 int ret;
99 char *idmap = malloc(IDMAP_LEN);
100 ret = snprintf(idmap, IDMAP_LEN, "%d %d 1", id, lowerid);
101 if (ret < 0 || (size_t)ret >= IDMAP_LEN) {
102 free(idmap);
103 fprintf(stderr, "Could not build id map.\n");
104 exit(1);
105 }
106 return idmap;
107 }
108
usage(const char * progn)109 static void usage(const char *progn)
110 {
111 size_t i;
112 /* clang-format off */
113 printf("Usage: %s [-GhHiIKlLnNprstUvyY]\n"
114 " [-a <table>]\n"
115 " [-b <src>,<dest>[,<writeable>]] [-k <src>,<dest>,<type>[,<flags>][,<data>]]\n"
116 " [-c <caps>] [-C <dir>] [-P <dir>] [-e[file]] [-f <file>] [-g <group>]\n"
117 " [-m[<uid> <loweruid> <count>]*] [-M[<gid> <lowergid> <count>]*]\n"
118 " [-S <file>] [-T <type>] [-u <user>] [-V <file>]\n"
119 " <program> [args...]\n"
120 " -a <table>: Use alternate syscall table <table>.\n"
121 " -b: Bind <src> to <dest> in chroot.\n"
122 " Multiple instances allowed.\n"
123 " -k: Mount <src> at <dest> in chroot.\n"
124 " <flags> and <data> can be specified as in mount(2).\n"
125 " Multiple instances allowed.\n"
126 " -c <caps>: Restrict caps to <caps>.\n"
127 " -C <dir>: chroot(2) to <dir>.\n"
128 " Not compatible with -P.\n"
129 " -P <dir>: pivot_root(2) to <dir> (implies -v).\n"
130 " Not compatible with -C.\n"
131 " -e[file]: Enter new network namespace, or existing one if |file| is provided.\n"
132 " -f <file>: Write the pid of the jailed process to <file>.\n"
133 " -g <group>: Change gid to <group>.\n"
134 " -G: Inherit supplementary groups from uid.\n"
135 " Not compatible with -y.\n"
136 " -y: Keep uid's supplementary groups.\n"
137 " Not compatible with -G.\n"
138 " -h: Help (this message).\n"
139 " -H: Seccomp filter help message.\n"
140 " -i: Exit immediately after fork (do not act as init).\n"
141 " Not compatible with -p.\n"
142 " -I: Run <program> as init (pid 1) inside a new pid namespace (implies -p).\n"
143 " -K: Don't mark all existing mounts as MS_PRIVATE.\n"
144 " -l: Enter new IPC namespace.\n"
145 " -L: Report blocked syscalls to syslog when using seccomp filter.\n"
146 " Forces the following syscalls to be allowed:\n"
147 " ", progn);
148 /* clang-format on */
149 for (i = 0; i < log_syscalls_len; i++)
150 printf("%s ", log_syscalls[i]);
151
152 /* clang-format off */
153 printf("\n"
154 " -m[map]: Set the uid map of a user namespace (implies -pU).\n"
155 " Same arguments as newuidmap(1), multiple mappings should be separated by ',' (comma).\n"
156 " With no mapping, map the current uid to root inside the user namespace.\n"
157 " Not compatible with -b without the 'writable' option.\n"
158 " -M[map]: Set the gid map of a user namespace (implies -pU).\n"
159 " Same arguments as newgidmap(1), multiple mappings should be separated by ',' (comma).\n"
160 " With no mapping, map the current gid to root inside the user namespace.\n"
161 " Not compatible with -b without the 'writable' option.\n"
162 " -n: Set no_new_privs.\n"
163 " -N: Enter a new cgroup namespace.\n"
164 " -p: Enter new pid namespace (implies -vr).\n"
165 " -r: Remount /proc read-only (implies -v).\n"
166 " -s: Use seccomp.\n"
167 " -S <file>: Set seccomp filter using <file>.\n"
168 " E.g., '-S /usr/share/filters/<prog>.$(uname -m)'.\n"
169 " Requires -n when not running as root.\n"
170 " -t[size]: Mount tmpfs at /tmp (implies -v).\n"
171 " Optional argument specifies size (default \"64M\").\n"
172 " -T <type>: Don't access <program> before execve(2), assume <type> ELF binary.\n"
173 " <type> must be 'static' or 'dynamic'.\n"
174 " -u <user>: Change uid to <user>.\n"
175 " -U: Enter new user namespace (implies -p).\n"
176 " -v: Enter new mount namespace.\n"
177 " -V <file>: Enter specified mount namespace.\n"
178 " -w: Create and join a new anonymous session keyring.\n"
179 " -Y: Synchronize seccomp filters across thread group.\n");
180 /* clang-format on */
181 }
182
seccomp_filter_usage(const char * progn)183 static void seccomp_filter_usage(const char *progn)
184 {
185 const struct syscall_entry *entry = syscall_table;
186 printf("Usage: %s -S <policy.file> <program> [args...]\n\n"
187 "System call names supported:\n", progn);
188 for (; entry->name && entry->nr >= 0; ++entry)
189 printf(" %s [%d]\n", entry->name, entry->nr);
190 printf("\nSee minijail0(5) for example policies.\n");
191 }
192
parse_args(struct minijail * j,int argc,char * argv[],int * exit_immediately,ElfType * elftype)193 static int parse_args(struct minijail *j, int argc, char *argv[],
194 int *exit_immediately, ElfType *elftype)
195 {
196 int opt;
197 int use_seccomp_filter = 0;
198 int binding = 0;
199 int pivot_root = 0, chroot = 0;
200 int mount_ns = 0, skip_remount = 0;
201 int inherit_suppl_gids = 0, keep_suppl_gids = 0;
202 const size_t path_max = 4096;
203 char *map;
204 size_t size;
205 const char *filter_path;
206 if (argc > 1 && argv[1][0] != '-')
207 return 1;
208
209 const char *optstring =
210 "u:g:sS:c:C:P:b:V:f:m::M::k:a:e::T:vrGhHinNplLt::IUKwyY";
211 while ((opt = getopt(argc, argv, optstring)) != -1) {
212 switch (opt) {
213 case 'u':
214 set_user(j, optarg);
215 break;
216 case 'g':
217 set_group(j, optarg);
218 break;
219 case 'n':
220 minijail_no_new_privs(j);
221 break;
222 case 's':
223 minijail_use_seccomp(j);
224 break;
225 case 'S':
226 minijail_use_seccomp_filter(j);
227 if (strlen(optarg) >= path_max) {
228 fprintf(stderr, "Filter path is too long.\n");
229 exit(1);
230 }
231 filter_path = strndup(optarg, path_max);
232 if (!filter_path) {
233 fprintf(stderr,
234 "Could not strndup(3) filter path.\n");
235 exit(1);
236 }
237 use_seccomp_filter = 1;
238 break;
239 case 'l':
240 minijail_namespace_ipc(j);
241 break;
242 case 'L':
243 minijail_log_seccomp_filter_failures(j);
244 break;
245 case 'b':
246 add_binding(j, optarg);
247 binding = 1;
248 break;
249 case 'c':
250 use_caps(j, optarg);
251 break;
252 case 'C':
253 if (pivot_root) {
254 fprintf(stderr, "Could not set chroot because "
255 "'-P' was specified.\n");
256 exit(1);
257 }
258 if (0 != minijail_enter_chroot(j, optarg)) {
259 fprintf(stderr, "Could not set chroot.\n");
260 exit(1);
261 }
262 chroot = 1;
263 break;
264 case 'k':
265 add_mount(j, optarg);
266 break;
267 case 'K':
268 minijail_skip_remount_private(j);
269 skip_remount = 1;
270 break;
271 case 'P':
272 if (chroot) {
273 fprintf(stderr,
274 "Could not set pivot_root because "
275 "'-C' was specified.\n");
276 exit(1);
277 }
278 if (0 != minijail_enter_pivot_root(j, optarg)) {
279 fprintf(stderr, "Could not set pivot_root.\n");
280 exit(1);
281 }
282 minijail_namespace_vfs(j);
283 pivot_root = 1;
284 break;
285 case 'f':
286 if (0 != minijail_write_pid_file(j, optarg)) {
287 fprintf(stderr,
288 "Could not prepare pid file path.\n");
289 exit(1);
290 }
291 break;
292 case 't':
293 minijail_namespace_vfs(j);
294 size = 64 * 1024 * 1024;
295 if (optarg != NULL && 0 != parse_size(&size, optarg)) {
296 fprintf(stderr, "Invalid /tmp tmpfs size.\n");
297 exit(1);
298 }
299 minijail_mount_tmp_size(j, size);
300 break;
301 case 'v':
302 minijail_namespace_vfs(j);
303 mount_ns = 1;
304 break;
305 case 'V':
306 minijail_namespace_enter_vfs(j, optarg);
307 break;
308 case 'r':
309 minijail_remount_proc_readonly(j);
310 break;
311 case 'G':
312 if (keep_suppl_gids) {
313 fprintf(stderr,
314 "-y and -G are not compatible.\n");
315 exit(1);
316 }
317 minijail_inherit_usergroups(j);
318 inherit_suppl_gids = 1;
319 break;
320 case 'y':
321 if (inherit_suppl_gids) {
322 fprintf(stderr,
323 "-y and -G are not compatible.\n");
324 exit(1);
325 }
326 minijail_keep_supplementary_gids(j);
327 keep_suppl_gids = 1;
328 break;
329 case 'N':
330 minijail_namespace_cgroups(j);
331 break;
332 case 'p':
333 minijail_namespace_pids(j);
334 break;
335 case 'e':
336 if (optarg)
337 minijail_namespace_enter_net(j, optarg);
338 else
339 minijail_namespace_net(j);
340 break;
341 case 'i':
342 *exit_immediately = 1;
343 break;
344 case 'H':
345 seccomp_filter_usage(argv[0]);
346 exit(1);
347 case 'I':
348 minijail_namespace_pids(j);
349 minijail_run_as_init(j);
350 break;
351 case 'U':
352 minijail_namespace_user(j);
353 minijail_namespace_pids(j);
354 break;
355 case 'm':
356 minijail_namespace_user(j);
357 minijail_namespace_pids(j);
358
359 if (optarg) {
360 map = strdup(optarg);
361 } else {
362 /*
363 * If no map is passed, map the current uid to
364 * root.
365 */
366 map = build_idmap(0, getuid());
367 }
368 if (0 != minijail_uidmap(j, map)) {
369 fprintf(stderr, "Could not set uid map.\n");
370 exit(1);
371 }
372 free(map);
373 break;
374 case 'M':
375 minijail_namespace_user(j);
376 minijail_namespace_pids(j);
377
378 if (optarg) {
379 map = strdup(optarg);
380 } else {
381 /*
382 * If no map is passed, map the current gid to
383 * root.
384 * This means that we're likely *not* running as
385 * root, so we also have to disable
386 * setgroups(2) to be able to set the gid map.
387 * See http://man7.org/linux/man-pages/man7/user_namespaces.7.html
388 */
389 minijail_namespace_user_disable_setgroups(j);
390
391 map = build_idmap(0, getgid());
392 }
393 if (0 != minijail_gidmap(j, map)) {
394 fprintf(stderr, "Could not set gid map.\n");
395 exit(1);
396 }
397 free(map);
398 break;
399 case 'a':
400 if (0 != minijail_use_alt_syscall(j, optarg)) {
401 fprintf(stderr,
402 "Could not set alt-syscall table.\n");
403 exit(1);
404 }
405 break;
406 case 'T':
407 if (!strcmp(optarg, "static"))
408 *elftype = ELFSTATIC;
409 else if (!strcmp(optarg, "dynamic"))
410 *elftype = ELFDYNAMIC;
411 else {
412 fprintf(stderr, "ELF type must be 'static' or "
413 "'dynamic'.\n");
414 exit(1);
415 }
416 break;
417 case 'w':
418 minijail_new_session_keyring(j);
419 break;
420 case 'Y':
421 minijail_set_seccomp_filter_tsync(j);
422 break;
423 default:
424 usage(argv[0]);
425 exit(1);
426 }
427 if (optind < argc && argv[optind][0] != '-')
428 break;
429 }
430
431 /* Only allow bind mounts when entering a chroot or using pivot_root. */
432 if (binding && !(chroot || pivot_root)) {
433 fprintf(stderr, "Can't add bind mounts without chroot or"
434 " pivot_root.\n");
435 exit(1);
436 }
437
438 /*
439 * Remounting / as MS_PRIVATE only happens when entering a new mount
440 * namespace, so skipping it only applies in that case.
441 */
442 if (skip_remount && !mount_ns) {
443 fprintf(stderr, "Can't skip marking mounts as MS_PRIVATE"
444 " without mount namespaces.\n");
445 exit(1);
446 }
447
448 /*
449 * We parse seccomp filters here to make sure we've collected all
450 * cmdline options.
451 */
452 if (use_seccomp_filter) {
453 minijail_parse_seccomp_filters(j, filter_path);
454 free((void*)filter_path);
455 }
456
457 if (argc == optind) {
458 usage(argv[0]);
459 exit(1);
460 }
461
462 return optind;
463 }
464
main(int argc,char * argv[])465 int main(int argc, char *argv[])
466 {
467 struct minijail *j = minijail_new();
468 const char *dl_mesg = NULL;
469 int exit_immediately = 0;
470 ElfType elftype = ELFERROR;
471 int consumed = parse_args(j, argc, argv, &exit_immediately, &elftype);
472 argc -= consumed;
473 argv += consumed;
474
475 if (elftype == ELFERROR) {
476 /*
477 * -T was not specified.
478 * Get the path to the program adjusted for changing root.
479 */
480 char *program_path = minijail_get_original_path(j, argv[0]);
481
482 /* Check that we can access the target program. */
483 if (access(program_path, X_OK)) {
484 fprintf(stderr,
485 "Target program '%s' is not accessible.\n",
486 argv[0]);
487 return 1;
488 }
489
490 /* Check if target is statically or dynamically linked. */
491 elftype = get_elf_linkage(program_path);
492 free(program_path);
493 }
494
495 if (elftype == ELFSTATIC) {
496 /*
497 * Target binary is statically linked so we cannot use
498 * libminijailpreload.so.
499 */
500 minijail_run_no_preload(j, argv[0], argv);
501 } else if (elftype == ELFDYNAMIC) {
502 /*
503 * Target binary is dynamically linked so we can
504 * inject libminijailpreload.so into it.
505 */
506
507 /* Check that we can dlopen() libminijailpreload.so. */
508 if (!dlopen(PRELOADPATH, RTLD_LAZY | RTLD_LOCAL)) {
509 dl_mesg = dlerror();
510 fprintf(stderr, "dlopen(): %s\n", dl_mesg);
511 return 1;
512 }
513 minijail_run(j, argv[0], argv);
514 } else {
515 fprintf(stderr,
516 "Target program '%s' is not a valid ELF file.\n",
517 argv[0]);
518 return 1;
519 }
520
521 if (exit_immediately) {
522 info("not running init loop, exiting immediately");
523 return 0;
524 }
525 return minijail_wait(j);
526 }
527