libminijail.c revision e1749eb93a119bf03b5b033d74c541dbb45be00e
1/* Copyright (c) 2011 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#define _BSD_SOURCE
7#define _GNU_SOURCE
8#include <ctype.h>
9#include <errno.h>
10#include <grp.h>
11#include <inttypes.h>
12#include <limits.h>
13#include <linux/capability.h>
14#include <linux/securebits.h>
15#include <pwd.h>
16#include <sched.h>
17#include <signal.h>
18#include <stdarg.h>
19#include <stdio.h>
20#include <stdlib.h>
21#include <string.h>
22#include <syscall.h>
23#include <sys/capability.h>
24#include <sys/mount.h>
25#include <sys/param.h>
26#include <sys/prctl.h>
27#include <sys/wait.h>
28#include <syslog.h>
29#include <unistd.h>
30
31#include "libminijail.h"
32#include "libsyscalls.h"
33#include "libminijail-private.h"
34
35/* Until these are reliably available in linux/prctl.h */
36#ifndef PR_SET_SECCOMP_FILTER
37# define PR_SECCOMP_FILTER_SYSCALL 0
38# define PR_SECCOMP_FILTER_EVENT 1
39# define PR_GET_SECCOMP_FILTER 35
40# define PR_SET_SECCOMP_FILTER 36
41# define PR_CLEAR_SECCOMP_FILTER 37
42#endif
43
44#define die(_msg, ...) do { \
45	syslog(LOG_ERR, "libminijail: " _msg, ## __VA_ARGS__); \
46	abort(); \
47} while (0)
48
49#define pdie(_msg, ...) \
50	die(_msg ": %s", ## __VA_ARGS__, strerror(errno))
51
52#define warn(_msg, ...) \
53	syslog(LOG_WARNING, "libminijail: " _msg, ## __VA_ARGS__)
54
55struct seccomp_filter {
56	int nr;
57	char *filter;
58	struct seccomp_filter *next, *prev;
59};
60
61struct minijail {
62	struct {
63		int uid:1;
64		int gid:1;
65		int caps:1;
66		int vfs:1;
67		int pids:1;
68		int seccomp:1;
69		int readonly:1;
70		int usergroups:1;
71		int ptrace:1;
72		int seccomp_filter:1;
73	} flags;
74	uid_t uid;
75	gid_t gid;
76	gid_t usergid;
77	char *user;
78	uint64_t caps;
79	pid_t initpid;
80	int filter_count;
81	struct seccomp_filter *filters;
82};
83
84struct minijail *minijail_new(void)
85{
86	struct minijail *j = malloc(sizeof(*j));
87	if (j)
88		memset(j, 0, sizeof(*j));
89	return j;
90}
91
92void minijail_change_uid(struct minijail *j, uid_t uid)
93{
94	if (uid == 0)
95		die("useless change to uid 0");
96	j->uid = uid;
97	j->flags.uid = 1;
98}
99
100void minijail_change_gid(struct minijail *j, gid_t gid)
101{
102	if (gid == 0)
103		die("useless change to gid 0");
104	j->gid = gid;
105	j->flags.gid = 1;
106}
107
108int minijail_change_user(struct minijail *j, const char *user)
109{
110	char *buf = NULL;
111	struct passwd pw;
112	struct passwd *ppw = NULL;
113	ssize_t sz = sysconf(_SC_GETPW_R_SIZE_MAX);
114	if (sz == -1)
115		sz = 65536;	/* your guess is as good as mine... */
116
117	/* sysconf(_SC_GETPW_R_SIZE_MAX), under glibc, is documented to return
118	 * the maximum needed size of the buffer, so we don't have to search.
119	 */
120	buf = malloc(sz);
121	if (!buf)
122		return -ENOMEM;
123	getpwnam_r(user, &pw, buf, sz, &ppw);
124	free(buf);
125	if (!ppw)
126		return -errno;
127	minijail_change_uid(j, ppw->pw_uid);
128	j->user = strdup(user);
129	if (!j->user)
130		return -ENOMEM;
131	j->usergid = ppw->pw_gid;
132	return 0;
133}
134
135int minijail_change_group(struct minijail *j, const char *group)
136{
137	char *buf = NULL;
138	struct group gr;
139	struct group *pgr = NULL;
140	ssize_t sz = sysconf(_SC_GETGR_R_SIZE_MAX);
141	if (sz == -1)
142		sz = 65536;	/* and mine is as good as yours, really */
143
144	/* sysconf(_SC_GETGR_R_SIZE_MAX), under glibc, is documented to return
145	 * the maximum needed size of the buffer, so we don't have to search.
146	 */
147	buf = malloc(sz);
148	if (!buf)
149		return -ENOMEM;
150	getgrnam_r(group, &gr, buf, sz, &pgr);
151	free(buf);
152	if (!pgr)
153		return -errno;
154	minijail_change_gid(j, pgr->gr_gid);
155	return 0;
156}
157
158void minijail_use_seccomp(struct minijail *j)
159{
160	j->flags.seccomp = 1;
161}
162
163void minijail_use_seccomp_filter(struct minijail *j)
164{
165	j->flags.seccomp_filter = 1;
166}
167
168void minijail_use_caps(struct minijail *j, uint64_t capmask)
169{
170	j->caps = capmask;
171	j->flags.caps = 1;
172}
173
174void minijail_namespace_vfs(struct minijail *j)
175{
176	j->flags.vfs = 1;
177}
178
179void minijail_namespace_pids(struct minijail *j)
180{
181	j->flags.pids = 1;
182}
183
184void minijail_remount_readonly(struct minijail *j)
185{
186	j->flags.vfs = 1;
187	j->flags.readonly = 1;
188}
189
190void minijail_inherit_usergroups(struct minijail *j)
191{
192	j->flags.usergroups = 1;
193}
194
195void minijail_disable_ptrace(struct minijail *j)
196{
197	j->flags.ptrace = 1;
198}
199
200int minijail_add_seccomp_filter(struct minijail *j, int nr, const char *filter)
201{
202	struct seccomp_filter *sf;
203	if (!filter || nr < 0)
204		return -EINVAL;
205
206	sf = malloc(sizeof(*sf));
207	if (!sf)
208		return -ENOMEM;
209	sf->nr = nr;
210	sf->filter = strndup(filter, MINIJAIL_MAX_SECCOMP_FILTER_LINE);
211	if (!sf->filter) {
212		free(sf);
213		return -ENOMEM;
214	}
215
216	j->filter_count++;
217
218	if (!j->filters) {
219		j->filters = sf;
220		sf->next = sf;
221		sf->prev = sf;
222		return 0;
223	}
224	sf->next = j->filters;
225	sf->prev = j->filters->prev;
226	sf->prev->next = sf;
227	j->filters->prev = sf;
228	return 0;
229}
230
231int minijail_lookup_syscall(const char *name)
232{
233	const struct syscall_entry *entry = syscall_table;
234	for (; entry->name && entry->nr >= 0; ++entry)
235		if (!strcmp(entry->name, name))
236			return entry->nr;
237	return -1;
238}
239
240static char *strip(char *s)
241{
242	char *end;
243	while (*s && isblank(*s))
244		s++;
245	end = s + strlen(s) - 1;
246	while (*end && (isblank(*end) || *end == '\n'))
247		end--;
248	*(end + 1) = '\0';
249	return s;
250}
251
252void minijail_parse_seccomp_filters(struct minijail *j, const char *path)
253{
254	FILE *file = fopen(path, "r");
255	char line[MINIJAIL_MAX_SECCOMP_FILTER_LINE];
256	int count = 1;
257	if (!file)
258		pdie("failed to open seccomp filters file");
259
260	/* Format is simple:
261	 * syscall_name<COLON><FILTER STRING>[\n|EOF]
262	 * #...comment...
263	 * <empty line?
264	 */
265	while (fgets(line, sizeof(line), file)) {
266		char *filter = line;
267		char *name = strsep(&filter, ":");
268		char *name_end = NULL;
269		int nr = -1;
270
271		if (!name)
272			die("invalid filter on line %d", count);
273
274		name = strip(name);
275
276		if (!filter) {
277			if (strlen(name))
278				die("invalid filter on line %d", count);
279			/* Allow empty lines */
280			continue;
281		}
282
283		/* Allow comment lines */
284		if (*name == '#')
285			continue;
286
287		filter = strip(filter);
288
289		/* Take direct syscall numbers */
290		nr = strtol(name, &name_end, 0);
291		/* Or fail-over to using names */
292		if (*name_end != '\0')
293			nr = minijail_lookup_syscall(name);
294		if (nr < 0)
295			die("syscall '%s' unknown", name);
296
297		if (minijail_add_seccomp_filter(j, nr, filter))
298			pdie("failed to add filter for syscall '%s'", name);
299	}
300	fclose(file);
301}
302
303struct marshal_state {
304	size_t available;
305	size_t total;
306	char *buf;
307};
308
309static void marshal_state_init(struct marshal_state *state,
310			       char *buf, size_t available)
311{
312	state->available = available;
313	state->buf = buf;
314	state->total = 0;
315}
316
317static void marshal_append(struct marshal_state *state,
318			   char *src, size_t length)
319{
320	size_t copy_len = MIN(state->available, length);
321
322	/* Up to |available| will be written. */
323	if (copy_len) {
324		memcpy(state->buf, src, copy_len);
325		state->buf += copy_len;
326		state->available -= copy_len;
327	}
328	/* |total| will contain the expected length. */
329	state->total += length;
330}
331
332static void minijail_marshal_helper(struct marshal_state *state,
333				    const struct minijail *j)
334{
335	marshal_append(state, (char *)j, sizeof(*j));
336	if (j->user)
337		marshal_append(state, j->user, strlen(j->user) + 1);
338	if (j->flags.seccomp_filter && j->filters) {
339		struct seccomp_filter *f = j->filters;
340		do {
341			marshal_append(state, (char *)&f->nr, sizeof(f->nr));
342			marshal_append(state, f->filter, strlen(f->filter) + 1);
343			f = f->next;
344		} while (f != j->filters);
345	}
346}
347
348size_t minijail_size(const struct minijail *j)
349{
350	struct marshal_state state;
351	marshal_state_init(&state, NULL, 0);
352	minijail_marshal_helper(&state, j);
353	return state.total;
354}
355
356int minijail_marshal(const struct minijail *j, char *buf, size_t available)
357{
358	struct marshal_state state;
359	marshal_state_init(&state, buf, available);
360	minijail_marshal_helper(&state, j);
361	return (state.total > available);
362}
363
364int minijail_unmarshal(struct minijail *j, char *serialized, size_t length)
365{
366	if (length < sizeof(*j))
367		return -EINVAL;
368	memcpy((void *)j, serialized, sizeof(*j));
369	serialized += sizeof(*j);
370	length -= sizeof(*j);
371
372	if (j->user) {		/* stale pointer */
373		if (!length)
374			return -EINVAL;
375		j->user = strndup(serialized, length);
376		length -= strlen(j->user) + 1;
377		serialized += strlen(j->user) + 1;
378	}
379
380	if (j->flags.seccomp_filter && j->filter_count) {
381		int count = j->filter_count;
382		/* Let add_seccomp_filter recompute the value. */
383		j->filter_count = 0;
384		j->filters = NULL;	/* Don't follow the stale pointer. */
385		for (; count > 0; --count) {
386			int *nr = (int *)serialized;
387			char *filter;
388			if (length < sizeof(*nr))
389				return -EINVAL;
390			length -= sizeof(*nr);
391			serialized += sizeof(*nr);
392			if (!length)
393				return -EINVAL;
394			filter = serialized;
395			if (minijail_add_seccomp_filter(j, *nr, filter))
396				return -EINVAL;
397			length -= strlen(filter) + 1;
398			serialized += strlen(filter) + 1;
399		}
400	}
401	return 0;
402}
403
404void minijail_preenter(struct minijail *j)
405{
406	/* Strip out options which are minijail_run() only. */
407	j->flags.vfs = 0;
408	j->flags.readonly = 0;
409	j->flags.pids = 0;
410}
411
412void minijail_preexec(struct minijail *j)
413{
414	int vfs = j->flags.vfs;
415	int readonly = j->flags.readonly;
416	if (j->user)
417		free(j->user);
418	j->user = NULL;
419	memset(&j->flags, 0, sizeof(j->flags));
420	/* Now restore anything we meant to keep. */
421	j->flags.vfs = vfs;
422	j->flags.readonly = readonly;
423	/* Note, pidns will already have been used before this call. */
424}
425
426static int remount_readonly(void)
427{
428	const char *kProcPath = "/proc";
429	const unsigned int kSafeFlags = MS_NODEV | MS_NOEXEC | MS_NOSUID;
430	/* Right now, we're holding a reference to our parent's old mount of
431	 * /proc in our namespace, which means using MS_REMOUNT here would
432	 * mutate our parent's mount as well, even though we're in a VFS
433	 * namespace (!). Instead, remove their mount from our namespace
434	 * and make our own.
435	 */
436	if (umount(kProcPath))
437		return -errno;
438	if (mount("", kProcPath, "proc", kSafeFlags | MS_RDONLY, ""))
439		return -errno;
440	return 0;
441}
442
443static void drop_caps(const struct minijail *j)
444{
445	cap_t caps = cap_get_proc();
446	cap_value_t raise_flag[1];
447	unsigned int i;
448	if (!caps)
449		die("can't get process caps");
450	if (cap_clear_flag(caps, CAP_INHERITABLE))
451		die("can't clear inheritable caps");
452	if (cap_clear_flag(caps, CAP_EFFECTIVE))
453		die("can't clear effective caps");
454	if (cap_clear_flag(caps, CAP_PERMITTED))
455		die("can't clear permitted caps");
456	for (i = 0; i < sizeof(j->caps) * 8 && cap_valid((int)i); ++i) {
457		if (i != CAP_SETPCAP && !(j->caps & (1 << i)))
458			continue;
459		raise_flag[0] = i;
460		if (cap_set_flag(caps, CAP_EFFECTIVE, 1, raise_flag, CAP_SET))
461			die("can't add effective cap");
462		if (cap_set_flag(caps, CAP_PERMITTED, 1, raise_flag, CAP_SET))
463			die("can't add permitted cap");
464		if (cap_set_flag(caps, CAP_INHERITABLE, 1, raise_flag, CAP_SET))
465			die("can't add inheritable cap");
466	}
467	if (cap_set_proc(caps))
468		die("can't apply cleaned capset");
469	cap_free(caps);
470	for (i = 0; i < sizeof(j->caps) * 8 && cap_valid((int)i); ++i) {
471		if (j->caps & (1 << i))
472			continue;
473		if (prctl(PR_CAPBSET_DROP, i))
474			pdie("prctl(PR_CAPBSET_DROP)");
475	}
476}
477
478static int setup_seccomp_filters(const struct minijail *j)
479{
480	const struct seccomp_filter *sf = j->filters;
481	int ret = 0;
482	int broaden = 0;
483
484	/* No filters installed isn't necessarily an error. */
485	if (!sf)
486		return ret;
487
488	do {
489		errno = 0;
490		ret = prctl(PR_SET_SECCOMP_FILTER, PR_SECCOMP_FILTER_SYSCALL,
491			    sf->nr, broaden ? "1" : sf->filter);
492		if (ret) {
493			switch (errno) {
494			case ENOSYS:
495				/* TODO(wad) make this a config option */
496				if (broaden)
497					die("CONFIG_SECCOMP_FILTER is not"
498					    "supported by your kernel");
499				warn("missing CONFIG_FTRACE_SYSCALLS; relaxing"
500				     "the filter for %d", sf->nr);
501				broaden = 1;
502				continue;
503			case E2BIG:
504				warn("seccomp filter too long: %d", sf->nr);
505				pdie("filter too long");
506			case ENOSPC:
507				pdie("too many seccomp filters");
508			case EPERM:
509				warn("syscall filter disallowed for %d",
510				     sf->nr);
511				pdie("failed to install seccomp filter");
512			case EINVAL:
513				warn("seccomp filter or call method is"
514				     " invalid. %d:'%s'", sf->nr, sf->filter);
515			default:
516				pdie("failed to install seccomp filter");
517			}
518		}
519		sf = sf->next;
520		broaden = 0;
521	} while (sf != j->filters);
522	return ret;
523}
524
525void minijail_enter(const struct minijail *j)
526{
527	if (j->flags.pids)
528		die("tried to enter a pid-namespaced jail;"
529		    "try minijail_run()?");
530
531	if (j->flags.seccomp_filter && setup_seccomp_filters(j))
532		pdie("failed to configure seccomp filters");
533
534	if (j->flags.usergroups && !j->user)
535		die("usergroup inheritance without username");
536
537	/* We can't recover from failures if we've dropped privileges partially,
538	 * so we don't even try. If any of our operations fail, we abort() the
539	 * entire process.
540	 */
541	if (j->flags.vfs && unshare(CLONE_NEWNS))
542		pdie("unshare");
543
544	if (j->flags.readonly && remount_readonly())
545		pdie("remount");
546
547	if (j->flags.caps) {
548		/* POSIX capabilities are a bit tricky. If we drop our
549		 * capability to change uids, our attempt to use setuid()
550		 * below will fail. Hang on to root caps across setuid(), then
551		 * lock securebits.
552		 */
553		if (prctl(PR_SET_KEEPCAPS, 1))
554			pdie("prctl(PR_SET_KEEPCAPS)");
555		if (prctl
556		    (PR_SET_SECUREBITS, SECURE_ALL_BITS | SECURE_ALL_LOCKS))
557			pdie("prctl(PR_SET_SECUREBITS)");
558	}
559
560	if (j->flags.usergroups) {
561		if (initgroups(j->user, j->usergid))
562			pdie("initgroups");
563	} else {
564		/* Only attempt to clear supplemental groups if we are changing
565		 * users. */
566		if ((j->uid || j->gid) && setgroups(0, NULL))
567			pdie("setgroups");
568	}
569
570	if (j->flags.gid && setresgid(j->gid, j->gid, j->gid))
571		pdie("setresgid");
572
573	if (j->flags.uid && setresuid(j->uid, j->uid, j->uid))
574		pdie("setresuid");
575
576	if (j->flags.caps)
577		drop_caps(j);
578
579	/* seccomp has to come last since it cuts off all the other
580	 * privilege-dropping syscalls :)
581	 */
582	if (j->flags.seccomp_filter && prctl(PR_SET_SECCOMP, 13))
583		pdie("prctl(PR_SET_SECCOMP, 13)");
584
585	if (j->flags.seccomp && prctl(PR_SET_SECCOMP, 1))
586		pdie("prctl(PR_SET_SECCOMP)");
587}
588
589static int init_exitstatus = 0;
590
591static void init_term(int __attribute__ ((unused)) sig)
592{
593	_exit(init_exitstatus);
594}
595
596static int init(pid_t rootpid)
597{
598	pid_t pid;
599	int status;
600	/* so that we exit with the right status */
601	signal(SIGTERM, init_term);
602	/* TODO(wad) self jail with seccomp_filters here. */
603	while ((pid = wait(&status)) > 0) {
604		/* This loop will only end when either there are no processes
605		 * left inside our pid namespace or we get a signal.
606		 */
607		if (pid == rootpid)
608			init_exitstatus = status;
609	}
610	if (!WIFEXITED(init_exitstatus))
611		_exit(MINIJAIL_ERR_INIT);
612	_exit(WEXITSTATUS(init_exitstatus));
613}
614
615int minijail_from_fd(int fd, struct minijail *j)
616{
617	size_t sz = 0;
618	size_t bytes = read(fd, &sz, sizeof(sz));
619	char *buf;
620	int r;
621	if (sizeof(sz) != bytes)
622		return -EINVAL;
623	if (sz > USHRT_MAX)	/* Arbitrary sanity check */
624		return -E2BIG;
625	buf = malloc(sz);
626	if (!buf)
627		return -ENOMEM;
628	bytes = read(fd, buf, sz);
629	if (bytes != sz) {
630		free(buf);
631		return -EINVAL;
632	}
633	r = minijail_unmarshal(j, buf, sz);
634	free(buf);
635	return r;
636}
637
638int minijail_to_fd(struct minijail *j, int fd)
639{
640	char *buf;
641	size_t sz = minijail_size(j);
642	ssize_t written;
643	int r;
644
645	if (!sz)
646		return -EINVAL;
647	buf = malloc(sz);
648	r = minijail_marshal(j, buf, sz);
649	if (r) {
650		free(buf);
651		return r;
652	}
653	/* Sends [size][minijail]. */
654	written = write(fd, &sz, sizeof(sz));
655	if (written != sizeof(sz)) {
656		free(buf);
657		return -EFAULT;
658	}
659	written = write(fd, buf, sz);
660	if (written < 0 || (size_t) written != sz) {
661		free(buf);
662		return -EFAULT;
663	}
664	free(buf);
665	return 0;
666}
667
668static int setup_preload(void)
669{
670	char *oldenv = getenv(kLdPreloadEnvVar) ? : "";
671	char *newenv = malloc(strlen(oldenv) + 2 + strlen(PRELOADPATH));
672	if (!newenv)
673		return -ENOMEM;
674
675	/* Only insert a separating space if we have something to separate... */
676	sprintf(newenv, "%s%s%s", oldenv, strlen(oldenv) ? " " : "",
677		PRELOADPATH);
678
679	/* setenv() makes a copy of the string we give it */
680	setenv(kLdPreloadEnvVar, newenv, 1);
681	free(newenv);
682	return 0;
683}
684
685static int setup_pipe(int fds[2])
686{
687	int r = pipe(fds);
688	char fd_buf[11];
689	if (r)
690		return r;
691	r = snprintf(fd_buf, sizeof(fd_buf), "%d", fds[0]);
692	if (r <= 0)
693		return -EINVAL;
694	setenv(kFdEnvVar, fd_buf, 1);
695	return 0;
696}
697
698int minijail_run(struct minijail *j, const char *filename, char *const argv[])
699{
700	unsigned int pidns = j->flags.pids ? CLONE_NEWPID : 0;
701	char *oldenv, *oldenv_copy = NULL;
702	pid_t child_pid;
703	int pipe_fds[2];
704	int ret;
705
706	oldenv = getenv(kLdPreloadEnvVar);
707	if (oldenv) {
708		oldenv_copy = strdup(oldenv);
709		if (!oldenv_copy)
710			return -ENOMEM;
711	}
712
713	if (setup_preload())
714		return -EFAULT;
715
716	/* Before we fork(2) and execve(2) the child process, we need to open
717	 * a pipe(2) to send the minijail configuration over.
718	 */
719	if (setup_pipe(pipe_fds))
720		return -EFAULT;
721
722	child_pid = syscall(SYS_clone, pidns | SIGCHLD, NULL);
723	if (child_pid < 0) {
724		free(oldenv_copy);
725		return child_pid;
726	}
727
728	if (child_pid) {
729		/* Restore parent's LD_PRELOAD. */
730		if (oldenv_copy) {
731			setenv(kLdPreloadEnvVar, oldenv_copy, 1);
732			free(oldenv_copy);
733		} else {
734			unsetenv(kLdPreloadEnvVar);
735		}
736		unsetenv(kFdEnvVar);
737		j->initpid = child_pid;
738		close(pipe_fds[0]);	/* read endpoint */
739		ret = minijail_to_fd(j, pipe_fds[1]);
740		close(pipe_fds[1]);	/* write endpoint */
741		if (ret) {
742			kill(j->initpid, SIGKILL);
743			die("failed to send marshalled minijail");
744		}
745		return 0;
746	}
747	free(oldenv_copy);
748
749	/* Drop everything that cannot be inherited across execve. */
750	minijail_preexec(j);
751	/* Jail this process and its descendants... */
752	minijail_enter(j);
753
754	if (pidns) {
755		/* pid namespace: this process will become init inside the new
756		 * namespace, so fork off a child to actually run the program
757		 * (we don't want all programs we might exec to have to know
758		 * how to be init).
759		 */
760		child_pid = fork();
761		if (child_pid < 0)
762			_exit(child_pid);
763		else if (child_pid > 0)
764			init(child_pid);	/* never returns */
765	}
766
767	/* If we aren't pid-namespaced:
768	 *   calling process
769	 *   -> execve()-ing process
770	 * If we are:
771	 *   calling process
772	 *   -> init()-ing process
773	 *      -> execve()-ing process
774	 */
775	_exit(execve(filename, argv, environ));
776}
777
778int minijail_kill(struct minijail *j)
779{
780	int st;
781	if (kill(j->initpid, SIGTERM))
782		return -errno;
783	if (waitpid(j->initpid, &st, 0) < 0)
784		return -errno;
785	return st;
786}
787
788int minijail_wait(struct minijail *j)
789{
790	int st;
791	if (waitpid(j->initpid, &st, 0) < 0)
792		return -errno;
793	if (!WIFEXITED(st))
794		return MINIJAIL_ERR_JAIL;
795	return WEXITSTATUS(st);
796}
797
798void minijail_destroy(struct minijail *j)
799{
800	struct seccomp_filter *f = j->filters;
801	/* Unlink the tail and head */
802	if (f)
803		f->prev->next = NULL;
804	while (f) {
805		struct seccomp_filter *next = f->next;
806		free(f->filter);
807		free(f);
808		f = next;
809	}
810	if (j->user)
811		free(j->user);
812	free(j);
813}
814