diff --git a/lib/libc/sys/jail.2 b/lib/libc/sys/jail.2 index b0619a042337..a2d692ad2999 100644 --- a/lib/libc/sys/jail.2 +++ b/lib/libc/sys/jail.2 @@ -247,44 +247,6 @@ They return \-1 on failure, and set to indicate the error. .Pp .Rv -std jail_attach jail_remove -.Sh PRISON? -Once a process has been put in a prison, it and its descendants cannot escape -the prison. -.Pp -Inside the prison, the concept of -.Dq superuser -is very diluted. -In general, -it can be assumed that nothing can be mangled from inside a prison which -does not exist entirely inside that prison. -For instance the directory -tree below -.Dq Li path -can be manipulated all the ways a root can normally do it, including -.Dq Li "rm -rf /*" -but new device special nodes cannot be created because they reference -shared resources (the device drivers in the kernel). -The effective -.Dq securelevel -for a process is the greater of the global -.Dq securelevel -or, if present, the per-jail -.Dq securelevel . -.Pp -All IP activity will be forced to happen to/from the IP number specified, -which should be an alias on one of the network interfaces. -All connections to/from the loopback address -.Pf ( Li 127.0.0.1 -for IPv4, -.Li ::1 -for IPv6) will be changed to be to/from the primary address -of the jail for the given address family. -.Pp -It is possible to identify a process as jailed by examining -.Dq Li /proc//status : -it will show a field near the end of the line, either as -a single hyphen for a process at large, or the name currently -set for the prison for jailed processes. .Sh ERRORS The .Fn jail @@ -415,7 +377,7 @@ and .Fn jail_attach call .Xr chroot 2 -internally, so it can fail for all the same reasons. +internally, so they can fail for all the same reasons. Please consult the .Xr chroot 2 manual page for details. diff --git a/usr.sbin/jail/Makefile b/usr.sbin/jail/Makefile index 52d237b3339a..00d7ff50b523 100644 --- a/usr.sbin/jail/Makefile +++ b/usr.sbin/jail/Makefile @@ -3,9 +3,14 @@ .include PROG= jail -MAN= jail.8 -DPADD= ${LIBJAIL} ${LIBUTIL} -LDADD= -ljail -lutil +MAN= jail.8 jail.conf.5 +SRCS= jail.c command.c config.c state.c jailp.h jaillex.l jailparse.y y.tab.h + +DPADD= ${LIBJAIL} ${LIBKVM} ${LIBUTIL} ${LIBL} +LDADD= -ljail -lkvm -lutil -ll + +YFLAGS+=-v +CFLAGS+=-I. -I${.CURDIR} .if ${MK_INET6_SUPPORT} != "no" CFLAGS+= -DINET6 @@ -14,4 +19,6 @@ CFLAGS+= -DINET6 CFLAGS+= -DINET .endif +CLEANFILES= y.output + .include diff --git a/usr.sbin/jail/command.c b/usr.sbin/jail/command.c new file mode 100644 index 000000000000..1c17da09a7d8 --- /dev/null +++ b/usr.sbin/jail/command.c @@ -0,0 +1,857 @@ +/*- + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 void 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; + +/* + * Run the next command associated with a jail. + */ +int +next_command(struct cfjail *j) +{ + enum intparam comparam; + int create_failed; + + if (paralimit == 0) { + requeue(j, &runnable); + return 1; + } + create_failed = (j->flags & (JF_STOP | JF_FAILED)) == JF_FAILED; + comparam = *j->comparam; + for (;;) { + if (j->comstring == NULL) { + j->comparam += create_failed ? -1 : 1; + switch ((comparam = *j->comparam)) { + case 0: + return 0; + case IP_MOUNT_DEVFS: + if (!bool_param(j->intparams[IP_MOUNT_DEVFS])) + continue; + /* FALLTHROUGH */ + case IP__OP: + case IP_STOP_TIMEOUT: + j->comstring = &dummystring; + break; + default: + if (j->intparams[comparam] == NULL) + continue; + j->comstring = create_failed + ? TAILQ_LAST(&j->intparams[comparam]->val, + cfstrings) + : TAILQ_FIRST(&j->intparams[comparam]->val); + } + } else { + j->comstring = j->comstring == &dummystring ? NULL : + create_failed + ? 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_START || comparam == IP_COMMAND || + comparam == IP_EXEC_POSTSTART))) + continue; + switch (run_command(j)) { + case -1: + failed(j); + /* FALLTHROUGH */ + case 1: + return 1; + } + } +} + +/* + * Check command exit status + */ +int +finish_command(struct cfjail *j) +{ + 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)) + requeue(TAILQ_FIRST(&runnable), &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, possible inside the jail. + */ +int +run_command(struct cfjail *j) +{ + const struct passwd *pwd; + const struct cfstring *comstring, *s; + login_cap_t *lcap; + char **argv; + char *cs, *comcs, *devpath; + const char *jidstr, *conslog, *path, *ruleset, *term, *username; + enum intparam comparam; + size_t comlen; + pid_t pid; + int argc, bg, clean, consfd, down, fib, i, injail, sjuser, timeout; +#if defined(INET) || defined(INET6) + char *addr; +#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 (verbose >= 0 && (j->name || verbose > 0)) + jail_note(j, "created\n"); + 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: + argv = alloca(8 * sizeof(char *)); + *(const char **)&argv[0] = _PATH_IFCONFIG; + if ((cs = strchr(comstring->s, '|'))) { + argv[1] = alloca(cs - comstring->s + 1); + strlcpy(argv[1], comstring->s, cs - comstring->s + 1); + addr = cs + 1; + } else { + *(const char **)&argv[1] = + string_param(j->intparams[IP_INTERFACE]); + addr = comstring->s; + } + *(const char **)&argv[2] = "inet"; + if (!(cs = strchr(addr, '/'))) { + argv[3] = addr; + *(const char **)&argv[4] = "netmask"; + *(const char **)&argv[5] = "255.255.255.255"; + argc = 6; + } else if (strchr(cs + 1, '.')) { + argv[3] = alloca(cs - addr + 1); + strlcpy(argv[3], addr, cs - addr + 1); + *(const char **)&argv[4] = "netmask"; + *(const char **)&argv[5] = cs + 1; + argc = 6; + } else { + argv[3] = addr; + argc = 4; + } + *(const char **)&argv[argc] = down ? "-alias" : "alias"; + argv[argc + 1] = NULL; + break; +#endif + +#ifdef INET6 + case IP__IP6_IFADDR: + argv = alloca(8 * sizeof(char *)); + *(const char **)&argv[0] = _PATH_IFCONFIG; + if ((cs = strchr(comstring->s, '|'))) { + argv[1] = alloca(cs - comstring->s + 1); + strlcpy(argv[1], comstring->s, cs - comstring->s + 1); + addr = cs + 1; + } else { + *(const char **)&argv[1] = + string_param(j->intparams[IP_INTERFACE]); + addr = comstring->s; + } + *(const char **)&argv[2] = "inet6"; + argv[3] = addr; + if (!(cs = strchr(addr, '/'))) { + *(const char **)&argv[4] = "prefixlen"; + *(const char **)&argv[5] = "128"; + argc = 6; + } else + argc = 4; + *(const char **)&argv[argc] = down ? "-alias" : "alias"; + argv[argc + 1] = NULL; + break; +#endif + + case IP_VNET_INTERFACE: + argv = alloca(5 * sizeof(char *)); + *(const char **)&argv[0] = _PATH_IFCONFIG; + argv[1] = comstring->s; + *(const char **)&argv[2] = down ? "-vnet" : "vnet"; + jidstr = string_param(j->intparams[KP_JID]); + *(const char **)&argv[3] = + jidstr ? jidstr : string_param(j->intparams[KP_NAME]); + 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")) + 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]; + *(const char **)&argv[0] = "/sbin/umount"; + } else { + if (argc == 4) { + argv[7] = NULL; + argv[6] = argv[1]; + argv[5] = argv[0]; + argv[4] = argv[3]; + *(const char **)&argv[3] = "-o"; + } else { + argv[5] = NULL; + argv[4] = argv[1]; + argv[3] = argv[0]; + } + *(const char **)&argv[0] = _PATH_MOUNT; + } + *(const char **)&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 path"); + 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) { + *(const char **)&argv[0] = "/sbin/umount"; + argv[1] = devpath; + argv[2] = NULL; + } else { + *(const char **)&argv[0] = _PATH_MOUNT; + *(const char **)&argv[1] = "-t"; + *(const char **)&argv[2] = "devfs"; + ruleset = string_param(j->intparams[KP_DEVFS_RULESET]); + if (!ruleset) + ruleset = "4"; /* devfsrules_jail */ + argv[3] = alloca(11 + strlen(ruleset)); + sprintf(argv[3], "-oruleset=%s", ruleset); + *(const char **)&argv[4] = "."; + argv[5] = devpath; + argv[6] = 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 *)); + *(const char **)&argv[0] = _PATH_BSHELL; + *(const char **)&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; + 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) { + free(j->comline); + j->comline = NULL; + return 0; + } else { + paralimit--; + add_proc(j, pid); + 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); + } + 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); + setenv("TERM", term, 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 && chdir(pwd->pw_dir) < 0) { + jail_warnx(j, "chdir %s: %s", + pwd->pw_dir, strerror(errno)); + exit(1); + } + endpwent(); + } + + 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], argv); + jail_warnx(j, "exec %s: %s", argv[0], strerror(errno)); + exit(1); +} + +/* + * Add a process to the hash, tied to a jail. + */ +static void +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) + 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 acording 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; + } +} + +/* + * 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) { + 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; + + *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..7080a59e35d3 --- /dev/null +++ b/usr.sbin/jail/config.c @@ -0,0 +1,831 @@ +/*- + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "jailp.h" + +struct ipspec { + const char *name; + unsigned flags; +}; + +extern FILE *yyin; +extern int yynerrs; + +struct cfjails cfjails = TAILQ_HEAD_INITIALIZER(cfjails); + +static void free_param(struct cfparams *pp, struct cfparam *p); +static void free_param_strings(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_PRESTART] = {"exec.prestart", PF_INTERNAL}, + [IP_EXEC_PRESTOP] = {"exec.prestop", 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}, + [IP_MOUNT_DEVFS] = {"mount.devfs", 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}, +#ifdef INET + [IP__IP4_IFADDR] = {"ip4.addr", PF_INTERNAL | PF_CONV}, +#endif +#ifdef INET6 + [IP__IP6_IFADDR] = {"ip6.addr", PF_INTERNAL | PF_CONV}, +#endif + [IP__MOUNT_FROM_FSTAB] = {"mount.fstab", PF_INTERNAL | PF_CONV}, + [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_ENFORCE_STATFS] = {"enforce_statfs", 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", 0}, + [KP_NAME] = {"name", 0}, + [KP_PATH] = {"path", 0}, + [KP_PERSIST] = {"persist", 0}, + [KP_SECURELEVEL] = {"securelevel", 0}, + [KP_VNET] = {"vnet", 0}, +}; + +/* + * Parse the jail configuration file. + */ +void +load_config(void) +{ + struct cfjails wild; + struct cfparams opp; + struct cfjail *j, *tj, *wj; + struct cfparam *p, *vp, *tp; + struct cfstring *s, *vs, *ns; + struct cfvar *v; + char *ep; + size_t varoff; + int did_self, jseq, pgen; + + if (!strcmp(cfname, "-")) { + cfname = "STDIN"; + yyin = stdin; + } else { + yyin = fopen(cfname, "r"); + if (!yyin) + err(1, "%s", cfname); + } + if (yyparse() || yynerrs) + exit(1); + + /* 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. + */ + 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); + + /* Resolve any variable substitutions. */ + pgen = 0; + TAILQ_FOREACH(p, &j->params, tq) { + p->gen = ++pgen; + find_vars: + TAILQ_FOREACH(s, &p->val, tq) { + varoff = 0; + while ((v = STAILQ_FIRST(&s->vars))) { + TAILQ_FOREACH(vp, &j->params, tq) + if (!strcmp(vp->name, v->name)) + break; + if (!vp) { + 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 + varoff + vs->len, + s->s + v->pos + varoff, + s->len - (v->pos + varoff) + 1); + memcpy(s->s + v->pos + varoff, vs->s, vs->len); + varoff += 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); + } +} + +/* + * 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 != 0) { + 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 != 0) + 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 (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 != 0) + j->intparams[ipnum] = np; + else + for (ipnum = 1; ipnum < IP_NPARAM; ipnum++) + if (!(intparams[ipnum].flags & PF_CONV) && + equalopts(name, intparams[ipnum].name)) { + 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, prefix; +#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, + * and a netmask/suffix for that address. + */ + 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) { + strcpy(s->s, cs + 1); + s->len -= cs + 1 - s->s; + } + if ((cs = strchr(s->s, '/'))) { + prefix = strtol(cs + 1, &ep, 10); + if (*ep == '.' + ? inet_pton(AF_INET, cs + 1, &addr4) != 1 + : *ep || prefix < 0 || prefix > 32) { + jail_warnx(j, + "ip4.addr: bad netmask \"%s\"", cs); + error = -1; + } + *cs = '\0'; + s->len = cs - s->s + 1; + } + } + } +#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) { + strcpy(s->s, cs + 1); + s->len -= cs + 1 - s->s; + } + if ((cs = strchr(s->s, '/'))) { + prefix = strtol(cs + 1, &ep, 10); + if (*ep || prefix < 0 || prefix > 128) { + jail_warnx(j, + "ip6.addr: bad prefixlen \"%s\"", + cs); + error = -1; + } + *cs = '\0'; + s->len = cs - s->s + 1; + } + } + } +#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); + 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) { + strcpy(cs, s->s); + if (ts != NULL) { + cs += s->len + 1; + cs[-1] = ','; + } + } + } + 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); +} + +static 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 index eeaf99b7f33a..653e2270df82 100644 --- a/usr.sbin/jail/jail.8 +++ b/usr.sbin/jail/jail.8 @@ -1,6 +1,5 @@ -.\" .\" Copyright (c) 2000, 2003 Robert N. M. Watson -.\" Copyright (c) 2008 James Gritton +.\" Copyright (c) 2008-2012 James Gritton .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without @@ -24,116 +23,68 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" -.\" ---------------------------------------------------------------------------- -.\" "THE BEER-WARE LICENSE" (Revision 42): -.\" wrote this file. As long as you retain this notice you -.\" can do whatever you want with this stuff. If we meet some day, and you think -.\" this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp -.\" ---------------------------------------------------------------------------- -.\" .\" $FreeBSD$ .\" -.Dd February 29, 2012 +.Dd April 26, 2012 .Dt JAIL 8 .Os .Sh NAME .Nm jail -.Nd "create or modify a system jail" +.Nd "manage system jails" .Sh SYNOPSIS .Nm -.Op Fl dhi +.Op Fl dhilqv .Op Fl J Ar jid_file -.Op Fl l u Ar username | Fl U Ar username -.Op Fl c | m -.Op Ar parameter=value ... +.Op Fl u Ar username +.Op Fl U Ar username +.Op Fl cmr +.Ar param Ns = Ns Ar value ... +.Op Cm command Ns = Ns Ar command ... .Nm -.Op Fl hi +.Op Fl dqv +.Op Fl f Ar conf_file +.Op Fl p Ar limit +.Op Fl cmr +.Op Ar jail +.Nm +.Op Fl qv +.Op Fl f Ar conf_file +.Op Fl rR +.Op Cm * | Ar jail ... +.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 J Ar jid_file .Op Fl s Ar securelevel -.Op Fl l u Ar username | Fl U Ar username -.Op Ar path hostname [ip[,..]] command ... -.Nm -.Op Fl r Ar jail +.Op Ar path hostname [ Ar ip Ns [ Ns Ar ,... Ns ]] Ar command ... .Sh DESCRIPTION The .Nm -utility creates a new jail or modifies an existing jail, optionally -imprisoning the current process (and future descendants) inside it. +utility creates new jails, or modifies or removes existing jails. +A jail is specified via parameters on the command line, or in the +.Xr jail.conf 5 +file. .Pp -The options are as follows: +At least one of the options +.Fl c , +.Fl m +or +.Fl r +must be specified. +These options are used alone or in combination describe the operation to +perform: .Bl -tag -width indent -.It Fl d -Allow making changes to a dying jail. -.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 -.Va ip -addresses for this prison. -This may affect default address selection for outgoing IPv4 connections -of prisons. -The address first returned by the resolver for each address family -will be used as primary address. -See the -.Va ip4.addr -and -.Va ip6.addr -parameters further down for details. -.It Fl i -Output the jail identifier of the newly created jail. -.It Fl n Ar jailname -Set the jail's name. -This is deprecated and is equivalent to setting the -.Va name -parameter. -.It Fl J Ar jid_file -Write a -.Ar jid_file -file, containing jail identifier, path, hostname, IP and -command used to start the jail. -.It Fl l -Run program in the 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. -The environment variables from the login class capability database for the -target login are also set. -.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 setting the -.Va securelevel -parameter. -.It Fl u Ar username -The user name from host environment as whom the -.Ar command -should run. -.It Fl U Ar username -The user name from jailed environment as whom the -.Ar command -should run. .It Fl c Create a new jail. -The +The jail .Va jid and .Va name -parameters (if specified) must not refer to an existing jail. +parameters (if specified) on the command line, +or any jails +must not refer to an existing jail. .It Fl m Modify an existing jail. One of the @@ -141,27 +92,158 @@ One of the or .Va name parameters must exist and refer to an existing jail. -.It Fl cm -Create a jail if it does not exist, or modify a jail if it does exist. +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 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 c +and +.Dq Nm Fl r +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 -At least one of the -.Fl c , -.Fl m -or +Other available options are: +.Bl -tag -width indent +.It Fl d +Allow making changes to a dying jail, equivalent to the +.Va allow.dying +parameter. +.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 prison. +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 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 -options must be specified. +option that removes an existing jail without using the configuration file. +No removal-related parameters for this jail will be used - 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 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. +.El .Pp -.Ar Parameters -are listed in +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. +.P +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 be specified with parameters directly on the command line. +In this case, the +.Xr jail.conf 5 +file will not be used. +For backward compatibility, the command line may also have four fixed +parameters, without names: +.Ar path , +.Ar hostname , +.Ar ip , +and +.Ar command . +This mode will always create a new jail, and the +.Fl c +and +.Fl m +options don't apply (and must not exist). +.Ss Jail Parameters +Parameters in the +.Xr jail.conf 5 +file, or on the command line, are generally in .Dq name=value -form, following the options. +form. Some parameters are boolean, and do not have a value but are set by the name alone with or without a .Dq no @@ -169,41 +251,35 @@ prefix, e.g. .Va persist or .Va nopersist . -Any parameters not set will be given default values, often based on the -current environment. -.Pp -The pseudo-parameter -.Va command -specifies that the current process should enter the new (or modified) jail, -and run the specified command. -It must be the last parameter specified, because it includes not only -the value following the -.Sq = -sign, but also passes the rest of the arguments to the command. -.Pp -Instead of supplying named -.Ar parameters , -four fixed parameters may be supplied in order on the command line: -.Ar path , -.Ar hostname , -.Ar ip , +They can also be given the values +.Dq true and -.Ar command . -As the -.Va jid -and -.Va name -parameters aren't in this list, this mode will always create a new jail, and -the -.Fl c -and -.Fl m -options don't apply (and must not exist). +.Dq false . +Other partameters 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). .Pp -Jails have a set a core parameters, and modules can add their own jail -parameters. +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, +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 a 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 @@ -231,14 +307,21 @@ If no .Va name is supplied, a default is assumed that is the same as the .Va jid . -.It Va path -Directory which is to be the root of the prison. The -.Va command -(if any) is run from this directory, as are commands from -.Xr jexec 8 . +.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 prison. +Any commands run inside the prison, either by +.Nm +or from +.Xr jexec 8 , +are run from this directory. .It Va ip4.addr -A comma-separated list of IPv4 addresses assigned to the prison. +A list of IPv4 addresses assigned to the prison. 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. @@ -252,7 +335,7 @@ assigned to itself. A boolean option to change the formerly mentioned behaviour and disable IPv4 source address selection for the prison in favour of the primary IPv4 address of the jail. -Source address selection is enabled by default for all jails and a +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 @@ -277,8 +360,20 @@ A set of IPv6 options for the prison, the counterparts to and .Va ip4 above. +.It vnet +Create the prison 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 -Hostname of the prison. +The hostname of the prison. Other similar parameters are .Va host.domainname , .Va host.hostuuid @@ -314,13 +409,24 @@ 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 -.Va "Hierarchical Jails" +.Sx "Hierarchical Jails" section for more information. .It Va children.cur The number of descendents of this jail, including its own child jails @@ -345,10 +451,13 @@ 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 jail is destroyed as its last process exits. +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 @@ -459,6 +568,171 @@ have not had jail functionality added to them. .El .El .Pp +There are pseudo-parameters that aren't passed to the kernel, but are +used by +.Nm +to set up the prison 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 prison environment. +They may be given multiple values, which run would the specified +commands in sequence. +All commands must succed (return a zero exit status), or the jail will +not be created or removed. +.Pp +The pseudo-parameters are: +.Bl -tag -width indent +.It Va exec.prestart +Command(s) to run in the system environment before a prison is created. +.It Va exec.start +Command(s) to run in the prison 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 prison 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 prison 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 . +.It Va exec.poststop +Command(s) to run in the system environment after a jail is removed. +.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. +The environment variables from the login class capability database for the +target login are also set. +.It Va exec.jail_user +The user to run commands as, when running in the prison 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 prison'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. +If a command is still running after this many seconds have passed, +the jail not be created or removed. +.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 prison. +.It Va stop.timeout +The maximum amount of time to wait for a prison'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 prison will be removed, which +will kill any remaining processes. +If this is set to zero, no +.Dv SIGTERM +is sent and the prison is immediately removed. +The default is 10 seconds. +.It Va interface +A network interface to add the prison's IP addresses +.Va ( ip4.addr +and +.Va ip6.addr ) +to. +An alias for each address will be added to the interface before the +prison is created, and will be removed from the interface after the +prison is removed. +.It Op Va ip4.addr +In addition to the IP addresses that are passed to the kernel, and +interface and/or a netmask may also be specified, in the form +.Dq Ar interface Ns | Ns Ar ip-address Ns / Ns Ar netmask . +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 IP address, it will be used when adding the IP alias. +.It Op Va ip6.addr +In addition to the IP addresses that are passed to the kernel, +and interface and/or a prefix may also be specified, in the form +.Dq Ar interface Ns | Ns Ar ip-address Ns / Ns Ar prefix . +.It Va vnet.interface +A network interface to give to a vnet-enabled jail after is it created. +The interface will automatically be returned when the jail is removed. +.It Va ip_hostname +Resolve the +.Va host.hostname +parameter and add all IP addresses returned by the resolver +to the list of addresses +.Va ( ip4.addr +or +.Va ip6.addr ) +for this prison. +This may affect default address selection for outgoing IPv4 connections +of prisons. +The address first returned by the resolver for each address family +will be used as 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 +filesystem on the chrooted /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 prison. +.It Va allow.dying +Allow making changes to a +.Va dying +jail. +.It Va depend +Specify a jail (or jails) that this jail depends on. +Any such jails must be fully created, up to 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 must be fully removed, up to the last +.Va exec.poststop +command, before the 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 @@ -476,7 +750,6 @@ process. This manual page documents the configuration steps necessary to support either of these steps, although the configuration steps may be refined based on local requirements. -.Sh EXAMPLES .Ss "Setting up a Jail Directory Tree" To set up a jail directory tree containing an entire .Fx @@ -489,20 +762,8 @@ cd /usr/src mkdir -p $D make world DESTDIR=$D make distribution DESTDIR=$D -mount -t devfs devfs $D/dev .Ed .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 . -.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. @@ -520,8 +781,9 @@ Do what was described in to build the jail directory tree. For the sake of this example, we will assume you built it in -.Pa /data/jail/192.0.2.100 , -named for the jailed IP address. +.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" @@ -619,7 +881,7 @@ or for running a virtual server. .Pp Start a shell in the jail: .Bd -literal -offset indent -jail -c path=/data/jail/192.0.2.100 host.hostname=testhostname \\ +jail -c path=/data/jail/testjail mount.devfs host.hostname=testhostname \\ ip4.addr=192.0.2.100 command=/bin/sh .Ed .Pp @@ -633,15 +895,6 @@ etc. .Pp .Bl -bullet -offset indent -compact .It -Create an empty -.Pa /etc/fstab -to quell startup warnings about missing fstab (virtual server only) -.It -Disable the port mapper -.Pa ( /etc/rc.conf : -.Li rpcbind_enable="NO" ) -(virtual server only) -.It Configure .Pa /etc/resolv.conf so that name resolution within the jail will work correctly @@ -652,11 +905,6 @@ to quell .Xr sendmail 8 warnings. .It -Disable interface configuration to quell startup warnings about -.Xr ifconfig 8 -.Pq Li network_interfaces="" -(virtual server only) -.It Set a root password, probably different from the real host system .It Set the timezone @@ -674,36 +922,47 @@ 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/192.0.2.100/var/run/log . +.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. -If you are running a single application in the jail, substitute the -command used to start the application for -.Pa /etc/rc -in the examples below. -To start a virtual server environment, -.Pa /etc/rc -is run to launch various daemons and services. -To do this, first bring up the -virtual host interface, and then start the jail's -.Pa /etc/rc -script from within the jail. +Create an entry for the jail in +.Pa /etc/jail.conf : .Bd -literal -offset indent -ifconfig ed0 inet alias 192.0.2.100/32 -mount -t procfs proc /data/jail/192.0.2.100/proc -jail -c path=/data/jail/192.0.2.100 host.hostname=testhostname \\ - ip4.addr=192.0.2.100 command=/bin/sh /etc/rc +testjail { + path = /tmp/jail/testjail; + mount.devfs; + host.hostname = testhostname; + ip4.addr = 192.0.2.100; + interface = ed0; + exec.start = "/bin/sh /etc/rc"; + exec.stop = "/bin/sh /etc/rc.shutdown"; +} .Ed .Pp -A few warnings will be produced, because most -.Xr sysctl 8 -configuration variables cannot be set from within the jail, as they are -global across all jails and the host environment. -However, it should all -work properly. +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 , @@ -726,15 +985,6 @@ Please refer to the variables in .Xr rc.conf 5 for more information. -The -.Xr rc 8 -jail script provides a flexible system to start/stop jails: -.Bd -literal -/etc/rc.d/jail start -/etc/rc.d/jail stop -/etc/rc.d/jail start myjail -/etc/rc.d/jail stop myjail -.Ed .Ss "Managing the Jail" Normal machine shutdown commands, such as .Xr halt 8 , @@ -742,9 +992,8 @@ Normal machine shutdown commands, such as and .Xr shutdown 8 , cannot be used successfully within the jail. -To kill all processes in a -jail, you may log into the jail and, as root, use one of the following -commands, depending on what you want to accomplish: +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 @@ -754,21 +1003,27 @@ This will send the .Dv SIGTERM or .Dv SIGKILL -signals to all processes in the jail from within the jail. +signals to all processes in the jail - 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. -To kill processes from outside the jail, use the -.Xr jexec 8 -utility in conjunction with the one of the -.Xr kill 1 -commands above. -You may also remove the jail with +.Pp +To shut down the jail from the outside, simply remove it with .Nm .Ar -r , -which will killall the jail's processes with -.Dv SIGKILL . +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 @@ -888,7 +1143,7 @@ unique jid. .Pp Like the names, a child jail's .Va path -is relative to its creator's own +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. @@ -900,12 +1155,12 @@ environment of the first jail. .Xr pkill 1 , .Xr ps 1 , .Xr quota 1 , -.Xr chroot 2 , .Xr jail_set 2 , -.Xr jail_attach 2 , +.Xr jail.conf 5 , .Xr procfs 5 , .Xr rc.conf 5 , .Xr sysctl.conf 5 , +.Xr chroot 8 , .Xr devfs 8 , .Xr halt 8 , .Xr inetd 8 , @@ -927,6 +1182,8 @@ utility appeared in .Fx 4.0 . Hierarchical/extensible jails were introduced in .Fx 8.0 . +The configuration file was introduced in +.Fx 10.0 . .Sh AUTHORS .An -nosplit The jail feature was written by @@ -947,14 +1204,10 @@ originally done by for IPv4. .Pp .An James Gritton -added the extensible jail parameters and hierarchical jails. +added the extensible jail parameters, hierarchical jails, +and the configuration file. .Sh BUGS -Jail currently lacks the ability to allow access to -specific jail information via -.Xr ps 1 -as opposed to -.Xr procfs 5 . -Similarly, it might be a good idea to add an +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 diff --git a/usr.sbin/jail/jail.c b/usr.sbin/jail/jail.c index 69c5e5362c9d..d6de5cabe996 100644 --- a/usr.sbin/jail/jail.c +++ b/usr.sbin/jail/jail.c @@ -1,6 +1,6 @@ /*- * Copyright (c) 1999 Poul-Henning Kamp. - * Copyright (c) 2009 James Gritton + * Copyright (c) 2009-2012 James Gritton * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -28,155 +28,189 @@ #include __FBSDID("$FreeBSD$"); -#include -#include +#include +#include #include #include #include #include -#include #include #include -#include -#include -#include -#include -#include -#include +#include #include #include #include #include -static struct jailparam *params; -static char **param_values; -static int nparams; +#include "jailp.h" -#ifdef INET6 -static int ip6_ok; -static char *ip6_addr; -#endif -#ifdef INET -static int ip4_ok; -static char *ip4_addr; -#endif +#define JP_RDTUN(jp) (((jp)->jp_ctltype & CTLFLAG_RDTUN) == CTLFLAG_RDTUN) -#if defined(INET6) || defined(INET) -static void add_ip_addr(char **addrp, char *newaddr); -#endif -#ifdef INET6 -static void add_ip_addr46(char *newaddr); -#endif -static void add_ip_addrinfo(int ai_flags, char *value); -static void quoted_print(FILE *fp, char *str); -static void set_param(const char *name, char *value); -static void usage(void); - -static const char *perm_sysctl[][3] = { - { "security.jail.set_hostname_allowed", - "allow.noset_hostname", "allow.set_hostname" }, - { "security.jail.sysvipc_allowed", - "allow.nosysvipc", "allow.sysvipc" }, - { "security.jail.allow_raw_sockets", - "allow.noraw_sockets", "allow.raw_sockets" }, - { "security.jail.chflags_allowed", - "allow.nochflags", "allow.chflags" }, - { "security.jail.mount_allowed", - "allow.nomount", "allow.mount" }, - { "security.jail.socket_unixiproute_only", - "allow.socket_af", "allow.nosocket_af" }, +struct permspec { + const char *name; + enum intparam ipnum; + int rev; }; -extern char **environ; +const char *cfname; +int note_remove; +int verbose; -#define GET_USER_INFO do { \ - pwd = getpwnam(username); \ - if (pwd == NULL) { \ - if (errno) \ - err(1, "getpwnam: %s", username); \ - else \ - errx(1, "%s: no such user", username); \ - } \ - lcap = login_getpwclass(pwd); \ - if (lcap == NULL) \ - err(1, "getpwclass: %s", username); \ - ngroups = ngroups_max; \ - if (getgrouplist(username, pwd->pw_gid, groups, &ngroups) != 0) \ - err(1, "getgrouplist: %s", username); \ -} while (0) +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, int dflag); +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); +static void print_param(FILE *fp, const struct cfparam *p, int sep, int doname); +static void quoted_print(FILE *fp, char *str); +static void usage(void); + +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[] = { + 0, +#ifdef INET + IP__IP4_IFADDR, +#endif +#ifdef INET6 + IP__IP6_IFADDR, +#endif + IP_MOUNT, + IP__MOUNT_FROM_FSTAB, + IP_MOUNT_DEVFS, + IP_EXEC_PRESTART, + IP__OP, + IP_VNET_INTERFACE, + IP_EXEC_START, + IP_COMMAND, + IP_EXEC_POSTSTART, + 0 +}; + +static const enum intparam stopcommands[] = { + 0, + IP_EXEC_PRESTOP, + IP_EXEC_STOP, + IP_STOP_TIMEOUT, + IP__OP, + IP_EXEC_POSTSTOP, + IP_MOUNT_DEVFS, + IP__MOUNT_FROM_FSTAB, + IP_MOUNT, +#ifdef INET6 + IP__IP6_IFADDR, +#endif +#ifdef INET + IP__IP4_IFADDR, +#endif + 0 +}; int main(int argc, char **argv) { - login_cap_t *lcap = NULL; - struct passwd *pwd = NULL; - gid_t *groups; + struct stat st; + FILE *jfp; + struct cfjail *j; + char *JidFile; size_t sysvallen; - int ch, cmdarg, i, jail_set_flags, jid, ngroups, sysval; - int hflag, iflag, Jflag, lflag, rflag, uflag, Uflag; - long ngroups_max; - unsigned pi; - char *jailname, *securelevel, *username, *JidFile; + unsigned op, pi; + int ch, docf, error, i, oldcl, sysval; + int dflag, iflag, Rflag; char enforce_statfs[4]; - static char *cleanenv; - const char *shell, *p = NULL; - FILE *fp; +#if defined(INET) || defined(INET6) + char *cs, *ncs; +#endif +#if defined(INET) && defined(INET6) + struct in6_addr addr6; +#endif - hflag = iflag = Jflag = lflag = rflag = uflag = Uflag = - jail_set_flags = 0; - cmdarg = jid = -1; - jailname = securelevel = username = JidFile = cleanenv = NULL; - fp = NULL; + op = 0; + dflag = iflag = Rflag = 0; + docf = 1; + cfname = CONF_FILE; + JidFile = NULL; - ngroups_max = sysconf(_SC_NGROUPS_MAX) + 1; - if ((groups = malloc(sizeof(gid_t) * ngroups_max)) == NULL) - err(1, "malloc"); - - while ((ch = getopt(argc, argv, "cdhilmn:r:s:u:U:J:")) != -1) { + while ((ch = getopt(argc, argv, "cdf:hiJ:lmn:p:qrRs:U:v")) != -1) { switch (ch) { + case 'c': + op |= JF_START; + break; case 'd': - jail_set_flags |= JAIL_DYING; + dflag = 1; + break; + case 'f': + cfname = optarg; break; case 'h': - hflag = 1; +#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; - Jflag = 1; - break; - case 'n': - jailname = optarg; - break; - case 's': - securelevel = optarg; - break; - case 'u': - username = optarg; - uflag = 1; - break; - case 'U': - username = optarg; - Uflag = 1; break; case 'l': - lflag = 1; - break; - case 'c': - jail_set_flags |= JAIL_CREATE; + add_param(NULL, NULL, IP_EXEC_CLEAN, NULL); + docf = 0; break; case 'm': - jail_set_flags |= JAIL_UPDATE; + 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': - jid = jail_getid(optarg); - if (jid < 0) - errx(1, "%s", jail_errmsg); - rflag = 1; + 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(); @@ -184,312 +218,747 @@ main(int argc, char **argv) } argc -= optind; argv += optind; - if (rflag) { - if (argc > 0 || iflag || Jflag || lflag || uflag || Uflag) - usage(); - if (jail_remove(jid) < 0) - err(1, "jail_remove"); - exit (0); - } - if (argc == 0) - usage(); - if (uflag && Uflag) - usage(); - if (lflag && username == NULL) - usage(); - if (uflag) - GET_USER_INFO; -#ifdef INET6 - ip6_ok = feature_present("inet6"); -#endif -#ifdef INET - ip4_ok = feature_present("inet"); -#endif - - if (jailname) - set_param("name", jailname); - if (securelevel) - set_param("securelevel", securelevel); - if (jail_set_flags) { - for (i = 0; i < argc; i++) { - if (!strncmp(argv[i], "command=", 8)) { - cmdarg = i; - argv[cmdarg] += 8; - jail_set_flags |= JAIL_ATTACH; - break; - } - if (hflag) { -#ifdef INET - if (!strncmp(argv[i], "ip4.addr=", 9)) { - add_ip_addr(&ip4_addr, argv[i] + 9); - break; - } -#endif -#ifdef INET6 - if (!strncmp(argv[i], "ip6.addr=", 9)) { - add_ip_addr(&ip6_addr, argv[i] + 9); - break; - } -#endif - if (!strncmp(argv[i], "host.hostname=", 14)) - add_ip_addrinfo(0, argv[i] + 14); - } - set_param(NULL, argv[i]); - } - } else { + /* Find out which of the four command line styles this is. */ + oldcl = 0; + if (!op) { + /* Old-style command line with four fixed parameters */ if (argc < 4 || argv[0][0] != '/') - errx(1, "%s\n%s", - "no -c or -m, so this must be an old-style command.", - "But it doesn't look like one."); - set_param("path", argv[0]); - set_param("host.hostname", argv[1]); - if (hflag) - add_ip_addrinfo(0, argv[1]); -#if defined(INET6) || defined(INET) - if (argv[2][0] != '\0') -#ifdef INET6 - add_ip_addr46(argv[2]); -#else - add_ip_addr(&ip4_addr, argv[2]); + 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 - cmdarg = 3; - /* Emulate the defaults from security.jail.* sysctls */ + 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][0], + if (sysctlbyname(perm_sysctl[pi].name, &sysval, &sysvallen, NULL, 0) == 0) - set_param(perm_sysctl[pi] - [sysval ? 2 : 1], NULL); + add_param(NULL, NULL, + perm_sysctl[pi].ipnum, + (sysval ? 1 : 0) ^ + perm_sysctl[pi].rev + ? NULL : "false"); } sysvallen = sizeof(sysval); if (sysctlbyname("security.jail.enforce_statfs", &sysval, &sysvallen, NULL, 0) == 0) { snprintf(enforce_statfs, sizeof(enforce_statfs), "%d", sysval); - set_param("enforce_statfs", enforce_statfs); + add_param(NULL, NULL, KP_ENFORCE_STATFS, + enforce_statfs); + } + } + } 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(); + 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) + usage(); + docf = 0; + for (i = 0; i < argc; i++) { + 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]); + break; + } + add_param(NULL, NULL, 0, argv[i]); + } + } else { + /* From the config file, perhaps with a specified jail */ + if (Rflag || !docf) + usage(); + load_config(); + } + + /* Find out which jails will be run. */ + dep_setup(docf); + error = 0; + if (op == 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. + */ + 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->jid) + running_jid(j, + (j->flags & (JF_SET | JF_DEPEND)) == JF_SET + ? dflag || bool_param(j->intparams[IP_ALLOW_DYING]) + : 0); + 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 ? JF_START : JF_SET; + break; + case JF_SET_RESTART: + if (j->jid < 0) { + jail_quoted_warnx(j, "not found", + "no jail specified"); + failed(j); + continue; + } + j->flags = rdtun_params(j, 0) ? 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) ? 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; + j->comparam = startcommands; + j->comstring = NULL; + } + if (next_command(j)) + continue; + jail_create_done: + clear_persist(j); + if (iflag) + printf("%d\n", j->jid); + if (jfp != NULL) + print_jail(jfp, j, oldcl); + 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->jid < 0) { + if (!(j->flags & (JF_DEPEND | JF_WILD)) + && verbose >= 0) + jail_quoted_warnx(j, + "not found", NULL); + goto jail_remove_done; + } + 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; + 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 iovec jiov[4]; + struct stat st; + struct jailparam *jp, *setparams, *setparams2, *sjp; + const char *path; + int dopersist, ns, jid, dying, didfail; + + /* + * 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; + + didfail = 0; + j->jid = jailparam_set_note(j, setparams, ns, JAIL_CREATE); + if (j->jid < 0 && errno == EEXIST && + bool_param(j->intparams[IP_ALLOW_DYING]) && + int_param(j->intparams[KP_JID], &jid) && jid != 0) { + /* + * The jail already exists, but may be dying. + * Make sure it is, in which case an update is appropriate. + */ + *(const void **)&jiov[0].iov_base = "jid"; + jiov[0].iov_len = sizeof("jid"); + jiov[1].iov_base = &jid; + jiov[1].iov_len = sizeof(jid); + *(const void **)&jiov[2].iov_base = "dying"; + jiov[2].iov_len = sizeof("dying"); + jiov[3].iov_base = &dying; + jiov[3].iov_len = sizeof(dying); + if (jail_get(jiov, 4, JAIL_DYING) < 0) { + /* + * It could be that the jail just barely finished + * dying, or it could be that the jid never existed + * but the name does. In either case, another try + * at creating the jail should do the right thing. + */ + if (errno == ENOENT) + j->jid = jailparam_set_note(j, setparams, ns, + JAIL_CREATE); + } else if (dying) { + j->jid = jid; + if (rdtun_params(j, 1) < 0) { + j->jid = -1; + didfail = 1; + } else { + sjp = setparams2 = alloca((j->njp + dopersist) * + sizeof(struct jailparam)); + for (jp = setparams; jp < setparams + ns; jp++) + if (!JP_RDTUN(jp) || + !strcmp(jp->jp_name, "jid")) + *sjp++ = *jp; + j->jid = jailparam_set_note(j, setparams2, + sjp - setparams2, JAIL_UPDATE | JAIL_DYING); + /* + * Again, perhaps the jail just finished dying. + */ + if (j->jid < 0 && errno == ENOENT) + j->jid = jailparam_set_note(j, + setparams, ns, JAIL_CREATE); } } } -#ifdef INET - if (ip4_addr != NULL) - set_param("ip4.addr", ip4_addr); -#endif -#ifdef INET6 - if (ip6_addr != NULL) - set_param("ip6.addr", ip6_addr); -#endif + if (j->jid < 0 && !didfail) { + 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; +} - if (Jflag) { - fp = fopen(JidFile, "w"); - if (fp == NULL) - errx(1, "Could not create JidFile: %s", JidFile); +/* + * 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; + *(const void **)&jiov[0].iov_base = "jid"; + jiov[0].iov_len = sizeof("jid"); + jiov[1].iov_base = &j->jid; + jiov[1].iov_len = sizeof(j->jid); + *(const void **)&jiov[2].iov_base = "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; } - jid = jailparam_set(params, nparams, - jail_set_flags ? jail_set_flags : JAIL_CREATE | JAIL_ATTACH); - if (jid < 0) - errx(1, "%s", jail_errmsg); - if (iflag) { - printf("%d\n", jid); - fflush(stdout); + for (jp = j->jp; jp < j->jp + j->njp; jp++) + if (!JP_RDTUN(jp)) + *++sjp = *jp; + + jid = jailparam_set_note(j, setparams, ns, + bool_param(j->intparams[IP_ALLOW_DYING]) + ? JAIL_UPDATE | JAIL_DYING : JAIL_UPDATE); + if (jid < 0) { + jail_warnx(j, "%s", jail_errmsg); + failed(j); } - if (Jflag) { - if (jail_set_flags) { - fprintf(fp, "jid=%d", jid); - for (i = 0; i < nparams; i++) - if (strcmp(params[i].jp_name, "jid")) { - fprintf(fp, " %s", - (char *)params[i].jp_name); - if (param_values[i]) { - putc('=', fp); - quoted_print(fp, - param_values[i]); - } - } - fprintf(fp, "\n"); - } else { - for (i = 0; i < nparams; i++) - if (!strcmp(params[i].jp_name, "path")) + 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; + int nrt, rval; + + 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; + rval = 0; + if (jailparam_get(rtparams, nrt, + bool_param(j->intparams[IP_ALLOW_DYING]) ? JAIL_DYING : 0) > 0) { + rtjp = rtparams + 1; + for (jp = j->jp, rtjp = rtparams + 1; rtjp < rtparams + nrt; + jp++) { + if (JP_RDTUN(jp) && strcmp(jp->jp_name, "jid")) { + if (!((jp->jp_flags & (JP_BOOL | JP_NOBOOL)) && + jp->jp_valuelen == 0 && + *(int *)jp->jp_value) && + !(rtjp->jp_valuelen == jp->jp_valuelen && + !memcmp(rtjp->jp_value, jp->jp_value, + jp->jp_valuelen))) { + if (dofail) { + jail_warnx(j, "%s cannot be " + "changed after creation", + jp->jp_name); + failed(j); + rval = -1; + } else + rval = 1; break; -#if defined(INET6) && defined(INET) - fprintf(fp, "%d\t%s\t%s\t%s%s%s\t%s\n", - jid, i < nparams - ? (char *)params[i].jp_value : argv[0], - argv[1], ip4_addr ? ip4_addr : "", - ip4_addr && ip4_addr[0] && ip6_addr && ip6_addr[0] - ? "," : "", ip6_addr ? ip6_addr : "", argv[3]); -#elif defined(INET6) - fprintf(fp, "%d\t%s\t%s\t%s\t%s\n", - jid, i < nparams - ? (char *)params[i].jp_value : argv[0], - argv[1], ip6_addr ? ip6_addr : "", argv[3]); -#elif defined(INET) - fprintf(fp, "%d\t%s\t%s\t%s\t%s\n", - jid, i < nparams - ? (char *)params[i].jp_value : argv[0], - argv[1], ip4_addr ? ip4_addr : "", argv[3]); -#endif + } + rtjp++; + } } - (void)fclose(fp); } - if (cmdarg < 0) - exit(0); - if (username != NULL) { - if (Uflag) - GET_USER_INFO; - if (lflag) { - p = getenv("TERM"); - environ = &cleanenv; + 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, int dflag) +{ + 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; } - if (setgroups(ngroups, groups) != 0) - err(1, "setgroups"); - if (setgid(pwd->pw_gid) != 0) - err(1, "setgid"); - if (setusercontext(lcap, pwd, pwd->pw_uid, - LOGIN_SETALL & ~LOGIN_SETGROUP & ~LOGIN_SETLOGIN) != 0) - err(1, "setusercontext"); - login_close(lcap); + *(const void **)&jiov[0].iov_base = "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]))) { + *(const void **)&jiov[0].iov_base = "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; } - if (lflag) { - if (*pwd->pw_shell) - shell = pwd->pw_shell; - else - shell = _PATH_BSHELL; - if (chdir(pwd->pw_dir) < 0) - errx(1, "no home directory"); - setenv("HOME", pwd->pw_dir, 1); - setenv("SHELL", shell, 1); - setenv("USER", pwd->pw_name, 1); - if (p) - setenv("TERM", p, 1); - } - execvp(argv[cmdarg], argv + cmdarg); - err(1, "execvp: %s", argv[cmdarg]); + j->jid = jail_get(jiov, 2, dflag ? JAIL_DYING : 0); } -#if defined(INET6) || defined(INET) static void -add_ip_addr(char **addrp, char *value) +jail_quoted_warnx(const struct cfjail *j, const char *name_msg, + const char *noname_msg) { - int addrlen; - char *addr; + const char *pval; - if (!*addrp) { - *addrp = strdup(value); - if (!*addrp) - err(1, "malloc"); - } else if (value[0]) { - addrlen = strlen(*addrp) + strlen(value) + 2; - addr = malloc(addrlen); - if (!addr) - err(1, "malloc"); - snprintf(addr, addrlen, "%s,%s", *addrp, value); - free(*addrp); - *addrp = addr; - } + 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); } -#endif -#ifdef INET6 -static void -add_ip_addr46(char *value) +/* + * Set jail parameters and possible print them out. + */ +static int +jailparam_set_note(const struct cfjail *j, struct jailparam *jp, unsigned njp, + int flags) { - char *p, *np; + char *value; + int jid; + unsigned i; - for (p = value;; p = np + 1) - { - np = strchr(p, ','); - if (np) - *np = '\0'; - add_ip_addrinfo(AI_NUMERICHOST, p); - if (!np) - break; + jid = jailparam_set(jp, njp, flags); + if (verbose > 0) { + jail_note(j, "jail_set(%s%s)", + (flags & (JAIL_CREATE | JAIL_UPDATE)) == JAIL_CREATE + ? "JAIL_CREATE" : "JAIL_UPDATE", + (flags & JAIL_DYING) ? " | JAIL_DYING" : ""); + 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; } -#endif +/* + * Print a jail record. + */ static void -add_ip_addrinfo(int ai_flags, char *value) +print_jail(FILE *fp, struct cfjail *j, int oldcl) { - struct addrinfo hints, *ai0, *ai; - int error; + struct cfparam *p; + + if (oldcl) { + fprintf(fp, "%d\t", j->jid); + print_param(fp, j->intparams[KP_PATH], ',', 0); + putc('\t', fp); + print_param(fp, j->intparams[KP_HOST_HOSTNAME], ',', 0); + putc('\t', fp); #ifdef INET - char avalue4[INET_ADDRSTRLEN]; - struct in_addr addr4; + 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 - char avalue6[INET6_ADDRSTRLEN]; - struct in6_addr addr6; + print_param(fp, j->intparams[KP_IP6_ADDR], ',', 0); #endif - - /* Look up the hostname (or get the address) */ - memset(&hints, 0, sizeof(hints)); - hints.ai_socktype = SOCK_STREAM; -#if defined(INET6) && defined(INET) - hints.ai_family = PF_UNSPEC; -#elif defined(INET6) - hints.ai_family = PF_INET6; -#elif defined(INET) - hints.ai_family = PF_INET; -#endif - hints.ai_flags = ai_flags; - error = getaddrinfo(value, NULL, &hints, &ai0); - if (error != 0) - errx(1, "hostname %s: %s", value, gai_strerror(error)); - - /* Convert the addresses to ASCII so set_param can convert them back. */ - for (ai = ai0; ai; ai = ai->ai_next) - switch (ai->ai_family) { -#ifdef INET - case AF_INET: - if (!ip4_ok && (ai_flags & AI_NUMERICHOST) == 0) - break; - 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_ip_addr(&ip4_addr, avalue4); - break; -#endif -#ifdef INET6 - case AF_INET6: - if (!ip6_ok && (ai_flags & AI_NUMERICHOST) == 0) - break; - 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_ip_addr(&ip6_addr, avalue6); - break; -#endif - } - freeaddrinfo(ai0); + putc('\t', fp); + print_param(fp, j->intparams[IP_COMMAND], ' ', 0); + } else { + fprintf(fp, "jid=%d", j->jid); + TAILQ_FOREACH(p, &j->params, tq) + if (strcmp(p->name, "jid")) { + putc(' ', fp); + print_param(fp, p, ',', 1); + } + } + putc('\n', fp); } +/* + * 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; - /* An empty string needs quoting. */ - if (!*p) { - fputs("\"\"", fp); - return; - } - - /* - * The value will be surrounded by quotes if it contains spaces - * or quotes. - */ - qc = strchr(p, '\'') ? '"' + qc = !*p ? '"' + : strchr(p, '\'') ? '"' : strchr(p, '"') ? '\'' : strchr(p, ' ') || strchr(p, '\t') ? '"' : 0; @@ -504,72 +973,17 @@ quoted_print(FILE *fp, char *str) putc(qc, fp); } -static void -set_param(const char *name, char *value) -{ - struct jailparam *param; - char path[PATH_MAX]; - int i; - - static int paramlistsize; - - /* Separate the name from the value, if not done already. */ - if (name == NULL) { - name = value; - if ((value = strchr(value, '='))) - *value++ = '\0'; - } - - /* jail_set won't chdir along with its chroot, so do it here. */ - if (!strcmp(name, "path")) { - /* resolve the path with realpath(3) */ - if (realpath(value, path) != NULL) - value = path; - if (chdir(value) < 0) - err(1, "chdir: %s", value); - } - - /* Check for repeat parameters */ - for (i = 0; i < nparams; i++) - if (!strcmp(name, params[i].jp_name)) { - jailparam_free(params + i, 1); - memcpy(params + i, params + i + 1, - (--nparams - i) * sizeof(struct jailparam)); - break; - } - - /* Make sure there is room for the new param record. */ - if (!nparams) { - paramlistsize = 32; - params = malloc(paramlistsize * sizeof(*params)); - param_values = malloc(paramlistsize * sizeof(*param_values)); - if (params == NULL || param_values == NULL) - err(1, "malloc"); - } else if (nparams >= paramlistsize) { - paramlistsize *= 2; - params = realloc(params, paramlistsize * sizeof(*params)); - param_values = realloc(param_values, - paramlistsize * sizeof(*param_values)); - if (params == NULL) - err(1, "realloc"); - } - - /* Look up the paramter. */ - param_values[nparams] = value; - param = params + nparams++; - if (jailparam_init(param, name) < 0 || - jailparam_import(param, value) < 0) - errx(1, "%s", jail_errmsg); -} - static void usage(void) { (void)fprintf(stderr, - "usage: jail [-d] [-h] [-i] [-J jid_file] " - "[-l -u username | -U username]\n" - " [-c | -m] param=value ... [command=command ...]\n" - " jail [-r jail]\n"); + "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"); exit(1); } diff --git a/usr.sbin/jail/jail.conf.5 b/usr.sbin/jail/jail.conf.5 new file mode 100644 index 000000000000..1954b05d59e9 --- /dev/null +++ b/usr.sbin/jail/jail.conf.5 @@ -0,0 +1,231 @@ +.\" 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. +.\" +.\" $FreeBSD$ +.\" +.Dd October 20, 2010 +.Dt JAIL.CONF 5 +.Os +.Sh NAME +.Nm jail.conf +.Nd configuration file for +.Xr jail 8 +.Sh DESCRIPTION +A +.Xr jail 8 +configuration file consists of one or more jail definitions statements, +and parameter or variable statements within those jail definitions. +A jail definition statement looks something like a C compound statement. +A parameter statement looks like a C assigment, +including a terminating semicolon. +.Pp +The general syntax of a jail definition is: +.Pp +.Bd -literal -offset indent +jailname { + parameter = "value"; + parameter = "value"; + ... +} +.Ed +.Pp +Each jail is required to have a +.Va name +at the front of its definition. +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. +See +.Xr jail 8 +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 aren't 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 undescores, 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 preceeding 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 preceeded 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 substition 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 preceeded 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 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 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"; +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 +} +.Sh SEE ALSO +.Xr jail_set 2 +.Xr jail 8 +.Xr jls 8 +.Sh HISTORY +The +.Xr jail 8 +utility appeared in +.Fx 4.0 . +The +.Nm +file was added in +.Fx 9.0 . +.Sh AUTHORS +.An -nosplit +The jail feature was written by +.An Poul-Henning Kamp +for R&D Associates +.Pa http://www.rndassociates.com/ +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..157db9276b35 --- /dev/null +++ b/usr.sbin/jail/jaillex.l @@ -0,0 +1,232 @@ +%{ +/*- + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include "jailp.h" +#include "y.tab.h" + +#define YY_NO_UNPUT + +extern int yynerrs; + +static ssize_t text2lval(size_t triml, size_t trimr, int tovar); + +static int instr; +static int lineno = 1; +%} + +%start _ DQ + +%% + + /* Whitespace or equivalent */ +<_>[ \t]+ instr = 0; +<_>#.* ; +<_>\/\/.* ; +<_>\/\*([^*]|(\*+([^*\/])))*\*+\/ { + const char *s; + + for (s = yytext; s < yytext + yyleng; s++) + if (*s == '\n') + lineno++; + instr = 0; + } +<_>\n { + lineno++; + 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); + return instr ? STR1 : (instr = 1, STR); + } + + /* Single and double quoted strings */ +<_>'([^\'\\]|\\(.|\n))*' { + (void)text2lval(1, 1, 0); + return instr ? STR1 : (instr = 1, STR); + } +<_>\"([^"\\]|\\(.|\n))*\" | +[^\"$\\]([^"\\]|\\(.|\n))*\" { + size_t skip; + ssize_t atvar; + + skip = yytext[0] == '"' ? 1 : 0; + atvar = text2lval(skip, 1, 1); + 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); + } +\" BEGIN _; + + /* Variables, single-word or bracketed */ +<_,DQ>$[A-Za-z_][A-Za-z_0-9]* { + (void)text2lval(1, 0, 0); + return instr ? VAR1 : (instr = 1, VAR); + } +<_>$\{([^\n{}]|\\(.|\n))*\} | +$\{([^\n\"{}]|\\(.|\n))*\} { + (void)text2lval(2, 1, 0); + return instr ? VAR1 : (instr = 1, VAR); + } + + /* Partially formed bits worth complaining about */ +<_>\/\*([^*]|(\*+([^*\/])))*\** { + warnx("%s line %d: unterminated comment", + cfname, lineno); + yynerrs++; + } +<_>'([^\n'\\]|\\.)* | +<_>\"([^\n\"\\]|\\.)* { + warnx("%s line %d: unterminated string", + cfname, lineno); + yynerrs++; + } +<_>$\{([^\n{}]|\\.)* | +$\{([^\n\"{}]|\\.)* { + warnx("%s line %d: unterminated variable", + cfname, lineno); + yynerrs++; + } + + /* A hack because "<0>" rules aren't allowed */ +<_>. return yytext[0]; +.|\n { + BEGIN _; + yyless(0); + } + +%% + +void +yyerror(const char *s) +{ + if (!yytext) + warnx("%s line %d: %s", cfname, lineno, s); + else if (!yytext[0]) + warnx("%s: unexpected EOF", cfname); + else + warnx("%s line %d: %s: %s", cfname, lineno, yytext, s); +} + +/* + * 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) +{ + char *d; + const char *s, *se; + + 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; + } + if (*s == '\n') + lineno++; + *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; + case '\n': d--; lineno++; break; + default: *d = *s; 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] <= 'a') + *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] <= 'a') + *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..226ecd45a5d4 --- /dev/null +++ b/usr.sbin/jail/jailp.h @@ -0,0 +1,232 @@ +/*- + * 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. + * + * $FreeBSD$ + */ + +#include +#include +#include +#include +#include + +#include + +#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 propigate failed jails */ + +#define PF_VAR 0x01 /* This is a variable, not a true parameter */ +#define PF_APPEND 0x02 /* Append to existing parameter list */ +#define PF_BAD 0x04 /* Unable to resolve parameter value */ +#define PF_INTERNAL 0x08 /* Internal parameter, not passed to kernel */ +#define PF_BOOL 0x10 /* Boolean parameter */ +#define PF_INT 0x20 /* Integer parameter */ +#define PF_CONV 0x40 /* Parameter duplicated in converted 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_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_ALLOW_DYING = 1, /* 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_PRESTART, /* Commands run outside jail before creating */ + IP_EXEC_PRESTOP, /* Commands run outside jail before removing */ + 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_FSTAB, /* A standard fstab(5) file */ + IP_STOP_TIMEOUT, /* Time to wait after sending SIGTERM */ + IP_VNET_INTERFACE, /* Assign interface(s) to vnet jail */ +#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_ENFORCE_STATFS, + 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 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; +}; + +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(void); +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 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 yyerror(const char *); +extern int yylex(void); +extern int yyparse(void); + +extern struct cfjails cfjails; +extern struct cfjails ready; +extern struct cfjails depend; +extern const char *cfname; +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..2df337fc4270 --- /dev/null +++ b/usr.sbin/jail/jailparse.y @@ -0,0 +1,216 @@ +%{ +/*- + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include + +#include "jailp.h" + +#ifdef DEBUG +#define YYDEBUG 1 +#endif +%} + +%union { + struct cfjail *j; + struct cfparams *pp; + struct cfparam *p; + struct cfstrings *ss; + struct cfstring *s; + char *cs; +} + +%token PLEQ +%token STR STR1 VAR VAR1 + +%type jail +%type param_l +%type

param name +%type value +%type string + +%% + +/* + * A config file is a series of jails (containing parameters) and jail-less + * parameters which realy belong to a global pseudo-jail. + */ +conf : + ; + | conf jail + ; + | conf param ';' + { + struct cfjail *j; + + j = TAILQ_LAST(&cfjails, cfjails); + if (!j || strcmp(j->name, "*")) { + j = add_jail(); + j->name = estrdup("*"); + } + TAILQ_INSERT_TAIL(&j->params, $2, tq); + } + | conf ';' + +jail : STR '{' param_l '}' + { + $$ = add_jail(); + $$->name = $1; + TAILQ_CONCAT(&$$->params, $3, tq); + free($3); + } + ; + +param_l : + { + $$ = emalloc(sizeof(struct cfparams)); + TAILQ_INIT($$); + } + | param_l param ';' + { + $$ = $1; + TAILQ_INSERT_TAIL($$, $2, tq); + } + | param_l ';' + ; + +/* + * Parameters have a name and an optional list of value strings, + * which may have "+=" or "=" preceeding 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); + 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); + } + ; + +%% diff --git a/usr.sbin/jail/state.c b/usr.sbin/jail/state.c new file mode 100644 index 000000000000..17b2a0c266a2 --- /dev/null +++ b/usr.sbin/jail/state.c @@ -0,0 +1,469 @@ +/*- + * 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 +__FBSDID("$FreeBSD$"); + +#include + +#include +#include +#include + +#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 int running_jid(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 error, 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_jid(s->s, 0) < 0) { + 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); + error = 0; + 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_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. + */ + *(const void **)&jiov[0].iov_base = "lastjid"; + jiov[0].iov_len = sizeof("lastjid"); + jiov[1].iov_base = &jid; + jiov[1].iov_len = sizeof(jid); + *(const void **)&jiov[2].iov_base = "jid"; + jiov[2].iov_len = sizeof("jid"); + jiov[3].iov_base = &jid; + jiov[3].iov_len = sizeof(jid); + *(const void **)&jiov[4].iov_base = "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_STOP) { + /* Allow -[rR] to specify a currently running jail. */ + if ((jid = running_jid(target, JAIL_DYING)) > 0) { + j = add_jail(); + j->name = estrdup(target); + j->jid = jid; + } + } + 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; + } +} + +/* + * 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; + + jp = bsearch(name, jails_byname, njails, sizeof(struct cfjail *), + cmp_jailptr_name); + return jp ? *jp : NULL; +} + +/* + * Return the named jail's jid if it is running, and -1 if it isn't. + */ +static int +running_jid(const char *name, int flags) +{ + struct iovec jiov[2]; + char *ep; + int jid; + + if ((jid = strtol(name, &ep, 10)) && !*ep) { + *(const void **)&jiov[0].iov_base = "jid"; + jiov[0].iov_len = sizeof("jid"); + jiov[1].iov_base = &jid; + jiov[1].iov_len = sizeof(jid); + } else { + *(const void **)&jiov[0].iov_base = "name"; + jiov[0].iov_len = sizeof("name"); + jiov[1].iov_len = strlen(name) + 1; + jiov[1].iov_base = alloca(jiov[1].iov_len); + strcpy(jiov[1].iov_base, name); + } + return jail_get(jiov, 2, flags); +}