diff options
Diffstat (limited to 'usr.sbin/jail')
| -rw-r--r-- | usr.sbin/jail/Makefile | 36 | ||||
| -rw-r--r-- | usr.sbin/jail/Makefile.depend | 20 | ||||
| -rw-r--r-- | usr.sbin/jail/command.c | 1076 | ||||
| -rw-r--r-- | usr.sbin/jail/config.c | 919 | ||||
| -rw-r--r-- | usr.sbin/jail/jail.8 | 1617 | ||||
| -rw-r--r-- | usr.sbin/jail/jail.c | 1071 | ||||
| -rw-r--r-- | usr.sbin/jail/jail.conf.5 | 275 | ||||
| -rw-r--r-- | usr.sbin/jail/jaillex.l | 218 | ||||
| -rw-r--r-- | usr.sbin/jail/jailp.h | 254 | ||||
| -rw-r--r-- | usr.sbin/jail/jailparse.y | 276 | ||||
| -rw-r--r-- | usr.sbin/jail/state.c | 492 | ||||
| -rw-r--r-- | usr.sbin/jail/tests/Makefile | 9 | ||||
| -rw-r--r-- | usr.sbin/jail/tests/Makefile.depend | 10 | ||||
| -rw-r--r-- | usr.sbin/jail/tests/commands.jail.conf | 7 | ||||
| -rwxr-xr-x | usr.sbin/jail/tests/jail_basic_test.sh | 337 | 
15 files changed, 6617 insertions, 0 deletions
| diff --git a/usr.sbin/jail/Makefile b/usr.sbin/jail/Makefile new file mode 100644 index 000000000000..babe7b9459c0 --- /dev/null +++ b/usr.sbin/jail/Makefile @@ -0,0 +1,36 @@ +.include <src.opts.mk> + +PROG=	jail +MAN=	jail.8 jail.conf.5 +SRCS=	jail.c command.c config.c state.c jailp.h jaillex.l jailparse.y y.tab.h + +LIBADD=	jail kvm util + +PACKAGE=jail + +NO_WMISSING_VARIABLE_DECLARATIONS= +CWARNFLAGS.jailparse.c=	${NO_WUNUSED_BUT_SET_VARIABLE} + +YFLAGS+=-v +CFLAGS+=-I. -I${.CURDIR} + +# workaround for GNU ld (GNU Binutils) 2.33.1: +#   relocation truncated to fit: R_RISCV_GPREL_I against `.LANCHOR2' +# https://bugs.freebsd.org/242109 +.if defined(LINKER_TYPE) && ${LINKER_TYPE} == "bfd" && ${MACHINE} == "riscv" +CFLAGS+=-Wl,--no-relax +.endif + +.if ${MK_INET6_SUPPORT} != "no" +CFLAGS+= -DINET6 +.endif +.if ${MK_INET_SUPPORT} != "no" +CFLAGS+= -DINET +.endif + +CLEANFILES= y.output + +HAS_TESTS= +SUBDIR.${MK_TESTS}+= tests + +.include <bsd.prog.mk> diff --git a/usr.sbin/jail/Makefile.depend b/usr.sbin/jail/Makefile.depend new file mode 100644 index 000000000000..ef2f6c1424ca --- /dev/null +++ b/usr.sbin/jail/Makefile.depend @@ -0,0 +1,20 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ +	include \ +	include/arpa \ +	include/xlocale \ +	lib/${CSU_DIR} \ +	lib/libc \ +	lib/libcompiler_rt \ +	lib/libjail \ +	lib/libkvm \ +	lib/libutil \ +	usr.bin/yacc.host \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/usr.sbin/jail/command.c b/usr.sbin/jail/command.c new file mode 100644 index 000000000000..9da4fe51673a --- /dev/null +++ b/usr.sbin/jail/command.c @@ -0,0 +1,1076 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2011 James Gritton + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/cpuset.h> +#include <sys/event.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <sys/sysctl.h> +#include <sys/user.h> +#include <sys/wait.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <kvm.h> +#include <login_cap.h> +#include <paths.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <vis.h> + +#include "jailp.h" + +#define DEFAULT_STOP_TIMEOUT	10 +#define PHASH_SIZE		256 + +LIST_HEAD(phhead, phash); + +struct phash { +	LIST_ENTRY(phash)	le; +	struct cfjail		*j; +	pid_t			pid; +}; + +int paralimit = -1; + +extern char **environ; + +static int run_command(struct cfjail *j); +static int add_proc(struct cfjail *j, pid_t pid); +static void clear_procs(struct cfjail *j); +static struct cfjail *find_proc(pid_t pid); +static int term_procs(struct cfjail *j); +static int get_user_info(struct cfjail *j, const char *username, +    const struct passwd **pwdp, login_cap_t **lcapp); +static int check_path(struct cfjail *j, const char *pname, const char *path, +    int isfile, const char *umount_type); + +static struct cfjails sleeping = TAILQ_HEAD_INITIALIZER(sleeping); +static struct cfjails runnable = TAILQ_HEAD_INITIALIZER(runnable); +static struct cfstring dummystring = { .len = 1 }; +static struct phhead phash[PHASH_SIZE]; +static int kq; + +static cpusetid_t +root_cpuset_id(void) +{ +	static cpusetid_t setid = CPUSET_INVALID; +	static int error; + +	/* Only try to get the cpuset once. */ +	if (error == 0 && setid == CPUSET_INVALID) +		error = cpuset_getid(CPU_LEVEL_ROOT, CPU_WHICH_PID, -1, &setid); +	if (error != 0) +		return (CPUSET_INVALID); +	return (setid); +} + +/* + * Run the next command associated with a jail. + */ +int +next_command(struct cfjail *j) +{ +	enum intparam comparam; +	int create_failed, stopping; + +	if (paralimit == 0) { +		if (j->flags & JF_FROM_RUNQ) +			requeue_head(j, &runnable); +		else +			requeue(j, &runnable); +		return 1; +	} +	j->flags &= ~JF_FROM_RUNQ; +	create_failed = (j->flags & (JF_STOP | JF_FAILED)) == JF_FAILED; +	stopping = (j->flags & JF_STOP) != 0; +	comparam = *j->comparam; +	for (;;) { +		if (j->comstring == NULL) { +			j->comparam += create_failed ? -1 : 1; +			switch ((comparam = *j->comparam)) { +			case IP__NULL: +				return 0; +			case IP_MOUNT_DEVFS: +				if (!bool_param(j->intparams[IP_MOUNT_DEVFS])) +					continue; +				j->comstring = &dummystring; +				break; +			case IP_MOUNT_FDESCFS: +				if (!bool_param(j->intparams[IP_MOUNT_FDESCFS])) +					continue; +				j->comstring = &dummystring; +				break; +			case IP_MOUNT_PROCFS: +				if (!bool_param(j->intparams[IP_MOUNT_PROCFS])) +					continue; +				j->comstring = &dummystring; +				break; +			case IP__OP: +			case IP_STOP_TIMEOUT: +				j->comstring = &dummystring; +				break; +			default: +				if (j->intparams[comparam] == NULL) +					continue; +				j->comstring = create_failed || (stopping && +				    (j->intparams[comparam]->flags & PF_REV)) +				    ? TAILQ_LAST(&j->intparams[comparam]->val, +					cfstrings) +				    : TAILQ_FIRST(&j->intparams[comparam]->val); +			} +		} else { +			j->comstring = j->comstring == &dummystring ? NULL : +			    create_failed || (stopping && +			    (j->intparams[comparam]->flags & PF_REV)) +			    ? TAILQ_PREV(j->comstring, cfstrings, tq) +			    : TAILQ_NEXT(j->comstring, tq); +		} +		if (j->comstring == NULL || j->comstring->len == 0 || +		    (create_failed && (comparam == IP_EXEC_PRESTART || +		    comparam == IP_EXEC_CREATED || comparam == IP_EXEC_START || +		    comparam == IP_COMMAND || comparam == IP_EXEC_POSTSTART || +		    comparam == IP_EXEC_PREPARE))) +			continue; +		switch (run_command(j)) { +		case -1: +			failed(j); +			/* FALLTHROUGH */ +		case 1: +			return 1; +		} +	} +} + +/* + * Check command exit status + */ +int +finish_command(struct cfjail *j) +{ +	struct cfjail *rj; +	int error; + +	if (!(j->flags & JF_SLEEPQ)) +		return 0; +	j->flags &= ~JF_SLEEPQ; +	if (*j->comparam == IP_STOP_TIMEOUT) { +		j->flags &= ~JF_TIMEOUT; +		j->pstatus = 0; +		return 0; +	} +	paralimit++; +	if (!TAILQ_EMPTY(&runnable)) { +		rj = TAILQ_FIRST(&runnable); +		rj->flags |= JF_FROM_RUNQ; +		requeue(rj, &ready); +	} +	error = 0; +	if (j->flags & JF_TIMEOUT) { +		j->flags &= ~JF_TIMEOUT; +		if (*j->comparam != IP_STOP_TIMEOUT) { +			jail_warnx(j, "%s: timed out", j->comline); +			failed(j); +			error = -1; +		} else if (verbose > 0) +			jail_note(j, "timed out\n"); +	} else if (j->pstatus != 0) { +		if (WIFSIGNALED(j->pstatus)) +			jail_warnx(j, "%s: exited on signal %d", +			    j->comline, WTERMSIG(j->pstatus)); +		else +			jail_warnx(j, "%s: failed", j->comline); +		j->pstatus = 0; +		failed(j); +		error = -1; +	} +	free(j->comline); +	j->comline = NULL; +	return error; +} + +/* + * Check for finished processes or timeouts. + */ +struct cfjail * +next_proc(int nonblock) +{ +	struct kevent ke; +	struct timespec ts; +	struct timespec *tsp; +	struct cfjail *j; + +	if (!TAILQ_EMPTY(&sleeping)) { +	again: +		tsp = NULL; +		if ((j = TAILQ_FIRST(&sleeping)) && j->timeout.tv_sec) { +			clock_gettime(CLOCK_REALTIME, &ts); +			ts.tv_sec = j->timeout.tv_sec - ts.tv_sec; +			ts.tv_nsec = j->timeout.tv_nsec - ts.tv_nsec; +			if (ts.tv_nsec < 0) { +				ts.tv_sec--; +				ts.tv_nsec += 1000000000; +			} +			if (ts.tv_sec < 0 || +			    (ts.tv_sec == 0 && ts.tv_nsec == 0)) { +				j->flags |= JF_TIMEOUT; +				clear_procs(j); +				return j; +			} +			tsp = &ts; +		} +		if (nonblock) { +			ts.tv_sec = 0; +			ts.tv_nsec = 0; +			tsp = &ts; +		} +		switch (kevent(kq, NULL, 0, &ke, 1, tsp)) { +		case -1: +			if (errno != EINTR) +				err(1, "kevent"); +			goto again; +		case 0: +			if (!nonblock) { +				j = TAILQ_FIRST(&sleeping); +				j->flags |= JF_TIMEOUT; +				clear_procs(j); +				return j; +			} +			break; +		case 1: +			(void)waitpid(ke.ident, NULL, WNOHANG); +			if ((j = find_proc(ke.ident))) { +				j->pstatus = ke.data; +				return j; +			} +			goto again; +		} +	} +	return NULL; +} + +/* + * Run a single command for a jail, possibly inside the jail. + */ +static int +run_command(struct cfjail *j) +{ +	const struct passwd *pwd; +	const struct cfstring *comstring, *s; +	login_cap_t *lcap; +	const char **argv; +	char *acs, *cs, *comcs, *devpath; +	const char *jidstr, *conslog, *fmt, *path, *ruleset, *term, *username; +	enum intparam comparam; +	size_t comlen, ret; +	pid_t pid; +	cpusetid_t setid; +	int argc, bg, clean, consfd, down, fib, i, injail, sjuser, timeout; +#if defined(INET) || defined(INET6) +	char *addr, *extrap, *p, *val; +#endif + +	static char *cleanenv; + +	/* Perform some operations that aren't actually commands */ +	comparam = *j->comparam; +	down = j->flags & (JF_STOP | JF_FAILED); +	switch (comparam) { +	case IP_STOP_TIMEOUT: +		return term_procs(j); + +	case IP__OP: +		if (down) { +			if (jail_remove(j->jid) < 0 && errno == EPERM) { +				jail_warnx(j, "jail_remove: %s", +					   strerror(errno)); +				return -1; +			} +			if (verbose > 0 || (verbose == 0 && (j->flags & JF_STOP +			    ? note_remove : j->name != NULL))) +			    jail_note(j, "removed\n"); +			j->jid = -1; +			if (j->flags & JF_STOP) +				dep_done(j, DF_LIGHT); +			else +				j->flags &= ~JF_PERSIST; +		} else { +			if (create_jail(j) < 0) +				return -1; +			if (iflag) +				printf("%d\n", j->jid); +			if (verbose >= 0 && (j->name || verbose > 0)) +				jail_note(j, "created\n"); + +			/* +			 * Populate our jid and name parameters if they were not +			 * provided.  This simplifies later logic that wants to +			 * use the jid or name to be able to do so reliably. +			 */ +			if (j->intparams[KP_JID] == NULL) { +				char ljidstr[16]; + +				(void)snprintf(ljidstr, sizeof(ljidstr), "%d", +				    j->jid); +				add_param(j, NULL, KP_JID, ljidstr); +			} + +			/* This matches the kernel behavior. */ +			if (j->intparams[KP_NAME] == NULL) +				add_param(j, j->intparams[KP_JID], KP_NAME, +				    NULL); + +			dep_done(j, DF_LIGHT); +		} +		return 0; + +	default: ; +	} +	/* +	 * Collect exec arguments.  Internal commands for network and +	 * mounting build their own argument lists. +	 */ +	comstring = j->comstring; +	bg = 0; +	switch (comparam) { +#ifdef INET +	case IP__IP4_IFADDR: +		argc = 0; +		val = alloca(strlen(comstring->s) + 1); +		strcpy(val, comstring->s); +		cs = val; +		extrap = NULL; +		while ((p = strchr(cs, ' ')) != NULL && strlen(p) > 1) { +			if (extrap == NULL) { +				*p = '\0'; +				extrap = p + 1; +			} +			cs = p + 1; +			argc++; +		} + +		argv = alloca((8 + argc) * sizeof(char *)); +		argv[0] = _PATH_IFCONFIG; +		if ((cs = strchr(val, '|'))) { +			argv[1] = acs = alloca(cs - val + 1); +			strlcpy(acs, val, cs - val + 1); +			addr = cs + 1; +		} else { +			argv[1] = string_param(j->intparams[IP_INTERFACE]); +			addr = val; +		} +		argv[2] = "inet"; +		if (!(cs = strchr(addr, '/'))) { +			argv[3] = addr; +			argv[4] = "netmask"; +			argv[5] = "255.255.255.255"; +			argc = 6; +		} else if (strchr(cs + 1, '.')) { +			argv[3] = acs = alloca(cs - addr + 1); +			strlcpy(acs, addr, cs - addr + 1); +			argv[4] = "netmask"; +			argv[5] = cs + 1; +			argc = 6; +		} else { +			argv[3] = addr; +			argc = 4; +		} + +		if (!down && extrap != NULL) { +			for (cs = strtok(extrap, " "); cs; +			     cs = strtok(NULL, " ")) { +				size_t len = strlen(cs) + 1; +				argv[argc++] = acs = alloca(len); +				strlcpy(acs, cs, len); +			} +		} + +		argv[argc] = down ? "-alias" : "alias"; +		argv[argc + 1] = NULL; +		break; +#endif + +#ifdef INET6 +	case IP__IP6_IFADDR: +		argc = 0; +		val = alloca(strlen(comstring->s) + 1); +		strcpy(val, comstring->s); +		cs = val; +		extrap = NULL; +		while ((p = strchr(cs, ' ')) != NULL && strlen(p) > 1) { +			if (extrap == NULL) { +				*p = '\0'; +				extrap = p + 1; +			} +			cs = p + 1; +			argc++; +		} + +		argv = alloca((8 + argc) * sizeof(char *)); +		argv[0] = _PATH_IFCONFIG; +		if ((cs = strchr(val, '|'))) { +			argv[1] = acs = alloca(cs - val + 1); +			strlcpy(acs, val, cs - val + 1); +			addr = cs + 1; +		} else { +			argv[1] = string_param(j->intparams[IP_INTERFACE]); +			addr = val; +		} +		argv[2] = "inet6"; +		argv[3] = addr; +		if (!(cs = strchr(addr, '/'))) { +			argv[4] = "prefixlen"; +			argv[5] = "128"; +			argc = 6; +		} else +			argc = 4; + +		if (!down && extrap != NULL) { +			for (cs = strtok(extrap, " "); cs; +			     cs = strtok(NULL, " ")) { +				size_t len = strlen(cs) + 1; +				argv[argc++] = acs = alloca(len); +				strlcpy(acs, cs, len); +			} +		} + +		argv[argc] = down ? "-alias" : "alias"; +		argv[argc + 1] = NULL; +		break; +#endif + +	case IP_VNET_INTERFACE: +		argv = alloca(5 * sizeof(char *)); +		argv[0] = _PATH_IFCONFIG; +		argv[1] = comstring->s; +		argv[2] = down ? "-vnet" : "vnet"; +		argv[3] = string_param(j->intparams[KP_JID]); +		argv[4] = NULL; +		break; + +	case IP_MOUNT: +	case IP__MOUNT_FROM_FSTAB: +		argv = alloca(8 * sizeof(char *)); +		comcs = alloca(comstring->len + 1); +		strcpy(comcs, comstring->s); +		argc = 0; +		for (cs = strtok(comcs, " \t\f\v\r\n"); cs && argc < 4; +		     cs = strtok(NULL, " \t\f\v\r\n")) { +			if (argc <= 1 && strunvis(cs, cs) < 0) { +				jail_warnx(j, "%s: %s: fstab parse error", +				    j->intparams[comparam]->name, comstring->s); +				return -1; +			} +			argv[argc++] = cs; +		} +		if (argc == 0) +			return 0; +		if (argc < 3) { +			jail_warnx(j, "%s: %s: missing information", +			    j->intparams[comparam]->name, comstring->s); +			return -1; +		} +		if (check_path(j, j->intparams[comparam]->name, argv[1], 0, +		    down ? argv[2] : NULL) < 0) +			return -1; +		if (down) { +			argv[4] = NULL; +			argv[3] = argv[1]; +			argv[0] = "/sbin/umount"; +		} else { +			if (argc == 4) { +				argv[7] = NULL; +				argv[6] = argv[1]; +				argv[5] = argv[0]; +				argv[4] = argv[3]; +				argv[3] = "-o"; +			} else { +				argv[5] = NULL; +				argv[4] = argv[1]; +				argv[3] = argv[0]; +			} +			argv[0] = _PATH_MOUNT; +		} +		argv[1] = "-t"; +		break; + +	case IP_MOUNT_DEVFS: +		argv = alloca(7 * sizeof(char *)); +		path = string_param(j->intparams[KP_PATH]); +		if (path == NULL) { +			jail_warnx(j, "mount.devfs: no jail root path defined"); +			return -1; +		} +		devpath = alloca(strlen(path) + 5); +		sprintf(devpath, "%s/dev", path); +		if (check_path(j, "mount.devfs", devpath, 0, +		    down ? "devfs" : NULL) < 0) +			return -1; +		if (down) { +			argv[0] = "/sbin/umount"; +			argv[1] = devpath; +			argv[2] = NULL; +		} else { +			argv[0] = _PATH_MOUNT; +			argv[1] = "-t"; +			argv[2] = "devfs"; +			ruleset = string_param(j->intparams[KP_DEVFS_RULESET]); +			if (!ruleset) +			    ruleset = "4";	/* devfsrules_jail */ +			argv[3] = acs = alloca(11 + strlen(ruleset)); +			sprintf(acs, "-oruleset=%s", ruleset); +			argv[4] = "."; +			argv[5] = devpath; +			argv[6] = NULL; +		} +		break; + +	case IP_MOUNT_FDESCFS: +		argv = alloca(7 * sizeof(char *)); +		path = string_param(j->intparams[KP_PATH]); +		if (path == NULL) { +			jail_warnx(j, "mount.fdescfs: no jail root path defined"); +			return -1; +		} +		devpath = alloca(strlen(path) + 8); +		sprintf(devpath, "%s/dev/fd", path); +		if (check_path(j, "mount.fdescfs", devpath, 0, +		    down ? "fdescfs" : NULL) < 0) +			return -1; +		if (down) { +			argv[0] = "/sbin/umount"; +			argv[1] = devpath; +			argv[2] = NULL; +		} else { +			argv[0] = _PATH_MOUNT; +			argv[1] = "-t"; +			argv[2] = "fdescfs"; +			argv[3] = "."; +			argv[4] = devpath; +			argv[5] = NULL; +		} +		break; + +	case IP_MOUNT_PROCFS: +		argv = alloca(7 * sizeof(char *)); +		path = string_param(j->intparams[KP_PATH]); +		if (path == NULL) { +			jail_warnx(j, "mount.procfs: no jail root path defined"); +			return -1; +		} +		devpath = alloca(strlen(path) + 6); +		sprintf(devpath, "%s/proc", path); +		if (check_path(j, "mount.procfs", devpath, 0, +		    down ? "procfs" : NULL) < 0) +			return -1; +		if (down) { +			argv[0] = "/sbin/umount"; +			argv[1] = devpath; +			argv[2] = NULL; +		} else { +			argv[0] = _PATH_MOUNT; +			argv[1] = "-t"; +			argv[2] = "procfs"; +			argv[3] = "."; +			argv[4] = devpath; +			argv[5] = NULL; +		} +		break; + +	case IP_ZFS_DATASET: +		argv = alloca(4 * sizeof(char *)); +		jidstr = string_param(j->intparams[KP_JID]); +		fmt = "if [ $(/sbin/zfs get -H -o value jailed %s) = on ]; then /sbin/zfs jail %s %s || echo error, attaching %s to jail %s failed; else echo error, you need to set jailed=on for dataset %s; fi"; +		comlen = strlen(fmt) +		    + 2 * strlen(jidstr) +		    + 4 * comstring->len +		    - 6 * 2	/* 6 * "%s" */ +		    + 1; +		comcs = alloca(comlen); +		ret = snprintf(comcs, comlen, fmt, comstring->s, +		    jidstr, comstring->s, comstring->s, jidstr, +		    comstring->s); +		if (ret >= comlen) { +			jail_warnx(j, "internal error in ZFS dataset handling"); +			exit(1); +		} +		argv[0] = _PATH_BSHELL; +		argv[1] = "-c"; +		argv[2] = comcs; +		argv[3] = NULL; +		break; + +	case IP_COMMAND: +		if (j->name != NULL) +			goto default_command; +		argc = 0; +		TAILQ_FOREACH(s, &j->intparams[IP_COMMAND]->val, tq) +			argc++; +		argv = alloca((argc + 1) * sizeof(char *)); +		argc = 0; +		TAILQ_FOREACH(s, &j->intparams[IP_COMMAND]->val, tq) +			argv[argc++] = s->s; +		argv[argc] = NULL; +		j->comstring = &dummystring; +		break; + +	default: +	default_command: +		if ((cs = strpbrk(comstring->s, "!\"$&'()*;<>?[\\]`{|}~")) && +		    !(cs[0] == '&' && cs[1] == '\0')) { +			argv = alloca(4 * sizeof(char *)); +			argv[0] = _PATH_BSHELL; +			argv[1] = "-c"; +			argv[2] = comstring->s; +			argv[3] = NULL; +		} else { +			if (cs) { +				*cs = 0; +				bg = 1; +			} +			comcs = alloca(comstring->len + 1); +			strcpy(comcs, comstring->s); +			argc = 0; +			for (cs = strtok(comcs, " \t\f\v\r\n"); cs; +			     cs = strtok(NULL, " \t\f\v\r\n")) +				argc++; +			argv = alloca((argc + 1) * sizeof(char *)); +			strcpy(comcs, comstring->s); +			argc = 0; +			for (cs = strtok(comcs, " \t\f\v\r\n"); cs; +			     cs = strtok(NULL, " \t\f\v\r\n")) +				argv[argc++] = cs; +			argv[argc] = NULL; +		} +	} +	if (argv[0] == NULL) +		return 0; + +	if (int_param(j->intparams[IP_EXEC_TIMEOUT], &timeout) && +	    timeout != 0) { +		clock_gettime(CLOCK_REALTIME, &j->timeout); +		j->timeout.tv_sec += timeout; +	} else +		j->timeout.tv_sec = 0; + +	injail = comparam == IP_EXEC_START || comparam == IP_COMMAND || +	    comparam == IP_EXEC_STOP; +	if (injail) +		setid = root_cpuset_id(); +	else +		setid = CPUSET_INVALID; +	clean = bool_param(j->intparams[IP_EXEC_CLEAN]); +	username = string_param(j->intparams[injail +	    ? IP_EXEC_JAIL_USER : IP_EXEC_SYSTEM_USER]); +	sjuser = bool_param(j->intparams[IP_EXEC_SYSTEM_JAIL_USER]); + +	consfd = 0; +	if (injail && +	    (conslog = string_param(j->intparams[IP_EXEC_CONSOLELOG]))) { +		if (check_path(j, "exec.consolelog", conslog, 1, NULL) < 0) +			return -1; +		consfd = +		    open(conslog, O_WRONLY | O_CREAT | O_APPEND, DEFFILEMODE); +		if (consfd < 0) { +			jail_warnx(j, "open %s: %s", conslog, strerror(errno)); +			return -1; +		} +	} + +	comlen = 0; +	for (i = 0; argv[i]; i++) +		comlen += strlen(argv[i]) + 1; +	j->comline = cs = emalloc(comlen); +	for (i = 0; argv[i]; i++) { +		strcpy(cs, argv[i]); +		if (argv[i + 1]) { +			cs += strlen(argv[i]) + 1; +			cs[-1] = ' '; +		} +	} +	if (verbose > 0) +		jail_note(j, "run command%s%s%s: %s\n", +		    injail ? " in jail" : "", username ? " as " : "", +		    username ? username : "", j->comline); + +	pid = fork(); +	if (pid < 0) +		err(1, "fork"); +	if (pid > 0) { +		if (bg || !add_proc(j, pid)) { +			free(j->comline); +			j->comline = NULL; +			return 0; +		} else { +			paralimit--; +			return 1; +		} +	} +	if (bg) +		setsid(); + +	/* Set up the environment and run the command */ +	pwd = NULL; +	lcap = NULL; +	if ((clean || username) && injail && sjuser && +	    get_user_info(j, username, &pwd, &lcap) < 0) +		exit(1); +	if (injail) { +		/* jail_attach won't chdir along with its chroot. */ +		path = string_param(j->intparams[KP_PATH]); +		if (path && chdir(path) < 0) { +			jail_warnx(j, "chdir %s: %s", path, strerror(errno)); +			exit(1); +		} +		if (int_param(j->intparams[IP_EXEC_FIB], &fib) && +		    setfib(fib) < 0) { +			jail_warnx(j, "setfib: %s", strerror(errno)); +			exit(1); +		} + +		/* +		 * We wouldn't have specialized our affinity, so just setid to +		 * root.  We do this prior to attaching to avoid the kernel +		 * having to create a transient cpuset that we'll promptly +		 * free up with a reset to the jail's cpuset. +		 * +		 * This is just a best-effort to use as wide of mask as +		 * possible. +		 */ +		if (setid != CPUSET_INVALID) +			(void)cpuset_setid(CPU_WHICH_PID, -1, setid); + +		if (jail_attach(j->jid) < 0) { +			jail_warnx(j, "jail_attach: %s", strerror(errno)); +			exit(1); +		} +	} +	if (clean || username) { +		if (!(injail && sjuser) && +		    get_user_info(j, username, &pwd, &lcap) < 0) +			exit(1); +		if (clean) { +			term = getenv("TERM"); +			environ = &cleanenv; +			setenv("PATH", "/bin:/usr/bin", 0); +			if (term != NULL) +				setenv("TERM", term, 1); +		} +		if (setgid(pwd->pw_gid) < 0) { +			jail_warnx(j, "setgid %d: %s", pwd->pw_gid, +			    strerror(errno)); +			exit(1); +		} +		if (setusercontext(lcap, pwd, pwd->pw_uid, username +		    ? LOGIN_SETALL & ~LOGIN_SETGROUP & ~LOGIN_SETLOGIN +		    : LOGIN_SETPATH | LOGIN_SETENV) < 0) { +			jail_warnx(j, "setusercontext %s: %s", pwd->pw_name, +			    strerror(errno)); +			exit(1); +		} +		login_close(lcap); +		setenv("USER", pwd->pw_name, 1); +		setenv("HOME", pwd->pw_dir, 1); +		setenv("SHELL", +		    *pwd->pw_shell ? pwd->pw_shell : _PATH_BSHELL, 1); +		if (clean && username && chdir(pwd->pw_dir) < 0) { +			jail_warnx(j, "chdir %s: %s", +			    pwd->pw_dir, strerror(errno)); +			exit(1); +		} +		endpwent(); +	} +	if (!injail) { +		if (string_param(j->intparams[KP_JID])) +			setenv("JID", string_param(j->intparams[KP_JID]), 1); +		setenv("JNAME", string_param(j->intparams[KP_NAME]), 1); + +		path = string_param(j->intparams[KP_PATH]); +		setenv("JPATH", path ? path : "", 1); +	} + +	if (consfd != 0 && (dup2(consfd, 1) < 0 || dup2(consfd, 2) < 0)) { +		jail_warnx(j, "exec.consolelog: %s", strerror(errno)); +		exit(1); +	} +	closefrom(3); +	execvp(argv[0], __DECONST(char *const*, argv)); +	jail_warnx(j, "exec %s: %s", argv[0], strerror(errno)); +	exit(1); +} + +/* + * Add a process to the hash, tied to a jail. + */ +static int +add_proc(struct cfjail *j, pid_t pid) +{ +	struct kevent ke; +	struct cfjail *tj; +	struct phash *ph; + +	if (!kq && (kq = kqueue()) < 0) +		err(1, "kqueue"); +	EV_SET(&ke, pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL); +	if (kevent(kq, &ke, 1, NULL, 0, NULL) < 0) { +		if (errno == ESRCH) +			return 0; +		err(1, "kevent"); +	} +	ph = emalloc(sizeof(struct phash)); +	ph->j = j; +	ph->pid = pid; +	LIST_INSERT_HEAD(&phash[pid % PHASH_SIZE], ph, le); +	j->nprocs++; +	j->flags |= JF_SLEEPQ; +	if (j->timeout.tv_sec == 0) +		requeue(j, &sleeping); +	else { +		/* File the jail in the sleep queue according to its timeout. */ +		TAILQ_REMOVE(j->queue, j, tq); +		TAILQ_FOREACH(tj, &sleeping, tq) { +			if (!tj->timeout.tv_sec || +			    j->timeout.tv_sec < tj->timeout.tv_sec || +			    (j->timeout.tv_sec == tj->timeout.tv_sec && +			    j->timeout.tv_nsec <= tj->timeout.tv_nsec)) { +				TAILQ_INSERT_BEFORE(tj, j, tq); +				break; +			} +		} +		if (tj == NULL) +			TAILQ_INSERT_TAIL(&sleeping, j, tq); +		j->queue = &sleeping; +	} +	return 1; +} + +/* + * Remove any processes from the hash that correspond to a jail. + */ +static void +clear_procs(struct cfjail *j) +{ +	struct kevent ke; +	struct phash *ph, *tph; +	int i; + +	j->nprocs = 0; +	for (i = 0; i < PHASH_SIZE; i++) +		LIST_FOREACH_SAFE(ph, &phash[i], le, tph) +			if (ph->j == j) { +				EV_SET(&ke, ph->pid, EVFILT_PROC, EV_DELETE, +				    NOTE_EXIT, 0, NULL); +				(void)kevent(kq, &ke, 1, NULL, 0, NULL); +				LIST_REMOVE(ph, le); +				free(ph); +			} +} + +/* + * Find the jail that corresponds to an exited process. + */ +static struct cfjail * +find_proc(pid_t pid) +{ +	struct cfjail *j; +	struct phash *ph; + +	LIST_FOREACH(ph, &phash[pid % PHASH_SIZE], le) +		if (ph->pid == pid) { +			j = ph->j; +			LIST_REMOVE(ph, le); +			free(ph); +			return --j->nprocs ? NULL : j; +		} +	return NULL; +} + +/* + * Send SIGTERM to all processes in a jail and wait for them to die. + */ +static int +term_procs(struct cfjail *j) +{ +	struct kinfo_proc *ki; +	int i, noted, pcnt, timeout; + +	static kvm_t *kd; + +	if (!int_param(j->intparams[IP_STOP_TIMEOUT], &timeout)) +		timeout = DEFAULT_STOP_TIMEOUT; +	else if (timeout == 0) +		return 0; + +	if (kd == NULL) { +		kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL); +		if (kd == NULL) +			return 0; +	} + +	ki = kvm_getprocs(kd, KERN_PROC_PROC, 0, &pcnt); +	if (ki == NULL) +		return 0; +	noted = 0; +	for (i = 0; i < pcnt; i++) +		if (ki[i].ki_jid == j->jid && +		    kill(ki[i].ki_pid, SIGTERM) == 0) { +			(void)add_proc(j, ki[i].ki_pid); +			if (verbose > 0) { +				if (!noted) { +					noted = 1; +					jail_note(j, "sent SIGTERM to:"); +				} +				printf(" %d", ki[i].ki_pid); +			} +		} +	if (noted) +		printf("\n"); +	if (j->nprocs > 0) { +		clock_gettime(CLOCK_REALTIME, &j->timeout); +		j->timeout.tv_sec += timeout; +		return 1; +	} +	return 0; +} + +/* + * Look up a user in the passwd and login.conf files. + */ +static int +get_user_info(struct cfjail *j, const char *username, +    const struct passwd **pwdp, login_cap_t **lcapp) +{ +	const struct passwd *pwd; + +	errno = 0; +	*pwdp = pwd = username ? getpwnam(username) : getpwuid(getuid()); +	if (pwd == NULL) { +		if (errno) +			jail_warnx(j, "getpwnam%s%s: %s", username ? " " : "", +			    username ? username : "", strerror(errno)); +		else if (username) +			jail_warnx(j, "%s: no such user", username); +		else +			jail_warnx(j, "unknown uid %d", getuid()); +		return -1; +	} +	*lcapp = login_getpwclass(pwd); +	if (*lcapp == NULL) { +		jail_warnx(j, "getpwclass %s: %s", pwd->pw_name, +		    strerror(errno)); +		return -1; +	} +	/* Set the groups while the group file is still available */ +	if (initgroups(pwd->pw_name, pwd->pw_gid) < 0) { +		jail_warnx(j, "initgroups %s: %s", pwd->pw_name, +		    strerror(errno)); +		return -1; +	} +	return 0; +} + +/* + * Make sure a mount or consolelog path is a valid absolute pathname + * with no symlinks. + */ +static int +check_path(struct cfjail *j, const char *pname, const char *path, int isfile, +    const char *umount_type) +{ +	struct stat st, mpst; +	struct statfs stfs; +	char *tpath, *p; +	const char *jailpath; +	size_t jplen; + +	if (path[0] != '/') { +		jail_warnx(j, "%s: %s: not an absolute pathname", +		    pname, path); +		return -1; +	} +	/* +	 * Only check for symlinks in components below the jail's path, +	 * since that's where the security risk lies. +	 */ +	jailpath = string_param(j->intparams[KP_PATH]); +	if (jailpath == NULL) +		jailpath = ""; +	jplen = strlen(jailpath); +	if (!strncmp(path, jailpath, jplen) && path[jplen] == '/') { +		tpath = alloca(strlen(path) + 1); +		strcpy(tpath, path); +		for (p = tpath + jplen; p != NULL; ) { +			p = strchr(p + 1, '/'); +			if (p) +				*p = '\0'; +			if (lstat(tpath, &st) < 0) { +				if (errno == ENOENT && isfile && !p) +					break; +				jail_warnx(j, "%s: %s: %s", pname, tpath, +				    strerror(errno)); +				return -1; +			} +			if (S_ISLNK(st.st_mode)) { +				jail_warnx(j, "%s: %s is a symbolic link", +				    pname, tpath); +				return -1; +			} +			if (p) +				*p = '/'; +		} +	} +	if (umount_type != NULL) { +		if (stat(path, &st) < 0 || statfs(path, &stfs) < 0) { +			jail_warnx(j, "%s: %s: %s", pname, path, +			    strerror(errno)); +			return -1; +		} +		if (stat(stfs.f_mntonname, &mpst) < 0) { +			jail_warnx(j, "%s: %s: %s", pname, stfs.f_mntonname, +			    strerror(errno)); +			return -1; +		} +		if (st.st_ino != mpst.st_ino) { +			jail_warnx(j, "%s: %s: not a mount point", +			    pname, path); +			return -1; +		} +		if (strcmp(stfs.f_fstypename, umount_type)) { +			jail_warnx(j, "%s: %s: not a %s mount", +			    pname, path, umount_type); +			return -1; +		} +	} +	return 0; +} diff --git a/usr.sbin/jail/config.c b/usr.sbin/jail/config.c new file mode 100644 index 000000000000..1bad04ccde68 --- /dev/null +++ b/usr.sbin/jail/config.c @@ -0,0 +1,919 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2011 James Gritton + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/errno.h> +#include <sys/socket.h> +#include <sys/sysctl.h> + +#include <arpa/inet.h> +#include <netinet/in.h> + +#include <err.h> +#include <glob.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "jailp.h" + +#define MAX_INCLUDE_DEPTH 32 + +struct ipspec { +	const char	*name; +	unsigned	flags; +}; + +extern int yylex_init_extra(struct cflex *extra, void *scanner); +extern int yylex_destroy(void *scanner); +extern int yyparse(void *scanner); +extern int yyset_in(FILE *fp, void *scanner); + +struct cfjails cfjails = TAILQ_HEAD_INITIALIZER(cfjails); + +static void parse_config(const char *fname, int is_stdin); +static void free_param(struct cfparams *pp, struct cfparam *p); + +static const struct ipspec intparams[] = { +    [IP_ALLOW_DYING] =		{"allow.dying",		PF_INTERNAL | PF_BOOL}, +    [IP_COMMAND] =		{"command",		PF_INTERNAL}, +    [IP_DEPEND] =		{"depend",		PF_INTERNAL}, +    [IP_EXEC_CLEAN] =		{"exec.clean",		PF_INTERNAL | PF_BOOL}, +    [IP_EXEC_CONSOLELOG] =	{"exec.consolelog",	PF_INTERNAL}, +    [IP_EXEC_FIB] =		{"exec.fib",		PF_INTERNAL | PF_INT}, +    [IP_EXEC_JAIL_USER] =	{"exec.jail_user",	PF_INTERNAL}, +    [IP_EXEC_POSTSTART] =	{"exec.poststart",	PF_INTERNAL}, +    [IP_EXEC_POSTSTOP] =	{"exec.poststop",	PF_INTERNAL}, +    [IP_EXEC_PREPARE] =		{"exec.prepare",	PF_INTERNAL}, +    [IP_EXEC_PRESTART] =	{"exec.prestart",	PF_INTERNAL}, +    [IP_EXEC_PRESTOP] =		{"exec.prestop",	PF_INTERNAL}, +    [IP_EXEC_RELEASE] =		{"exec.release",	PF_INTERNAL}, +    [IP_EXEC_CREATED] =		{"exec.created",	PF_INTERNAL}, +    [IP_EXEC_START] =		{"exec.start",		PF_INTERNAL}, +    [IP_EXEC_STOP] =		{"exec.stop",		PF_INTERNAL}, +    [IP_EXEC_SYSTEM_JAIL_USER]=	{"exec.system_jail_user", +							PF_INTERNAL | PF_BOOL}, +    [IP_EXEC_SYSTEM_USER] =	{"exec.system_user",	PF_INTERNAL}, +    [IP_EXEC_TIMEOUT] =		{"exec.timeout",	PF_INTERNAL | PF_INT}, +#if defined(INET) || defined(INET6) +    [IP_INTERFACE] =		{"interface",		PF_INTERNAL}, +    [IP_IP_HOSTNAME] =		{"ip_hostname",		PF_INTERNAL | PF_BOOL}, +#endif +    [IP_MOUNT] =		{"mount",		PF_INTERNAL | PF_REV}, +    [IP_MOUNT_DEVFS] =		{"mount.devfs",		PF_INTERNAL | PF_BOOL}, +    [IP_MOUNT_FDESCFS] =	{"mount.fdescfs",	PF_INTERNAL | PF_BOOL}, +    [IP_MOUNT_PROCFS] =		{"mount.procfs",	PF_INTERNAL | PF_BOOL}, +    [IP_MOUNT_FSTAB] =		{"mount.fstab",		PF_INTERNAL}, +    [IP_STOP_TIMEOUT] =		{"stop.timeout",	PF_INTERNAL | PF_INT}, +    [IP_VNET_INTERFACE] =	{"vnet.interface",	PF_INTERNAL}, +    [IP_ZFS_DATASET] =		{"zfs.dataset",		PF_INTERNAL}, +#ifdef INET +    [IP__IP4_IFADDR] =		{"ip4.addr",	PF_INTERNAL | PF_CONV | PF_REV}, +#endif +#ifdef INET6 +    [IP__IP6_IFADDR] =		{"ip6.addr",	PF_INTERNAL | PF_CONV | PF_REV}, +#endif +    [IP__MOUNT_FROM_FSTAB] =	{"mount.fstab",	PF_INTERNAL | PF_CONV | PF_REV}, +    [IP__OP] =			{NULL,			PF_CONV}, +    [KP_ALLOW_CHFLAGS] =	{"allow.chflags",	0}, +    [KP_ALLOW_MOUNT] =		{"allow.mount",		0}, +    [KP_ALLOW_RAW_SOCKETS] =	{"allow.raw_sockets",	0}, +    [KP_ALLOW_SET_HOSTNAME]=	{"allow.set_hostname",	0}, +    [KP_ALLOW_SOCKET_AF] =	{"allow.socket_af",	0}, +    [KP_ALLOW_SYSVIPC] =	{"allow.sysvipc",	0}, +    [KP_DEVFS_RULESET] =	{"devfs_ruleset",	0}, +    [KP_HOST_HOSTNAME] =	{"host.hostname",	0}, +#ifdef INET +    [KP_IP4_ADDR] =		{"ip4.addr",		0}, +#endif +#ifdef INET6 +    [KP_IP6_ADDR] =		{"ip6.addr",		0}, +#endif +    [KP_JID] =			{"jid",			PF_IMMUTABLE}, +    [KP_NAME] =			{"name",		PF_IMMUTABLE}, +    [KP_PATH] =			{"path",		0}, +    [KP_PERSIST] =		{"persist",		0}, +    [KP_SECURELEVEL] =		{"securelevel",		0}, +    [KP_VNET] =			{"vnet",		0}, +}; + +/* + * Parse the jail configuration file. + */ +void +load_config(const char *cfname) +{ +	struct cfjails wild; +	struct cfparams opp; +	struct cfjail *j, *tj, *wj; +	struct cfparam *p, *vp, *tp; +	struct cfstring *s, *vs, *ns; +	struct cfvar *v, *vv; +	char *ep; +	int did_self, jseq, pgen; + +	parse_config(cfname, !strcmp(cfname, "-")); + +	/* Separate the wildcard jails out from the actual jails. */ +	jseq = 0; +	TAILQ_INIT(&wild); +	TAILQ_FOREACH_SAFE(j, &cfjails, tq, tj) { +		j->seq = ++jseq; +		if (wild_jail_name(j->name)) +			requeue(j, &wild); +	} + +	TAILQ_FOREACH(j, &cfjails, tq) { +		/* Set aside the jail's parameters. */ +		TAILQ_INIT(&opp); +		TAILQ_CONCAT(&opp, &j->params, tq); +		/* +		 * The jail name implies its "name" or "jid" parameter, +		 * though they may also be explicitly set later on.  After we +		 * collect other parameters, we'll go back and ensure they're +		 * both set if we need to do so here. +		 */ +		add_param(j, NULL, +		    strtol(j->name, &ep, 10) && !*ep ? KP_JID : KP_NAME, +		    j->name); + +		/* +		 * Collect parameters for the jail, global parameters/variables, +		 * and any matching wildcard jails. +		 */ +		did_self = 0; +		TAILQ_FOREACH(wj, &wild, tq) { +			if (j->seq < wj->seq && !did_self) { +				TAILQ_FOREACH(p, &opp, tq) +					add_param(j, p, 0, NULL); +				did_self = 1; +			} +			if (wild_jail_match(j->name, wj->name)) +				TAILQ_FOREACH(p, &wj->params, tq) +					add_param(j, p, 0, NULL); +		} +		if (!did_self) +			TAILQ_FOREACH(p, &opp, tq) +				add_param(j, p, 0, NULL); + +		/* +		 * We only backfill if it's the name that wasn't set; if it was +		 * the jid, we can assume that will be populated later when the +		 * jail is created or found. +		 */ +		if (j->intparams[KP_NAME] == NULL) +			add_param(j, NULL, KP_NAME, j->name); + +		/* Resolve any variable substitutions. */ +		pgen = 0; +		TAILQ_FOREACH(p, &j->params, tq) { +		    p->gen = ++pgen; +		find_vars: +		    TAILQ_FOREACH(s, &p->val, tq) { +			while ((v = STAILQ_FIRST(&s->vars))) { +				TAILQ_FOREACH(vp, &j->params, tq) +					if (!strcmp(vp->name, v->name)) +						break; +				if (!vp || TAILQ_EMPTY(&vp->val)) { +					jail_warnx(j, +					    "%s: variable \"%s\" not found", +					    p->name, v->name); +				bad_var: +					j->flags |= JF_FAILED; +					TAILQ_FOREACH(vp, &j->params, tq) +						if (vp->gen == pgen) +							vp->flags |= PF_BAD; +					goto free_var; +				} +				if (vp->flags & PF_BAD) +					goto bad_var; +				if (vp->gen == pgen) { +					jail_warnx(j, "%s: variable loop", +					    v->name); +					goto bad_var; +				} +				TAILQ_FOREACH(vs, &vp->val, tq) +					if (!STAILQ_EMPTY(&vs->vars)) { +						vp->gen = pgen; +						TAILQ_REMOVE(&j->params, vp, +						    tq); +						TAILQ_INSERT_BEFORE(p, vp, tq); +						p = vp; +						goto find_vars; +					} +				vs = TAILQ_FIRST(&vp->val); +				if (TAILQ_NEXT(vs, tq) != NULL && +				    (s->s[0] != '\0' || +				     STAILQ_NEXT(v, tq))) { +					jail_warnx(j, "%s: array cannot be " +					    "substituted inline", +					    p->name); +					goto bad_var; +				} +				s->s = erealloc(s->s, s->len + vs->len + 1); +				memmove(s->s + v->pos + vs->len, +				    s->s + v->pos, +				    s->len - v->pos + 1); +				memcpy(s->s + v->pos, vs->s, vs->len); +				vv = v; +				while ((vv = STAILQ_NEXT(vv, tq))) +					vv->pos += vs->len; +				s->len += vs->len; +				while ((vs = TAILQ_NEXT(vs, tq))) { +					ns = emalloc(sizeof(struct cfstring)); +					ns->s = estrdup(vs->s); +					ns->len = vs->len; +					STAILQ_INIT(&ns->vars); +					TAILQ_INSERT_AFTER(&p->val, s, ns, tq); +					s = ns; +				} +			free_var: +				free(v->name); +				STAILQ_REMOVE_HEAD(&s->vars, tq); +				free(v); +			} +		    } +		} + +		/* Free the jail's original parameter list and any variables. */ +		while ((p = TAILQ_FIRST(&opp))) +			free_param(&opp, p); +		TAILQ_FOREACH_SAFE(p, &j->params, tq, tp) +			if (p->flags & PF_VAR) +				free_param(&j->params, p); +	} +	while ((wj = TAILQ_FIRST(&wild))) { +		free(wj->name); +		while ((p = TAILQ_FIRST(&wj->params))) +			free_param(&wj->params, p); +		TAILQ_REMOVE(&wild, wj, tq); +	} +} + +void +include_config(void *scanner, const char *cfname) +{ +	static unsigned int depth; +	glob_t g = {0}; +	const char *slash; +	char *fullpath = NULL; + +	/* Simple sanity check for include loops. */ +	if (++depth > MAX_INCLUDE_DEPTH) +		errx(1, "maximum include depth exceeded"); +	/* Base relative pathnames on the current config file. */ +	if (yyget_in(scanner) != stdin && cfname[0] != '/') { +		const char *outer_cfname = yyget_extra(scanner)->cfname; +		if ((slash = strrchr(outer_cfname, '/')) != NULL) { +			size_t dirlen = (slash - outer_cfname) + 1; + +			fullpath = emalloc(dirlen + strlen(cfname) + 1); +			strncpy(fullpath, outer_cfname, dirlen); +			strcpy(fullpath + dirlen, cfname); +			cfname = fullpath; +		} +	} +	/* +	 * Check if the include statement had a filename glob. +	 * Globbing doesn't need to catch any files, but a non-glob +	 * file needs to exist (enforced by parse_config). +	 */ +	if (glob(cfname, GLOB_NOCHECK, NULL, &g) != 0) +		errx(1, "%s: filename glob failed", cfname); +	if (g.gl_flags & GLOB_MAGCHAR) { +		for (size_t gi = 0; gi < g.gl_matchc; gi++) +			parse_config(g.gl_pathv[gi], 0); +	} else +		parse_config(cfname, 0); +	if (fullpath) +		free(fullpath); +	--depth; +} + +static void +parse_config(const char *cfname, int is_stdin) +{ +	struct cflex cflex = {.cfname = cfname, .error = 0}; +	void *scanner; + +	yylex_init_extra(&cflex, &scanner); +	if (is_stdin) { +		cflex.cfname = "STDIN"; +		yyset_in(stdin, scanner); +	} else { +		FILE *yfp = fopen(cfname, "r"); +		if (!yfp) +			err(1, "%s", cfname); +		yyset_in(yfp, scanner); +	} +	if (yyparse(scanner) || cflex.error) +		exit(1); +	yylex_destroy(scanner); +} + +/* + * Create a new jail record. + */ +struct cfjail * +add_jail(void) +{ +	struct cfjail *j; + +	j = emalloc(sizeof(struct cfjail)); +	memset(j, 0, sizeof(struct cfjail)); +	TAILQ_INIT(&j->params); +	STAILQ_INIT(&j->dep[DEP_FROM]); +	STAILQ_INIT(&j->dep[DEP_TO]); +	j->queue = &cfjails; +	TAILQ_INSERT_TAIL(&cfjails, j, tq); +	return j; +} + +/* + * Add a parameter to a jail. + */ +void +add_param(struct cfjail *j, const struct cfparam *p, enum intparam ipnum, +    const char *value) +{ +	struct cfstrings nss; +	struct cfparam *dp, *np; +	struct cfstring *s, *ns; +	struct cfvar *v, *nv; +	const char *name; +	char *cs, *tname; +	unsigned flags; + +	if (j == NULL) { +		/* Create a single anonymous jail if one doesn't yet exist. */ +		j = TAILQ_LAST(&cfjails, cfjails); +		if (j == NULL) +			j = add_jail(); +	} +	TAILQ_INIT(&nss); +	if (p != NULL) { +		name = p->name; +		flags = p->flags; +		/* +		 * Make a copy of the parameter's string list, +		 * which may be freed if it's overridden later. +		 */ +		TAILQ_FOREACH(s, &p->val, tq) { +			ns = emalloc(sizeof(struct cfstring)); +			ns->s = estrdup(s->s); +			ns->len = s->len; +			STAILQ_INIT(&ns->vars); +			STAILQ_FOREACH(v, &s->vars, tq) { +				nv = emalloc(sizeof(struct cfvar)); +				nv->name = strdup(v->name); +				nv->pos = v->pos; +				STAILQ_INSERT_TAIL(&ns->vars, nv, tq); +			} +			TAILQ_INSERT_TAIL(&nss, ns, tq); +		} +	} else { +		flags = PF_APPEND; +		if (ipnum != IP__NULL) { +			name = intparams[ipnum].name; +			flags |= intparams[ipnum].flags; +		} else if ((cs = strchr(value, '='))) { +			tname = alloca(cs - value + 1); +			strlcpy(tname, value, cs - value + 1); +			name = tname; +			value = cs + 1; +		} else { +			name = value; +			value = NULL; +		} +		if (value != NULL) { +			ns = emalloc(sizeof(struct cfstring)); +			ns->s = estrdup(value); +			ns->len = strlen(value); +			STAILQ_INIT(&ns->vars); +			TAILQ_INSERT_TAIL(&nss, ns, tq); +		} +	} + +	/* See if this parameter has already been added. */ +	if (ipnum != IP__NULL) +		dp = j->intparams[ipnum]; +	else +		TAILQ_FOREACH(dp, &j->params, tq) +			if (!(dp->flags & PF_CONV) && equalopts(dp->name, name)) +				break; +	if (dp != NULL) { +		/* Found it - append or replace. */ +		if ((flags ^ dp->flags) & PF_VAR) { +			jail_warnx(j, "variable \"$%s\" cannot have the same " +			    "name as a parameter.", name); +			j->flags |= JF_FAILED; +			return; +		} +		if (dp->flags & PF_IMMUTABLE) { +			jail_warnx(j, "cannot redefine parameter \"%s\".", +			    dp->name); +			j->flags |= JF_FAILED; +			return; +		} +		if (strcmp(dp->name, name)) { +			free(dp->name); +			dp->name = estrdup(name); +		} +		if (!(flags & PF_APPEND) || TAILQ_EMPTY(&nss)) +			free_param_strings(dp); +		TAILQ_CONCAT(&dp->val, &nss, tq); +		dp->flags |= flags; +	} else { +		/* Not found - add it. */ +		np = emalloc(sizeof(struct cfparam)); +		np->name = estrdup(name); +		TAILQ_INIT(&np->val); +		TAILQ_CONCAT(&np->val, &nss, tq); +		np->flags = flags; +		np->gen = 0; +		TAILQ_INSERT_TAIL(&j->params, np, tq); +		if (ipnum != IP__NULL) +			j->intparams[ipnum] = np; +		else +			for (ipnum = IP__NULL + 1; ipnum < IP_NPARAM; ipnum++) +				if (!(intparams[ipnum].flags & PF_CONV) && +				    equalopts(name, intparams[ipnum].name)) { +					if (flags & PF_VAR) { +						jail_warnx(j, +						    "variable \"$%s\" " +						    "cannot have the same " +						    "name as a parameter.", +						    name); +						j->flags |= JF_FAILED; +						return; +					} +					j->intparams[ipnum] = np; +					np->flags |= intparams[ipnum].flags; +					break; +				} +	} +} + +/* + * Return if a boolean parameter exists and is true. + */ +int +bool_param(const struct cfparam *p) +{ +	const char *cs; + +	if (p == NULL) +		return 0; +	cs = strrchr(p->name, '.'); +	return !strncmp(cs ? cs + 1 : p->name, "no", 2) ^ +	    (TAILQ_EMPTY(&p->val) || +	     !strcasecmp(TAILQ_LAST(&p->val, cfstrings)->s, "true") || +	     (strtol(TAILQ_LAST(&p->val, cfstrings)->s, NULL, 10))); +} + +/* + * Set an integer if a parameter if it exists. + */ +int +int_param(const struct cfparam *p, int *ip) +{ +	if (p == NULL || TAILQ_EMPTY(&p->val)) +		return 0; +	*ip = strtol(TAILQ_LAST(&p->val, cfstrings)->s, NULL, 10); +	return 1; +} + +/* + * Return the string value of a scalar parameter if it exists. + */ +const char * +string_param(const struct cfparam *p) +{ +	return (p && !TAILQ_EMPTY(&p->val) +	    ? TAILQ_LAST(&p->val, cfstrings)->s : NULL); +} + +/* + * Check syntax and values of internal parameters.  Set some internal + * parameters based on the values of others. + */ +int +check_intparams(struct cfjail *j) +{ +	struct cfparam *p; +	struct cfstring *s; +	FILE *f; +	const char *val; +	char *cs, *ep, *ln; +	size_t lnlen; +	int error; +#if defined(INET) || defined(INET6) +	struct addrinfo hints; +	struct addrinfo *ai0, *ai; +	const char *hostname; +	int gicode, defif; +#endif +#ifdef INET +	struct in_addr addr4; +	int ip4ok; +	char avalue4[INET_ADDRSTRLEN]; +#endif +#ifdef INET6 +	struct in6_addr addr6; +	int ip6ok; +	char avalue6[INET6_ADDRSTRLEN]; +#endif + +	error = 0; +	/* Check format of boolan and integer values. */ +	TAILQ_FOREACH(p, &j->params, tq) { +		if (!TAILQ_EMPTY(&p->val) && (p->flags & (PF_BOOL | PF_INT))) { +			val = TAILQ_LAST(&p->val, cfstrings)->s; +			if (p->flags & PF_BOOL) { +				if (strcasecmp(val, "false") && +				    strcasecmp(val, "true") && +				    ((void)strtol(val, &ep, 10), *ep)) { +					jail_warnx(j, +					    "%s: unknown boolean value \"%s\"", +					    p->name, val); +					error = -1; +				} +			} else { +				(void)strtol(val, &ep, 10); +				if (ep == val || *ep) { +					jail_warnx(j, +					    "%s: non-integer value \"%s\"", +					    p->name, val); +					error = -1; +				} +			} +		} +	} + +#if defined(INET) || defined(INET6) +	/* +	 * The ip_hostname parameter looks up the hostname, and adds parameters +	 * for any IP addresses it finds. +	 */ +	if (((j->flags & JF_OP_MASK) != JF_STOP || +	    j->intparams[IP_INTERFACE] != NULL) && +	    bool_param(j->intparams[IP_IP_HOSTNAME]) && +	    (hostname = string_param(j->intparams[KP_HOST_HOSTNAME]))) { +		j->intparams[IP_IP_HOSTNAME] = NULL; +		/* +		 * Silently ignore unsupported address families from +		 * DNS lookups. +		 */ +#ifdef INET +		ip4ok = feature_present("inet"); +#endif +#ifdef INET6 +		ip6ok = feature_present("inet6"); +#endif +		if ( +#if defined(INET) && defined(INET6) +		    ip4ok || ip6ok +#elif defined(INET) +		    ip4ok +#elif defined(INET6) +		    ip6ok +#endif +			 ) { +			/* Look up the hostname (or get the address) */ +			memset(&hints, 0, sizeof(hints)); +			hints.ai_socktype = SOCK_STREAM; +			hints.ai_family = +#if defined(INET) && defined(INET6) +			    ip4ok ? (ip6ok ? PF_UNSPEC : PF_INET) :  PF_INET6; +#elif defined(INET) +			    PF_INET; +#elif defined(INET6) +			    PF_INET6; +#endif +			gicode = getaddrinfo(hostname, NULL, &hints, &ai0); +			if (gicode != 0) { +				jail_warnx(j, "host.hostname %s: %s", hostname, +				    gai_strerror(gicode)); +				error = -1; +			} else { +				/* +				 * Convert the addresses to ASCII so jailparam +				 * can convert them back.  Errors are not +				 * expected here. +				 */ +				for (ai = ai0; ai; ai = ai->ai_next) +					switch (ai->ai_family) { +#ifdef INET +					case AF_INET: +						memcpy(&addr4, +						    &((struct sockaddr_in *) +						    (void *)ai->ai_addr)-> +						    sin_addr, sizeof(addr4)); +						if (inet_ntop(AF_INET, +						    &addr4, avalue4, +						    INET_ADDRSTRLEN) == NULL) +							err(1, "inet_ntop"); +						add_param(j, NULL, KP_IP4_ADDR, +						    avalue4); +						break; +#endif +#ifdef INET6 +					case AF_INET6: +						memcpy(&addr6, +						    &((struct sockaddr_in6 *) +						    (void *)ai->ai_addr)-> +						    sin6_addr, sizeof(addr6)); +						if (inet_ntop(AF_INET6, +						    &addr6, avalue6, +						    INET6_ADDRSTRLEN) == NULL) +							err(1, "inet_ntop"); +						add_param(j, NULL, KP_IP6_ADDR, +						    avalue6); +						break; +#endif +					} +				freeaddrinfo(ai0); +			} +		} +	} + +	/* +	 * IP addresses may include an interface to set that address on, +	 * a netmask/suffix for that address and options for ifconfig. +	 * These are copied to an internal command parameter and then stripped +	 * so they won't be passed on to jailparam_set. +	 */ +	defif = string_param(j->intparams[IP_INTERFACE]) != NULL; +#ifdef INET +	if (j->intparams[KP_IP4_ADDR] != NULL) { +		TAILQ_FOREACH(s, &j->intparams[KP_IP4_ADDR]->val, tq) { +			cs = strchr(s->s, '|'); +			if (cs || defif) +				add_param(j, NULL, IP__IP4_IFADDR, s->s); +			if (cs) { +				s->len -= cs + 1 - s->s; +				memmove(s->s, cs + 1, s->len + 1); +			} +			if ((cs = strchr(s->s, '/')) != NULL) { +				*cs = '\0'; +				s->len = cs - s->s; +			} +			if ((cs = strchr(s->s, ' ')) != NULL) { +				*cs = '\0'; +				s->len = cs - s->s; +			} +		} +	} +#endif +#ifdef INET6 +	if (j->intparams[KP_IP6_ADDR] != NULL) { +		TAILQ_FOREACH(s, &j->intparams[KP_IP6_ADDR]->val, tq) { +			cs = strchr(s->s, '|'); +			if (cs || defif) +				add_param(j, NULL, IP__IP6_IFADDR, s->s); +			if (cs) { +				s->len -= cs + 1 - s->s; +				memmove(s->s, cs + 1, s->len + 1); +			} +			if ((cs = strchr(s->s, '/')) != NULL) { +				*cs = '\0'; +				s->len = cs - s->s; +			} +			if ((cs = strchr(s->s, ' ')) != NULL) { +				*cs = '\0'; +				s->len = cs - s->s; +			} +		} +	} +#endif +#endif + +	/* +	 * Read mount.fstab file(s), and treat each line as its own mount +	 * parameter. +	 */ +	if (j->intparams[IP_MOUNT_FSTAB] != NULL) { +		TAILQ_FOREACH(s, &j->intparams[IP_MOUNT_FSTAB]->val, tq) { +			if (s->len == 0) +				continue; +			f = fopen(s->s, "r"); +			if (f == NULL) { +				jail_warnx(j, "mount.fstab: %s: %s", +				    s->s, strerror(errno)); +				error = -1; +				continue; +			} +			while ((ln = fgetln(f, &lnlen))) { +				if ((cs = memchr(ln, '#', lnlen - 1))) +					lnlen = cs - ln + 1; +				if (ln[lnlen - 1] == '\n' || +				    ln[lnlen - 1] == '#') +					ln[lnlen - 1] = '\0'; +				else { +					cs = alloca(lnlen + 1); +					strlcpy(cs, ln, lnlen + 1); +					ln = cs; +				} +				add_param(j, NULL, IP__MOUNT_FROM_FSTAB, ln); +			} +			fclose(f); +		} +	} +	if (error) +		failed(j); +	return error; +} + +/* + * Import parameters into libjail's binary jailparam format. + */ +int +import_params(struct cfjail *j) +{ +	struct cfparam *p; +	struct cfstring *s, *ts; +	struct jailparam *jp; +	char *value, *cs; +	size_t vallen; +	int error; + +	error = 0; +	j->njp = 0; +	TAILQ_FOREACH(p, &j->params, tq) +		if (!(p->flags & PF_INTERNAL)) +			j->njp++; +	j->jp = jp = emalloc(j->njp * sizeof(struct jailparam)); +	TAILQ_FOREACH(p, &j->params, tq) { +		if (p->flags & PF_INTERNAL) +			continue; +		if (jailparam_init(jp, p->name) < 0) { +			error = -1; +			jail_warnx(j, "%s", jail_errmsg); +			jp++; +			continue; +		} +		if (TAILQ_EMPTY(&p->val)) +			value = NULL; +		else if (!jp->jp_elemlen || +			 !TAILQ_NEXT(TAILQ_FIRST(&p->val), tq)) { +			/* +			 * Scalar parameters silently discard multiple (array) +			 * values, keeping only the last value added.  This +			 * lets values added from the command line append to +			 * arrays wthout pre-checking the type. +			 */ +			value = TAILQ_LAST(&p->val, cfstrings)->s; +		} else { +			/* +			 * Convert arrays into comma-separated strings, which +			 * jailparam_import will then convert back into arrays. +			 */ +			vallen = 0; +			TAILQ_FOREACH(s, &p->val, tq) +				vallen += s->len + 1; +			value = alloca(vallen); +			cs = value; +			TAILQ_FOREACH_SAFE(s, &p->val, tq, ts) { +				memcpy(cs, s->s, s->len); +				cs += s->len + 1; +				cs[-1] = ','; +			} +			value[vallen - 1] = '\0'; +		} +		if (jailparam_import(jp, value) < 0) { +			error = -1; +			jail_warnx(j, "%s", jail_errmsg); +		} +		jp++; +	} +	if (error) { +		jailparam_free(j->jp, j->njp); +		free(j->jp); +		j->jp = NULL; +		failed(j); +	} +	return error; +} + +/* + * Check if options are equal (with or without the "no" prefix). + */ +int +equalopts(const char *opt1, const char *opt2) +{ +	char *p; + +	/* "opt" vs. "opt" or "noopt" vs. "noopt" */ +	if (strcmp(opt1, opt2) == 0) +		return (1); +	/* "noopt" vs. "opt" */ +	if (strncmp(opt1, "no", 2) == 0 && strcmp(opt1 + 2, opt2) == 0) +		return (1); +	/* "opt" vs. "noopt" */ +	if (strncmp(opt2, "no", 2) == 0 && strcmp(opt1, opt2 + 2) == 0) +		return (1); +	while ((p = strchr(opt1, '.')) != NULL && +	    !strncmp(opt1, opt2, ++p - opt1)) { +		opt2 += p - opt1; +		opt1 = p; +		/* "foo.noopt" vs. "foo.opt" */ +		if (strncmp(opt1, "no", 2) == 0 && strcmp(opt1 + 2, opt2) == 0) +			return (1); +		/* "foo.opt" vs. "foo.noopt" */ +		if (strncmp(opt2, "no", 2) == 0 && strcmp(opt1, opt2 + 2) == 0) +			return (1); +	} +	return (0); +} + +/* + * See if a jail name matches a wildcard. + */ +int +wild_jail_match(const char *jname, const char *wname) +{ +	const char *jc, *jd, *wc, *wd; + +	/* +	 * A non-final "*" component in the wild name matches a single jail +	 * component, and a final "*" matches one or more jail components. +	 */ +	for (jc = jname, wc = wname; +	     (jd = strchr(jc, '.')) && (wd = strchr(wc, '.')); +	     jc = jd + 1, wc = wd + 1) +		if (strncmp(jc, wc, jd - jc + 1) && strncmp(wc, "*.", 2)) +			return 0; +	return (!strcmp(jc, wc) || !strcmp(wc, "*")); +} + +/* + * Return if a jail name is a wildcard. + */ +int +wild_jail_name(const char *wname) +{ +	const char *wc; + +	for (wc = strchr(wname, '*'); wc; wc = strchr(wc + 1, '*')) +		if ((wc == wname || wc[-1] == '.') && +		    (wc[1] == '\0' || wc[1] == '.')) +			return 1; +	return 0; +} + +/* + * Free a parameter record and all its strings and variables. + */ +static void +free_param(struct cfparams *pp, struct cfparam *p) +{ +	free(p->name); +	free_param_strings(p); +	TAILQ_REMOVE(pp, p, tq); +	free(p); +} + +void +free_param_strings(struct cfparam *p) +{ +	struct cfstring *s; +	struct cfvar *v; + +	while ((s = TAILQ_FIRST(&p->val))) { +		free(s->s); +		while ((v = STAILQ_FIRST(&s->vars))) { +			free(v->name); +			STAILQ_REMOVE_HEAD(&s->vars, tq); +			free(v); +		} +		TAILQ_REMOVE(&p->val, s, tq); +		free(s); +	} +} diff --git a/usr.sbin/jail/jail.8 b/usr.sbin/jail/jail.8 new file mode 100644 index 000000000000..9aed9b671b9e --- /dev/null +++ b/usr.sbin/jail/jail.8 @@ -0,0 +1,1617 @@ +.\" Copyright (c) 2000, 2003 Robert N. M. Watson +.\" Copyright (c) 2008-2012 James Gritton +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\"    notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\"    notice, this list of conditions and the following disclaimer in the +.\"    documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd October 8, 2025 +.Dt JAIL 8 +.Os +.Sh NAME +.Nm jail +.Nd "manage system jails" +.Sh SYNOPSIS +.Ss From Configuration File +.Nm +.Op Fl cm +.Op Fl Cdqv +.Op Fl f Ar conf_file +.Op Fl p Ar limit +.Op Ar jail +.Nm +.Op Fl r +.Op Fl Cqv +.Op Fl f Ar conf_file +.Op Fl p Ar limit +.Op Cm * | Ar jail ... +.Ss Without Configuration File +.Nm +.Op Fl cm +.Op Fl dhilqv +.Op Fl J Ar jid_file +.Op Fl u Ar username +.Op Fl U Ar username +.Ar param Ns = Ns Ar value ... +.Op Cm command Ns = Ns Ar command ... +.Nm +.Op Fl rR +.Op Fl qv +.Op Cm * | Ar jail ... +.Ss Show Parameters +.Nm +.Op Fl f Ar conf_file +.Fl e +.Ar separator +.Ss Backward Compatibility +.Nm +.Op Fl dhilqv +.Op Fl J Ar jid_file +.Op Fl u Ar username +.Op Fl U Ar username +.Op Fl n Ar jailname +.Op Fl s Ar securelevel +.Ar path hostname ip Ns Op Cm \&, Ns Ar ... +.Ar command ... +.Sh DESCRIPTION +The +.Nm +utility creates new jails, or modifies or removes existing jails. +It can also print a list of configured jails and their parameters. +A jail +.Pq or Dq prison +is specified via parameters on the command line, or in the +.Xr jail.conf 5 +file. +.Pp +At least one of the options +.Fl c , +.Fl e , +.Fl m +or +.Fl r +must be specified. +These options are used alone or in combination to describe the operation to +perform: +.Bl -tag -width indent +.It Fl c +Create a new jail. +The jail +.Va jid +and +.Va name +parameters (if specified on the command line) +must not refer to an existing jail. +.It Fl e Ar separator +Exhibit a list of all configured non-wildcard jails and their parameters. +No jail creation, modification or removal performed if this option is used. +The +.Ar separator +string is used to separate parameters. +Use +.Xr jls 8 +utility to list running jails. +.It Fl m +Modify an existing jail. +One of the +.Va jid +or +.Va name +parameters must exist and refer to an existing jail. +Some parameters may not be changed on a running jail. +.It Fl r +Remove the +.Ar jail +specified by jid or name. +All jailed processes are killed, and all jails that are +children of this jail are also +removed. +.It Fl rc +Restart an existing jail. +The jail is first removed and then re-created, as if +.Dq Nm Fl r +and +.Dq Nm Fl c +were run in succession. +.It Fl cm +Create a jail if it does not exist, or modify the jail if it does exist. +.It Fl mr +Modify an existing jail. +The jail may be restarted if necessary to modify parameters than could +not otherwise be changed. +.It Fl cmr +Create a jail if it doesn't exist, or modify (and possibly restart) the +jail if it does exist. +.El +.Pp +Other available options are: +.Bl -tag -width indent +.It Fl C +Clean up after an already-removed jail, running commands and operations +that are typically run following jail removal. +.It Fl f Ar conf_file +Use configuration file +.Ar conf_file +instead of the default +.Pa /etc/jail.conf . +.It Fl h +Resolve the +.Va host.hostname +parameter (or +.Va hostname ) +and add all IP addresses returned by the resolver +to the list of addresses for this jail. +This is equivalent to the +.Va ip_hostname +parameter. +.It Fl i +Output (only) the jail identifier of the newly created jail(s). +This implies the +.Fl q +option. +.It Fl J Ar jid_file +Write a +.Ar jid_file +file, containing the parameters used to start the jail. +.It Fl l +Run commands in a clean environment. +This is deprecated and is equivalent to the exec.clean parameter. +.It Fl n Ar jailname +Set the jail's name. +This is deprecated and is equivalent to the +.Va name +parameter. +.It Fl p Ar limit +Limit the number of commands from +.Va  exec.* +that can run simultaneously. +.It Fl q +Suppress the message printed whenever a jail is created, modified or removed. +Only error messages will be printed. +.It Fl R +A variation of the +.Fl r +option that removes an existing jail without using the configuration file. +No removal-related parameters for this jail will be used \(em the jail will +simply be removed. +.It Fl s Ar securelevel +Set the +.Va kern.securelevel +MIB entry to the specified value inside the newly created jail. +This is deprecated and is equivalent to the +.Va securelevel +parameter. +.It Fl u Ar username +The user name from host environment as whom jailed commands should run. +This is deprecated and is equivalent to the +.Va exec.jail_user +and +.Va exec.system_jail_user +parameters. +.It Fl U Ar username +The user name from the jailed environment as whom jailed commands should run. +This is deprecated and is equivalent to the +.Va exec.jail_user +parameter. +.It Fl v +Print a message on every operation, such as running commands and +mounting filesystems. +.It Fl d +This is deprecated and is equivalent to the +.Va allow.dying +parameter, which is also deprecated. +It used to allow making changes to a +.Va dying +jail. +Now such jails are always replaced when a new jail is created with the same +.Va jid +or +.Va name . +.El +.Pp +If no arguments are given after the options, the operation (except +remove) will be performed on all jails specified in the +.Xr jail.conf 5 +file. +A single argument of a jail name will operate only on the specified jail. +The +.Fl r +and +.Fl R +options can also remove running jails that aren't in the +.Xr jail.conf 5 +file, specified by name or jid. +.Pp +An argument of +.Dq * +is a wildcard that will operate on all jails, regardless of whether +they appear in +.Xr jail.conf 5 ; +this is the surest way for +.Fl r +to remove all jails. +If hierarchical jails exist, a partial-matching wildcard definition may +be specified. +For example, an argument of +.Dq foo.* +would apply to jails with names like +.Dq foo.bar +and +.Dq foo.bar.baz . +.Pp +A jail may also be specified via parameters directly on the command line in +.Dq name=value +form, ignoring the contents of +.Xr jail.conf 5 . +For backward compatibility, the command line may also have four fixed +parameters, without names: +.Ar path , +.Ar hostname , +.Ar ip , +and +.Ar command . +.Ss Jail Parameters +Parameters in the +.Xr jail.conf 5 +file, or on the command line, are generally of the form +.Dq name=value . +Some parameters are boolean, and do not have a value but are set by the +name alone with or without a +.Dq no +prefix, e.g. +.Va persist +or +.Va nopersist . +They can also be given the values +.Dq true +and +.Dq false . +Other parameters may have more than one value, specified as a +comma-separated list, or with +.Dq += +in the configuration file (see +.Xr jail.conf 5 +for details). +List-based parameters may also be specified multiple times on the command +line, i.e., +.Dq name=value1,value2 +and +.Dq name=value1 name=value2 +are equivalent for such parameters. +.Pp +The +.Nm +utility recognizes two classes of parameters. +There are the true jail +parameters that are passed to the kernel when the jail is created, +which can be seen with +.Xr jls 8 , +and can (usually) be changed with +.Dq Nm Fl m . +Then there are pseudo-parameters that are only used by +.Nm +itself. +.Pp +Jails have a set of core parameters, and kernel modules can add their own +jail parameters. +The current set of available parameters can be retrieved via +.Dq Nm sysctl Fl d Va security.jail.param . +Any parameters not set will be given default values, often based on the +current environment. +The core parameters are: +.Bl -tag -width indent +.It Va jid +The jail identifier. +This will be assigned automatically to a new jail (or can be explicitly +set), and can be used to identify the jail for later modification, or +for such commands as +.Xr jls 8 +or +.Xr jexec 8 . +.It Va name +The jail name. +This is an arbitrary string that identifies a jail (except it may not +contain a +.Sq \&. ) . +Like the +.Va jid , +it can be passed to later +.Nm +commands, or to +.Xr jls 8 +or +.Xr jexec 8 . +If no +.Va name +is supplied, a default is assumed that is the same as the +.Va jid . +The +.Va name +parameter is implied by the +.Xr jail.conf 5 +file format, and need not be explicitly set when using the configuration +file. +.It Va path +The directory which is to be the root of the jail. +Any commands run inside the jail, either by +.Nm +or from +.Xr jexec 8 , +are run from this directory. +.It Va ip4.addr +A list of IPv4 addresses assigned to the jail. +If this is set, the jail is restricted to using only these addresses. +Any attempts to use other addresses fail, and attempts to use wildcard +addresses silently use the jailed address instead. +For IPv4 the first address given will be used as the source address +when source address selection on unbound sockets cannot find a better +match. +It is only possible to start multiple jails with the same IP address +if none of the jails has more than this single overlapping IP address +assigned to itself. +.It Va ip4.saddrsel +A boolean option to change the formerly mentioned behaviour and disable +IPv4 source address selection for the jail in favour of the primary +IPv4 address of the jail. +Source address selection is enabled by default for all jails and the +.Va ip4.nosaddrsel +setting of a parent jail is not inherited for any child jails. +.It Va ip4 +Control the availability of IPv4 addresses. +Possible values are +.Dq inherit +to allow unrestricted access to all system addresses, +.Dq new +to restrict addresses via +.Va ip4.addr , +and +.Dq disable +to stop the jail from using IPv4 entirely. +Setting the +.Va ip4.addr +parameter implies a value of +.Dq new . +.It Va ip6.addr , Va ip6.saddrsel , Va ip6 +A set of IPv6 options for the jail, the counterparts to +.Va ip4.addr , +.Va ip4.saddrsel +and +.Va ip4 +above. +.It Va vnet +Create the jail with its own virtual network stack, +with its own network interfaces, addresses, routing table, etc. +The kernel must have been compiled with the +.Sy VIMAGE option +for this to be available. +Possible values are +.Dq inherit +to use the system network stack, possibly with restricted IP addresses, +and +.Dq new +to create a new network stack. +.It Va host.hostname +The hostname of the jail. +Other similar parameters are +.Va host.domainname , +.Va host.hostuuid +and +.Va host.hostid . +.It Va host +Set the origin of hostname and related information. +Possible values are +.Dq inherit +to use the system information and +.Dq new +for the jail to use the information from the above fields. +Setting any of the above fields implies a value of +.Dq new . +.It Va securelevel +The value of the jail's +.Va kern.securelevel +sysctl. +A jail never has a lower securelevel than its parent system, but by +setting this parameter it may have a higher one. +If the system securelevel is changed, any jail securelevels will be at +least as secure. +.It Va devfs_ruleset +The number of the devfs ruleset that is enforced for mounting devfs in +this jail. +A value of zero (default) means no ruleset is enforced. +Descendant jails inherit the parent jail's devfs ruleset enforcement. +Mounting devfs inside a jail is possible only if the +.Va allow.mount +and +.Va allow.mount.devfs +permissions are effective and +.Va enforce_statfs +is set to a value lower than 2. +Devfs rules and rulesets cannot be viewed or modified from inside a jail. +.Pp +NOTE: It is important that only appropriate device nodes in devfs be +exposed to a jail; access to disk devices in the jail may permit processes +in the jail to bypass the jail sandboxing by modifying files outside of +the jail. +See +.Xr devfs 8 +for information on how to use devfs rules to limit access to entries +in the per-jail devfs. +A simple devfs ruleset for jails is available as ruleset #4 in +.Pa /etc/defaults/devfs.rules . +.It Va children.max +The number of child jails allowed to be created by this jail (or by +other jails under this jail). +This limit is zero by default, indicating the jail is not allowed to +create child jails. +See the +.Sx "Hierarchical Jails" +section for more information. +.It Va children.cur +The number of descendants of this jail, including its own child jails +and any jails created under them. +.It Va enforce_statfs +This determines what information processes in a jail are able to get +about mount points. +It affects the behaviour of the following syscalls: +.Xr statfs 2 , +.Xr fstatfs 2 , +.Xr getfsstat 2 , +and +.Xr fhstatfs 2 +(as well as similar compatibility syscalls). +When set to 0, all mount points are available without any restrictions. +When set to 1, only mount points below the jail's chroot directory are +visible. +In addition to that, the path to the jail's chroot directory is removed +from the front of their pathnames. +When set to 2 (default), above syscalls can operate only on a mount-point +where the jail's chroot directory is located. +.It Va persist +Setting this boolean parameter allows a jail to exist without any +processes. +Normally, a command is run as part of jail creation, and then the jail +is destroyed as its last process exits. +A new jail must have either the +.Va persist +parameter or +.Va exec.start +or +.Va command +pseudo-parameter set. +.It Va cpuset.id +The ID of the cpuset associated with this jail (read-only). +.It Va dying +This is true if the jail is in the process of shutting down (read-only). +.It Va parent +The +.Va jid +of the parent of this jail, or zero if this is a top-level jail +(read-only). +.It Va osrelease +The string for the jail's +.Va kern.osrelease +sysctl and uname -r. +.It Va osreldate +The number for the jail's +.Va kern.osreldate +and uname -K. +.It Va meta , Va env +An arbitrary string associated with the jail. +Its maximum buffer size is controlled by the global +.Va security.jail.meta_maxbufsize +sysctl, which can only be adjusted by the non-jailed root user. +While the +.Va meta +is hidden from the jail, the +.Va env +is readable through the +.Va security.jail.env +sysctl. +.Pp +Each buffer can be treated as a set of key=value\\n strings. +In order to add or replace a specific key the +.Va meta.keyname=value +or +.Va env.keyname=value +parameter notations must be used. +While +.Va meta.keyname= +or +.Va env.keyname= +reset the value to an empty string, the +.Va meta.keyname +or +.Va env.keyname +notations, without the equal sign, remove the given key. +Respectively, the same +.Va meta.keyname +or +.Va env.keyname +notations are used to query a specific key while reading jail parameters +using such commands as +.Xr jls 8 . +Multiple keys can be queried or modified with a single command. +.It Va allow.* +Some restrictions of the jail environment may be set on a per-jail +basis. +With the exception of +.Va allow.set_hostname +and +.Va allow.reserved_ports , +these boolean parameters are off by default. +.Bl -tag -width indent +.It Va allow.set_hostname +The jail's hostname may be changed via +.Xr hostname 1 +or +.Xr sethostname 3 . +.It Va allow.sysvipc +A process within the jail has access to System V IPC primitives. +This is deprecated in favor of the per-module parameters (see below). +When this parameter is set, it is equivalent to setting +.Va sysvmsg , +.Va sysvsem , +and +.Va sysvshm +all to +.Dq inherit . +.It Va allow.raw_sockets +The jail root is allowed to create raw sockets. +Setting this parameter allows utilities like +.Xr ping 8 +and +.Xr traceroute 8 +to operate inside the jail. +If this is set, the source IP addresses are enforced to comply +with the IP address bound to the jail, regardless of whether or not +the +.Dv IP_HDRINCL +flag has been set on the socket. +Since raw sockets can be used to configure and interact with various +network subsystems, extra caution should be used where privileged access +to jails is given out to untrusted parties. +.It Va allow.chflags +Normally, privileged users inside a jail are treated as unprivileged by +.Xr chflags 2 . +When this parameter is set, such users are treated as privileged, and +may manipulate system file flags subject to the usual constraints on +.Va kern.securelevel . +.It Va allow.mount +privileged users inside the jail will be able to mount and unmount file +system types marked as jail-friendly. +The +.Xr lsvfs 1 +command can be used to find file system types available for mount from +within a jail. +This permission is effective only if +.Va enforce_statfs +is set to a value lower than 2. +.It Va allow.mount.devfs +privileged users inside the jail will be able to mount and unmount the +devfs file system. +This permission is effective only together with +.Va allow.mount +and only when +.Va enforce_statfs +is set to a value lower than 2. +The devfs ruleset should be restricted from the default by using the +.Va devfs_ruleset +option. +.It Va allow.quotas +The jail root may administer quotas on the jail's filesystem(s). +This includes filesystems that the jail may share with other jails or +with non-jailed parts of the system. +.It Va allow.read_msgbuf +Jailed users may read the kernel message buffer. +If the +.Va security.bsd.unprivileged_read_msgbuf +MIB entry is zero, this will be restricted to the root user. +.It Va allow.socket_af +Sockets within a jail are normally restricted to IPv4, IPv6, local +(UNIX), and route. +This allows access to other protocol stacks that have not had jail +functionality added to them. +.It Va allow.mlock +Locking or unlocking physical pages in memory are normally not available +within a jail. +When this parameter is set, users may +.Xr mlock 2 +or +.Xr munlock 2 +memory subject to +.Va security.bsd.unprivileged_mlock +and resource limits. +.It Va allow.nfsd +The +.Xr mountd 8 , +.Xr nfsd 8 , +.Xr nfsuserd 8 , +.Xr gssd 8 +and +.Xr rpc.tlsservd 8 +daemons are permitted to run inside a properly configured vnet-enabled jail. +The jail's root must be a file system mount point and +.Va enforce_statfs +must not be set to 0, so that +.Xr mountd 8 +can export file systems visible within the jail. +.Va enforce_statfs +must be set to 1 if file systems mounted under the +jail's file system need to be exported by +.Xr mount 8 . +For exporting only the jail's file system, a setting of 2 +is sufficient. +If the kernel configuration does not include the +.Sy NFSD +option, +.Pa nfsd.ko +must be loaded outside of the jails. +This is normally done by adding +.Dq nfsd +to +.Va kld_list +in the +.Xr rc.conf 5 +file outside of the jails. +Similarily, if the +.Xr gssd 8 +is to be run in a jail, either the kernel +.Sy KGSSAPI +option needs to be specified or +.Dq kgssapi +and +.Dq kgssapi_krb5 +need to be in +.Va kld_list +in the +.Xr rc.conf 5 +file outside of the jails. +.It Va allow.reserved_ports +The jail root may bind to ports lower than 1024. +.It Va allow.unprivileged_parent_tampering +Unprivileged processes in the jail's parent may tamper with processes of the +same UID in the jail. +This includes the ability to signal, debug, and +.Xr cpuset 1 +processes that belong to the jail. +.It Va allow.unprivileged_proc_debug +Unprivileged processes in the jail may use debugging facilities. +.It Va allow.suser +The value of the jail's +.Va security.bsd.suser_enabled +sysctl. +The super-user will be disabled automatically if its parent system has it +disabled. +The super-user is enabled by default. +.It Va allow.extattr +Allow privileged processes in the jail to manipulate filesystem extended +attributes in the system namespace. +.It Va allow.adjtime +Allow privileged processes in the jail to slowly adjusting global operating +system time. +For example through utilities like +.Xr ntpd 8 . +.It Va allow.settime +Allow privileged processes in the jail to set global operating system data +and time. +For example through utilities like +.Xr date 1 . +This permission includes also +.Va allow.adjtime . +.It Va allow.routing +Allow privileged process in the non-VNET jail to modify the system routing +table. +.It Va allow.setaudit +Allow privileged processes in the jail to set +.Xr audit 4 +session state using +.Xr setaudit 2 +and related system calls. +This is useful, for example, for allowing a jailed +.Xr sshd 8 +to set the audit user ID for an authenticated session. +However, it gives jailed processes the ability to modify or disable audit +session state, so should be configured with care. +.El +.El +.Pp +Kernel modules may add their own parameters, which only exist when the +module is loaded. +These are typically headed under a parameter named after the module, +with values of +.Dq inherit +to give the jail full use of the module, +.Dq new +to encapsulate the jail in some module-specific way, +and +.Dq disable +to make the module unavailable to the jail. +There also may be other parameters to define jail behavior within the module. +Module-specific parameters include: +.Bl -tag -width indent +.It Va allow.mount.fdescfs +privileged users inside the jail will be able to mount and unmount the +fdescfs file system. +This permission is effective only together with +.Va allow.mount +and only when +.Va enforce_statfs +is set to a value lower than 2. +.It Va allow.mount.fusefs +privileged users inside the jail will be able to mount and unmount +fuse-based file systems. +This permission is effective only together with +.Va allow.mount +and only when +.Va enforce_statfs +is set to a value lower than 2. +.It Va allow.mount.nullfs +privileged users inside the jail will be able to mount and unmount the +nullfs file system. +This permission is effective only together with +.Va allow.mount +and only when +.Va enforce_statfs +is set to a value lower than 2. +.It Va allow.mount.procfs +privileged users inside the jail will be able to mount and unmount the +procfs file system. +This permission is effective only together with +.Va allow.mount +and only when +.Va enforce_statfs +is set to a value lower than 2. +.It Va allow.mount.linprocfs +privileged users inside the jail will be able to mount and unmount the +linprocfs file system. +This permission is effective only together with +.Va allow.mount +and only when +.Va enforce_statfs +is set to a value lower than 2. +.It Va allow.mount.linsysfs +privileged users inside the jail will be able to mount and unmount the +linsysfs file system. +This permission is effective only together with +.Va allow.mount +and only when +.Va enforce_statfs +is set to a value lower than 2. +.It Va allow.mount.tmpfs +privileged users inside the jail will be able to mount and unmount the +tmpfs file system. +This permission is effective only together with +.Va allow.mount +and only when +.Va enforce_statfs +is set to a value lower than 2. +.It Va allow.mount.zfs +privileged users inside the jail will be able to mount and unmount the +ZFS file system. +This permission is effective only together with +.Va allow.mount +and only when +.Va enforce_statfs +is set to a value lower than 2. +See +.Xr zfs-jail 8 +for information on how to configure the ZFS filesystem to operate from +within a jail. +.It Va allow.vmm +The jail may access +.Xr vmm 4 . +This flag is only available when the +.Xr vmm 4 +kernel module is loaded. +.It Va linux +Determine how a jail's Linux emulation environment appears. +A value of +.Dq inherit +will keep the same environment, and +.Dq new +will give the jail its own environment (still originally inherited when +the jail is created). +.It Va linux.osname , linux.osrelease , linux.oss_version +The Linux OS name, OS release, and OSS version associated with this jail. +.It Va sysvmsg +Allow access to SYSV IPC message primitives. +If set to +.Dq inherit , +all IPC objects on the system are visible to this jail, whether they +were created by the jail itself, the base system, or other jails. +If set to +.Dq new , +the jail will have its own key namespace, and can only see the objects +that it has created; +the system (or parent jail) has access to the jail's objects, but not to +its keys. +If set to +.Dq disable , +the jail cannot perform any sysvmsg-related system calls. +.It Va sysvsem, sysvshm +Allow access to SYSV IPC semaphore and shared memory primitives, in the +same manner as +.Va sysvmsg . +.It Va zfs.mount_snapshot +When set to 1, jailed users may access the contents of ZFS snapshots +under the filesystem's +.Pa .zfs +directory. +If +.Va allow.mount.zfs +is set, the snapshots may also be mounted. +.El +.Pp +There are pseudo-parameters that are not passed to the kernel, but are +used by +.Nm +to set up the jail environment, often by running specified commands +when jails are created or removed. +The +.Va exec.* +command parameters are +.Xr sh 1 +command lines that are run in either the system or jail environment. +They may be given multiple values, which would run the specified +commands in sequence. +All commands must succeed (return a zero exit status), or the jail will +not be created or removed, as appropriate. +.Pp +The following variables are added to the environment: +.Bl -tag -width indent -offset indent +.It Ev JID +The +.Va jid , +or jail identifier. +.It Ev JNAME +The +.Va name +of the jail. +.It Ev JPATH +The +.Va path +of the jail. +.El +.Pp +The pseudo-parameters are: +.Bl -tag -width indent +.It Va exec.prepare +Command(s) to run in the system environment to prepare a jail for creation. +These commands are executed before assigning IP addresses and mounting +filesystems, so they may be used to create a new jail filesystem if it does +not already exist. +.It Va exec.prestart +Command(s) to run in the system environment before a jail is created. +.It Va exec.created +Command(s) to run in the system environment right after a jail has been +created, but before commands (or services) get executed in the jail. +.It Va exec.start +Command(s) to run in the jail environment when a jail is created. +A typical command to run is +.Dq sh /etc/rc . +.It Va command +A synonym for +.Va exec.start +for use when specifying a jail directly on the command line. +Unlike other parameters whose value is a single string, +.Va command +uses the remainder of the +.Nm +command line as its own arguments. +.It Va exec.poststart +Command(s) to run in the system environment after a jail is created, +and after any +.Va exec.start +commands have completed. +.It Va exec.prestop +Command(s) to run in the system environment before a jail is removed. +.It Va exec.stop +Command(s) to run in the jail environment before a jail is removed, +and after any +.Va exec.prestop +commands have completed. +A typical command to run is +.Dq sh /etc/rc.shutdown jail . +.It Va exec.poststop +Command(s) to run in the system environment after a jail is removed. +.It Va exec.release +Command(s) to run in the system environment after all other actions are done. +These commands are executed after unmounting filesystems and removing IP +addresses, so they may be used to remove a jail filesystem if it is no longer +needed. +.It Va exec.clean +Run commands in a clean environment. +The environment is discarded except for +.Ev HOME , SHELL , TERM +and +.Ev USER . +.Ev HOME +and +.Ev SHELL +are set to the target login's default values. +.Ev USER +is set to the target login. +.Ev TERM +is imported from the current environment. +.Ev PATH +is set to "/bin:/usr/bin". +The environment variables from the login class capability database for the +target login are also set. +.Ev JID , +.Ev JNAME , +and +.Ev JPATH +are not set. +If a user is specified (as with +.Va exec.jail_user ) , +commands are run from that (possibly jailed) user's directory. +.It Va exec.jail_user +The user to run commands as, when running in the jail environment. +The default is to run the commands as the current user. +.It Va exec.system_jail_user +This boolean option looks for the +.Va exec.jail_user +in the system +.Xr passwd 5 +file, instead of in the jail's file. +.It Va exec.system_user +The user to run commands as, when running in the system environment. +The default is to run the commands as the current user. +.It Va exec.timeout +The maximum amount of time to wait for a command to complete, in +seconds. +If a command is still running after this timeout has passed, +the jail will not be created or removed, as appropriate. +.It Va exec.consolelog +A file to direct command output (stdout and stderr) to. +.It Va exec.fib +The FIB (routing table) to set when running commands inside the jail. +.It Va stop.timeout +The maximum amount of time to wait for a jail's processes to exit +after sending them a +.Dv SIGTERM +signal (which happens after the +.Va exec.stop +commands have completed). +After this many seconds have passed, the jail will be removed, which +will kill any remaining processes. +If this is set to zero, no +.Dv SIGTERM +is sent and the jail is immediately removed. +The default is 10 seconds. +.It Va interface +A network interface to add the jail's IP addresses +.Va ( ip4.addr +and +.Va ip6.addr ) +to. +An alias for each address will be added to the interface before the +jail is created, and will be removed from the interface after the +jail is removed. +.It Va ip4.addr +In addition to the IP addresses that are passed to the kernel, an +interface, netmask and additional parameters (as supported by +.Xr ifconfig 8 ) +may also be specified, in the form +.Dq Ar interface Ns | Ns Ar ip-address Ns / Ns Ar netmask param ... . +If an interface is given before the IP address, an alias for the address +will be added to that interface, as it is with the +.Va interface +parameter. +If a netmask in either dotted-quad or CIDR form is given +after an IP address, it will be used when adding the IP alias. +If additional parameters are specified then they will also be used when +adding the IP alias. +.It Va ip6.addr +In addition to the IP addresses that are passed to the kernel, +an interface, prefix and additional parameters (as supported by +.Xr ifconfig 8 ) +may also be specified, in the form +.Dq Ar interface Ns | Ns Ar ip-address Ns / Ns Ar prefix param ... . +.It Va vnet.interface +A comma separated list of network interfaces to give to a vnet-enabled jail +after is it created. +The interfaces will automatically be released when the jail is removed. +.It Va zfs.dataset +A list of ZFS datasets to be attached to the jail. +This requires +.Va allow.mount.zfs +to be set. +See +.Xr zfs-jail 8 +for information on how to configure a ZFS dataset to be operated from +within a jail. +.It Va ip_hostname +Resolve the +.Va host.hostname +parameter and add all IP addresses returned by the resolver +to the list of addresses +.Po Va ip4.addr +or +.Va ip6.addr Pc +for this jail. +This may affect default address selection for outgoing IPv4 connections +from jails. +The address first returned by the resolver for each address family +will be used as the primary address. +.It Va mount +A filesystem to mount before creating the jail (and to unmount after +removing it), given as a single +.Xr fstab 5 +line. +.It Va mount.fstab +An +.Xr fstab 5 +format file containing filesystems to mount before creating a jail. +.It Va mount.devfs +Mount a +.Xr devfs 4 +filesystem on the chrooted +.Pa /dev +directory, and apply the ruleset in the +.Va devfs_ruleset +parameter (or a default of ruleset 4: devfsrules_jail) +to restrict the devices visible inside the jail. +.It Va mount.fdescfs +Mount a +.Xr fdescfs 4 +filesystem on the chrooted +.Pa /dev/fd +directory. +.It Va mount.procfs +Mount a +.Xr procfs 4 +filesystem on the chrooted +.Pa /proc +directory. +.It Va allow.dying +This is deprecated and has no effect. +It used to allow making changes to a +.Va dying +jail. +Now such jails are always replaced when a new jail is created with the same +.Va jid +or +.Va name . +.It Va depend +Specify a jail (or jails) that this jail depends on. +When this jail is to be created, any jail(s) it depends on must already exist. +If not, they will be created automatically, up to the completion of the last +.Va exec.poststart +command, before any action will taken to create this jail. +When jails are removed the opposite is true: +this jail will be removed, up to the last +.Va exec.poststop +command, before any jail(s) it depends on are stopped. +.El +.Sh EXAMPLES +Jails are typically set up using one of two philosophies: either to +constrain a specific application (possibly running with privilege), or +to create a +.Dq "virtual system image" +running a variety of daemons and services. +In both cases, a fairly complete file system install of +.Fx +is +required, so as to provide the necessary command line tools, daemons, +libraries, application configuration files, etc. +However, for a virtual server configuration, a fair amount of +additional work is required so as to replace the +.Dq boot +process. +This manual page documents the configuration steps necessary to support +either of these steps, although the configuration steps may need to be +refined based on local requirements. +.Ss "Setting up a Jail Directory Tree" +To set up a jail directory tree containing an entire +.Fx +distribution, the following +.Xr sh 1 +command script can be used: +.Bd -literal -offset indent +D=/here/is/the/jail +cd /usr/src +mkdir -p $D +make world DESTDIR=$D +make distribution DESTDIR=$D +.Ed +.Pp +In many cases this example would put far more in the jail than needed. +In the other extreme case a jail might contain only one file: +the executable to be run in the jail. +.Pp +We recommend experimentation, and caution that it is a lot easier to +start with a +.Dq fat +jail and remove things until it stops working, +than it is to start with a +.Dq thin +jail and add things until it works. +.Ss "Setting Up a Jail" +Do what was described in +.Sx "Setting Up a Jail Directory Tree" +to build the jail directory tree. +For the sake of this example, we will +assume you built it in +.Pa /data/jail/testjail , +for a jail named +.Dq testjail . +Substitute below as needed with your +own directory, IP address, and hostname. +.Ss "Setting up the Host Environment" +First, set up the real system's environment to be +.Dq jail-friendly . +For consistency, we will refer to the parent box as the +.Dq "host environment" , +and to the jailed virtual machine as the +.Dq "jail environment" . +Since jails are implemented using IP aliases, one of the first things to do +is to disable IP services on the host system that listen on all local +IP addresses for a service. +If a network service is present in the host environment that binds all +available IP addresses rather than specific IP addresses, it may service +requests sent to jail IP addresses if the jail did not bind the port. +This means changing +.Xr inetd 8 +to only listen on the +appropriate IP address, and so forth. +Add the following to +.Pa /etc/rc.conf +in the host environment: +.Bd -literal -offset indent +sendmail_enable="NO" +inetd_flags="-wW -a 192.0.2.23" +rpcbind_enable="NO" +.Ed +.Pp +.Li 192.0.2.23 +is the native IP address for the host system, in this example. +Daemons that run out of +.Xr inetd 8 +can be easily configured to use only the specified host IP address. +Other daemons +will need to be manually configured \(em for some this is possible through +.Xr rc.conf 5 +flags entries; for others it is necessary to modify per-application +configuration files, or to recompile the application. +The following frequently deployed services must have their individual +configuration files modified to limit the application to listening +to a specific IP address: +.Pp +To configure +.Xr sshd 8 , +it is necessary to modify +.Pa /etc/ssh/sshd_config . +.Pp +To configure +.Xr sendmail 8 , +it is necessary to modify +.Pa /etc/mail/sendmail.cf . +.Pp +In addition, a number of services must be recompiled in order to run +them in the host environment. +This includes most applications providing services using +.Xr rpc 3 , +such as +.Xr rpcbind 8 , +.Xr nfsd 8 , +and +.Xr mountd 8 . +In general, applications for which it is not possible to specify which +IP address to bind should not be run in the host environment unless they +should also service requests sent to jail IP addresses. +Attempting to serve +NFS from the host environment may also cause confusion, and cannot be +easily reconfigured to use only specific IPs, as some NFS services are +hosted directly from the kernel. +Any third-party network software running +in the host environment should also be checked and configured so that it +does not bind all IP addresses, which would result in those services also +appearing to be offered by the jail environments. +.Pp +Once +these daemons have been disabled or fixed in the host environment, it is +best to reboot so that all daemons are in a known state, to reduce the +potential for confusion later (such as finding that when you send mail +to a jail, and its sendmail is down, the mail is delivered to the host, +etc.). +.Ss "Configuring the Jail" +Start any jail for the first time without configuring the network +interface so that you can clean it up a little and set up accounts. +As +with any machine (virtual or not), you will need to set a root password, time +zone, etc. +Some of these steps apply only if you intend to run a full virtual server +inside the jail; others apply both for constraining a particular application +or for running a virtual server. +.Pp +Start a shell in the jail: +.Bd -literal -offset indent +jail -c path=/data/jail/testjail mount.devfs \\ +	host.hostname=testhostname ip4.addr=192.0.2.100 \\ +	command=/bin/sh +.Ed +.Pp +Assuming no errors, you will end up with a shell prompt within the jail. +You can now run +.Xr bsdconfig 8 +and do the post-install configuration to set various configuration options, +or perform these actions manually by editing +.Pa /etc/rc.conf , +etc. +.Pp +.Bl -bullet -offset indent -compact +.It +Configure +.Pa /etc/resolv.conf +so that name resolution within the jail will work correctly. +.It +Run +.Xr newaliases 1 +to quell +.Xr sendmail 8 +warnings. +.It +Set a root password, probably different from the real host system. +.It +Set the timezone. +.It +Add accounts for users in the jail environment. +.It +Install any packages the environment requires. +.El +.Pp +You may also want to perform any package-specific configuration (web servers, +SSH servers, etc), patch up +.Pa /etc/syslog.conf +so it logs as you would like, etc. +If you are not using a virtual server, you may wish to modify +.Xr syslogd 8 +in the host environment to listen on the syslog socket in the jail +environment; in this example, the syslog socket would be stored in +.Pa /data/jail/testjail/var/run/log . +.Pp +Exit from the shell, and the jail will be shut down. +.Ss "Starting the Jail" +You are now ready to restart the jail and bring up the environment with +all of its daemons and other programs. +Create an entry for the jail in +.Pa /etc/jail.conf : +.Bd -literal -offset indent +testjail { +	path = /tmp/jail/testjail; +	mount.devfs; +	host.hostname = testhostname; +	ip4.addr = 192.0.2.100; +	interface = em0; +	exec.start = "/bin/sh /etc/rc"; +	exec.stop = "/bin/sh /etc/rc.shutdown jail"; +} +.Ed +.Pp +To start a virtual server environment, +.Pa /etc/rc +is run to launch various daemons and services, and +.Pa /etc/rc.shutdown +is run to shut them down when the jail is removed. +If you are running a single application in the jail, +substitute the command used to start the application for +.Dq /bin/sh /etc/rc ; +there may be some script available to cleanly shut down the application, +or it may be sufficient to go without a stop command, and have +.Nm +send +.Dv SIGTERM +to the application. +.Pp +Start the jail by running: +.Bd -literal -offset indent +jail -c testjail +.Ed +.Pp +A few warnings may be produced; however, it should all work properly. +You should be able to see +.Xr inetd 8 , +.Xr syslogd 8 , +and other processes running within the jail using +.Xr ps 1 , +with the +.Ql J +flag appearing beside jailed processes. +To see an active list of jails, use +.Xr jls 8 . +If +.Xr sshd 8 +is enabled in the jail environment, you should be able to +.Xr ssh 1 +to the hostname or IP address of the jailed environment, and log +in using the accounts you created previously. +.Pp +It is possible to have jails started at boot time. +Please refer to the +.Dq jail_* +variables in +.Xr rc.conf 5 +for more information. +.Ss "Managing the Jail" +Normal machine shutdown commands, such as +.Xr halt 8 , +.Xr reboot 8 , +and +.Xr shutdown 8 , +cannot be used successfully within the jail. +To kill all processes from within a jail, you may use one of the +following commands, depending on what you want to accomplish: +.Bd -literal -offset indent +kill -TERM -1 +kill -KILL -1 +.Ed +.Pp +This will send the +.Dv SIGTERM +or +.Dv SIGKILL +signals to all processes in the jail \(em be careful not to run this from +the host environment! +Once all of the jail's processes have died, unless the jail was created +with the +.Va persist +parameter, the jail will be removed. +Depending on +the intended use of the jail, you may also want to run +.Pa /etc/rc.shutdown +from within the jail. +.Pp +To shut down the jail from the outside, simply remove it with: +.Bd -literal -offset indent +jail -r +.Ed +.Pp +which will run any commands specified by +.Va exec.stop , +and then send +.Dv SIGTERM +and eventually +.Dv SIGKILL +to any remaining jailed processes. +.Pp +The +.Pa /proc/ Ns Ar pid Ns Pa /status +file contains, as its last field, the name of the jail in which the +process runs, or +.Dq Li - +to indicate that the process is not running within a jail. +The +.Xr ps 1 +command also shows a +.Ql J +flag for processes in a jail. +.Pp +You can also list/kill processes based on their jail ID. +To show processes and their jail ID, use the following command: +.Pp +.Dl "ps ax -o pid,jid,args" +.Pp +To show and then kill processes in jail number 3 use the following commands: +.Bd -literal -offset indent +pgrep -lfj 3 +pkill -j 3 +.Ed +or: +.Pp +.Dl "killall -j 3" +.Ss "Jails and File Systems" +It is not possible to +.Xr mount 8 +or +.Xr umount 8 +any file system inside a jail unless the file system is marked +jail-friendly, the jail's +.Va allow.mount +parameter is set, and the jail's +.Va enforce_statfs +parameter is lower than 2. +.Pp +Multiple jails sharing the same file system can influence each other. +For example, a user in one jail can fill the file system, +leaving no space for processes in the other jail. +Trying to use +.Xr quota 1 +to prevent this will not work either, as the file system quotas +are not aware of jails but only look at the user and group IDs. +This means the same user ID in two jails share a single file +system quota. +One would need to use one file system per jail to make this work. +.Ss "Sysctl MIB Entries" +The read-only entry +.Va security.jail.jailed +can be used to determine if a process is running inside a jail (value +is one) or not (value is zero). +.Pp +The variable +.Va security.jail.jail_max_af_ips +determines how may address per address family a jail may have. +The default is 255. +.Pp +Some MIB variables have per-jail settings. +Changes to these variables by a jailed process do not affect the host +environment, only the jail environment. +These variables are +.Va kern.securelevel , +.Va security.bsd.suser_enabled , +.Va kern.hostname , +.Va kern.domainname , +.Va kern.hostid , +and +.Va kern.hostuuid . +.Ss "Hierarchical Jails" +By setting a jail's +.Va children.max +parameter, processes within a jail may be able to create jails of their own. +These child jails are kept in a hierarchy, with jails only able to see and/or +modify the jails they created (or those jails' children). +Each jail has a read-only +.Va parent +parameter, containing the +.Va jid +of the jail that created it; a +.Va jid +of 0 indicates the jail is a child of the current jail (or is a top-level +jail if the current process isn't jailed). +.Pp +Jailed processes are not allowed to confer greater permissions than they +themselves are given, e.g., if a jail is created with +.Va allow.nomount , +it is not able to create a jail with +.Va allow.mount +set. +Similarly, such restrictions as +.Va ip4.addr +and +.Va securelevel +may not be bypassed in child jails. +.Pp +A child jail may in turn create its own child jails if its own +.Va children.max +parameter is set (remember it is zero by default). +These jails are visible to and can be modified by their parent and all +ancestors. +.Pp +Jail names reflect this hierarchy, with a full name being an MIB-type string +separated by dots. +For example, if a base system process creates a jail +.Dq foo , +and a process under that jail creates another jail +.Dq bar , +then the second jail will be seen as +.Dq foo.bar +in the base system (though it is only seen as +.Dq bar +to any processes inside jail +.Dq foo ) . +Jids on the other hand exist in a single space, and each jail must have a +unique jid. +.Pp +Like the names, a child jail's +.Va path +appears relative to its creator's own +.Va path . +This is by virtue of the child jail being created in the chrooted +environment of the first jail. +.Sh SEE ALSO +.Xr date 1 , +.Xr killall 1 , +.Xr lsvfs 1 , +.Xr newaliases 1 , +.Xr pgrep 1 , +.Xr pkill 1 , +.Xr ps 1 , +.Xr quota 1 , +.Xr adjtime 2 , +.Xr clock_settime 2 , +.Xr jail_set 2 , +.Xr ntp_adjtime 2 , +.Xr devfs 4 , +.Xr fdescfs 4 , +.Xr linprocfs 4 , +.Xr linsysfs 4 , +.Xr procfs 4 , +.Xr vmm 4 , +.Xr jail.conf 5 , +.Xr rc.conf 5 , +.Xr sysctl.conf 5 , +.Xr bsdconfig 8 , +.Xr chroot 8 , +.Xr devfs 8 , +.Xr halt 8 , +.Xr ifconfig 8 , +.Xr inetd 8 , +.Xr jexec 8 , +.Xr jls 8 , +.Xr mount 8 , +.Xr mountd 8 , +.Xr nfsd 8 , +.Xr ntpd 8 , +.Xr reboot 8 , +.Xr rpcbind 8 , +.Xr sendmail 8 , +.Xr shutdown 8 , +.Xr sysctl 8 , +.Xr syslogd 8 , +.Xr umount 8 , +.Xr zfs-jail 8 , +.Xr extattr 9 +.Sh HISTORY +The +.Nm +utility appeared in +.Fx 4.0 . +Hierarchical/extensible jails were introduced in +.Fx 8.0 . +The configuration file was introduced in +.Fx 9.1 . +.Sh AUTHORS +.An -nosplit +The jail feature was written by +.An Poul-Henning Kamp +for R&D Associates +who contributed it to +.Fx . +.Pp +.An Robert Watson +wrote the extended documentation, found a few bugs, added +a few new features, and cleaned up the userland jail environment. +.Pp +.An Bjoern A. Zeeb +added multi-IP jail support for IPv4 and IPv6 based on a patch +originally done by +.An Pawel Jakub Dawidek +for IPv4. +.Pp +.An James Gritton +added the extensible jail parameters, hierarchical jails, +and the configuration file. +.Sh BUGS +It might be a good idea to add an +address alias flag such that daemons listening on all IPs +.Pq Dv INADDR_ANY +will not bind on that address, which would facilitate building a safe +host environment such that host daemons do not impose on services offered +from within jails. +Currently, the simplest answer is to minimize services +offered on the host, possibly limiting it to services offered from +.Xr inetd 8 +which is easily configurable. +.Sh NOTES +Great care should be taken when managing directories visible within the jail. +For example, if a jailed process has its current working directory set to a +directory that is moved out of the jail's chroot, then the process may gain +access to the file space outside of the jail. +It is recommended that directories always be copied, rather than moved, out +of a jail. +.Pp +In addition, there are several ways in which an unprivileged user +outside the jail can cooperate with a privileged user inside the jail +and thereby obtain elevated privileges in the host environment. +Most of these attacks can be mitigated by ensuring that the jail root +is not accessible to unprivileged users in the host environment. +Regardless, as a general rule, untrusted users with privileged access +to a jail should not be given access to the host environment. diff --git a/usr.sbin/jail/jail.c b/usr.sbin/jail/jail.c new file mode 100644 index 000000000000..46cabf76ae11 --- /dev/null +++ b/usr.sbin/jail/jail.c @@ -0,0 +1,1071 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 1999 Poul-Henning Kamp. + * Copyright (c) 2009-2012 James Gritton + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/sysctl.h> + +#include <arpa/inet.h> +#include <netinet/in.h> + +#include <err.h> +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "jailp.h" + +#define JP_RDTUN(jp)	(((jp)->jp_ctltype & CTLFLAG_RDTUN) == CTLFLAG_RDTUN) + +struct permspec { +	const char	*name; +	enum intparam	ipnum; +	int		rev; +}; + +int iflag; +int note_remove; +int verbose; +const char *separator = "\t"; + +static void clear_persist(struct cfjail *j); +static int update_jail(struct cfjail *j); +static int rdtun_params(struct cfjail *j, int dofail); +static void running_jid(struct cfjail *j); +static void jail_quoted_warnx(const struct cfjail *j, const char *name_msg, +    const char *noname_msg); +static int jailparam_set_note(const struct cfjail *j, struct jailparam *jp, +    unsigned njp, int flags); +static void print_jail(FILE *fp, struct cfjail *j, int oldcl, int running); +static void print_param(FILE *fp, const struct cfparam *p, int sep, int doname); +static void show_jails(void); +static void quoted_print(FILE *fp, char *str); +static void usage(void) __dead2; + +static struct permspec perm_sysctl[] = { +    { "security.jail.set_hostname_allowed", KP_ALLOW_SET_HOSTNAME, 0 }, +    { "security.jail.sysvipc_allowed", KP_ALLOW_SYSVIPC, 0 }, +    { "security.jail.allow_raw_sockets", KP_ALLOW_RAW_SOCKETS, 0 }, +    { "security.jail.chflags_allowed", KP_ALLOW_CHFLAGS, 0 }, +    { "security.jail.mount_allowed", KP_ALLOW_MOUNT, 0 }, +    { "security.jail.socket_unixiproute_only", KP_ALLOW_SOCKET_AF, 1 }, +}; + +static const enum intparam startcommands[] = { +    IP__NULL, +    IP_EXEC_PREPARE, +#ifdef INET +    IP__IP4_IFADDR, +#endif +#ifdef INET6 +    IP__IP6_IFADDR, +#endif +    IP_MOUNT, +    IP__MOUNT_FROM_FSTAB, +    IP_MOUNT_DEVFS, +    IP_MOUNT_FDESCFS, +    IP_MOUNT_PROCFS, +    IP_EXEC_PRESTART, +    IP__OP, +    IP_EXEC_CREATED, +    IP_ZFS_DATASET, +    IP_VNET_INTERFACE, +    IP_EXEC_START, +    IP_COMMAND, +    IP_EXEC_POSTSTART, +    IP__NULL +}; + +static const enum intparam stopcommands[] = { +    IP__NULL, +    IP_EXEC_PRESTOP, +    IP_EXEC_STOP, +    IP_STOP_TIMEOUT, +    IP__OP, +    IP_EXEC_POSTSTOP, +    IP_MOUNT_PROCFS, +    IP_MOUNT_FDESCFS, +    IP_MOUNT_DEVFS, +    IP__MOUNT_FROM_FSTAB, +    IP_MOUNT, +#ifdef INET6 +    IP__IP6_IFADDR, +#endif +#ifdef INET +    IP__IP4_IFADDR, +#endif +    IP_EXEC_RELEASE, +    IP__NULL +}; + +static const enum intparam cleancommands[] = { +    IP__NULL, +    IP_EXEC_POSTSTOP, +    IP_MOUNT_PROCFS, +    IP_MOUNT_FDESCFS, +    IP_MOUNT_DEVFS, +    IP__MOUNT_FROM_FSTAB, +    IP_MOUNT, +#ifdef INET6 +    IP__IP6_IFADDR, +#endif +#ifdef INET +    IP__IP4_IFADDR, +#endif +    IP_EXEC_RELEASE, +    IP__NULL +}; + +static const struct { +	const char *name; +	enum intparam param; +} listparams[] = { +#ifdef INET +	{ "ip4.addr", KP_IP4_ADDR }, +#endif +#ifdef INET6 +	{ "ip6.addr", KP_IP6_ADDR }, +#endif +	{ "vnet.interface", IP_VNET_INTERFACE }, +	{ "zfs.dataset", IP_ZFS_DATASET }, +}; + +int +main(int argc, char **argv) +{ +	struct stat st; +	FILE *jfp; +	struct cfjail *j; +	char *JidFile; +	const char *cfname; +	size_t sysvallen; +	unsigned op, pi; +	int ch, docf, dying_warned, error, i, oldcl, sysval; +	int dflag, eflag, Rflag; +#if defined(INET) || defined(INET6) +	char *cs, *ncs; +#endif +#if defined(INET) && defined(INET6) +	struct in6_addr addr6; +#endif + +	op = 0; +	dflag = eflag = Rflag = 0; +	docf = 1; +	cfname = CONF_FILE; +	JidFile = NULL; + +	while ((ch = getopt(argc, argv, "cCde:f:hiJ:lmn:p:qrRs:u:U:v")) != -1) { +		switch (ch) { +		case 'c': +			op |= JF_START; +			break; +		case 'C': +			op |= JF_CLEANUP; +			break; +		case 'd': +			dflag = 1; +			break; +		case 'e': +			eflag = 1; +			separator = optarg; +			break; +		case 'f': +			cfname = optarg; +			break; +		case 'h': +#if defined(INET) || defined(INET6) +			add_param(NULL, NULL, IP_IP_HOSTNAME, NULL); +#endif +			docf = 0; +			break; +		case 'i': +			iflag = 1; +			verbose = -1; +			break; +		case 'J': +			JidFile = optarg; +			break; +		case 'l': +			add_param(NULL, NULL, IP_EXEC_CLEAN, NULL); +			docf = 0; +			break; +		case 'm': +			op |= JF_SET; +			break; +		case 'n': +			add_param(NULL, NULL, KP_NAME, optarg); +			docf = 0; +			break; +		case 'p': +			paralimit = strtol(optarg, NULL, 10); +			if (paralimit == 0) +				paralimit = -1; +			break; +		case 'q': +			verbose = -1; +			break; +		case 'r': +			op |= JF_STOP; +			break; +		case 'R': +			op |= JF_STOP; +			Rflag = 1; +			break; +		case 's': +			add_param(NULL, NULL, KP_SECURELEVEL, optarg); +			docf = 0; +			break; +		case 'u': +			add_param(NULL, NULL, IP_EXEC_JAIL_USER, optarg); +			add_param(NULL, NULL, IP_EXEC_SYSTEM_JAIL_USER, NULL); +			docf = 0; +			break; +		case 'U': +			add_param(NULL, NULL, IP_EXEC_JAIL_USER, optarg); +			add_param(NULL, NULL, IP_EXEC_SYSTEM_JAIL_USER, +			    "false"); +			docf = 0; +			break; +		case 'v': +			verbose = 1; +			break; +		default: +			usage(); +		} +	} +	argc -= optind; +	argv += optind; + +	if (eflag) { +		/* Just print list of all configured non-wildcard jails */ +		if (op || argc > 0) +			usage(); +		load_config(cfname); +		show_jails(); +		exit(0); +	} + +	/* Find out which of the command line styles this is. */ +	oldcl = 0; +	if (!op) { +		/* Old-style command line with four fixed parameters */ +		if (argc < 4 || argv[0][0] != '/') +			usage(); +		op = JF_START; +		docf = 0; +		oldcl = 1; +		add_param(NULL, NULL, KP_PATH, argv[0]); +		add_param(NULL, NULL, KP_HOST_HOSTNAME, argv[1]); +#if defined(INET) || defined(INET6) +		if (argv[2][0] != '\0') { +			for (cs = argv[2];; cs = ncs + 1) { +				ncs = strchr(cs, ','); +				if (ncs) +					*ncs = '\0'; +				add_param(NULL, NULL, +#if defined(INET) && defined(INET6) +				    inet_pton(AF_INET6, cs, &addr6) == 1 +				    ? KP_IP6_ADDR : KP_IP4_ADDR, +#elif defined(INET) +				    KP_IP4_ADDR, +#elif defined(INET6) +				    KP_IP6_ADDR, +#endif +				    cs); +				if (!ncs) +					break; +			} +		} +#endif +		for (i = 3; i < argc; i++) +			add_param(NULL, NULL, IP_COMMAND, argv[i]); +		/* Emulate the defaults from security.jail.* sysctls. */ +		sysvallen = sizeof(sysval); +		if (sysctlbyname("security.jail.jailed", &sysval, &sysvallen, +		    NULL, 0) == 0 && sysval == 0) { +			for (pi = 0; pi < sizeof(perm_sysctl) / +			     sizeof(perm_sysctl[0]); pi++) { +				sysvallen = sizeof(sysval); +				if (sysctlbyname(perm_sysctl[pi].name, +				    &sysval, &sysvallen, NULL, 0) == 0) +					add_param(NULL, NULL, +					    perm_sysctl[pi].ipnum, +					    (sysval ? 1 : 0) ^ +					    perm_sysctl[pi].rev +					    ? NULL : "false"); +			} +		} +	} else if (op == JF_STOP) { +		/* Jail remove, perhaps using the config file */ +		if (!docf || argc == 0) +			usage(); +		if (!Rflag) +			for (i = 0; i < argc; i++) +				if (strchr(argv[i], '=')) +					usage(); +		if ((docf = !Rflag && +		     (!strcmp(cfname, "-") || stat(cfname, &st) == 0))) +			load_config(cfname); +		note_remove = docf || argc > 1 || wild_jail_name(argv[0]); +	} else if (argc > 1 || (argc == 1 && strchr(argv[0], '='))) { +		/* Single jail specified on the command line */ +		if (Rflag || (op & JF_CLEANUP)) +			usage(); +		docf = 0; +		for (i = 0; i < argc; i++) { +			size_t l; + +			if (!strncmp(argv[i], "command", 7) && +			    (argv[i][7] == '\0' || argv[i][7] == '=')) { +				if (argv[i][7]  == '=') +					add_param(NULL, NULL, IP_COMMAND, +					    argv[i] + 8); +				for (i++; i < argc; i++) +					add_param(NULL, NULL, IP_COMMAND, +					    argv[i]); +				continue; +			} + +			/* +			 * Is this parameter a list? +			 */ +			for (l = 0; l < nitems(listparams); l++) { +				size_t len; + +				len = strlen(listparams[l].name); +				if (strncmp(argv[i], listparams[l].name, len) || +				    argv[i][len] != '=') +					continue; + +				for (cs = argv[i] + len + 1;; cs = ncs + 1) { +					ncs = strchr(cs, ','); +					if (ncs) +						*ncs = '\0'; +					add_param(NULL, NULL, +					    listparams[l].param, cs); +					if (!ncs) +						break; +				} +				break; +			} +			if (l == nitems(listparams)) +				add_param(NULL, NULL, 0, argv[i]); +		} +	} else { +		/* From the config file, perhaps with a specified jail */ +		if (Rflag || !docf) +			usage(); +		load_config(cfname); +	} + +	/* Find out which jails will be run. */ +	dep_setup(docf); +	error = 0; +	if ((op & JF_OP_MASK) == JF_STOP) { +		for (i = 0; i < argc; i++) +			if (start_state(argv[i], docf, op, Rflag) < 0) +				error = 1; +	} else { +		if (start_state(argv[0], docf, op, 0) < 0) +			exit(1); +	} + +	jfp = NULL; +	if (JidFile != NULL) { +		jfp = fopen(JidFile, "w"); +		if (jfp == NULL) +			err(1, "open %s", JidFile); +		setlinebuf(jfp); +	} +	setlinebuf(stdout); + +	/* +	 * The main loop: Get an available jail and perform the required +	 * operation on it.  When that is done, the jail may be finished, +	 * or it may go back for the next step. +	 */ +	dying_warned = 0; +	while ((j = next_jail())) +	{ +		if (j->flags & JF_FAILED) { +			error = 1; +			if (j->comparam == NULL) { +				dep_done(j, 0); +				continue; +			} +		} +		if (!(j->flags & JF_PARAMS)) +		{ +			j->flags |= JF_PARAMS; +			if (dflag) +				add_param(j, NULL, IP_ALLOW_DYING, NULL); +			if (check_intparams(j) < 0) +				continue; +			if ((j->flags & (JF_START | JF_SET)) && +			    import_params(j) < 0) +				continue; +		} +		if (j->intparams[IP_ALLOW_DYING] && !dying_warned) { +			warnx("%s", "the 'allow.dying' parameter and '-d' flag " +			    "are deprecated and have no effect."); +			dying_warned = 1; +		} +		if (!j->jid) +			running_jid(j); +		if (finish_command(j)) +			continue; + +		switch (j->flags & JF_OP_MASK) { +			/* +			 * These operations just turn into a different op +			 * depending on the jail's current status. +			 */ +		case JF_START_SET: +			j->flags = j->jid < 0 +			    ? (j->flags & JF_CLEANUP) | JF_START : JF_SET; +			break; +		case JF_SET_RESTART: +			if (j->jid < 0 && !(j->flags & JF_CLEANUP)) { +				jail_quoted_warnx(j, "not found", +				    "no jail specified"); +				failed(j); +				continue; +			} +			j->flags = rdtun_params(j, 0) +			    ? (j->flags & JF_CLEANUP) | JF_RESTART : JF_SET; +			if (j->flags == JF_RESTART) +				dep_reset(j); +			break; +		case JF_START_SET_RESTART: +			j->flags = j->jid < 0 ? JF_START : rdtun_params(j, 0) +			    ? (j->flags & JF_CLEANUP) | JF_RESTART : JF_SET; +			if (j->flags == JF_RESTART) +				dep_reset(j); +		} + +		switch (j->flags & JF_OP_MASK) { +		case JF_START: +			if (j->comparam == NULL) { +				if (j->jid > 0 && +				    !(j->flags & (JF_DEPEND | JF_WILD))) { +					jail_quoted_warnx(j, "already exists", +					    NULL); +					failed(j); +					continue; +				} +				if (dep_check(j)) +					continue; +				if (j->jid > 0) +					goto jail_create_done; +				if (j->flags & JF_CLEANUP) { +					j->flags |= JF_STOP; +					j->comparam = cleancommands; +				} else +					j->comparam = startcommands; +				j->comparam = startcommands; +				j->comstring = NULL; +			} +			if (next_command(j)) +				continue; +			if (j->flags & JF_STOP) +				goto jail_remove_done; +		jail_create_done: +			clear_persist(j); +			if (jfp != NULL) +				print_jail(jfp, j, oldcl, 1); +			dep_done(j, 0); +			break; + +		case JF_SET: +			if (j->jid < 0 && !(j->flags & JF_DEPEND)) { +				jail_quoted_warnx(j, "not found", +				    "no jail specified"); +				failed(j); +				continue; +			} +			if (dep_check(j)) +				continue; +			if (!(j->flags & JF_DEPEND)) { +				if (rdtun_params(j, 1) < 0 || +				    update_jail(j) < 0) +					continue; +				if (verbose >= 0 && (j->name || verbose > 0)) +					jail_note(j, "updated\n"); +			} +			dep_done(j, 0); +			break; + +		case JF_STOP: +		case JF_RESTART: +			if (j->comparam == NULL) { +				if (dep_check(j)) +					continue; +				if (j->flags & JF_CLEANUP) { +					j->comparam = j->jid < 0 +					    ? cleancommands : stopcommands; +				} else if (j->jid < 0) { +					if (!(j->flags & (JF_DEPEND|JF_WILD))) { +						if (verbose >= 0) +							jail_quoted_warnx(j, +							    "not found", NULL); +						failed(j); +					} +					goto jail_remove_done; +				} +				else +					j->comparam = stopcommands; +				j->comstring = NULL; +			} else if ((j->flags & JF_FAILED) && j->jid > 0) +				goto jail_remove_done; +			if (next_command(j)) +				continue; +		jail_remove_done: +			dep_done(j, 0); +			if ((j->flags & (JF_START | JF_FAILED)) == JF_START) { +				j->comparam = NULL; +				j->flags &= ~(JF_STOP | JF_CLEANUP); +				dep_reset(j); +				requeue(j, j->ndeps ? &depend : &ready); +			} +			break; +		} +	} + +	if (jfp != NULL) +		fclose(jfp); +	exit(error); +} + +/* + * Mark a jail's failure for future handling. + */ +void +failed(struct cfjail *j) +{ +	j->flags |= JF_FAILED; +	TAILQ_REMOVE(j->queue, j, tq); +	TAILQ_INSERT_HEAD(&ready, j, tq); +	j->queue = &ready; +} + +/* + * Exit slightly more gracefully when out of memory. + */ +void * +emalloc(size_t size) +{ +	void *p; + +	p = malloc(size); +	if (!p) +		err(1, "malloc"); +	return p; +} + +void * +erealloc(void *ptr, size_t size) +{ +	void *p; + +	p = realloc(ptr, size); +	if (!p) +		err(1, "malloc"); +	return p; +} + +char * +estrdup(const char *str) +{ +	char *ns; + +	ns = strdup(str); +	if (!ns) +		err(1, "malloc"); +	return ns; +} + +/* + * Print a message including an optional jail name. + */ +void +jail_note(const struct cfjail *j, const char *fmt, ...) +{ +	va_list ap, tap; +	char *cs; +	size_t len; + +	va_start(ap, fmt); +	va_copy(tap, ap); +	len = vsnprintf(NULL, 0, fmt, tap); +	va_end(tap); +	cs = alloca(len + 1); +	(void)vsnprintf(cs, len + 1, fmt, ap); +	va_end(ap); +	if (j->name) +		printf("%s: %s", j->name, cs); +	else +		printf("%s", cs); +} + +/* + * Print a warning message including an optional jail name. + */ +void +jail_warnx(const struct cfjail *j, const char *fmt, ...) +{ +	va_list ap, tap; +	char *cs; +	size_t len; + +	va_start(ap, fmt); +	va_copy(tap, ap); +	len = vsnprintf(NULL, 0, fmt, tap); +	va_end(tap); +	cs = alloca(len + 1); +	(void)vsnprintf(cs, len + 1, fmt, ap); +	va_end(ap); +	if (j->name) +		warnx("%s: %s", j->name, cs); +	else +		warnx("%s", cs); +} + +/* + * Create a new jail. + */ +int +create_jail(struct cfjail *j) +{ +	struct stat st; +	struct jailparam *jp, *setparams, *sjp; +	const char *path; +	int dopersist, ns; + +	/* +	 * Check the jail's path, with a better error message than jail_set +	 * gives. +	 */ +	if ((path = string_param(j->intparams[KP_PATH]))) { +		if (j->name != NULL && path[0] != '/') { +			jail_warnx(j, "path %s: not an absolute pathname", +			    path); +			return -1; +		} +		if (stat(path, &st) < 0) { +			jail_warnx(j, "path %s: %s", path, strerror(errno)); +			return -1; +		} +		if (!S_ISDIR(st.st_mode)) { +			jail_warnx(j, "path %s: %s", path, strerror(ENOTDIR)); +			return -1; +		} +	} + +	/* +	 * Copy all the parameters, except that "persist" is always set when +	 * there are commands to run later. +	 */ +	dopersist = !bool_param(j->intparams[KP_PERSIST]) && +	    (j->intparams[IP_EXEC_START] || j->intparams[IP_COMMAND] || +	     j->intparams[IP_EXEC_POSTSTART]); +	sjp = setparams = +	    alloca((j->njp + dopersist) * sizeof(struct jailparam)); +	if (dopersist && jailparam_init(sjp++, "persist") < 0) { +		jail_warnx(j, "%s", jail_errmsg); +		return -1; +	} +	for (jp = j->jp; jp < j->jp + j->njp; jp++) +		if (!dopersist || !equalopts(jp->jp_name, "persist")) +			*sjp++ = *jp; +	ns = sjp - setparams; + +	j->jid = jailparam_set_note(j, setparams, ns, JAIL_CREATE); +	if (j->jid < 0) { +		jail_warnx(j, "%s", jail_errmsg); +		failed(j); +	} +	if (dopersist) { +		jailparam_free(setparams, 1); +		if (j->jid > 0) +			j->flags |= JF_PERSIST; +	} +	return j->jid; +} + +/* + * Remove a temporarily set "persist" parameter. + */ +static void +clear_persist(struct cfjail *j) +{ +	struct iovec jiov[4]; +	int jid; + +	if (!(j->flags & JF_PERSIST)) +		return; +	j->flags &= ~JF_PERSIST; +	jiov[0].iov_base = __DECONST(char *, "jid"); +	jiov[0].iov_len = sizeof("jid"); +	jiov[1].iov_base = &j->jid; +	jiov[1].iov_len = sizeof(j->jid); +	jiov[2].iov_base = __DECONST(char *, "nopersist"); +	jiov[2].iov_len = sizeof("nopersist"); +	jiov[3].iov_base = NULL; +	jiov[3].iov_len = 0; +	jid = jail_set(jiov, 4, JAIL_UPDATE); +	if (verbose > 0) +		jail_note(j, "jail_set(JAIL_UPDATE) jid=%d nopersist%s%s\n", +		    j->jid, jid < 0 ? ": " : "", +		    jid < 0 ? strerror(errno) : ""); +} + +/* + * Set a jail's parameters. + */ +static int +update_jail(struct cfjail *j) +{ +	struct jailparam *jp, *setparams, *sjp; +	int ns, jid; + +	ns = 0; +	for (jp = j->jp; jp < j->jp + j->njp; jp++) +		if (!JP_RDTUN(jp)) +			ns++; +	if (ns == 0) +		return 0; +	sjp = setparams = alloca(++ns * sizeof(struct jailparam)); +	if (jailparam_init(sjp, "jid") < 0 || +	    jailparam_import_raw(sjp, &j->jid, sizeof j->jid) < 0) { +		jail_warnx(j, "%s", jail_errmsg); +		failed(j); +		return -1; +	} +	for (jp = j->jp; jp < j->jp + j->njp; jp++) +		if (!JP_RDTUN(jp)) +			*++sjp = *jp; + +	jid = jailparam_set_note(j, setparams, ns, JAIL_UPDATE); +	if (jid < 0) { +		jail_warnx(j, "%s", jail_errmsg); +		failed(j); +	} +	jailparam_free(setparams, 1); +	return jid; +} + +/* + * Return if a jail set would change any create-only parameters. + */ +static int +rdtun_params(struct cfjail *j, int dofail) +{ +	struct jailparam *jp, *rtparams, *rtjp; +	const void *jp_value; +	size_t jp_valuelen; +	int nrt, rval, bool_true; + +	if (j->flags & JF_RDTUN) +		return 0; +	j->flags |= JF_RDTUN; +	nrt = 0; +	for (jp = j->jp; jp < j->jp + j->njp; jp++) +		if (JP_RDTUN(jp) && strcmp(jp->jp_name, "jid")) +			nrt++; +	if (nrt == 0) +		return 0; +	rtjp = rtparams = alloca(++nrt * sizeof(struct jailparam)); +	if (jailparam_init(rtjp, "jid") < 0 || +	    jailparam_import_raw(rtjp, &j->jid, sizeof j->jid) < 0) { +		jail_warnx(j, "%s", jail_errmsg); +		exit(1); +	} +	for (jp = j->jp; jp < j->jp + j->njp; jp++) +		if (JP_RDTUN(jp) && strcmp(jp->jp_name, "jid")) { +			*++rtjp = *jp; +			rtjp->jp_value = NULL; +		} +	rval = 0; +	if (jailparam_get(rtparams, nrt, 0) > 0) { +		rtjp = rtparams + 1; +		for (jp = j->jp; rtjp < rtparams + nrt; jp++) { +			if (JP_RDTUN(jp) && strcmp(jp->jp_name, "jid")) { +				jp_value = jp->jp_value; +				jp_valuelen = jp->jp_valuelen; +				if (jp_value == NULL && jp_valuelen > 0) { +					if (jp->jp_flags & (JP_BOOL | +					    JP_NOBOOL | JP_JAILSYS)) { +						bool_true = 1; +						jp_value = &bool_true; +						jp_valuelen = sizeof(bool_true); +					} else if ((jp->jp_ctltype & CTLTYPE) == +					    CTLTYPE_STRING) +						jp_value = ""; +					else +						jp_valuelen = 0; +				} +				if (rtjp->jp_valuelen != jp_valuelen || +				    (CTLTYPE_STRING ? strncmp(rtjp->jp_value, +				    jp_value, jp_valuelen) +				    : memcmp(rtjp->jp_value, jp_value, +				    jp_valuelen))) { +					if (dofail) { +						jail_warnx(j, "%s cannot be " +						    "changed after creation", +						    jp->jp_name); +						failed(j); +						rval = -1; +					} else +						rval = 1; +					break; +				} +				rtjp++; +			} +		} +	} +	for (rtjp = rtparams + 1; rtjp < rtparams + nrt; rtjp++) +		rtjp->jp_name = NULL; +	jailparam_free(rtparams, nrt); +	return rval; +} + +/* + * Get the jail's jid if it is running. + */ +static void +running_jid(struct cfjail *j) +{ +	struct iovec jiov[2]; +	const char *pval; +	char *ep; +	int jid; + +	if ((pval = string_param(j->intparams[KP_JID]))) { +		if (!(jid = strtol(pval, &ep, 10)) || *ep) { +			j->jid = -1; +			return; +		} +		jiov[0].iov_base = __DECONST(char *, "jid"); +		jiov[0].iov_len = sizeof("jid"); +		jiov[1].iov_base = &jid; +		jiov[1].iov_len = sizeof(jid); +	} else if ((pval = string_param(j->intparams[KP_NAME]))) { +		jiov[0].iov_base = __DECONST(char *, "name"); +		jiov[0].iov_len = sizeof("name"); +		jiov[1].iov_len = strlen(pval) + 1; +		jiov[1].iov_base = alloca(jiov[1].iov_len); +		strcpy(jiov[1].iov_base, pval); +	} else { +		j->jid = -1; +		return; +	} + +	j->jid = jail_get(jiov, 2, 0); +	if (j->jid > 0 && j->intparams[KP_JID] == NULL) { +		char jidstr[16]; + +		(void)snprintf(jidstr, sizeof(jidstr), "%d", j->jid); +		add_param(j, NULL, KP_JID, jidstr); +	} +} + +static void +jail_quoted_warnx(const struct cfjail *j, const char *name_msg, +    const char *noname_msg) +{ +	const char *pval; + +	if ((pval = j->name) || (pval = string_param(j->intparams[KP_JID])) || +	    (pval = string_param(j->intparams[KP_NAME]))) +		warnx("\"%s\" %s", pval, name_msg); +	else +		warnx("%s", noname_msg); +} + +/* + * Set jail parameters and possibly print them out. + */ +static int +jailparam_set_note(const struct cfjail *j, struct jailparam *jp, unsigned njp, +    int flags) +{ +	char *value; +	int jid; +	unsigned i; + +	jid = jailparam_set(jp, njp, flags); +	if (verbose > 0) { +		jail_note(j, "jail_set(%s)", +		    (flags & (JAIL_CREATE | JAIL_UPDATE)) == JAIL_CREATE +		    ? "JAIL_CREATE" : "JAIL_UPDATE"); +		for (i = 0; i < njp; i++) { +			printf(" %s", jp[i].jp_name); +			if (jp[i].jp_value == NULL) +				continue; +			putchar('='); +			value = jailparam_export(jp + i); +			if (value == NULL) +				err(1, "jailparam_export"); +			quoted_print(stdout, value); +			free(value); +		} +		if (jid < 0) +			printf(": %s", strerror(errno)); +		printf("\n"); +	} +	return jid; +} + +/* + * Print a jail record. + */ +static void +print_jail(FILE *fp, struct cfjail *j, int oldcl, int running) +{ +	struct cfparam *p; +	int printsep; + +	if (oldcl) { +		if (running) +			fprintf(fp, "%d%s", j->jid, separator); +		print_param(fp, j->intparams[KP_PATH], ',', 0); +		fputs(separator, fp); +		print_param(fp, j->intparams[KP_HOST_HOSTNAME], ',', 0); +		fputs(separator, fp); +#ifdef INET +		print_param(fp, j->intparams[KP_IP4_ADDR], ',', 0); +#ifdef INET6 +		if (j->intparams[KP_IP4_ADDR] && +		    !TAILQ_EMPTY(&j->intparams[KP_IP4_ADDR]->val) && +		    j->intparams[KP_IP6_ADDR] && +		    !TAILQ_EMPTY(&j->intparams[KP_IP6_ADDR]->val)) +		    putc(',', fp); +#endif +#endif +#ifdef INET6 +		print_param(fp, j->intparams[KP_IP6_ADDR], ',', 0); +#endif +		fputs(separator, fp); +		print_param(fp, j->intparams[IP_COMMAND], ' ', 0); +	} else { +		printsep = 0; +		if (running) { +			fprintf(fp, "jid=%d", j->jid); +			printsep = 1; +		} +		TAILQ_FOREACH(p, &j->params, tq) +			if (strcmp(p->name, "jid")) { +				if (printsep) +					fputs(separator, fp); +				else +					printsep = 1; +				print_param(fp, p, ',', 1); +			} +	} +	putc('\n', fp); +} + +/* + * Exhibit list of all configured non-wildcard jails + */ +static void +show_jails(void) +{ +	struct cfjail *j; +	 +	TAILQ_FOREACH(j, &cfjails, tq) +		print_jail(stdout, j, 0, 0); +} + +/* + * Print a parameter value, or a name=value pair. + */ +static void +print_param(FILE *fp, const struct cfparam *p, int sep, int doname) +{ +	const struct cfstring *s, *ts; + +	if (doname) +		fputs(p->name, fp); +	if (p == NULL || TAILQ_EMPTY(&p->val)) +		return; +	if (doname) +		putc('=', fp); +	TAILQ_FOREACH_SAFE(s, &p->val, tq, ts) { +		quoted_print(fp, s->s); +		if (ts != NULL) +			putc(sep, fp); +	} +} + +/* + * Print a string with quotes around spaces. + */ +static void +quoted_print(FILE *fp, char *str) +{ +	int c, qc; +	char *p = str; + +	qc = !*p ? '"' +	    : strchr(p, '\'') ? '"' +	    : strchr(p, '"') ? '\'' +	    : strchr(p, ' ') || strchr(p, '\t') ? '"' +	    : 0; +	if (qc) +		putc(qc, fp); +	while ((c = *p++)) { +		if (c == '\\' || c == qc) +			putc('\\', fp); +		putc(c, fp); +	} +	if (qc) +		putc(qc, fp); +} + +static void +usage(void) +{ + +	(void)fprintf(stderr, +	    "usage: jail [-dhilqv] [-J jid_file] [-u username] [-U username]\n" +	    "            -[cmr] param=value ... [command=command ...]\n" +	    "       jail [-dqv] [-f file] -[cmr] [jail]\n" +	    "       jail [-qv] [-f file] -[rR] ['*' | jail ...]\n" +	    "       jail [-dhilqv] [-J jid_file] [-u username] [-U username]\n" +	    "            [-n jailname] [-s securelevel]\n" +	    "            path hostname ip[,...] command ...\n" +	    "       jail [-f file] -e separator\n"); +	exit(1); +} diff --git a/usr.sbin/jail/jail.conf.5 b/usr.sbin/jail/jail.conf.5 new file mode 100644 index 000000000000..627c38b0f8b6 --- /dev/null +++ b/usr.sbin/jail/jail.conf.5 @@ -0,0 +1,275 @@ +.\"- +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.\" Copyright (c) 2012 James Gritton +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\"    notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\"    notice, this list of conditions and the following disclaimer in the +.\"    documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd September 21, 2024 +.Dt JAIL.CONF 5 +.Os +.Sh NAME +.Nm jail.conf +.Nd configuration file for system jails +.Sh DESCRIPTION +The +.Nm +file consists of one or more jail definitions statements for use by the +.Xr jail 8 +management program. +A jail definition statement consists of a single word, the name of the jail, +an opening curly brace, a list of at least two parameter assignments, +and a closing curly brace. +A parameter assignment consists of a single word, the parameter name, +an equals sign, a value enclosed in double quotes, and a terminating semicolon. +.Pp +The syntax of a jail definition is as follows: +.Bd -unfilled +. Ar jailname Cm \&{ +.Bd -unfilled -offset indent -compact +.Ar parameter Cm = Qq Ar value ; +\&... +.Ed +.Cm \&} +.Ed +.Pp +This is used by +.Xr jail 8 +to specify a jail on the command line and report the jail status, +and is also passed to the kernel when creating the jail. +.Ss Parameters +A jail is defined by a set of named parameters, specified inside the +jail definition. +.Em See +.Xr jail 8 +.Em for a list of jail parameters +passed to the kernel, as well as internal parameters used when creating and +removing jails. +.Pp +A typical parameter has a name and a value. +Some parameters are boolean and may be specified with values of +.Dq true +or +.Dq false , +or as valueless shortcuts, with a +.Dq no +prefix indicating a false value. +For example, these are equivalent: +.Bd -literal -offset indent +allow.mount = "false"; +allow.nomount; +.Ed +.Pp +Other parameters may have more than one value. +A comma-separated list of values may be set in a single statement, +or an existing parameter list may be appended to using +.Dq += : +.Bd -literal -offset indent +ip4.addr = 10.1.1.1, 10.1.1.2, 10.1.1.3; + +ip4.addr = 10.1.1.1; +ip4.addr += 10.1.1.2; +ip4.addr += 10.1.1.3; +.Ed +.Pp +Note the +.Va name +parameter is implicitly set to the name in the jail definition. +.Ss String format +Parameter values, including jail names, can be single tokens or quoted +strings. +A token is any sequence of characters that are not considered special in +the syntax of the configuration file (such as a semicolon or +whitespace). +If a value contains anything more than letters, numbers, dots, dashes +and underscores, it is advisable to put quote marks around that value. +Either single or double quotes may be used. +.Pp +Special characters may be quoted by preceding them with a backslash. +Common C-style backslash character codes are also supported, including +control characters and octal or hex ASCII codes. +A backslash at the end of a line will ignore the subsequent newline and +continue the string at the start of the next line. +.Ss Variables +A string may use shell-style variable substitution. +A parameter or variable name preceded by a dollar sign, and possibly +enclosed in braces, will be replaced with the value of that parameter or +variable. +For example, a jail's path may be defined in terms of its name or +hostname: +.Bd -literal -offset indent +path = "/var/jail/$name"; + +path = "/var/jail/${host.hostname}"; +.Ed +.Pp +Variable substitution occurs in unquoted tokens or in double-quoted +strings, but not in single-quote strings. +.Pp +A variable is defined in the same way a parameter is, except that the +variable name is preceded with a dollar sign: +.Bd -literal -offset indent +$parentdir = "/var/jail"; +path = "$parentdir/$name"; +.Ed +.Pp +The difference between parameters and variables is that variables are +only used for substitution, while parameters are used both for +substitution and for passing to the kernel. +.Ss Wildcards +A jail definition with a name of +.Dq * +is used to define wildcard parameters. +Every defined jail will contain both the parameters from its own +definition statement, as well as any parameters in a wildcard +definition. +.Pp +Variable substitution is done on a per-jail basis, even when that +substitution is for a parameter defined in a wildcard section. +This is useful for wildcard parameters based on e.g., a jail's name. +.Pp +Later definitions in the configuration file supersede earlier ones, so a +wildcard section placed before (above) a jail definition defines +parameters that could be changed on a per-jail basis. +Or a wildcard section placed after (below) all jails would contain +parameters that always apply to every jail. +Multiple wildcard statements are allowed, and wildcard parameters may +also be specified outside of a jail definition statement. +.Pp +If hierarchical jails are defined, a partial-matching wildcard +definition may be specified. +For example, a definition with a name of +.Dq foo.* +would apply to jails with names like +.Dq foo.bar +and +.Dq foo.bar.baz . +.Ss Includes +A line of the form +.Bd -literal -offset ident +\&.include "filename"; +.Ed +.Pp +will include another file (or files) in the configuration. +The filename should be either absolute, or relative to the +configuration file's directory. +It cannot contain variable expansions, but may contain +.Xr glob 3 +patterns. +.Pp +The included file must exist, though a filename glob may match zero or +more files. +This allows inclusion of any/all files in a directory, such as +.Dq Pa /etc/jail.conf.d/*.conf , +or conditional inclusion of a single file, such as +.Dq Pa jail.foo[.]conf . +.Ss Comments +The configuration file may contain comments in the common C, C++, and +shell formats: +.Bd -literal -offset indent +/* This is a C style comment. + * It may span multiple lines. + */ + +// This is a C++ style comment. + +#  This is a shell style comment. +.Ed +.Pp +Comments are legal wherever whitespace is allowed, i.e., anywhere except +in the middle of a string or a token. +.Sh FILES +.Bl -tag -width "indent" -compact +.It Pa /etc/jail.conf +.It Pa /etc/jail.*.conf +.It Pa /etc/jail.conf.d/*.conf +.It Pa /usr/share/examples/jails/ +.El +.Sh EXAMPLES +.Bd -literal +# Typical static defaults: +# Use the rc scripts to start and stop jails.  Mount jail's /dev. +exec.start = "/bin/sh /etc/rc"; +exec.stop = "/bin/sh /etc/rc.shutdown jail"; +exec.clean; +mount.devfs; + +# Dynamic wildcard parameter: +# Base the path off the jail name. +path = "/var/jail/$name"; + +# A typical jail. +foo { +	host.hostname = "foo.com"; +	ip4.addr = 10.1.1.1, 10.1.1.2, 10.1.1.3; +} + +# This jail overrides the defaults defined above. +bar { +	exec.start = ''; +	exec.stop = ''; +	path = /; +	mount.nodevfs; +	persist;	// Required because there are no processes +} + +# Include configurations from standard locations. +\[char46]include "/etc/jail.conf.d/*.conf"; +\[char46]include "/etc/jail.*.conf"; +\[char46]include "/usr/local/etc/jail[.]conf"; +\[char46]include "/usr/local/etc/jail.conf.d/*.conf"; +\[char46]include "/usr/local/etc/jail.*.conf"; +.Ed +.Sh SEE ALSO +.Xr jail 2 , +.Xr jail 3 , +.Xr jail 3lua , +.Xr rc.conf 5 , +.Xr jail 8 , +.Xr jexec 8 , +.Xr jls 8 , +.Xr zfs-jail 8 +.Pp +The +.Dq Jails and Containers +chapter of the +.%B FreeBSD Handbook . +.Sh HISTORY +The +.Xr jail 8 +utility appeared in +.Fx 4.0 . +The +.Nm +file was added in +.Fx 9.1 . +.Sh AUTHORS +.An -nosplit +The jail feature was written by +.An Poul-Henning Kamp +for R&D Associates +who contributed it to +.Fx . +.Pp +.An James Gritton +added the extensible jail parameters and configuration file. diff --git a/usr.sbin/jail/jaillex.l b/usr.sbin/jail/jaillex.l new file mode 100644 index 000000000000..1dc61c1e9ab0 --- /dev/null +++ b/usr.sbin/jail/jaillex.l @@ -0,0 +1,218 @@ +%{ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2011 James Gritton + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +#include <err.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +#include "jailp.h" +#include "y.tab.h" + +#define YY_DECL int yylex(YYSTYPE *yylval, yyscan_t yyscanner) +#define YY_EXTRA_TYPE struct cflex* + +extern YY_DECL; + +static ssize_t text2lval(size_t triml, size_t trimr, int tovar, +    YYSTYPE *yylval, yyscan_t scanner); + +static int instr; +%} + +%option noyywrap +%option noinput +%option nounput +%option reentrant +%option yylineno + +%start _ DQ + +%% + +			/* Whitespace or equivalent */ +<_>[ \t\r\n]+		instr = 0; +<_>#.*			instr = 0; +<_>\/\/.*		instr = 0; +<_>\/\*([^*]|(\*+([^*\/])))*\*+\/ instr = 0; + +			/* Reserved tokens */ +<_>\+=			{ +				instr = 0; +				return PLEQ; +			} +<_>[,;={}]		{ +				instr = 0; +				return yytext[0]; +			} + +			/* Atomic (unquoted) strings */ +<_,DQ>[A-Za-z0-9_!%&()\-.:<>?@\[\]^`|~]+ | +<_,DQ>\\(.|\n|[0-7]{1,3}|x[0-9A-Fa-f]{1,2}) | +<_,DQ>[$*+/\\]		{ +				(void)text2lval(0, 0, 0, yylval, yyscanner); +				return instr ? STR1 : (instr = 1, STR); +			} + +			/* Single and double quoted strings */ +<_>'([^\'\\]|\\(.|\n))*' { +				(void)text2lval(1, 1, 0, yylval, yyscanner); +				return instr ? STR1 : (instr = 1, STR); +			} +<_>\"([^"\\]|\\(.|\n))*\" | +<DQ>[^\"$\\]([^"\\]|\\(.|\n))*\" { +				size_t skip; +				ssize_t atvar; + +				skip = yytext[0] == '"' ? 1 : 0; +				atvar = text2lval(skip, 1, 1, yylval, +					yyscanner); +				if (atvar < 0) +					BEGIN _; +				else { +					/* +					 * The string has a variable inside it. +					 * Go into DQ mode to get the variable +					 * and then the rest of the string. +					 */ +					BEGIN DQ; +					yyless(atvar); +				} +				return instr ? STR1 : (instr = 1, STR); +			} +<DQ>\"			BEGIN _; + +			/* Variables, single-word or bracketed */ +<_,DQ>$[A-Za-z_][A-Za-z_0-9]* { +				(void)text2lval(1, 0, 0, yylval, yyscanner); +				return instr ? VAR1 : (instr = 1, VAR); +			} +<_>$\{([^\n{}]|\\(.|\n))*\} | +<DQ>$\{([^\n\"{}]|\\(.|\n))*\} { +				(void)text2lval(2, 1, 0, yylval, yyscanner); +				return instr ? VAR1 : (instr = 1, VAR); +			} + +			/* Partially formed bits worth complaining about */ +<_>\/\*([^*]|(\*+([^*\/])))*\** { +				warnx("%s line %d: unterminated comment", +				    yyextra->cfname, yylineno); +				yyextra->error = 1; +			} +<_>'([^\n'\\]|\\.)*	| +<_>\"([^\n\"\\]|\\.)*	{ +				warnx("%s line %d: unterminated string", +				    yyextra->cfname, yylineno); +				yyextra->error = 1; +			} +<_>$\{([^\n{}]|\\.)*	| +<DQ>$\{([^\n\"{}]|\\.)*	{ +				warnx("%s line %d: unterminated variable", +				    yyextra->cfname, yylineno); +				yyextra->error = 1; +			} + +			/* A hack because "<0>" rules aren't allowed */ +<_>.			return yytext[0]; +.|\n			{ +				BEGIN _; +				yyless(0); +			} + +%% + +/* + * Copy string from yytext to yylval, handling backslash escapes, + * and optionally stopping at the beginning of a variable. + */ +static ssize_t +text2lval(size_t triml, size_t trimr, int tovar, YYSTYPE *yylval, +    yyscan_t scanner) +{ +	char *d; +	const char *s, *se; + +	struct yyguts_t *yyg = scanner; +	yylval->cs = d = emalloc(yyleng - trimr - triml + 1); +	se = yytext + (yyleng - trimr); +	for (s = yytext + triml; s < se; s++, d++) { +		if (*s != '\\') { +			if (tovar && *s == '$') { +				*d = '\0'; +				return s - yytext; +			} +			*d = *s; +			continue; +		} +		s++; +		if (*s >= '0' && *s <= '7') { +			*d = *s - '0'; +			if (s + 1 < se && s[1] >= '0' && s[1] <= '7') { +				*d = 010 * *d + (*++s - '0'); +				if (s + 1 < se && s[1] >= '0' && s[1] <= '7') +					*d = 010 * *d + (*++s - '0'); +			} +			continue; +		} +		switch (*s) { +		case 'a':	*d = '\a';	break; +		case 'b':	*d = '\b';	break; +		case 'f':	*d = '\f';	break; +		case 'n':	*d = '\n';	break; +		case 'r':	*d = '\r';	break; +		case 't':	*d = '\t';	break; +		case 'v':	*d = '\v';	break; +		default:	*d = *s;	break; +		case '\n':	d--;		break; +		case 'x': +			*d = 0; +			if (s + 1 >= se) +				break; +			if (s[1] >= '0' && s[1] <= '9') +				*d = *++s - '0'; +			else if (s[1] >= 'A' && s[1] <= 'F') +				*d = *++s + (0xA - 'A'); +			else if (s[1] >= 'a' && s[1] <= 'f') +				*d = *++s + (0xa - 'a'); +			else +				break; +			if (s + 1 >= se) +				break; +			if (s[1] >= '0' && s[1] <= '9') +				*d = *d * 0x10 + (*++s - '0'); +			else if (s[1] >= 'A' && s[1] <= 'F') +				*d = *d * 0x10 + (*++s + (0xA - 'A')); +			else if (s[1] >= 'a' && s[1] <= 'f') +				*d = *d * 0x10 + (*++s + (0xa - 'a')); +		} +	} +	*d = '\0'; +	return -1; +} diff --git a/usr.sbin/jail/jailp.h b/usr.sbin/jail/jailp.h new file mode 100644 index 000000000000..ccd96f5f247e --- /dev/null +++ b/usr.sbin/jail/jailp.h @@ -0,0 +1,254 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2011 James Gritton. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/types.h> +#include <sys/jail.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <jail.h> +#include <stdio.h> + +#define CONF_FILE	"/etc/jail.conf" + +#define DEP_FROM	0 +#define DEP_TO		1 + +#define DF_SEEN		0x01	/* Dependency has been followed */ +#define DF_LIGHT	0x02	/* Implied dependency on jail existence only */ +#define DF_NOFAIL	0x04	/* Don't propagate failed jails */ + +#define PF_VAR		0x0001	/* This is a variable, not a true parameter */ +#define PF_APPEND	0x0002	/* Append to existing parameter list */ +#define PF_BAD		0x0004	/* Unable to resolve parameter value */ +#define PF_INTERNAL	0x0008	/* Internal parameter, not passed to kernel */ +#define PF_BOOL		0x0010	/* Boolean parameter */ +#define PF_INT		0x0020	/* Integer parameter */ +#define PF_CONV		0x0040	/* Parameter duplicated in converted form */ +#define PF_REV		0x0080	/* Run commands in reverse order on stopping */ +#define	PF_IMMUTABLE	0x0100	/* Immutable parameter */ +#define	PF_NAMEVAL	0x0200	/* Parameter is in "name value" form */ + +#define JF_START	0x0001	/* -c */ +#define JF_SET		0x0002	/* -m */ +#define JF_STOP		0x0004	/* -r */ +#define JF_DEPEND	0x0008	/* Operation required by dependency */ +#define JF_WILD		0x0010	/* Not specified on the command line */ +#define JF_FAILED	0x0020	/* Operation failed */ +#define JF_PARAMS	0x0040	/* Parameters checked and imported */ +#define JF_RDTUN	0x0080	/* Create-only parameter check has been done */ +#define JF_PERSIST	0x0100	/* Jail is temporarily persistent */ +#define JF_TIMEOUT	0x0200	/* A command (or process kill) timed out */ +#define JF_SLEEPQ	0x0400	/* Waiting on a command and/or timeout */ +#define JF_FROM_RUNQ	0x0800	/* Has already been on the run queue */ +#define JF_CLEANUP	0x1000	/* -C Run post-removal commands */ + +#define JF_OP_MASK		(JF_START | JF_SET | JF_STOP) +#define JF_RESTART		(JF_START | JF_STOP) +#define JF_START_SET		(JF_START | JF_SET) +#define JF_SET_RESTART		(JF_SET | JF_STOP) +#define JF_START_SET_RESTART	(JF_START | JF_SET | JF_STOP) +#define JF_DO_STOP(js)		(((js) & (JF_SET | JF_STOP)) == JF_STOP) + +enum intparam { +	IP__NULL = 0,		/* Null command */ +	IP_ALLOW_DYING,		/* Allow making changes to a dying jail */ +	IP_COMMAND,		/* Command run inside jail at creation */ +	IP_DEPEND,		/* Jail starts after (stops before) another */ +	IP_EXEC_CLEAN,		/* Run commands in a clean environment */ +	IP_EXEC_CONSOLELOG,	/* Redirect optput for commands run in jail */ +	IP_EXEC_FIB,		/* Run jailed commands with this FIB */ +	IP_EXEC_JAIL_USER,	/* Run jailed commands as this user */ +	IP_EXEC_POSTSTART,	/* Commands run outside jail after creating */ +	IP_EXEC_POSTSTOP,	/* Commands run outside jail after removing */ +	IP_EXEC_PREPARE,	/* Commands run outside jail before addrs and mounting */ +	IP_EXEC_PRESTART,	/* Commands run outside jail before creating */ +	IP_EXEC_PRESTOP,	/* Commands run outside jail before removing */ +	IP_EXEC_RELEASE,	/* Commands run outside jail after addrs and unmounted */ +	IP_EXEC_CREATED,	/* Commands run outside jail right after it was started */ +	IP_EXEC_START,		/* Commands run inside jail on creation */ +	IP_EXEC_STOP,		/* Commands run inside jail on removal */ +	IP_EXEC_SYSTEM_JAIL_USER,/* Get jail_user from system passwd file */ +	IP_EXEC_SYSTEM_USER,	/* Run non-jailed commands as this user */ +	IP_EXEC_TIMEOUT,	/* Time to wait for a command to complete */ +#if defined(INET) || defined(INET6) +	IP_INTERFACE,		/* Add IP addresses to this interface */ +	IP_IP_HOSTNAME,		/* Get jail IP address(es) from hostname */ +#endif +	IP_MOUNT,		/* Mount points in fstab(5) form */ +	IP_MOUNT_DEVFS,		/* Mount /dev under prison root */ +	IP_MOUNT_FDESCFS,	/* Mount /dev/fd under prison root */ +	IP_MOUNT_PROCFS,	/* Mount /proc under prison root */ +	IP_MOUNT_FSTAB,		/* A standard fstab(5) file */ +	IP_STOP_TIMEOUT,	/* Time to wait after sending SIGTERM */ +	IP_VNET_INTERFACE,	/* Assign interface(s) to vnet jail */ +	IP_ZFS_DATASET,		/* Jail ZFS datasets */ +#ifdef INET +	IP__IP4_IFADDR,		/* Copy of ip4.addr with interface/netmask */ +#endif +#ifdef INET6 +	IP__IP6_IFADDR,		/* Copy of ip6.addr with interface/prefixlen */ +#endif +	IP__MOUNT_FROM_FSTAB,	/* Line from mount.fstab file */ +	IP__OP,			/* Placeholder for requested operation */ +	KP_ALLOW_CHFLAGS, +	KP_ALLOW_MOUNT, +	KP_ALLOW_RAW_SOCKETS, +	KP_ALLOW_SET_HOSTNAME, +	KP_ALLOW_SOCKET_AF, +	KP_ALLOW_SYSVIPC, +	KP_DEVFS_RULESET, +	KP_HOST_HOSTNAME, +#ifdef INET +	KP_IP4_ADDR, +#endif +#ifdef INET6 +	KP_IP6_ADDR, +#endif +	KP_JID, +	KP_NAME, +	KP_PATH, +	KP_PERSIST, +	KP_SECURELEVEL, +	KP_VNET, +	IP_NPARAM +}; + +STAILQ_HEAD(cfvars, cfvar); + +struct cfvar { +	STAILQ_ENTRY(cfvar)	tq; +	char			*name; +	size_t			pos; +}; + +TAILQ_HEAD(cfstrings, cfstring); + +struct cfstring { +	TAILQ_ENTRY(cfstring)	tq; +	char			*s; +	size_t			len; +	struct cfvars		vars; +}; + +TAILQ_HEAD(cfparams, cfparam); + +struct cfparam { +	TAILQ_ENTRY(cfparam)	tq; +	char			*name; +	struct cfstrings	val; +	unsigned		flags; +	int			gen; +}; + +TAILQ_HEAD(cfjails, cfjail); +STAILQ_HEAD(cfdepends, cfdepend); + +struct cfjail { +	TAILQ_ENTRY(cfjail)	tq; +	char			*name; +	char			*comline; +	struct cfparams		params; +	struct cfdepends	dep[2]; +	struct cfjails		*queue; +	struct cfjail		*cfparent; +	struct cfparam		*intparams[IP_NPARAM]; +	struct cfstring		*comstring; +	struct jailparam	*jp; +	struct timespec		timeout; +	const enum intparam	*comparam; +	unsigned		flags; +	int			jid; +	int			seq; +	int			pstatus; +	int			ndeps; +	int			njp; +	int			nprocs; +}; + +struct cfdepend { +	STAILQ_ENTRY(cfdepend)	tq[2]; +	struct cfjail		*j[2]; +	unsigned		flags; +}; + +struct cflex { +	const char		*cfname; +	int			error; +}; + +extern void *emalloc(size_t); +extern void *erealloc(void *, size_t); +extern char *estrdup(const char *); +extern int create_jail(struct cfjail *j); +extern void failed(struct cfjail *j); +extern void jail_note(const struct cfjail *j, const char *fmt, ...); +extern void jail_warnx(const struct cfjail *j, const char *fmt, ...); + +extern int next_command(struct cfjail *j); +extern int finish_command(struct cfjail *j); +extern struct cfjail *next_proc(int nonblock); + +extern void load_config(const char *cfname); +extern void include_config(void *scanner, const char *cfname); +extern struct cfjail *add_jail(void); +extern void add_param(struct cfjail *j, const struct cfparam *p, +    enum intparam ipnum, const char *value); +extern int bool_param(const struct cfparam *p); +extern int int_param(const struct cfparam *p, int *ip); +extern const char *string_param(const struct cfparam *p); +extern int check_intparams(struct cfjail *j); +extern int import_params(struct cfjail *j); +extern int equalopts(const char *opt1, const char *opt2); +extern int wild_jail_name(const char *wname); +extern int wild_jail_match(const char *jname, const char *wname); +extern void free_param_strings(struct cfparam *p); + +extern void dep_setup(int docf); +extern int dep_check(struct cfjail *j); +extern void dep_done(struct cfjail *j, unsigned flags); +extern void dep_reset(struct cfjail *j); +extern struct cfjail *next_jail(void); +extern int start_state(const char *target, int docf, unsigned state, +    int running); +extern void requeue(struct cfjail *j, struct cfjails *queue); +extern void requeue_head(struct cfjail *j, struct cfjails *queue); + +extern struct cflex *yyget_extra(void *scanner); +extern FILE *yyget_in(void *scanner); +extern int yyget_lineno(void *scanner); +extern char *yyget_text(void *scanner); + +extern struct cfjails cfjails; +extern struct cfjails ready; +extern struct cfjails depend; +extern int iflag; +extern int note_remove; +extern int paralimit; +extern int verbose; diff --git a/usr.sbin/jail/jailparse.y b/usr.sbin/jail/jailparse.y new file mode 100644 index 000000000000..048cfcf11c2b --- /dev/null +++ b/usr.sbin/jail/jailparse.y @@ -0,0 +1,276 @@ +%{ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2011 James Gritton + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +#include <err.h> +#include <stdlib.h> +#include <string.h> + +#include "jailp.h" + +#ifdef DEBUG +#define YYDEBUG 1 +#endif + +static struct cfjail *current_jail; +static struct cfjail *global_jail; +%} + +%union { +	struct cfparam		*p; +	struct cfstrings	*ss; +	struct cfstring		*s; +	char			*cs; +} + +%token      PLEQ +%token <cs> STR STR1 VAR VAR1 + +%type <p>  param name +%type <ss> value +%type <s>  string + +%pure-parser + +%lex-param { void *scanner } +%parse-param { void *scanner } + +%% + +/* + * A config file is a list of jails and parameters.  Parameters are + * added to the current jail, otherwise to a global pesudo-jail. + */ +conf	: +	| conf jail +	| conf param ';' +	{ +		if (!special_param($2, scanner)) { +			struct cfjail *j = current_jail; + +			if (j == NULL) { +				if (global_jail == NULL) { +					global_jail = add_jail(); +					global_jail->name = estrdup("*"); +				} +				j = global_jail; +			} +			TAILQ_INSERT_TAIL(&j->params, $2, tq); +		} +	} +	| conf ';' +	; + +jail	: jail_name '{' conf '}' +	{ +		current_jail = current_jail->cfparent; +	} +	; + +jail_name : STR +	{ +		struct cfjail *j = add_jail(); + +		if (current_jail == NULL) +			j->name = $1; +		else { +			/* +			 * A nested jail definition becomes +			 * a hierarchically-named sub-jail. +			 */ +			size_t parentlen = strlen(current_jail->name); +			j->name = emalloc(parentlen + strlen($1) + 2); +			strcpy(j->name, current_jail->name); +			j->name[parentlen++] = '.'; +			strcpy(j->name + parentlen, $1); +			free($1); +		} +		j->cfparent = current_jail; +		current_jail = j; +	} +	; + +/* + * Parameters have a name and an optional list of value strings, + * which may have "+=" or "=" preceding them. + */ +param	: name +	{ +		$$ = $1; +	} +	| name '=' value +	{ +		$$ = $1; +		TAILQ_CONCAT(&$$->val, $3, tq); +		free($3); +	} +	| name PLEQ value +	{ +		$$ = $1; +		TAILQ_CONCAT(&$$->val, $3, tq); +		$$->flags |= PF_APPEND; +		free($3); +	} +	| name value +	{ +		$$ = $1; +		TAILQ_CONCAT(&$$->val, $2, tq); +		$$->flags |= PF_NAMEVAL; +		free($2); +	} +	| error +	; + +/* + * A parameter has a fixed name.  A variable definition looks just like a + * parameter except that the name is a variable. + */ +name	: STR +	{ +		$$ = emalloc(sizeof(struct cfparam)); +		$$->name = $1; +		TAILQ_INIT(&$$->val); +		$$->flags = 0; +	} +	| VAR +	{ +		$$ = emalloc(sizeof(struct cfparam)); +		$$->name = $1; +		TAILQ_INIT(&$$->val); +		$$->flags = PF_VAR; +	} +	; + +value	: string +	{ +		$$ = emalloc(sizeof(struct cfstrings)); +		TAILQ_INIT($$); +		TAILQ_INSERT_TAIL($$, $1, tq); +	} +	| value ',' string +	{ +		$$ = $1; +		TAILQ_INSERT_TAIL($$, $3, tq); +	} +	; + +/* + * Strings may be passed in pieces, because of quoting and/or variable + * interpolation.  Reassemble them into a single string. + */ +string	: STR +	{ +		$$ = emalloc(sizeof(struct cfstring)); +		$$->s = $1; +		$$->len = strlen($1); +		STAILQ_INIT(&$$->vars); +	} +	| VAR +	{ +		struct cfvar *v; + +		$$ = emalloc(sizeof(struct cfstring)); +		$$->s = estrdup(""); +		$$->len = 0; +		STAILQ_INIT(&$$->vars); +		v = emalloc(sizeof(struct cfvar)); +		v->name = $1; +		v->pos = 0; +		STAILQ_INSERT_TAIL(&$$->vars, v, tq); +	} +	| string STR1 +	{ +		size_t len1; + +		$$ = $1; +		len1 = strlen($2); +		$$->s = erealloc($$->s, $$->len + len1 + 1); +		strcpy($$->s + $$->len, $2); +		free($2); +		$$->len += len1; +	} +	| string VAR1 +	{ +		struct cfvar *v; + +		$$ = $1; +		v = emalloc(sizeof(struct cfvar)); +		v->name = $2; +		v->pos = $$->len; +		STAILQ_INSERT_TAIL(&$$->vars, v, tq); +	} +	; + +%% + +extern int YYLEX_DECL(); + +static void +YYERROR_DECL() +{ +	struct cflex *cflex = yyget_extra(scanner); + +	if (!yyget_text(scanner)) +		warnx("%s line %d: %s", +		    cflex->cfname, yyget_lineno(scanner), s); +	else if (!yyget_text(scanner)[0]) +		warnx("%s: unexpected EOF", +		    cflex->cfname); +	else +		warnx("%s line %d: %s: %s", +		    cflex->cfname, yyget_lineno(scanner), +		    yyget_text(scanner), s); +	cflex->error = 1; +} + +/* Handle special parameters (i.e. the include directive). + * Return true if the parameter was specially handled. + */ +static int +special_param(struct cfparam *p, void *scanner) +{ +	if ((p->flags & (PF_VAR | PF_APPEND | PF_NAMEVAL)) != PF_NAMEVAL +	    || strcmp(p->name, ".include")) +		return 0; +	struct cfstring *s; +	TAILQ_FOREACH(s, &p->val, tq) { +		if (STAILQ_EMPTY(&s->vars)) +			include_config(scanner, s->s); +		else { +			warnx("%s line %d: " +			    "variables not permitted in '.include' filename", +			    yyget_extra(scanner)->cfname, +			    yyget_lineno(scanner)); +			yyget_extra(scanner)->error = 1; +		} +	} +	free_param_strings(p); +	free(p); +	return 1; +} diff --git a/usr.sbin/jail/state.c b/usr.sbin/jail/state.c new file mode 100644 index 000000000000..1d200beacef9 --- /dev/null +++ b/usr.sbin/jail/state.c @@ -0,0 +1,492 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2011 James Gritton + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +#include <sys/uio.h> + +#include <err.h> +#include <stdlib.h> +#include <string.h> + +#include "jailp.h" + +struct cfjails ready = TAILQ_HEAD_INITIALIZER(ready); +struct cfjails depend = TAILQ_HEAD_INITIALIZER(depend); + +static void dep_add(struct cfjail *from, struct cfjail *to, unsigned flags); +static int cmp_jailptr(const void *a, const void *b); +static int cmp_jailptr_name(const void *a, const void *b); +static struct cfjail *find_jail(const char *name); +static struct cfjail *running_jail(const char *name, int flags); + +static struct cfjail **jails_byname; +static size_t njails; + +/* + * Set up jail dependency lists. + */ +void +dep_setup(int docf) +{ +	struct cfjail *j, *dj; +	struct cfparam *p; +	struct cfstring *s; +	struct cfdepend *d; +	const char *cs; +	char *pname; +	size_t plen; +	int deps, ldeps; + +	if (!docf) { +		/* +		 * With no config file, let "depend" for a single jail +		 * look at currently running jails. +		 */ +		if ((j = TAILQ_FIRST(&cfjails)) && +		    (p = j->intparams[IP_DEPEND])) { +			TAILQ_FOREACH(s, &p->val, tq) { +				if (running_jail(s->s, 0) == NULL) { +					warnx("depends on nonexistent jail " +					    "\"%s\"", s->s); +					j->flags |= JF_FAILED; +				} +			} +		} +		return; +	} + +	njails = 0; +	TAILQ_FOREACH(j, &cfjails, tq) +		njails++; +	jails_byname = emalloc(njails * sizeof(struct cfjail *)); +	njails = 0; +	TAILQ_FOREACH(j, &cfjails, tq) +		jails_byname[njails++] = j; +	qsort(jails_byname, njails, sizeof(struct cfjail *), cmp_jailptr); +	deps = 0; +	ldeps = 0; +	plen = 0; +	pname = NULL; +	TAILQ_FOREACH(j, &cfjails, tq) { +		if (j->flags & JF_FAILED) +			continue; +		if ((p = j->intparams[IP_DEPEND])) { +			TAILQ_FOREACH(s, &p->val, tq) { +				dj = find_jail(s->s); +				if (dj != NULL) { +					deps++; +					dep_add(j, dj, 0); +				} else { +					jail_warnx(j, +					    "depends on undefined jail \"%s\"", +					    s->s); +					j->flags |= JF_FAILED; +				} +			} +		} +		/* A jail has an implied dependency on its parent. */ +		if ((cs = strrchr(j->name, '.'))) +		{ +			if (plen < (size_t)(cs - j->name + 1)) { +				plen = (cs - j->name) + 1; +				pname = erealloc(pname, plen); +			} +			strlcpy(pname, j->name, plen); +			dj = find_jail(pname); +			if (dj != NULL) { +				ldeps++; +				dep_add(j, dj, DF_LIGHT); +			} +		} +	} + +	/* Look for dependency loops. */ +	if (deps && (deps > 1 || ldeps)) { +		(void)start_state(NULL, 0, 0, 0); +		while ((j = TAILQ_FIRST(&ready))) { +			requeue(j, &cfjails); +			dep_done(j, DF_NOFAIL); +		} +		while ((j = TAILQ_FIRST(&depend)) != NULL) { +			jail_warnx(j, "dependency loop"); +			j->flags |= JF_FAILED; +			do { +				requeue(j, &cfjails); +				dep_done(j, DF_NOFAIL); +			} while ((j = TAILQ_FIRST(&ready))); +		} +		TAILQ_FOREACH(j, &cfjails, tq) +			STAILQ_FOREACH(d, &j->dep[DEP_FROM], tq[DEP_FROM]) +				d->flags &= ~DF_SEEN; +	} +	if (pname != NULL) +		free(pname); +} + +/* + * Return if a jail has dependencies. + */ +int +dep_check(struct cfjail *j) +{ +	int reset, depfrom, depto, ndeps, rev; +	struct cfjail *dj; +	struct cfdepend *d; + +	static int bits[] = { 0, 1, 1, 2, 1, 2, 2, 3 }; + +	if (j->ndeps == 0) +		return 0; +	ndeps = 0; +	if ((rev = JF_DO_STOP(j->flags))) { +		depfrom = DEP_TO; +		depto = DEP_FROM; +	} else { +		depfrom = DEP_FROM; +		depto = DEP_TO; +	} +	STAILQ_FOREACH(d, &j->dep[depfrom], tq[depfrom]) { +		if (d->flags & DF_SEEN) +			continue; +		dj = d->j[depto]; +		if (dj->flags & JF_FAILED) { +			if (!(j->flags & (JF_DEPEND | JF_FAILED)) && +			    verbose >= 0) +				jail_warnx(j, "skipped"); +			j->flags |= JF_FAILED; +			continue; +		} +		/* +		 * The dependee's state may be set (or changed) as a result of +		 * being in a dependency it wasn't in earlier. +		 */ +		reset = 0; +		if (bits[dj->flags & JF_OP_MASK] <= 1) { +			if (!(dj->flags & JF_OP_MASK)) { +				reset = 1; +				dj->flags |= JF_DEPEND; +				requeue(dj, &ready); +			} +			/* Set or change the dependee's state. */ +			switch (j->flags & JF_OP_MASK) { +			case JF_START: +				dj->flags |= JF_START; +				break; +			case JF_SET: +				if (!(dj->flags & JF_OP_MASK)) +					dj->flags |= JF_SET; +				else if (dj->flags & JF_STOP) +					dj->flags |= JF_START; +				break; +			case JF_STOP: +			case JF_RESTART: +				if (!(dj->flags & JF_STOP)) +					reset = 1; +				dj->flags |= JF_STOP; +				if (dj->flags & JF_SET) +					dj->flags ^= (JF_START | JF_SET); +				break; +			} +		} +		if (reset) +			dep_reset(dj); +		if (!((d->flags & DF_LIGHT) && +		    (rev ? dj->jid < 0 : dj->jid > 0))) +			ndeps++; +	} +	if (ndeps == 0) +		return 0; +	requeue(j, &depend); +	return 1; +} + +/* + * Resolve any dependencies from a finished jail. + */ +void +dep_done(struct cfjail *j, unsigned flags) +{ +	struct cfjail *dj; +	struct cfdepend *d; +	int depfrom, depto; + +	if (JF_DO_STOP(j->flags)) { +		depfrom = DEP_TO; +		depto = DEP_FROM; +	} else { +		depfrom = DEP_FROM; +		depto = DEP_TO; +	} +	STAILQ_FOREACH(d, &j->dep[depto], tq[depto]) { +		if ((d->flags & DF_SEEN) | (flags & ~d->flags & DF_LIGHT)) +			continue; +		d->flags |= DF_SEEN; +		dj = d->j[depfrom]; +		if (!(flags & DF_NOFAIL) && (j->flags & JF_FAILED) && +		    (j->flags & (JF_OP_MASK | JF_DEPEND)) != +		    (JF_SET | JF_DEPEND)) { +			if (!(dj->flags & (JF_DEPEND | JF_FAILED)) && +			    verbose >= 0) +				jail_warnx(dj, "skipped"); +			dj->flags |= JF_FAILED; +		} +		if (!--dj->ndeps && dj->queue == &depend) +			requeue(dj, &ready); +	} +} + +/* + * Count a jail's dependencies and mark them as unseen. + */ +void +dep_reset(struct cfjail *j) +{ +	int depfrom; +	struct cfdepend *d; + +	depfrom = JF_DO_STOP(j->flags) ? DEP_TO : DEP_FROM; +	j->ndeps = 0; +	STAILQ_FOREACH(d, &j->dep[depfrom], tq[depfrom]) +		j->ndeps++; +} + +/* + * Find the next jail ready to do something. + */ +struct cfjail * +next_jail(void) +{ +	struct cfjail *j; + +	if (!(j = next_proc(!TAILQ_EMPTY(&ready))) && +	    (j = TAILQ_FIRST(&ready)) && JF_DO_STOP(j->flags) && +	    (j = TAILQ_LAST(&ready, cfjails)) && !JF_DO_STOP(j->flags)) { +		TAILQ_FOREACH_REVERSE(j, &ready, cfjails, tq) +			if (JF_DO_STOP(j->flags)) +				break; +	} +	if (j != NULL) +		requeue(j, &cfjails); +	return j; +} + +/* + * Set jails to the proper start state. + */ +int +start_state(const char *target, int docf, unsigned state, int running) +{ +	struct iovec jiov[6]; +	struct cfjail *j, *tj; +	int jid; +	char namebuf[MAXHOSTNAMELEN]; + +	if (!target || (!docf && (state & JF_OP_MASK) != JF_STOP) || +	    (!running && !strcmp(target, "*"))) { +		/* +		 * For a global wildcard (including no target specified), +		 * set the state on all jails and start with those that +		 * have no dependencies. +		 */ +		TAILQ_FOREACH_SAFE(j, &cfjails, tq, tj) { +			j->flags = (j->flags & JF_FAILED) | state | +			    (docf ? JF_WILD : 0); +			dep_reset(j); +			requeue(j, j->ndeps ? &depend : &ready); +		} +	} else if (wild_jail_name(target)) { +		/* +		 * For targets specified singly, or with a non-global wildcard, +		 * set their state and call them ready (even if there are +		 * dependencies).  Leave everything else unqueued for now. +		 */ +		if (running) { +			/* +			 * -R matches its wildcards against currently running +			 * jails, not against the config file. +			 */ +			jiov[0].iov_base = __DECONST(char *, "lastjid"); +			jiov[0].iov_len = sizeof("lastjid"); +			jiov[1].iov_base = &jid; +			jiov[1].iov_len = sizeof(jid); +			jiov[2].iov_base = __DECONST(char *, "jid"); +			jiov[2].iov_len = sizeof("jid"); +			jiov[3].iov_base = &jid; +			jiov[3].iov_len = sizeof(jid); +			jiov[4].iov_base = __DECONST(char *, "name"); +			jiov[4].iov_len = sizeof("name"); +			jiov[5].iov_base = &namebuf; +			jiov[5].iov_len = sizeof(namebuf); +			for (jid = 0; jail_get(jiov, 6, 0) > 0; ) { +				if (wild_jail_match(namebuf, target)) { +					j = add_jail(); +					j->name = estrdup(namebuf); +					j->jid = jid; +					j->flags = (j->flags & JF_FAILED) | +					    state | JF_WILD; +					dep_reset(j); +					requeue(j, &ready); +				} +			} +		} else { +			TAILQ_FOREACH_SAFE(j, &cfjails, tq, tj) { +				if (wild_jail_match(j->name, target)) { +					j->flags = (j->flags & JF_FAILED) | +					    state | JF_WILD; +					dep_reset(j); +					requeue(j, &ready); +				} +			} +		} +	} else { +		j = find_jail(target); +		if (j == NULL && (state & JF_OP_MASK) == JF_STOP) { +			/* Allow -[rR] to specify a currently running jail. */ +			j = running_jail(target, JAIL_DYING); +		} +		if (j == NULL) { +			warnx("\"%s\" not found", target); +			return -1; +		} +		j->flags = (j->flags & JF_FAILED) | state; +		dep_reset(j); +		requeue(j, &ready); +	} +	return 0; +} + +/* + * Move a jail to a new list. + */ +void +requeue(struct cfjail *j, struct cfjails *queue) +{ +	if (j->queue != queue) { +		TAILQ_REMOVE(j->queue, j, tq); +		TAILQ_INSERT_TAIL(queue, j, tq); +		j->queue = queue; +	} +} + +void +requeue_head(struct cfjail *j, struct cfjails *queue) +{ +    TAILQ_REMOVE(j->queue, j, tq); +    TAILQ_INSERT_HEAD(queue, j, tq); +    j->queue = queue; +} + +/* + * Add a dependency edge between two jails. + */ +static void +dep_add(struct cfjail *from, struct cfjail *to, unsigned flags) +{ +	struct cfdepend *d; + +	d = emalloc(sizeof(struct cfdepend)); +	d->flags = flags; +	d->j[DEP_FROM] = from; +	d->j[DEP_TO] = to; +	STAILQ_INSERT_TAIL(&from->dep[DEP_FROM], d, tq[DEP_FROM]); +	STAILQ_INSERT_TAIL(&to->dep[DEP_TO], d, tq[DEP_TO]); +} + +/* + * Compare jail pointers for qsort/bsearch. + */ +static int +cmp_jailptr(const void *a, const void *b) +{ +	return strcmp((*((struct cfjail * const *)a))->name, +	    ((*(struct cfjail * const *)b))->name); +} + +static int +cmp_jailptr_name(const void *a, const void *b) +{ +	return strcmp((const char *)a, ((*(struct cfjail * const *)b))->name); +} + +/* + * Find a jail object by name. + */ +static struct cfjail * +find_jail(const char *name) +{ +	struct cfjail **jp; +	 +	if (jails_byname == NULL) +		return NULL; + +	jp = bsearch(name, jails_byname, njails, sizeof(struct cfjail *), +	    cmp_jailptr_name); +	return jp ? *jp : NULL; +} + +/* + * Return jail if it is running, and NULL if it isn't. + */ +static struct cfjail * +running_jail(const char *name, int flags) +{ +	struct iovec jiov[4]; +	struct cfjail *jail; +	char *ep; +	char jailname[MAXHOSTNAMELEN]; +	int jid, ret, len; +	 +	if ((jid = strtol(name, &ep, 10)) && !*ep) { +		memset(jailname,0,sizeof(jailname)); +		len = sizeof(jailname); +	} else { +		strncpy(jailname, name,sizeof(jailname)); +		len = strlen(name) + 1;  +		jid = 0; +	} +	 +	jiov[0].iov_base = __DECONST(char *, "jid"); +	jiov[0].iov_len = sizeof("jid"); +	jiov[1].iov_base = &jid; +	jiov[1].iov_len = sizeof(jid); +	jiov[2].iov_base = __DECONST(char *, "name"); +	jiov[2].iov_len = sizeof("name"); +	jiov[3].iov_base = &jailname; +	jiov[3].iov_len = len; + +	if ((ret = jail_get(jiov, 4, flags)) < 0) +		return (NULL); + +	if ((jail = find_jail(jailname)) == NULL) { +		jail = add_jail(); +		jail->name = estrdup(jailname); +		jail->jid = ret; +	} + +	return (jail); +} diff --git a/usr.sbin/jail/tests/Makefile b/usr.sbin/jail/tests/Makefile new file mode 100644 index 000000000000..6a64557cafa8 --- /dev/null +++ b/usr.sbin/jail/tests/Makefile @@ -0,0 +1,9 @@ +PACKAGE=        tests + +ATF_TESTS_SH+=  jail_basic_test + +${PACKAGE}FILES+=	commands.jail.conf +# The different test cases create jails with the same name. +TEST_METADATA+= is_exclusive="true" + +.include <bsd.test.mk> diff --git a/usr.sbin/jail/tests/Makefile.depend b/usr.sbin/jail/tests/Makefile.depend new file mode 100644 index 000000000000..11aba52f82cf --- /dev/null +++ b/usr.sbin/jail/tests/Makefile.depend @@ -0,0 +1,10 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/usr.sbin/jail/tests/commands.jail.conf b/usr.sbin/jail/tests/commands.jail.conf new file mode 100644 index 000000000000..ad152a28b7fe --- /dev/null +++ b/usr.sbin/jail/tests/commands.jail.conf @@ -0,0 +1,7 @@ + +exec.prestop = "echo STOP"; +exec.prestart = "echo START"; +exec.poststart = "env"; +persist; + +basejail {} diff --git a/usr.sbin/jail/tests/jail_basic_test.sh b/usr.sbin/jail/tests/jail_basic_test.sh new file mode 100755 index 000000000000..c781eed78756 --- /dev/null +++ b/usr.sbin/jail/tests/jail_basic_test.sh @@ -0,0 +1,337 @@ +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (c) 2019 Michael Zhilin  +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +#    notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +#    notice, this list of conditions and the following disclaimer in the +#    documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +atf_test_case "basic" "cleanup" +basic_head() +{ +	atf_set descr 'Basic jail test' +	atf_set require.user root +} + +basic_body() +{ +	# Create the jail +	atf_check -s exit:0 -o ignore jail -c name=basejail persist ip4.addr=192.0.1.1 +	# Check output of jls +	atf_check -s exit:0 -o ignore jls +	atf_check -s exit:0 -o ignore jls -v +	atf_check -s exit:0 -o ignore jls -n +	# Stop jail +	atf_check -s exit:0 -o ignore jail -r basejail +	jail -c name=basejail persist ip4.addr=192.0.1.1 +	# Stop jail by jid +	atf_check -s exit:0 -o ignore jail -r `jls -j basejail jid` +	# Recreate +	atf_check -s exit:0 -o ignore jail -cm name=basejail persist ip4.addr=192.0.1.1 +	# Restart +	atf_check -s exit:0 -o ignore jail -rc name=basejail persist ip4.addr=192.0.1.1 +} + +basic_cleanup() +{ +	jail -r basejail +} + +atf_test_case "list" "cleanup" +list_head() +{ +	atf_set descr 'Specify some jail parameters as lists' +	atf_set require.user root +} + +list_body() +{ +	if [ "$(sysctl -qn kern.features.vimage)" -ne 1 ]; then +		atf_skip "cannot create VNET jails" +	fi +	atf_check -o save:epair ifconfig epair create + +	epair=$(cat epair) +	atf_check jail -c name=basejail vnet persist vnet.interface=${epair},${epair%a}b + +	atf_check -o ignore jexec basejail ifconfig ${epair} +	atf_check -o ignore jexec basejail ifconfig ${epair%a}b +} + +list_cleanup() +{ +	jail -r basejail +	if [ -f epair ]; then +		ifconfig $(cat epair) destroy +	fi +} + +atf_test_case "nested" "cleanup" +nested_head() +{ +	atf_set descr 'Hierarchical jails test' +	atf_set require.user root +} + +nested_body() +{ +	# Create the first jail +	jail -c name=basejail persist ip4.addr=192.0.1.1 children.max=1 +	atf_check -s exit:0 -o empty \ +		jexec basejail \ +			jail -c name=nestedjail persist ip4.addr=192.0.1.1 + +	atf_check -s exit:1 -o empty -e inline:"jail: prison limit exceeded\n"\ +		jexec basejail \ +			jail -c name=secondnestedjail persist ip4.addr=192.0.1.1  +	# Check output of jls +	atf_check -s exit:0 -o ignore \ +		jexec basejail jls +	atf_check -s exit:0 -o ignore \ +		jexec basejail jls -v +	atf_check -s exit:0 -o ignore \ +		jexec basejail jls -n +	# Create jail with no child - children.max should be 0 by default +	jail -c name=basejail_nochild persist ip4.addr=192.0.1.1 +	atf_check -s exit:1 -o empty \ +		-e inline:"jail: jail_set: Operation not permitted\n" \ +		jexec basejail_nochild \ +			jail -c name=nestedjail persist ip4.addr=192.0.1.1  +} + +nested_cleanup() +{ +	jail -r nestedjail +	jail -r basejail +	jail -r basejail_nochild +} + +atf_test_case "commands" "cleanup" +commands_head() +{ +	atf_set descr 'Commands jail test' +	atf_set require.user root +} + +commands_body() +{ +	cp "$(atf_get_srcdir)/commands.jail.conf" jail.conf +	echo "path = \"$PWD\";" >> jail.conf + +	# exec.prestart (START) and exec.poststart (env) +	atf_check -o save:stdout -e empty \ +		jail -f jail.conf -qc basejail + +	# exec.prestart output is missing +	atf_check grep -qE '^START$' stdout +	# JID was not set in the exec.poststart env +	atf_check grep -qE '^JID=[0-9]+' stdout +	# JNAME was not set in the exec.poststart env +	atf_check grep -qE '^JNAME=basejail$' stdout +	# JPATH was not set in the exec.poststart env +	atf_check grep -qE "^JPATH=$PWD$" stdout + +	# exec.prestop by jailname +	atf_check -s exit:0 -o inline:"STOP\n" \ +		jail -f jail.conf -qr basejail +	# exec.prestop by jid +	jail -f jail.conf -qc basejail +	atf_check -s exit:0 -o inline:"STOP\n" \ +		jail -f jail.conf -qr `jls -j basejail jid` +} + +commands_cleanup() +{ +	if jls -j basejail > /dev/null 2>&1; then +	    jail -r basejail +	fi +} + +atf_test_case "jid_name_set" "cleanup" +jid_name_set_head() +{ +	atf_set descr 'Test that one can set both the jid and name in a config file' +	atf_set require.user root +} + +find_unused_jid() +{ +	: ${JAIL_MAX=999999} + +	# We'll start at a higher jid number and roll through the space until +	# we find one that isn't taken.  We start high to avoid racing parallel +	# activity for the 'next available', though ideally we don't have a lot +	# of parallel jail activity like that. +	jid=5309 +	while jls -cj "$jid"; do +		if [ "$jid" -eq "$JAIL_MAX" ]; then +			atf_skip "System has too many jail, cannot find free slot" +		fi + +		jid=$((jid + 1)) +	done + +	echo "$jid" | tee -a jails.lst +} +clean_jails() +{ +	if [ ! -s jails.lst ]; then +		return 0 +	fi + +	while read jail; do +		if jls -c -j "$jail"; then +			jail -r "$jail" +		fi +	done < jails.lst +} + +jid_name_set_body() +{ +	local jid=$(find_unused_jid) + +	echo "basejail" >> jails.lst +	echo "$jid { name = basejail; persist; }" > jail.conf +	atf_check -o match:"$jid: created" jail -f jail.conf -c "$jid" +	# Confirm that we didn't override the explicitly-set name with the jid +	# as the name. +	atf_check -o match:"basejail" jls -j "$jid" name +	atf_check -o match:"$jid: removed" jail -f jail.conf -r "$jid" + +	echo "$jid { host.hostname = \"\${name}\"; persist; }" > jail.conf +	atf_check -o match:"$jid: created" jail -f jail.conf -c "$jid" +	# Confirm that ${name} expanded and expanded correctly to the +	# jid-implied name. +	atf_check -o match:"$jid" jls -j "$jid" host.hostname +	atf_check -o match:"$jid: removed" jail -f jail.conf -r "$jid" + +	echo "basejail { jid = $jid; persist; }" > jail.conf +	atf_check -o match:"basejail: created" jail -f jail.conf -c basejail +	# Confirm that our jid assigment in the definition worked out and we +	# did in-fact create the jail there. +	atf_check -o match:"$jid" jls -j "basejail" jid +	atf_check -o match:"basejail: removed" jail -f jail.conf -r basejail +} + +jid_name_set_cleanup() +{ +	clean_jails +} + +atf_test_case "param_consistency" "cleanup" +param_consistency_head() +{ +	atf_set descr 'Test for consistency in jid/name params being set implicitly' +	atf_set require.user root +} + +param_consistency_body() +{ +	local iface jid + +	echo "basejail" >> jails.lst + +	# Most basic test: exec.poststart running a command without a jail +	# config.  This would previously crash as we only had the jid and name +	# as populated at creation time. +	atf_check jail -c path=/ exec.poststart="true" command=/usr/bin/true + +	iface=$(ifconfig lo create) +	atf_check test -n "$iface" +	echo "$iface" >> interfaces.lst + +	# Now do it again but exercising IP_VNET_INTERFACE, which is an +	# implied command that wants to use the jid or name.  This would crash +	# as neither KP_JID or KP_NAME are populated when a jail is created, +	# just as above- just at a different spot. +	atf_check jail -c \ +		path=/ vnet=new vnet.interface="$iface" command=/usr/bin/true + +	# Test that a jail that we only know by name will have its jid resolved +	# and added to its param set. +	echo "basejail {path = /; exec.prestop = 'echo STOP'; persist; }" > jail.conf + +	atf_check -o ignore jail -f jail.conf -c basejail +	atf_check -o match:"STOP" jail -f jail.conf -r basejail + +	# Do the same sequence as above, but use a jail with a jid-ish name. +	jid=$(find_unused_jid) +	echo "$jid {path = /; exec.prestop = 'echo STOP'; persist; }" > jail.conf + +	atf_check -o ignore jail -f jail.conf -c "$jid" +	atf_check -o match:"STOP" jail -f jail.conf -r "$jid" + +	# Ditto, but now we set a name for that jid-jail. +	echo "$jid {name = basejail; path = /; exec.prestop = 'echo STOP'; persist; }" > jail.conf + +	atf_check -o ignore jail -f jail.conf -c "$jid" +	atf_check -o match:"STOP" jail -f jail.conf -r "$jid" + +	# Confirm that we have a valid jid available in exec.poststop.  It's +	# probably debatable whether we should or not. +	echo "basejail {path = /; exec.poststop = 'echo JID=\$JID'; persist; }" > jail.conf +	atf_check -o ignore jail -f jail.conf -c basejail +	jid=$(jls -j basejail jid) + +	atf_check -o match:"JID=$jid" jail -f jail.conf -r basejail + +} + +param_consistency_cleanup() +{ +	clean_jails + +	if [ -f "interfaces.lst" ]; then +		while read iface; do +			ifconfig "$iface" destroy +		done < interfaces.lst +	fi +} + +atf_test_case "setaudit" +setaudit_head() +{ +	atf_set descr 'Test that setaudit works in a jail when configured with allow.setaudit' +	atf_set require.user root +	atf_set require.progs setaudit +} + +setaudit_body() +{ +	# Try to modify the audit mask within a jail without +	# allow.setaudit configured. +	atf_check -s not-exit:0 -o empty -e not-empty jail -c name=setaudit_jail \ +	    command=setaudit -m fr ls / +	# The command should succeed if allow.setaudit is configured. +	atf_check -s exit:0 -o ignore -e empty jail -c name=setaudit_jail \ +	    allow.setaudit command=setaudit -m fr ls / +} + +atf_init_test_cases() +{ +	atf_add_test_case "basic" +	atf_add_test_case "list" +	atf_add_test_case "nested" +	atf_add_test_case "commands" +	atf_add_test_case "jid_name_set" +	atf_add_test_case "param_consistency" +	atf_add_test_case "setaudit" +} | 
