• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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