summaryrefslogtreecommitdiffstats
path: root/sys/kern/kern_jail.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/kern/kern_jail.c')
-rw-r--r--sys/kern/kern_jail.c1995
1 files changed, 1532 insertions, 463 deletions
diff --git a/sys/kern/kern_jail.c b/sys/kern/kern_jail.c
index 4f32248..7c0e7c9 100644
--- a/sys/kern/kern_jail.c
+++ b/sys/kern/kern_jail.c
@@ -1,6 +1,7 @@
/*-
* Copyright (c) 1999 Poul-Henning Kamp.
* Copyright (c) 2008 Bjoern A. Zeeb.
+ * Copyright (c) 2009 James Gritton.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -47,6 +48,7 @@ __FBSDID("$FreeBSD$");
#include <sys/jail.h>
#include <sys/lock.h>
#include <sys/mutex.h>
+#include <sys/osd.h>
#include <sys/sx.h>
#include <sys/namei.h>
#include <sys/mount.h>
@@ -56,7 +58,6 @@ __FBSDID("$FreeBSD$");
#include <sys/sysctl.h>
#include <sys/vnode.h>
#include <sys/vimage.h>
-#include <sys/osd.h>
#include <net/if.h>
#include <netinet/in.h>
#ifdef DDB
@@ -114,13 +115,15 @@ SYSCTL_INT(_security_jail, OID_AUTO, jail_max_af_ips, CTLFLAG_RW,
"Number of IP addresses a jail may have at most per address family");
/* allprison, lastprid, and prisoncount are protected by allprison_lock. */
-struct prisonlist allprison;
struct sx allprison_lock;
+SX_SYSINIT(allprison_lock, &allprison_lock, "allprison");
+struct prisonlist allprison = TAILQ_HEAD_INITIALIZER(allprison);
int lastprid = 0;
int prisoncount = 0;
-static void init_prison(void *);
+static int do_jail_attach(struct thread *td, struct prison *pr);
static void prison_complete(void *context, int pending);
+static void prison_deref(struct prison *pr, int flags);
#ifdef INET
static int _prison_check_ip4(struct prison *pr, struct in_addr *ia);
#endif
@@ -129,15 +132,12 @@ static int _prison_check_ip6(struct prison *pr, struct in6_addr *ia6);
#endif
static int sysctl_jail_list(SYSCTL_HANDLER_ARGS);
-static void
-init_prison(void *data __unused)
-{
-
- sx_init(&allprison_lock, "allprison");
- LIST_INIT(&allprison);
-}
-
-SYSINIT(prison, SI_SUB_INTRINSIC, SI_ORDER_ANY, init_prison, NULL);
+/* Flags for prison_deref */
+#define PD_DEREF 0x01
+#define PD_DEUREF 0x02
+#define PD_LOCKED 0x04
+#define PD_LIST_SLOCKED 0x08
+#define PD_LIST_XLOCKED 0x10
#ifdef INET
static int
@@ -187,411 +187,1279 @@ qcmp_v6(const void *ip1, const void *ip2)
}
#endif
-#if defined(INET) || defined(INET6)
-static int
-prison_check_conflicting_ips(struct prison *p)
+/*
+ * struct jail_args {
+ * struct jail *jail;
+ * };
+ */
+int
+jail(struct thread *td, struct jail_args *uap)
{
- struct prison *pr;
- int i;
+ struct iovec optiov[10];
+ struct uio opt;
+ char *u_path, *u_hostname, *u_name;
+#ifdef INET
+ struct in_addr *u_ip4;
+#endif
+#ifdef INET6
+ struct in6_addr *u_ip6;
+#endif
+ uint32_t version;
+ int error;
- sx_assert(&allprison_lock, SX_LOCKED);
+ error = copyin(uap->jail, &version, sizeof(uint32_t));
+ if (error)
+ return (error);
- if (p->pr_ip4s == 0 && p->pr_ip6s == 0)
- return (0);
+ switch (version) {
+ case 0:
+ {
+ /* FreeBSD single IPv4 jails. */
+ struct jail_v0 j0;
- LIST_FOREACH(pr, &allprison, pr_list) {
- /*
- * Skip 'dying' prisons to avoid problems when
- * restarting multi-IP jails.
- */
- if (pr->pr_state == PRISON_STATE_DYING)
- continue;
+ error = copyin(uap->jail, &j0, sizeof(struct jail_v0));
+ if (error)
+ return (error);
+ u_path = malloc(MAXPATHLEN + MAXHOSTNAMELEN, M_TEMP, M_WAITOK);
+ u_hostname = u_path + MAXPATHLEN;
+ opt.uio_iov = optiov;
+ opt.uio_iovcnt = 4;
+ opt.uio_offset = -1;
+ opt.uio_resid = -1;
+ opt.uio_segflg = UIO_SYSSPACE;
+ opt.uio_rw = UIO_READ;
+ opt.uio_td = td;
+ optiov[0].iov_base = "path";
+ optiov[0].iov_len = sizeof("path");
+ optiov[1].iov_base = u_path;
+ error =
+ copyinstr(j0.path, u_path, MAXPATHLEN, &optiov[1].iov_len);
+ if (error) {
+ free(u_path, M_TEMP);
+ return (error);
+ }
+ optiov[2].iov_base = "host.hostname";
+ optiov[2].iov_len = sizeof("host.hostname");
+ optiov[3].iov_base = u_hostname;
+ error = copyinstr(j0.hostname, u_hostname, MAXHOSTNAMELEN,
+ &optiov[3].iov_len);
+ if (error) {
+ free(u_path, M_TEMP);
+ return (error);
+ }
+#ifdef INET
+ optiov[opt.uio_iovcnt].iov_base = "ip4.addr";
+ optiov[opt.uio_iovcnt].iov_len = sizeof("ip4.addr");
+ opt.uio_iovcnt++;
+ optiov[opt.uio_iovcnt].iov_base = &j0.ip_number;
+ j0.ip_number = htonl(j0.ip_number);
+ optiov[opt.uio_iovcnt].iov_len = sizeof(j0.ip_number);
+ opt.uio_iovcnt++;
+#endif
+ break;
+ }
+ case 1:
/*
- * We permit conflicting IPs if there is no
- * more than 1 IP on eeach jail.
- * In case there is one duplicate on a jail with
- * more than one IP stop checking and return error.
+ * Version 1 was used by multi-IPv4 jail implementations
+ * that never made it into the official kernel.
*/
+ return (EINVAL);
+
+ case 2: /* JAIL_API_VERSION */
+ {
+ /* FreeBSD multi-IPv4/IPv6,noIP jails. */
+ struct jail j;
+ size_t tmplen;
+
+ error = copyin(uap->jail, &j, sizeof(struct jail));
+ if (error)
+ return (error);
+ tmplen = MAXPATHLEN + MAXHOSTNAMELEN + MAXHOSTNAMELEN;
#ifdef INET
- if ((p->pr_ip4s >= 1 && pr->pr_ip4s > 1) ||
- (p->pr_ip4s > 1 && pr->pr_ip4s >= 1)) {
- for (i = 0; i < p->pr_ip4s; i++) {
- if (_prison_check_ip4(pr, &p->pr_ip4[i]) == 0)
- return (EINVAL);
+ if (j.ip4s > jail_max_af_ips)
+ return (EINVAL);
+ tmplen += j.ip4s * sizeof(struct in_addr);
+#else
+ if (j.ip4s > 0)
+ return (EINVAL);
+#endif
+#ifdef INET6
+ if (j.ip6s > jail_max_af_ips)
+ return (EINVAL);
+ tmplen += j.ip6s * sizeof(struct in6_addr);
+#else
+ if (j.ip6s > 0)
+ return (EINVAL);
+#endif
+ u_path = malloc(tmplen, M_TEMP, M_WAITOK);
+ u_hostname = u_path + MAXPATHLEN;
+ u_name = u_hostname + MAXHOSTNAMELEN;
+#ifdef INET
+ u_ip4 = (struct in_addr *)(u_name + MAXHOSTNAMELEN);
+#endif
+#ifdef INET6
+#ifdef INET
+ u_ip6 = (struct in6_addr *)(u_ip4 + j.ip4s);
+#else
+ u_ip6 = (struct in6_addr *)(u_name + MAXHOSTNAMELEN);
+#endif
+#endif
+ opt.uio_iov = optiov;
+ opt.uio_iovcnt = 4;
+ opt.uio_offset = -1;
+ opt.uio_resid = -1;
+ opt.uio_segflg = UIO_SYSSPACE;
+ opt.uio_rw = UIO_READ;
+ opt.uio_td = td;
+ optiov[0].iov_base = "path";
+ optiov[0].iov_len = sizeof("path");
+ optiov[1].iov_base = u_path;
+ error =
+ copyinstr(j.path, u_path, MAXPATHLEN, &optiov[1].iov_len);
+ if (error) {
+ free(u_path, M_TEMP);
+ return (error);
+ }
+ optiov[2].iov_base = "host.hostname";
+ optiov[2].iov_len = sizeof("host.hostname");
+ optiov[3].iov_base = u_hostname;
+ error = copyinstr(j.hostname, u_hostname, MAXHOSTNAMELEN,
+ &optiov[3].iov_len);
+ if (error) {
+ free(u_path, M_TEMP);
+ return (error);
+ }
+ if (j.jailname != NULL) {
+ optiov[opt.uio_iovcnt].iov_base = "name";
+ optiov[opt.uio_iovcnt].iov_len = sizeof("name");
+ opt.uio_iovcnt++;
+ optiov[opt.uio_iovcnt].iov_base = u_name;
+ error = copyinstr(j.jailname, u_name, MAXHOSTNAMELEN,
+ &optiov[opt.uio_iovcnt].iov_len);
+ if (error) {
+ free(u_path, M_TEMP);
+ return (error);
}
+ opt.uio_iovcnt++;
}
+#ifdef INET
+ optiov[opt.uio_iovcnt].iov_base = "ip4.addr";
+ optiov[opt.uio_iovcnt].iov_len = sizeof("ip4.addr");
+ opt.uio_iovcnt++;
+ optiov[opt.uio_iovcnt].iov_base = u_ip4;
+ optiov[opt.uio_iovcnt].iov_len =
+ j.ip4s * sizeof(struct in_addr);
+ error = copyin(j.ip4, u_ip4, optiov[opt.uio_iovcnt].iov_len);
+ if (error) {
+ free(u_path, M_TEMP);
+ return (error);
+ }
+ opt.uio_iovcnt++;
#endif
#ifdef INET6
- if ((p->pr_ip6s >= 1 && pr->pr_ip6s > 1) ||
- (p->pr_ip6s > 1 && pr->pr_ip6s >= 1)) {
- for (i = 0; i < p->pr_ip6s; i++) {
- if (_prison_check_ip6(pr, &p->pr_ip6[i]) == 0)
- return (EINVAL);
- }
+ optiov[opt.uio_iovcnt].iov_base = "ip6.addr";
+ optiov[opt.uio_iovcnt].iov_len = sizeof("ip6.addr");
+ opt.uio_iovcnt++;
+ optiov[opt.uio_iovcnt].iov_base = u_ip6;
+ optiov[opt.uio_iovcnt].iov_len =
+ j.ip6s * sizeof(struct in6_addr);
+ error = copyin(j.ip6, u_ip6, optiov[opt.uio_iovcnt].iov_len);
+ if (error) {
+ free(u_path, M_TEMP);
+ return (error);
}
+ opt.uio_iovcnt++;
#endif
+ break;
}
- return (0);
+ default:
+ /* Sci-Fi jails are not supported, sorry. */
+ return (EINVAL);
+ }
+ error = kern_jail_set(td, &opt, JAIL_CREATE | JAIL_ATTACH);
+ free(u_path, M_TEMP);
+ return (error);
}
-static int
-jail_copyin_ips(struct jail *j)
+/*
+ * struct jail_set_args {
+ * struct iovec *iovp;
+ * unsigned int iovcnt;
+ * int flags;
+ * };
+ */
+int
+jail_set(struct thread *td, struct jail_set_args *uap)
{
+ struct uio *auio;
+ int error;
+
+ /* Check that we have an even number of iovecs. */
+ if (uap->iovcnt & 1)
+ return (EINVAL);
+
+ error = copyinuio(uap->iovp, uap->iovcnt, &auio);
+ if (error)
+ return (error);
+ error = kern_jail_set(td, auio, uap->flags);
+ free(auio, M_IOV);
+ return (error);
+}
+
+int
+kern_jail_set(struct thread *td, struct uio *optuio, int flags)
+{
+ struct nameidata nd;
#ifdef INET
struct in_addr *ip4;
#endif
#ifdef INET6
struct in6_addr *ip6;
#endif
- int error, i;
+ struct vfsopt *opt;
+ struct vfsoptlist *opts;
+ struct prison *pr, *deadpr, *tpr;
+ struct vnode *root;
+ char *errmsg, *host, *name, *p, *path;
+ void *op;
+ int created, cuflags, error, errmsg_len, errmsg_pos;
+ int gotslevel, jid, len;
+ int slevel, vfslocked;
+#if defined(INET) || defined(INET6)
+ int ii;
+#endif
+#ifdef INET
+ int ip4s;
+#endif
+#ifdef INET6
+ int ip6s;
+#endif
+ unsigned pr_flags, ch_flags;
+ char numbuf[12];
+
+ error = priv_check(td, PRIV_JAIL_SET);
+ if (!error && (flags & JAIL_ATTACH))
+ error = priv_check(td, PRIV_JAIL_ATTACH);
+ if (error)
+ return (error);
+ if (flags & ~JAIL_SET_MASK)
+ return (EINVAL);
/*
- * Copy in addresses, check for duplicate addresses and do some
- * simple 0 and broadcast checks. If users give other bogus addresses
- * it is their problem.
+ * Check all the parameters before committing to anything. Not all
+ * errors can be caught early, but we may as well try. Also, this
+ * takes care of some expensive stuff (path lookup) before getting
+ * the allprison lock.
*
- * IP addresses are all sorted but ip[0] to preserve the primary IP
- * address as given from userland. This special IP is used for
- * unbound outgoing connections as well for "loopback" traffic.
+ * XXX Jails are not filesystems, and jail parameters are not mount
+ * options. But it makes more sense to re-use the vfsopt code
+ * than duplicate it under a different name.
*/
+ error = vfs_buildopts(optuio, &opts);
+ if (error)
+ return (error);
#ifdef INET
ip4 = NULL;
#endif
#ifdef INET6
ip6 = NULL;
#endif
-#ifdef INET
- if (j->ip4s > 0) {
- ip4 = (struct in_addr *)malloc(j->ip4s * sizeof(struct in_addr),
- M_PRISON, M_WAITOK | M_ZERO);
- error = copyin(j->ip4, ip4, j->ip4s * sizeof(struct in_addr));
- if (error)
- goto e_free_ip;
- /* Sort all but the first IPv4 address. */
- if (j->ip4s > 1)
- qsort((ip4 + 1), j->ip4s - 1,
- sizeof(struct in_addr), qcmp_v4);
+ error = vfs_copyopt(opts, "jid", &jid, sizeof(jid));
+ if (error == ENOENT)
+ jid = 0;
+ else if (error != 0)
+ goto done_free;
+
+ error = vfs_copyopt(opts, "securelevel", &slevel, sizeof(slevel));
+ if (error == ENOENT)
+ gotslevel = 0;
+ else if (error != 0)
+ goto done_free;
+ else
+ gotslevel = 1;
+
+ pr_flags = ch_flags = 0;
+ vfs_flagopt(opts, "persist", &pr_flags, PR_PERSIST);
+ vfs_flagopt(opts, "nopersist", &ch_flags, PR_PERSIST);
+ ch_flags |= pr_flags;
+ if ((flags & (JAIL_CREATE | JAIL_UPDATE | JAIL_ATTACH)) == JAIL_CREATE
+ && !(pr_flags & PR_PERSIST)) {
+ error = EINVAL;
+ vfs_opterror(opts, "new jail must persist or attach");
+ goto done_errmsg;
+ }
+
+ error = vfs_getopt(opts, "name", (void **)&name, &len);
+ if (error == ENOENT)
+ name = NULL;
+ else if (error != 0)
+ goto done_free;
+ else {
+ if (len == 0 || name[len - 1] != '\0') {
+ error = EINVAL;
+ goto done_free;
+ }
+ if (len > MAXHOSTNAMELEN) {
+ error = ENAMETOOLONG;
+ goto done_free;
+ }
+ }
+
+ error = vfs_getopt(opts, "host.hostname", (void **)&host, &len);
+ if (error == ENOENT)
+ host = NULL;
+ else if (error != 0)
+ goto done_free;
+ else {
+ if (len == 0 || host[len - 1] != '\0') {
+ error = EINVAL;
+ goto done_free;
+ }
+ if (len > MAXHOSTNAMELEN) {
+ error = ENAMETOOLONG;
+ goto done_free;
+ }
+ }
+
+#ifdef INET
+ error = vfs_getopt(opts, "ip4.addr", &op, &ip4s);
+ if (error == ENOENT)
+ ip4s = -1;
+ else if (error != 0)
+ goto done_free;
+ else if (ip4s & (sizeof(*ip4) - 1)) {
+ error = EINVAL;
+ goto done_free;
+ } else if (ip4s > 0) {
+ ip4s /= sizeof(*ip4);
+ if (ip4s > jail_max_af_ips) {
+ error = EINVAL;
+ vfs_opterror(opts, "too many IPv4 addresses");
+ goto done_errmsg;
+ }
+ ip4 = malloc(ip4s * sizeof(*ip4), M_PRISON, M_WAITOK);
+ bcopy(op, ip4, ip4s * sizeof(*ip4));
+ /*
+ * IP addresses are all sorted but ip[0] to preserve the
+ * primary IP address as given from userland. This special IP
+ * is used for unbound outgoing connections as well for
+ * "loopback" traffic.
+ */
+ if (ip4s > 1)
+ qsort(ip4 + 1, ip4s - 1, sizeof(*ip4), qcmp_v4);
/*
- * We do not have to care about byte order for these checks
- * so we will do them in NBO.
+ * Check for duplicate addresses and do some simple zero and
+ * broadcast checks. If users give other bogus addresses it is
+ * their problem.
+ *
+ * We do not have to care about byte order for these checks so
+ * we will do them in NBO.
*/
- for (i=0; i<j->ip4s; i++) {
- if (ip4[i].s_addr == htonl(INADDR_ANY) ||
- ip4[i].s_addr == htonl(INADDR_BROADCAST)) {
+ for (ii = 0; ii < ip4s; ii++) {
+ if (ip4[ii].s_addr == INADDR_ANY ||
+ ip4[ii].s_addr == INADDR_BROADCAST) {
error = EINVAL;
- goto e_free_ip;
+ goto done_free;
}
- if ((i+1) < j->ip4s &&
- (ip4[0].s_addr == ip4[i+1].s_addr ||
- ip4[i].s_addr == ip4[i+1].s_addr)) {
+ if ((ii+1) < ip4s &&
+ (ip4[0].s_addr == ip4[ii+1].s_addr ||
+ ip4[ii].s_addr == ip4[ii+1].s_addr)) {
error = EINVAL;
- goto e_free_ip;
+ goto done_free;
}
}
-
- j->ip4 = ip4;
- } else
- j->ip4 = NULL;
+ }
#endif
+
#ifdef INET6
- if (j->ip6s > 0) {
- ip6 = (struct in6_addr *)malloc(j->ip6s * sizeof(struct in6_addr),
- M_PRISON, M_WAITOK | M_ZERO);
- error = copyin(j->ip6, ip6, j->ip6s * sizeof(struct in6_addr));
- if (error)
- goto e_free_ip;
- /* Sort all but the first IPv6 address. */
- if (j->ip6s > 1)
- qsort((ip6 + 1), j->ip6s - 1,
- sizeof(struct in6_addr), qcmp_v6);
- for (i=0; i<j->ip6s; i++) {
- if (IN6_IS_ADDR_UNSPECIFIED(&ip6[i])) {
+ error = vfs_getopt(opts, "ip6.addr", &op, &ip6s);
+ if (error == ENOENT)
+ ip6s = -1;
+ else if (error != 0)
+ goto done_free;
+ else if (ip6s & (sizeof(*ip6) - 1)) {
+ error = EINVAL;
+ goto done_free;
+ } else if (ip6s > 0) {
+ ip6s /= sizeof(*ip6);
+ if (ip6s > jail_max_af_ips) {
+ error = EINVAL;
+ vfs_opterror(opts, "too many IPv6 addresses");
+ goto done_errmsg;
+ }
+ ip6 = malloc(ip6s * sizeof(*ip6), M_PRISON, M_WAITOK);
+ bcopy(op, ip6, ip6s * sizeof(*ip6));
+ if (ip6s > 1)
+ qsort(ip6 + 1, ip6s - 1, sizeof(*ip6), qcmp_v6);
+ for (ii = 0; ii < ip6s; ii++) {
+ if (IN6_IS_ADDR_UNSPECIFIED(&ip6[0])) {
error = EINVAL;
- goto e_free_ip;
+ goto done_free;
}
- if ((i+1) < j->ip6s &&
- (IN6_ARE_ADDR_EQUAL(&ip6[0], &ip6[i+1]) ||
- IN6_ARE_ADDR_EQUAL(&ip6[i], &ip6[i+1]))) {
+ if ((ii+1) < ip6s &&
+ (IN6_ARE_ADDR_EQUAL(&ip6[0], &ip6[ii+1]) ||
+ IN6_ARE_ADDR_EQUAL(&ip6[ii], &ip6[ii+1])))
+ {
error = EINVAL;
- goto e_free_ip;
+ goto done_free;
}
}
-
- j->ip6 = ip6;
- } else
- j->ip6 = NULL;
+ }
#endif
- return (0);
-e_free_ip:
-#ifdef INET6
- free(ip6, M_PRISON);
-#endif
-#ifdef INET
- free(ip4, M_PRISON);
-#endif
- return (error);
-}
-#endif /* INET || INET6 */
+ root = NULL;
+ error = vfs_getopt(opts, "path", (void **)&path, &len);
+ if (error == ENOENT)
+ path = NULL;
+ else if (error != 0)
+ goto done_free;
+ else {
+ if (flags & JAIL_UPDATE) {
+ error = EINVAL;
+ vfs_opterror(opts,
+ "path cannot be changed after creation");
+ goto done_errmsg;
+ }
+ if (len == 0 || path[len - 1] != '\0') {
+ error = EINVAL;
+ goto done_free;
+ }
+ if (len > MAXPATHLEN) {
+ error = ENAMETOOLONG;
+ goto done_free;
+ }
+ if (len < 2 || (len == 2 && path[0] == '/'))
+ path = NULL;
+ else {
+ NDINIT(&nd, LOOKUP, MPSAFE | FOLLOW, UIO_SYSSPACE,
+ path, td);
+ error = namei(&nd);
+ if (error)
+ goto done_free;
+ vfslocked = NDHASGIANT(&nd);
+ root = nd.ni_vp;
+ NDFREE(&nd, NDF_ONLY_PNBUF);
+ if (root->v_type != VDIR) {
+ error = ENOTDIR;
+ vrele(root);
+ VFS_UNLOCK_GIANT(vfslocked);
+ goto done_free;
+ }
+ VFS_UNLOCK_GIANT(vfslocked);
+ }
+ }
-static int
-jail_handle_ips(struct jail *j)
-{
-#if defined(INET) || defined(INET6)
- int error;
-#endif
+ /*
+ * Grab the allprison lock before letting modules check their
+ * parameters. Once we have it, do not let go so we'll have a
+ * consistent view of the OSD list.
+ */
+ sx_xlock(&allprison_lock);
+ error = osd_jail_call(NULL, PR_METHOD_CHECK, opts);
+ if (error)
+ goto done_unlock_list;
+
+ /* By now, all parameters should have been noted. */
+ TAILQ_FOREACH(opt, opts, link) {
+ if (!opt->seen && strcmp(opt->name, "errmsg")) {
+ error = EINVAL;
+ vfs_opterror(opts, "unknown parameter: %s", opt->name);
+ goto done_unlock_list;
+ }
+ }
/*
- * Finish conversion for older versions, copyin and setup IPs.
+ * See if we are creating a new record or updating an existing one.
+ * This abuses the file error codes ENOENT and EEXIST.
*/
- switch (j->version) {
- case 0:
- {
-#ifdef INET
- /* FreeBSD single IPv4 jails. */
- struct in_addr *ip4;
+ cuflags = flags & (JAIL_CREATE | JAIL_UPDATE);
+ if (!cuflags) {
+ error = EINVAL;
+ vfs_opterror(opts, "no valid operation (create or update)");
+ goto done_unlock_list;
+ }
+ pr = NULL;
+ if (jid != 0) {
+ /* See if a requested jid already exists. */
+ if (jid < 0) {
+ error = EINVAL;
+ vfs_opterror(opts, "negative jid");
+ goto done_unlock_list;
+ }
+ pr = prison_find(jid);
+ if (pr != NULL) {
+ /* Create: jid must not exist. */
+ if (cuflags == JAIL_CREATE) {
+ mtx_unlock(&pr->pr_mtx);
+ error = EEXIST;
+ vfs_opterror(opts, "jail %d already exists",
+ jid);
+ goto done_unlock_list;
+ }
+ if (pr->pr_uref == 0) {
+ if (!(flags & JAIL_DYING)) {
+ mtx_unlock(&pr->pr_mtx);
+ error = ENOENT;
+ vfs_opterror(opts, "jail %d is dying",
+ jid);
+ goto done_unlock_list;
+ } else if ((flags & JAIL_ATTACH) ||
+ (pr_flags & PR_PERSIST)) {
+ /*
+ * A dying jail might be resurrected
+ * (via attach or persist), but first
+ * it must determine if another jail
+ * has claimed its name. Accomplish
+ * this by implicitly re-setting the
+ * name.
+ */
+ if (name == NULL)
+ name = pr->pr_name;
+ }
+ }
+ }
+ if (pr == NULL) {
+ /* Update: jid must exist. */
+ if (cuflags == JAIL_UPDATE) {
+ error = ENOENT;
+ vfs_opterror(opts, "jail %d not found", jid);
+ goto done_unlock_list;
+ }
+ }
+ }
+ /*
+ * If the caller provided a name, look for a jail by that name.
+ * This has different semantics for creates and updates keyed by jid
+ * (where the name must not already exist in a different jail),
+ * and updates keyed by the name itself (where the name must exist
+ * because that is the jail being updated).
+ */
+ if (name != NULL) {
+ if (name[0] != '\0') {
+ deadpr = NULL;
+ name_again:
+ TAILQ_FOREACH(tpr, &allprison, pr_list) {
+ if (tpr != pr && tpr->pr_ref > 0 &&
+ !strcmp(tpr->pr_name, name)) {
+ if (pr == NULL &&
+ cuflags != JAIL_CREATE) {
+ mtx_lock(&tpr->pr_mtx);
+ if (tpr->pr_ref > 0) {
+ /*
+ * Use this jail
+ * for updates.
+ */
+ if (tpr->pr_uref > 0) {
+ pr = tpr;
+ break;
+ }
+ deadpr = tpr;
+ }
+ mtx_unlock(&tpr->pr_mtx);
+ } else if (tpr->pr_uref > 0) {
+ /*
+ * Create, or update(jid):
+ * name must not exist in an
+ * active jail.
+ */
+ error = EEXIST;
+ if (pr != NULL)
+ mtx_unlock(&pr->pr_mtx);
+ vfs_opterror(opts,
+ "jail \"%s\" already exists",
+ name);
+ goto done_unlock_list;
+ }
+ }
+ }
+ /* If no active jail is found, use a dying one. */
+ if (deadpr != NULL && pr == NULL) {
+ if (flags & JAIL_DYING) {
+ mtx_lock(&deadpr->pr_mtx);
+ if (deadpr->pr_ref == 0) {
+ mtx_unlock(&deadpr->pr_mtx);
+ goto name_again;
+ }
+ pr = deadpr;
+ } else if (cuflags == JAIL_UPDATE) {
+ error = ENOENT;
+ vfs_opterror(opts,
+ "jail \"%s\" is dying", name);
+ goto done_unlock_list;
+ }
+ }
+ /* Update: name must exist if no jid. */
+ else if (cuflags == JAIL_UPDATE && pr == NULL) {
+ error = ENOENT;
+ vfs_opterror(opts, "jail \"%s\" not found",
+ name);
+ goto done_unlock_list;
+ }
+ }
+ }
+ /* Update: must provide a jid or name. */
+ else if (cuflags == JAIL_UPDATE && pr == NULL) {
+ error = ENOENT;
+ vfs_opterror(opts, "update specified no jail");
+ goto done_unlock_list;
+ }
- if (j->ip4s == INADDR_ANY || j->ip4s == INADDR_BROADCAST)
- return (EINVAL);
- ip4 = (struct in_addr *)malloc(sizeof(struct in_addr),
- M_PRISON, M_WAITOK | M_ZERO);
+ /* If there's no prison to update, create a new one and link it in. */
+ if (pr == NULL) {
+ created = 1;
+ pr = malloc(sizeof(*pr), M_PRISON, M_WAITOK | M_ZERO);
+ if (jid == 0) {
+ /* Find the next free jid. */
+ jid = lastprid + 1;
+ findnext:
+ if (jid == JAIL_MAX)
+ jid = 1;
+ TAILQ_FOREACH(tpr, &allprison, pr_list) {
+ if (tpr->pr_id < jid)
+ continue;
+ if (tpr->pr_id > jid || tpr->pr_ref == 0) {
+ TAILQ_INSERT_BEFORE(tpr, pr, pr_list);
+ break;
+ }
+ if (jid == lastprid) {
+ error = EAGAIN;
+ vfs_opterror(opts,
+ "no available jail IDs");
+ free(pr, M_PRISON);
+ goto done_unlock_list;
+ }
+ jid++;
+ goto findnext;
+ }
+ lastprid = jid;
+ } else {
+ /*
+ * The jail already has a jid (that did not yet exist),
+ * so just find where to insert it.
+ */
+ TAILQ_FOREACH(tpr, &allprison, pr_list)
+ if (tpr->pr_id >= jid) {
+ TAILQ_INSERT_BEFORE(tpr, pr, pr_list);
+ break;
+ }
+ }
+ if (tpr == NULL)
+ TAILQ_INSERT_TAIL(&allprison, pr, pr_list);
+ prisoncount++;
+
+ pr->pr_id = jid;
+ if (name == NULL)
+ name = "";
+ if (path == NULL) {
+ path = "/";
+ root = rootvnode;
+ vref(root);
+ }
+
+ mtx_init(&pr->pr_mtx, "jail mutex", NULL, MTX_DEF);
/*
- * Jail version 0 still used HBO for the IPv4 address.
+ * Allocate a dedicated cpuset for each jail.
+ * Unlike other initial settings, this may return an erorr.
*/
- ip4->s_addr = htonl(j->ip4s);
- j->ip4s = 1;
- j->ip4 = ip4;
- break;
-#else
- return (EINVAL);
-#endif
- }
+ error = cpuset_create_root(td, &pr->pr_cpuset);
+ if (error) {
+ prison_deref(pr, PD_LIST_XLOCKED);
+ goto done_releroot;
+ }
- case 1:
+ mtx_lock(&pr->pr_mtx);
/*
- * Version 1 was used by multi-IPv4 jail implementations
- * that never made it into the official kernel.
- * We should never hit this here; jail() should catch it.
+ * New prisons do not yet have a reference, because we do not
+ * want other to see the incomplete prison once the
+ * allprison_lock is downgraded.
*/
- return (EINVAL);
+ } else {
+ created = 0;
+ /*
+ * Grab a reference for existing prisons, to ensure they
+ * continue to exist for the duration of the call.
+ */
+ pr->pr_ref++;
+ }
- case 2: /* JAIL_API_VERSION */
- /* FreeBSD multi-IPv4/IPv6,noIP jails. */
+ /* Do final error checking before setting anything. */
+ error = 0;
#if defined(INET) || defined(INET6)
+ if (
#ifdef INET
- if (j->ip4s > jail_max_af_ips)
- return (EINVAL);
-#else
- if (j->ip4s != 0)
- return (EINVAL);
+ ip4s > 0
+#ifdef INET6
+ ||
+#endif
#endif
#ifdef INET6
- if (j->ip6s > jail_max_af_ips)
- return (EINVAL);
-#else
- if (j->ip6s != 0)
- return (EINVAL);
+ ip6s > 0
#endif
- error = jail_copyin_ips(j);
- if (error)
- return (error);
+ )
+ /*
+ * Check for conflicting IP addresses. We permit them if there
+ * is no more than 1 IP on each jail. If there is a duplicate
+ * on a jail with more than one IP stop checking and return
+ * error.
+ */
+ TAILQ_FOREACH(tpr, &allprison, pr_list) {
+ if (tpr == pr || tpr->pr_uref == 0)
+ continue;
+#ifdef INET
+ if ((ip4s > 0 && tpr->pr_ip4s > 1) ||
+ (ip4s > 1 && tpr->pr_ip4s > 0))
+ for (ii = 0; ii < ip4s; ii++)
+ if (_prison_check_ip4(tpr,
+ &ip4[ii]) == 0) {
+ error = EINVAL;
+ vfs_opterror(opts,
+ "IPv4 addresses clash");
+ goto done_deref_locked;
+ }
#endif
- break;
+#ifdef INET6
+ if ((ip6s > 0 && tpr->pr_ip6s > 1) ||
+ (ip6s > 1 && tpr->pr_ip6s > 0))
+ for (ii = 0; ii < ip6s; ii++)
+ if (_prison_check_ip6(tpr,
+ &ip6[ii]) == 0) {
+ error = EINVAL;
+ vfs_opterror(opts,
+ "IPv6 addresses clash");
+ goto done_deref_locked;
+ }
+#endif
+ }
+#endif
+ if (error == 0 && name != NULL) {
+ /* Give a default name of the jid. */
+ if (name[0] == '\0')
+ snprintf(name = numbuf, sizeof(numbuf), "%d", jid);
+ else if (strtoul(name, &p, 10) != jid && *p == '\0') {
+ error = EINVAL;
+ vfs_opterror(opts, "name cannot be numeric");
+ }
+ }
+ if (error) {
+ done_deref_locked:
+ /*
+ * Some parameter had an error so do not set anything.
+ * If this is a new jail, it will go away without ever
+ * having been seen.
+ */
+ prison_deref(pr, created
+ ? PD_LOCKED | PD_LIST_XLOCKED
+ : PD_DEREF | PD_LOCKED | PD_LIST_XLOCKED);
+ goto done_releroot;
+ }
- default:
- /* Sci-Fi jails are not supported, sorry. */
- return (EINVAL);
+ /* Set the parameters of the prison. */
+#ifdef INET
+ if (ip4s >= 0) {
+ pr->pr_ip4s = ip4s;
+ free(pr->pr_ip4, M_PRISON);
+ pr->pr_ip4 = ip4;
+ ip4 = NULL;
}
+#endif
+#ifdef INET6
+ if (ip6s >= 0) {
+ pr->pr_ip6s = ip6s;
+ free(pr->pr_ip6, M_PRISON);
+ pr->pr_ip6 = ip6;
+ ip6 = NULL;
+ }
+#endif
+ if (gotslevel)
+ pr->pr_securelevel = slevel;
+ if (name != NULL)
+ strlcpy(pr->pr_name, name, sizeof(pr->pr_name));
+ if (path != NULL) {
+ strlcpy(pr->pr_path, path, sizeof(pr->pr_path));
+ pr->pr_root = root;
+ }
+ if (host != NULL)
+ strlcpy(pr->pr_host, host, sizeof(pr->pr_host));
+ /*
+ * Persistent prisons get an extra reference, and prisons losing their
+ * persist flag lose that reference. Only do this for existing prisons
+ * for now, so new ones will remain unseen until after the module
+ * handlers have completed.
+ */
+ if (!created && (ch_flags & PR_PERSIST & (pr_flags ^ pr->pr_flags))) {
+ if (pr_flags & PR_PERSIST) {
+ pr->pr_ref++;
+ pr->pr_uref++;
+ } else {
+ pr->pr_ref--;
+ pr->pr_uref--;
+ }
+ }
+ pr->pr_flags = (pr->pr_flags & ~ch_flags) | pr_flags;
+ mtx_unlock(&pr->pr_mtx);
- return (0);
-}
+ /* Let the modules do their work. */
+ sx_downgrade(&allprison_lock);
+ if (created) {
+ error = osd_jail_call(pr, PR_METHOD_CREATE, opts);
+ if (error) {
+ prison_deref(pr, PD_LIST_SLOCKED);
+ goto done_errmsg;
+ }
+ }
+ error = osd_jail_call(pr, PR_METHOD_SET, opts);
+ if (error) {
+ prison_deref(pr, created
+ ? PD_LIST_SLOCKED
+ : PD_DEREF | PD_LIST_SLOCKED);
+ goto done_errmsg;
+ }
+ /* Attach this process to the prison if requested. */
+ if (flags & JAIL_ATTACH) {
+ mtx_lock(&pr->pr_mtx);
+ error = do_jail_attach(td, pr);
+ if (error) {
+ vfs_opterror(opts, "attach failed");
+ if (!created)
+ prison_deref(pr, PD_DEREF);
+ goto done_errmsg;
+ }
+ }
+
+ /*
+ * Now that it is all there, drop the temporary reference from existing
+ * prisons. Or add a reference to newly created persistent prisons
+ * (which was not done earlier so that the prison would not be publicly
+ * visible).
+ */
+ if (!created) {
+ prison_deref(pr, (flags & JAIL_ATTACH)
+ ? PD_DEREF
+ : PD_DEREF | PD_LIST_SLOCKED);
+ } else {
+ if (pr_flags & PR_PERSIST) {
+ mtx_lock(&pr->pr_mtx);
+ pr->pr_ref++;
+ pr->pr_uref++;
+ mtx_unlock(&pr->pr_mtx);
+ }
+ if (!(flags & JAIL_ATTACH))
+ sx_sunlock(&allprison_lock);
+ }
+ td->td_retval[0] = pr->pr_id;
+ goto done_errmsg;
+
+ done_unlock_list:
+ sx_xunlock(&allprison_lock);
+ done_releroot:
+ if (root != NULL) {
+ vfslocked = VFS_LOCK_GIANT(root->v_mount);
+ vrele(root);
+ VFS_UNLOCK_GIANT(vfslocked);
+ }
+ done_errmsg:
+ if (error) {
+ vfs_getopt(opts, "errmsg", (void **)&errmsg, &errmsg_len);
+ if (errmsg_len > 0) {
+ errmsg_pos = 2 * vfs_getopt_pos(opts, "errmsg") + 1;
+ if (errmsg_pos > 0) {
+ if (optuio->uio_segflg == UIO_SYSSPACE)
+ bcopy(errmsg,
+ optuio->uio_iov[errmsg_pos].iov_base,
+ errmsg_len);
+ else
+ copyout(errmsg,
+ optuio->uio_iov[errmsg_pos].iov_base,
+ errmsg_len);
+ }
+ }
+ }
+ done_free:
+#ifdef INET
+ free(ip4, M_PRISON);
+#endif
+#ifdef INET6
+ free(ip6, M_PRISON);
+#endif
+ vfs_freeopts(opts);
+ return (error);
+}
/*
- * struct jail_args {
- * struct jail *jail;
- * };
+ * Sysctl nodes to describe jail parameters. Maximum length of string
+ * parameters is returned in the string itself, and the other parameters
+ * exist merely to make themselves and their types known.
*/
+SYSCTL_NODE(_security_jail, OID_AUTO, param, CTLFLAG_RW, 0,
+ "Jail parameters");
+
int
-jail(struct thread *td, struct jail_args *uap)
+sysctl_jail_param(SYSCTL_HANDLER_ARGS)
{
- uint32_t version;
- int error;
- struct jail j;
-
- error = copyin(uap->jail, &version, sizeof(uint32_t));
- if (error)
- return (error);
+ int i;
+ long l;
+ size_t s;
+ char numbuf[12];
- switch (version) {
- case 0:
- /* FreeBSD single IPv4 jails. */
+ switch (oidp->oid_kind & CTLTYPE)
{
- struct jail_v0 j0;
-
- bzero(&j, sizeof(struct jail));
- error = copyin(uap->jail, &j0, sizeof(struct jail_v0));
- if (error)
- return (error);
- j.version = j0.version;
- j.path = j0.path;
- j.hostname = j0.hostname;
- j.ip4s = j0.ip_number;
- break;
+ case CTLTYPE_LONG:
+ case CTLTYPE_ULONG:
+ l = 0;
+#ifdef SCTL_MASK32
+ if (!(req->flags & SCTL_MASK32))
+#endif
+ return (SYSCTL_OUT(req, &l, sizeof(l)));
+ case CTLTYPE_INT:
+ case CTLTYPE_UINT:
+ i = 0;
+ return (SYSCTL_OUT(req, &i, sizeof(i)));
+ case CTLTYPE_STRING:
+ snprintf(numbuf, sizeof(numbuf), "%d", arg2);
+ return
+ (sysctl_handle_string(oidp, numbuf, sizeof(numbuf), req));
+ case CTLTYPE_STRUCT:
+ s = (size_t)arg2;
+ return (SYSCTL_OUT(req, &s, sizeof(s)));
}
+ return (0);
+}
- case 1:
- /*
- * Version 1 was used by multi-IPv4 jail implementations
- * that never made it into the official kernel.
- */
- return (EINVAL);
+SYSCTL_JAIL_PARAM(, jid, CTLTYPE_INT | CTLFLAG_RD, "I", "Jail ID");
+SYSCTL_JAIL_PARAM_STRING(, name, CTLFLAG_RW, MAXHOSTNAMELEN, "Jail name");
+SYSCTL_JAIL_PARAM(, cpuset, CTLTYPE_INT | CTLFLAG_RD, "I", "Jail cpuset ID");
+SYSCTL_JAIL_PARAM_STRING(, path, CTLFLAG_RD, MAXPATHLEN, "Jail root path");
+SYSCTL_JAIL_PARAM(, securelevel, CTLTYPE_INT | CTLFLAG_RW,
+ "I", "Jail secure level");
+SYSCTL_JAIL_PARAM(, persist, CTLTYPE_INT | CTLFLAG_RW,
+ "B", "Jail persistence");
+SYSCTL_JAIL_PARAM(, dying, CTLTYPE_INT | CTLFLAG_RD,
+ "B", "Jail is in the process of shutting down");
+
+SYSCTL_JAIL_PARAM_NODE(host, "Jail host info");
+SYSCTL_JAIL_PARAM_STRING(_host, hostname, CTLFLAG_RW, MAXHOSTNAMELEN,
+ "Jail hostname");
- case 2: /* JAIL_API_VERSION */
- /* FreeBSD multi-IPv4/IPv6,noIP jails. */
- error = copyin(uap->jail, &j, sizeof(struct jail));
- if (error)
- return (error);
- break;
+#ifdef INET
+SYSCTL_JAIL_PARAM_NODE(ip4, "Jail IPv4 address virtualization");
+SYSCTL_JAIL_PARAM_STRUCT(_ip4, addr, CTLFLAG_RW, sizeof(struct in_addr),
+ "S,in_addr,a", "Jail IPv4 addresses");
+#endif
+#ifdef INET6
+SYSCTL_JAIL_PARAM_NODE(ip6, "Jail IPv6 address virtualization");
+SYSCTL_JAIL_PARAM_STRUCT(_ip6, addr, CTLFLAG_RW, sizeof(struct in6_addr),
+ "S,in6_addr,a", "Jail IPv6 addresses");
+#endif
- default:
- /* Sci-Fi jails are not supported, sorry. */
+
+/*
+ * struct jail_get_args {
+ * struct iovec *iovp;
+ * unsigned int iovcnt;
+ * int flags;
+ * };
+ */
+int
+jail_get(struct thread *td, struct jail_get_args *uap)
+{
+ struct uio *auio;
+ int error;
+
+ /* Check that we have an even number of iovecs. */
+ if (uap->iovcnt & 1)
return (EINVAL);
- }
- return (kern_jail(td, &j));
+
+ error = copyinuio(uap->iovp, uap->iovcnt, &auio);
+ if (error)
+ return (error);
+ error = kern_jail_get(td, auio, uap->flags);
+ if (error == 0)
+ error = copyout(auio->uio_iov, uap->iovp,
+ uap->iovcnt * sizeof (struct iovec));
+ free(auio, M_IOV);
+ return (error);
}
int
-kern_jail(struct thread *td, struct jail *j)
+kern_jail_get(struct thread *td, struct uio *optuio, int flags)
{
- struct nameidata nd;
- struct prison *pr, *tpr;
- struct jail_attach_args jaa;
- int vfslocked, error, tryprid;
+ struct prison *pr;
+ struct vfsopt *opt;
+ struct vfsoptlist *opts;
+ char *errmsg, *name;
+ int error, errmsg_len, errmsg_pos, i, jid, len, locked, pos;
- KASSERT(j != NULL, ("%s: j is NULL", __func__));
+ if (flags & ~JAIL_GET_MASK)
+ return (EINVAL);
+ if (jailed(td->td_ucred)) {
+ /*
+ * Don't allow a jailed process to see any jails,
+ * not even its own.
+ */
+ vfs_opterror(opts, "jail not found");
+ return (ENOENT);
+ }
- /* Handle addresses - convert old structs, copyin, check IPs. */
- error = jail_handle_ips(j);
+ /* Get the parameter list. */
+ error = vfs_buildopts(optuio, &opts);
if (error)
return (error);
+ errmsg_pos = vfs_getopt_pos(opts, "errmsg");
- /* Allocate struct prison and fill it with life. */
- pr = malloc(sizeof(*pr), M_PRISON, M_WAITOK | M_ZERO);
- mtx_init(&pr->pr_mtx, "jail mutex", NULL, MTX_DEF);
- pr->pr_ref = 1;
- error = copyinstr(j->path, &pr->pr_path, sizeof(pr->pr_path), NULL);
- if (error)
- goto e_killmtx;
- NDINIT(&nd, LOOKUP, MPSAFE | FOLLOW | LOCKLEAF, UIO_SYSSPACE,
- pr->pr_path, td);
- error = namei(&nd);
- if (error)
- goto e_killmtx;
- vfslocked = NDHASGIANT(&nd);
- pr->pr_root = nd.ni_vp;
- VOP_UNLOCK(nd.ni_vp, 0);
- NDFREE(&nd, NDF_ONLY_PNBUF);
- VFS_UNLOCK_GIANT(vfslocked);
- error = copyinstr(j->hostname, &pr->pr_host, sizeof(pr->pr_host), NULL);
+ /*
+ * Find the prison specified by one of: lastjid, jid, name.
+ */
+ sx_slock(&allprison_lock);
+ error = vfs_copyopt(opts, "lastjid", &jid, sizeof(jid));
+ if (error == 0) {
+ TAILQ_FOREACH(pr, &allprison, pr_list) {
+ if (pr->pr_id > jid) {
+ mtx_lock(&pr->pr_mtx);
+ if (pr->pr_ref > 0 &&
+ (pr->pr_uref > 0 || (flags & JAIL_DYING)))
+ break;
+ mtx_unlock(&pr->pr_mtx);
+ }
+ }
+ if (pr != NULL)
+ goto found_prison;
+ error = ENOENT;
+ vfs_opterror(opts, "no jail after %d", jid);
+ goto done_unlock_list;
+ } else if (error != ENOENT)
+ goto done_unlock_list;
+
+ error = vfs_copyopt(opts, "jid", &jid, sizeof(jid));
+ if (error == 0) {
+ if (jid != 0) {
+ pr = prison_find(jid);
+ if (pr != NULL) {
+ if (pr->pr_uref == 0 && !(flags & JAIL_DYING)) {
+ mtx_unlock(&pr->pr_mtx);
+ error = ENOENT;
+ vfs_opterror(opts, "jail %d is dying",
+ jid);
+ goto done_unlock_list;
+ }
+ goto found_prison;
+ }
+ error = ENOENT;
+ vfs_opterror(opts, "jail %d not found", jid);
+ goto done_unlock_list;
+ }
+ } else if (error != ENOENT)
+ goto done_unlock_list;
+
+ error = vfs_getopt(opts, "name", (void **)&name, &len);
+ if (error == 0) {
+ if (len == 0 || name[len - 1] != '\0') {
+ error = EINVAL;
+ goto done_unlock_list;
+ }
+ pr = prison_find_name(name);
+ if (pr != NULL) {
+ if (pr->pr_uref == 0 && !(flags & JAIL_DYING)) {
+ mtx_unlock(&pr->pr_mtx);
+ error = ENOENT;
+ vfs_opterror(opts, "jail \"%s\" is dying",
+ name);
+ goto done_unlock_list;
+ }
+ goto found_prison;
+ }
+ error = ENOENT;
+ vfs_opterror(opts, "jail \"%s\" not found", name);
+ goto done_unlock_list;
+ } else if (error != ENOENT)
+ goto done_unlock_list;
+
+ vfs_opterror(opts, "no jail specified");
+ error = ENOENT;
+ goto done_unlock_list;
+
+ found_prison:
+ /* Get the parameters of the prison. */
+ pr->pr_ref++;
+ locked = PD_LOCKED;
+ td->td_retval[0] = pr->pr_id;
+ error = vfs_setopt(opts, "jid", &pr->pr_id, sizeof(pr->pr_id));
+ if (error != 0 && error != ENOENT)
+ goto done_deref;
+ error = vfs_setopts(opts, "name", pr->pr_name);
+ if (error != 0 && error != ENOENT)
+ goto done_deref;
+ error = vfs_setopt(opts, "cpuset", &pr->pr_cpuset->cs_id,
+ sizeof(pr->pr_cpuset->cs_id));
+ if (error != 0 && error != ENOENT)
+ goto done_deref;
+ error = vfs_setopts(opts, "path", pr->pr_path);
+ if (error != 0 && error != ENOENT)
+ goto done_deref;
+#ifdef INET
+ error = vfs_setopt_part(opts, "ip4.addr", pr->pr_ip4,
+ pr->pr_ip4s * sizeof(*pr->pr_ip4));
+ if (error != 0 && error != ENOENT)
+ goto done_deref;
+#endif
+#ifdef INET6
+ error = vfs_setopt_part(opts, "ip6.addr", pr->pr_ip6,
+ pr->pr_ip6s * sizeof(*pr->pr_ip6));
+ if (error != 0 && error != ENOENT)
+ goto done_deref;
+#endif
+ error = vfs_setopt(opts, "securelevel", &pr->pr_securelevel,
+ sizeof(pr->pr_securelevel));
+ if (error != 0 && error != ENOENT)
+ goto done_deref;
+ error = vfs_setopts(opts, "host.hostname", pr->pr_host);
+ if (error != 0 && error != ENOENT)
+ goto done_deref;
+ i = pr->pr_flags & PR_PERSIST ? 1 : 0;
+ error = vfs_setopt(opts, "persist", &i, sizeof(i));
+ if (error != 0 && error != ENOENT)
+ goto done_deref;
+ i = !i;
+ error = vfs_setopt(opts, "nopersist", &i, sizeof(i));
+ if (error != 0 && error != ENOENT)
+ goto done_deref;
+ i = (pr->pr_uref == 0);
+ error = vfs_setopt(opts, "dying", &i, sizeof(i));
+ if (error != 0 && error != ENOENT)
+ goto done_deref;
+ i = !i;
+ error = vfs_setopt(opts, "nodying", &i, sizeof(i));
+ if (error != 0 && error != ENOENT)
+ goto done_deref;
+
+ /* Get the module parameters. */
+ mtx_unlock(&pr->pr_mtx);
+ locked = 0;
+ error = osd_jail_call(pr, PR_METHOD_GET, opts);
if (error)
- goto e_dropvnref;
- if (j->jailname != NULL) {
- error = copyinstr(j->jailname, &pr->pr_name,
- sizeof(pr->pr_name), NULL);
- if (error)
- goto e_dropvnref;
+ goto done_deref;
+ prison_deref(pr, PD_DEREF | PD_LIST_SLOCKED);
+
+ /* By now, all parameters should have been noted. */
+ TAILQ_FOREACH(opt, opts, link) {
+ if (!opt->seen && strcmp(opt->name, "errmsg")) {
+ error = EINVAL;
+ vfs_opterror(opts, "unknown parameter: %s", opt->name);
+ goto done_errmsg;
+ }
}
- if (j->ip4s > 0) {
- pr->pr_ip4 = j->ip4;
- pr->pr_ip4s = j->ip4s;
+
+ /* Write the fetched parameters back to userspace. */
+ error = 0;
+ TAILQ_FOREACH(opt, opts, link) {
+ if (opt->pos >= 0 && opt->pos != errmsg_pos) {
+ pos = 2 * opt->pos + 1;
+ optuio->uio_iov[pos].iov_len = opt->len;
+ if (opt->value != NULL) {
+ if (optuio->uio_segflg == UIO_SYSSPACE) {
+ bcopy(opt->value,
+ optuio->uio_iov[pos].iov_base,
+ opt->len);
+ } else {
+ error = copyout(opt->value,
+ optuio->uio_iov[pos].iov_base,
+ opt->len);
+ if (error)
+ break;
+ }
+ }
+ }
}
-#ifdef INET6
- if (j->ip6s > 0) {
- pr->pr_ip6 = j->ip6;
- pr->pr_ip6s = j->ip6s;
+ goto done_errmsg;
+
+ done_deref:
+ prison_deref(pr, locked | PD_DEREF | PD_LIST_SLOCKED);
+ goto done_errmsg;
+
+ done_unlock_list:
+ sx_sunlock(&allprison_lock);
+ done_errmsg:
+ if (error && errmsg_pos >= 0) {
+ vfs_getopt(opts, "errmsg", (void **)&errmsg, &errmsg_len);
+ errmsg_pos = 2 * errmsg_pos + 1;
+ if (errmsg_len > 0) {
+ if (optuio->uio_segflg == UIO_SYSSPACE)
+ bcopy(errmsg,
+ optuio->uio_iov[errmsg_pos].iov_base,
+ errmsg_len);
+ else
+ copyout(errmsg,
+ optuio->uio_iov[errmsg_pos].iov_base,
+ errmsg_len);
+ }
}
-#endif
- pr->pr_linux = NULL;
- pr->pr_securelevel = securelevel;
- bzero(&pr->pr_osd, sizeof(pr->pr_osd));
+ vfs_freeopts(opts);
+ return (error);
+}
- /*
- * Pre-set prison state to ALIVE upon cration. This is needed so we
- * can later attach the process to it, etc (avoiding another extra
- * state for ther process of creation, complicating things).
- */
- pr->pr_state = PRISON_STATE_ALIVE;
+/*
+ * struct jail_remove_args {
+ * int jid;
+ * };
+ */
+int
+jail_remove(struct thread *td, struct jail_remove_args *uap)
+{
+ struct prison *pr;
+ struct proc *p;
+ int deuref, error;
- /* Allocate a dedicated cpuset for each jail. */
- error = cpuset_create_root(td, &pr->pr_cpuset);
+ error = priv_check(td, PRIV_JAIL_REMOVE);
if (error)
- goto e_dropvnref;
+ return (error);
sx_xlock(&allprison_lock);
- /* Make sure we cannot run into problems with ambiguous bind()ings. */
-#if defined(INET) || defined(INET6)
- error = prison_check_conflicting_ips(pr);
- if (error) {
+ pr = prison_find(uap->jid);
+ if (pr == NULL) {
sx_xunlock(&allprison_lock);
- goto e_dropcpuset;
+ return (EINVAL);
}
-#endif
- /* Determine next pr_id and add prison to allprison list. */
- tryprid = lastprid + 1;
- if (tryprid == JAIL_MAX)
- tryprid = 1;
-next:
- LIST_FOREACH(tpr, &allprison, pr_list) {
- if (tpr->pr_id == tryprid) {
- tryprid++;
- if (tryprid == JAIL_MAX) {
- sx_xunlock(&allprison_lock);
- error = EAGAIN;
- goto e_dropcpuset;
- }
- goto next;
- }
+ /* If the prison was persistent, it is not anymore. */
+ deuref = 0;
+ if (pr->pr_flags & PR_PERSIST) {
+ pr->pr_ref--;
+ deuref = PD_DEUREF;
+ pr->pr_flags &= ~PR_PERSIST;
}
- pr->pr_id = jaa.jid = lastprid = tryprid;
- LIST_INSERT_HEAD(&allprison, pr, pr_list);
- prisoncount++;
- sx_xunlock(&allprison_lock);
- error = jail_attach(td, &jaa);
- if (error)
- goto e_dropprref;
- mtx_lock(&pr->pr_mtx);
- pr->pr_ref--;
+ /* If there are no references left, remove the prison now. */
+ if (pr->pr_ref == 0) {
+ prison_deref(pr,
+ deuref | PD_DEREF | PD_LOCKED | PD_LIST_XLOCKED);
+ return (0);
+ }
+
+ /*
+ * Keep a temporary reference to make sure this prison sticks around.
+ */
+ pr->pr_ref++;
mtx_unlock(&pr->pr_mtx);
- td->td_retval[0] = jaa.jid;
- return (0);
-e_dropprref:
- sx_xlock(&allprison_lock);
- LIST_REMOVE(pr, pr_list);
- prisoncount--;
sx_xunlock(&allprison_lock);
-e_dropcpuset:
- cpuset_rel(pr->pr_cpuset);
-e_dropvnref:
- vfslocked = VFS_LOCK_GIANT(pr->pr_root->v_mount);
- vrele(pr->pr_root);
- VFS_UNLOCK_GIANT(vfslocked);
-e_killmtx:
- mtx_destroy(&pr->pr_mtx);
- free(pr, M_PRISON);
-#ifdef INET6
- free(j->ip6, M_PRISON);
-#endif
-#ifdef INET
- free(j->ip4, M_PRISON);
-#endif
- return (error);
+ /*
+ * Kill all processes unfortunate enough to be attached to this prison.
+ */
+ sx_slock(&allproc_lock);
+ LIST_FOREACH(p, &allproc, p_list) {
+ PROC_LOCK(p);
+ if (p->p_state != PRS_NEW && p->p_ucred &&
+ p->p_ucred->cr_prison == pr)
+ psignal(p, SIGKILL);
+ PROC_UNLOCK(p);
+ }
+ sx_sunlock(&allproc_lock);
+ /* Remove the temporary reference. */
+ prison_deref(pr, deuref | PD_DEREF);
+ return (0);
}
@@ -603,24 +1471,13 @@ e_killmtx:
int
jail_attach(struct thread *td, struct jail_attach_args *uap)
{
- struct proc *p;
- struct ucred *newcred, *oldcred;
struct prison *pr;
- int vfslocked, error;
+ int error;
- /*
- * XXX: Note that there is a slight race here if two threads
- * in the same privileged process attempt to attach to two
- * different jails at the same time. It is important for
- * user processes not to do this, or they might end up with
- * a process root from one prison, but attached to the jail
- * of another.
- */
error = priv_check(td, PRIV_JAIL_ATTACH);
if (error)
return (error);
- p = td->td_proc;
sx_slock(&allprison_lock);
pr = prison_find(uap->jid);
if (pr == NULL) {
@@ -630,23 +1487,51 @@ jail_attach(struct thread *td, struct jail_attach_args *uap)
/*
* Do not allow a process to attach to a prison that is not
- * considered to be "ALIVE".
+ * considered to be "alive".
*/
- if (pr->pr_state != PRISON_STATE_ALIVE) {
+ if (pr->pr_uref == 0) {
mtx_unlock(&pr->pr_mtx);
sx_sunlock(&allprison_lock);
return (EINVAL);
}
+
+ return (do_jail_attach(td, pr));
+}
+
+static int
+do_jail_attach(struct thread *td, struct prison *pr)
+{
+ struct proc *p;
+ struct ucred *newcred, *oldcred;
+ int vfslocked, error;
+
+ /*
+ * XXX: Note that there is a slight race here if two threads
+ * in the same privileged process attempt to attach to two
+ * different jails at the same time. It is important for
+ * user processes not to do this, or they might end up with
+ * a process root from one prison, but attached to the jail
+ * of another.
+ */
pr->pr_ref++;
+ pr->pr_uref++;
mtx_unlock(&pr->pr_mtx);
+
+ /* Let modules do whatever they need to prepare for attaching. */
+ error = osd_jail_call(pr, PR_METHOD_ATTACH, td);
+ if (error) {
+ prison_deref(pr, PD_DEREF | PD_DEUREF | PD_LIST_SLOCKED);
+ return (error);
+ }
sx_sunlock(&allprison_lock);
/*
* Reparent the newly attached process to this jail.
*/
+ p = td->td_proc;
error = cpuset_setproc_update_set(p, pr->pr_cpuset);
if (error)
- goto e_unref;
+ goto e_revert_osd;
vfslocked = VFS_LOCK_GIANT(pr->pr_root->v_mount);
vn_lock(pr->pr_root, LK_EXCLUSIVE | LK_RETRY);
@@ -657,7 +1542,8 @@ jail_attach(struct thread *td, struct jail_attach_args *uap)
goto e_unlock;
#endif
VOP_UNLOCK(pr->pr_root, 0);
- change_root(pr->pr_root, td);
+ if ((error = change_root(pr->pr_root, td)))
+ goto e_unlock_giant;
VFS_UNLOCK_GIANT(vfslocked);
newcred = crget();
@@ -667,17 +1553,17 @@ jail_attach(struct thread *td, struct jail_attach_args *uap)
crcopy(newcred, oldcred);
newcred->cr_prison = pr;
p->p_ucred = newcred;
- prison_proc_hold(pr);
PROC_UNLOCK(p);
crfree(oldcred);
return (0);
-e_unlock:
+ e_unlock:
VOP_UNLOCK(pr->pr_root, 0);
+ e_unlock_giant:
VFS_UNLOCK_GIANT(vfslocked);
-e_unref:
- mtx_lock(&pr->pr_mtx);
- pr->pr_ref--;
- mtx_unlock(&pr->pr_mtx);
+ e_revert_osd:
+ /* Tell modules this thread is still in its old jail after all. */
+ (void)osd_jail_call(td->td_ucred->cr_prison, PR_METHOD_ATTACH, td);
+ prison_deref(pr, PD_DEREF | PD_DEUREF);
return (error);
}
@@ -690,19 +1576,54 @@ prison_find(int prid)
struct prison *pr;
sx_assert(&allprison_lock, SX_LOCKED);
- LIST_FOREACH(pr, &allprison, pr_list) {
+ TAILQ_FOREACH(pr, &allprison, pr_list) {
if (pr->pr_id == prid) {
mtx_lock(&pr->pr_mtx);
- if (pr->pr_ref == 0) {
- mtx_unlock(&pr->pr_mtx);
- break;
- }
- return (pr);
+ if (pr->pr_ref > 0)
+ return (pr);
+ mtx_unlock(&pr->pr_mtx);
}
}
return (NULL);
}
+/*
+ * Look for the named prison. Returns a locked prison or NULL.
+ */
+struct prison *
+prison_find_name(const char *name)
+{
+ struct prison *pr, *deadpr;
+
+ sx_assert(&allprison_lock, SX_LOCKED);
+ again:
+ deadpr = NULL;
+ TAILQ_FOREACH(pr, &allprison, pr_list) {
+ if (!strcmp(pr->pr_name, name)) {
+ mtx_lock(&pr->pr_mtx);
+ if (pr->pr_ref > 0) {
+ if (pr->pr_uref > 0)
+ return (pr);
+ deadpr = pr;
+ }
+ mtx_unlock(&pr->pr_mtx);
+ }
+ }
+ /* There was no valid prison - perhaps there was a dying one */
+ if (deadpr != NULL) {
+ mtx_lock(&deadpr->pr_mtx);
+ if (deadpr->pr_ref == 0) {
+ mtx_unlock(&deadpr->pr_mtx);
+ goto again;
+ }
+ }
+ return (deadpr);
+}
+
+/*
+ * Remove a prison reference. If that was the last reference, remove the
+ * prison itself - but not in this context in case there are locks held.
+ */
void
prison_free_locked(struct prison *pr)
{
@@ -729,33 +1650,79 @@ prison_free(struct prison *pr)
static void
prison_complete(void *context, int pending)
{
- struct prison *pr;
- int vfslocked;
- pr = (struct prison *)context;
+ prison_deref((struct prison *)context, 0);
+}
- sx_xlock(&allprison_lock);
- LIST_REMOVE(pr, pr_list);
- prisoncount--;
- sx_xunlock(&allprison_lock);
+/*
+ * Remove a prison reference (usually). This internal version assumes no
+ * mutexes are held, except perhaps the prison itself. If there are no more
+ * references, release and delist the prison. On completion, the prison lock
+ * and the allprison lock are both unlocked.
+ */
+static void
+prison_deref(struct prison *pr, int flags)
+{
+ int vfslocked;
- cpuset_rel(pr->pr_cpuset);
+ if (!(flags & PD_LOCKED))
+ mtx_lock(&pr->pr_mtx);
+ if (flags & PD_DEUREF) {
+ pr->pr_uref--;
+ /* Done if there were only user references to remove. */
+ if (!(flags & PD_DEREF)) {
+ mtx_unlock(&pr->pr_mtx);
+ if (flags & PD_LIST_SLOCKED)
+ sx_sunlock(&allprison_lock);
+ else if (flags & PD_LIST_XLOCKED)
+ sx_xunlock(&allprison_lock);
+ return;
+ }
+ }
+ if (flags & PD_DEREF)
+ pr->pr_ref--;
+ /* If the prison still has references, nothing else to do. */
+ if (pr->pr_ref > 0) {
+ mtx_unlock(&pr->pr_mtx);
+ if (flags & PD_LIST_SLOCKED)
+ sx_sunlock(&allprison_lock);
+ else if (flags & PD_LIST_XLOCKED)
+ sx_xunlock(&allprison_lock);
+ return;
+ }
- /* Free all OSD associated to this jail. */
- osd_jail_exit(pr);
+ KASSERT(pr->pr_uref == 0,
+ ("%s: Trying to remove an active prison (jid=%d).", __func__,
+ pr->pr_id));
+ mtx_unlock(&pr->pr_mtx);
+ if (flags & PD_LIST_SLOCKED) {
+ if (!sx_try_upgrade(&allprison_lock)) {
+ sx_sunlock(&allprison_lock);
+ sx_xlock(&allprison_lock);
+ }
+ } else if (!(flags & PD_LIST_XLOCKED))
+ sx_xlock(&allprison_lock);
- vfslocked = VFS_LOCK_GIANT(pr->pr_root->v_mount);
- vrele(pr->pr_root);
- VFS_UNLOCK_GIANT(vfslocked);
+ TAILQ_REMOVE(&allprison, pr, pr_list);
+ prisoncount--;
+ sx_xunlock(&allprison_lock);
+ if (pr->pr_root != NULL) {
+ vfslocked = VFS_LOCK_GIANT(pr->pr_root->v_mount);
+ vrele(pr->pr_root);
+ VFS_UNLOCK_GIANT(vfslocked);
+ }
mtx_destroy(&pr->pr_mtx);
free(pr->pr_linux, M_PRISON);
-#ifdef INET6
- free(pr->pr_ip6, M_PRISON);
-#endif
#ifdef INET
free(pr->pr_ip4, M_PRISON);
#endif
+#ifdef INET6
+ free(pr->pr_ip6, M_PRISON);
+#endif
+ if (pr->pr_cpuset != NULL)
+ cpuset_rel(pr->pr_cpuset);
+ osd_jail_exit(pr);
free(pr, M_PRISON);
}
@@ -783,10 +1750,9 @@ prison_proc_hold(struct prison *pr)
{
mtx_lock(&pr->pr_mtx);
- KASSERT(pr->pr_state == PRISON_STATE_ALIVE,
- ("Cannot add a process to a non-alive prison (jid=%d).",
- pr->pr_id));
- pr->pr_nprocs++;
+ KASSERT(pr->pr_uref > 0,
+ ("Cannot add a process to a non-alive prison (jid=%d)", pr->pr_id));
+ pr->pr_uref++;
mtx_unlock(&pr->pr_mtx);
}
@@ -795,12 +1761,9 @@ prison_proc_free(struct prison *pr)
{
mtx_lock(&pr->pr_mtx);
- KASSERT(pr->pr_state == PRISON_STATE_ALIVE && pr->pr_nprocs > 0,
- ("Trying to kill a process in a dead prison (jid=%d).", pr->pr_id));
- pr->pr_nprocs--;
- if (pr->pr_nprocs == 0)
- pr->pr_state = PRISON_STATE_DYING;
- mtx_unlock(&pr->pr_mtx);
+ KASSERT(pr->pr_uref > 0,
+ ("Trying to kill a process in a dead prison (jid=%d)", pr->pr_id));
+ prison_deref(pr, PD_DEUREF | PD_LOCKED);
}
@@ -817,16 +1780,22 @@ prison_proc_free(struct prison *pr)
int
prison_get_ip4(struct ucred *cred, struct in_addr *ia)
{
+ struct prison *pr;
KASSERT(cred != NULL, ("%s: cred is NULL", __func__));
KASSERT(ia != NULL, ("%s: ia is NULL", __func__));
if (!jailed(cred))
return (0);
- if (cred->cr_prison->pr_ip4 == NULL)
+ pr = cred->cr_prison;
+ mtx_lock(&pr->pr_mtx);
+ if (pr->pr_ip4 == NULL) {
+ mtx_unlock(&pr->pr_mtx);
return (EAFNOSUPPORT);
+ }
- ia->s_addr = cred->cr_prison->pr_ip4[0].s_addr;
+ ia->s_addr = pr->pr_ip4[0].s_addr;
+ mtx_unlock(&pr->pr_mtx);
return (0);
}
@@ -841,19 +1810,26 @@ prison_get_ip4(struct ucred *cred, struct in_addr *ia)
int
prison_local_ip4(struct ucred *cred, struct in_addr *ia)
{
+ struct prison *pr;
struct in_addr ia0;
+ int error;
KASSERT(cred != NULL, ("%s: cred is NULL", __func__));
KASSERT(ia != NULL, ("%s: ia is NULL", __func__));
if (!jailed(cred))
return (0);
- if (cred->cr_prison->pr_ip4 == NULL)
+ pr = cred->cr_prison;
+ mtx_lock(&pr->pr_mtx);
+ if (pr->pr_ip4 == NULL) {
+ mtx_unlock(&pr->pr_mtx);
return (EAFNOSUPPORT);
+ }
ia0.s_addr = ntohl(ia->s_addr);
if (ia0.s_addr == INADDR_LOOPBACK) {
- ia->s_addr = cred->cr_prison->pr_ip4[0].s_addr;
+ ia->s_addr = pr->pr_ip4[0].s_addr;
+ mtx_unlock(&pr->pr_mtx);
return (0);
}
@@ -861,12 +1837,15 @@ prison_local_ip4(struct ucred *cred, struct in_addr *ia)
/*
* In case there is only 1 IPv4 address, bind directly.
*/
- if (cred->cr_prison->pr_ip4s == 1)
- ia->s_addr = cred->cr_prison->pr_ip4[0].s_addr;
+ if (pr->pr_ip4s == 1)
+ ia->s_addr = pr->pr_ip4[0].s_addr;
+ mtx_unlock(&pr->pr_mtx);
return (0);
}
- return (_prison_check_ip4(cred->cr_prison, ia));
+ error = _prison_check_ip4(pr, ia);
+ mtx_unlock(&pr->pr_mtx);
+ return (error);
}
/*
@@ -878,23 +1857,30 @@ prison_local_ip4(struct ucred *cred, struct in_addr *ia)
int
prison_remote_ip4(struct ucred *cred, struct in_addr *ia)
{
+ struct prison *pr;
KASSERT(cred != NULL, ("%s: cred is NULL", __func__));
KASSERT(ia != NULL, ("%s: ia is NULL", __func__));
if (!jailed(cred))
return (0);
- if (cred->cr_prison->pr_ip4 == NULL)
+ pr = cred->cr_prison;
+ mtx_lock(&pr->pr_mtx);
+ if (pr->pr_ip4 == NULL) {
+ mtx_unlock(&pr->pr_mtx);
return (EAFNOSUPPORT);
+ }
if (ntohl(ia->s_addr) == INADDR_LOOPBACK) {
- ia->s_addr = cred->cr_prison->pr_ip4[0].s_addr;
+ ia->s_addr = pr->pr_ip4[0].s_addr;
+ mtx_unlock(&pr->pr_mtx);
return (0);
}
/*
* Return success because nothing had to be changed.
*/
+ mtx_unlock(&pr->pr_mtx);
return (0);
}
@@ -938,16 +1924,24 @@ _prison_check_ip4(struct prison *pr, struct in_addr *ia)
int
prison_check_ip4(struct ucred *cred, struct in_addr *ia)
{
+ struct prison *pr;
+ int error;
KASSERT(cred != NULL, ("%s: cred is NULL", __func__));
KASSERT(ia != NULL, ("%s: ia is NULL", __func__));
if (!jailed(cred))
return (0);
- if (cred->cr_prison->pr_ip4 == NULL)
+ pr = cred->cr_prison;
+ mtx_lock(&pr->pr_mtx);
+ if (pr->pr_ip4 == NULL) {
+ mtx_unlock(&pr->pr_mtx);
return (EAFNOSUPPORT);
+ }
- return (_prison_check_ip4(cred->cr_prison, ia));
+ error = _prison_check_ip4(pr, ia);
+ mtx_unlock(&pr->pr_mtx);
+ return (error);
}
#endif
@@ -963,16 +1957,22 @@ prison_check_ip4(struct ucred *cred, struct in_addr *ia)
int
prison_get_ip6(struct ucred *cred, struct in6_addr *ia6)
{
+ struct prison *pr;
KASSERT(cred != NULL, ("%s: cred is NULL", __func__));
KASSERT(ia6 != NULL, ("%s: ia6 is NULL", __func__));
if (!jailed(cred))
return (0);
- if (cred->cr_prison->pr_ip6 == NULL)
+ pr = cred->cr_prison;
+ mtx_lock(&pr->pr_mtx);
+ if (pr->pr_ip6 == NULL) {
+ mtx_unlock(&pr->pr_mtx);
return (EAFNOSUPPORT);
+ }
- bcopy(&cred->cr_prison->pr_ip6[0], ia6, sizeof(struct in6_addr));
+ bcopy(&pr->pr_ip6[0], ia6, sizeof(struct in6_addr));
+ mtx_unlock(&pr->pr_mtx);
return (0);
}
@@ -988,18 +1988,24 @@ prison_get_ip6(struct ucred *cred, struct in6_addr *ia6)
int
prison_local_ip6(struct ucred *cred, struct in6_addr *ia6, int v6only)
{
+ struct prison *pr;
+ int error;
KASSERT(cred != NULL, ("%s: cred is NULL", __func__));
KASSERT(ia6 != NULL, ("%s: ia6 is NULL", __func__));
if (!jailed(cred))
return (0);
- if (cred->cr_prison->pr_ip6 == NULL)
+ pr = cred->cr_prison;
+ mtx_lock(&pr->pr_mtx);
+ if (pr->pr_ip6 == NULL) {
+ mtx_unlock(&pr->pr_mtx);
return (EAFNOSUPPORT);
+ }
if (IN6_IS_ADDR_LOOPBACK(ia6)) {
- bcopy(&cred->cr_prison->pr_ip6[0], ia6,
- sizeof(struct in6_addr));
+ bcopy(&pr->pr_ip6[0], ia6, sizeof(struct in6_addr));
+ mtx_unlock(&pr->pr_mtx);
return (0);
}
@@ -1008,13 +2014,15 @@ prison_local_ip6(struct ucred *cred, struct in6_addr *ia6, int v6only)
* In case there is only 1 IPv6 address, and v6only is true,
* then bind directly.
*/
- if (v6only != 0 && cred->cr_prison->pr_ip6s == 1)
- bcopy(&cred->cr_prison->pr_ip6[0], ia6,
- sizeof(struct in6_addr));
+ if (v6only != 0 && pr->pr_ip6s == 1)
+ bcopy(&pr->pr_ip6[0], ia6, sizeof(struct in6_addr));
+ mtx_unlock(&pr->pr_mtx);
return (0);
}
- return (_prison_check_ip6(cred->cr_prison, ia6));
+ error = _prison_check_ip6(pr, ia6);
+ mtx_unlock(&pr->pr_mtx);
+ return (error);
}
/*
@@ -1025,24 +2033,30 @@ prison_local_ip6(struct ucred *cred, struct in6_addr *ia6, int v6only)
int
prison_remote_ip6(struct ucred *cred, struct in6_addr *ia6)
{
+ struct prison *pr;
KASSERT(cred != NULL, ("%s: cred is NULL", __func__));
KASSERT(ia6 != NULL, ("%s: ia6 is NULL", __func__));
if (!jailed(cred))
return (0);
- if (cred->cr_prison->pr_ip6 == NULL)
+ pr = cred->cr_prison;
+ mtx_lock(&pr->pr_mtx);
+ if (pr->pr_ip6 == NULL) {
+ mtx_unlock(&pr->pr_mtx);
return (EAFNOSUPPORT);
+ }
if (IN6_IS_ADDR_LOOPBACK(ia6)) {
- bcopy(&cred->cr_prison->pr_ip6[0], ia6,
- sizeof(struct in6_addr));
+ bcopy(&pr->pr_ip6[0], ia6, sizeof(struct in6_addr));
+ mtx_unlock(&pr->pr_mtx);
return (0);
}
/*
* Return success because nothing had to be changed.
*/
+ mtx_unlock(&pr->pr_mtx);
return (0);
}
@@ -1085,16 +2099,24 @@ _prison_check_ip6(struct prison *pr, struct in6_addr *ia6)
int
prison_check_ip6(struct ucred *cred, struct in6_addr *ia6)
{
+ struct prison *pr;
+ int error;
KASSERT(cred != NULL, ("%s: cred is NULL", __func__));
KASSERT(ia6 != NULL, ("%s: ia6 is NULL", __func__));
if (!jailed(cred))
return (0);
- if (cred->cr_prison->pr_ip6 == NULL)
+ pr = cred->cr_prison;
+ mtx_lock(&pr->pr_mtx);
+ if (pr->pr_ip6 == NULL) {
+ mtx_unlock(&pr->pr_mtx);
return (EAFNOSUPPORT);
+ }
- return (_prison_check_ip6(cred->cr_prison, ia6));
+ error = _prison_check_ip6(pr, ia6);
+ mtx_unlock(&pr->pr_mtx);
+ return (error);
}
#endif
@@ -1508,70 +2530,100 @@ prison_priv_check(struct ucred *cred, int priv)
static int
sysctl_jail_list(SYSCTL_HANDLER_ARGS)
{
- struct xprison *xp, *sxp;
+ struct xprison *xp;
struct prison *pr;
- char *p;
- size_t len;
- int count, error;
+#ifdef INET
+ struct in_addr *ip4 = NULL;
+ int ip4s = 0;
+#endif
+#ifdef INET6
+ struct in_addr *ip6 = NULL;
+ int ip6s = 0;
+#endif
+ int error;
if (jailed(req->td->td_ucred))
return (0);
+ xp = malloc(sizeof(*xp), M_TEMP, M_WAITOK);
+ error = 0;
sx_slock(&allprison_lock);
- if ((count = prisoncount) == 0) {
- sx_sunlock(&allprison_lock);
- return (0);
- }
-
- len = sizeof(*xp) * count;
- LIST_FOREACH(pr, &allprison, pr_list) {
+ TAILQ_FOREACH(pr, &allprison, pr_list) {
+ again:
+ mtx_lock(&pr->pr_mtx);
#ifdef INET
- len += pr->pr_ip4s * sizeof(struct in_addr);
+ if (pr->pr_ip4s > 0) {
+ if (ip4s < pr->pr_ip4s) {
+ ip4s = pr->pr_ip4s;
+ mtx_unlock(&pr->pr_mtx);
+ ip4 = realloc(ip4, ip4s *
+ sizeof(struct in_addr), M_TEMP, M_WAITOK);
+ goto again;
+ }
+ bcopy(pr->pr_ip4, ip4,
+ pr->pr_ip4s * sizeof(struct in_addr));
+ }
#endif
#ifdef INET6
- len += pr->pr_ip6s * sizeof(struct in6_addr);
+ if (pr->pr_ip6s > 0) {
+ if (ip6s < pr->pr_ip6s) {
+ ip6s = pr->pr_ip6s;
+ mtx_unlock(&pr->pr_mtx);
+ ip6 = realloc(ip6, ip6s *
+ sizeof(struct in6_addr), M_TEMP, M_WAITOK);
+ goto again;
+ }
+ bcopy(pr->pr_ip6, ip6,
+ pr->pr_ip6s * sizeof(struct in6_addr));
+ }
#endif
- }
-
- sxp = xp = malloc(len, M_TEMP, M_WAITOK | M_ZERO);
-
- LIST_FOREACH(pr, &allprison, pr_list) {
+ if (pr->pr_ref == 0) {
+ mtx_unlock(&pr->pr_mtx);
+ continue;
+ }
+ bzero(xp, sizeof(*xp));
xp->pr_version = XPRISON_VERSION;
xp->pr_id = pr->pr_id;
- xp->pr_state = pr->pr_state;
- xp->pr_cpusetid = pr->pr_cpuset->cs_id;
+ xp->pr_state = pr->pr_uref > 0
+ ? PRISON_STATE_ALIVE : PRISON_STATE_DYING;
strlcpy(xp->pr_path, pr->pr_path, sizeof(xp->pr_path));
- mtx_lock(&pr->pr_mtx);
strlcpy(xp->pr_host, pr->pr_host, sizeof(xp->pr_host));
strlcpy(xp->pr_name, pr->pr_name, sizeof(xp->pr_name));
- mtx_unlock(&pr->pr_mtx);
#ifdef INET
xp->pr_ip4s = pr->pr_ip4s;
#endif
#ifdef INET6
xp->pr_ip6s = pr->pr_ip6s;
#endif
- p = (char *)(xp + 1);
+ mtx_unlock(&pr->pr_mtx);
+ error = SYSCTL_OUT(req, xp, sizeof(*xp));
+ if (error)
+ break;
#ifdef INET
- if (pr->pr_ip4s > 0) {
- bcopy(pr->pr_ip4, (struct in_addr *)p,
- pr->pr_ip4s * sizeof(struct in_addr));
- p += (pr->pr_ip4s * sizeof(struct in_addr));
+ if (xp->pr_ip4s > 0) {
+ error = SYSCTL_OUT(req, ip4,
+ xp->pr_ip4s * sizeof(struct in_addr));
+ if (error)
+ break;
}
#endif
#ifdef INET6
- if (pr->pr_ip6s > 0) {
- bcopy(pr->pr_ip6, (struct in6_addr *)p,
- pr->pr_ip6s * sizeof(struct in6_addr));
- p += (pr->pr_ip6s * sizeof(struct in6_addr));
+ if (xp->pr_ip6s > 0) {
+ error = SYSCTL_OUT(req, ip6,
+ xp->pr_ip6s * sizeof(struct in6_addr));
+ if (error)
+ break;
}
#endif
- xp = (struct xprison *)p;
}
sx_sunlock(&allprison_lock);
-
- error = SYSCTL_OUT(req, sxp, len);
- free(sxp, M_TEMP);
+ free(xp, M_TEMP);
+#ifdef INET
+ free(ip4, M_TEMP);
+#endif
+#ifdef INET6
+ free(ip6, M_TEMP);
+#endif
return (error);
}
@@ -1594,58 +2646,75 @@ SYSCTL_PROC(_security_jail, OID_AUTO, jailed,
sysctl_jail_jailed, "I", "Process in jail?");
#ifdef DDB
-DB_SHOW_COMMAND(jails, db_show_jails)
+
+static void
+db_show_prison(struct prison *pr)
{
- struct prison *pr;
-#ifdef INET
- struct in_addr ia;
+#if defined(INET) || defined(INET6)
+ int ii;
#endif
#ifdef INET6
char ip6buf[INET6_ADDRSTRLEN];
#endif
- const char *state;
-#if defined(INET) || defined(INET6)
- int i;
-#endif
- db_printf(
- " JID pr_ref pr_nprocs pr_ip4s pr_ip6s\n");
- db_printf(
- " Hostname Path\n");
- db_printf(
- " Name State\n");
- db_printf(
- " Cpusetid\n");
- db_printf(
- " IP Address(es)\n");
- LIST_FOREACH(pr, &allprison, pr_list) {
- db_printf("%6d %6d %9d %7d %7d\n",
- pr->pr_id, pr->pr_ref, pr->pr_nprocs,
- pr->pr_ip4s, pr->pr_ip6s);
- db_printf("%6s %-29.29s %.74s\n",
- "", pr->pr_host, pr->pr_path);
- if (pr->pr_state < 0 || pr->pr_state >= (int)((sizeof(
- prison_states) / sizeof(struct prison_state))))
- state = "(bogus)";
- else
- state = prison_states[pr->pr_state].state_name;
- db_printf("%6s %-29.29s %.74s\n",
- "", (pr->pr_name[0] != '\0') ? pr->pr_name : "", state);
- db_printf("%6s %-6d\n",
- "", pr->pr_cpuset->cs_id);
+ db_printf("prison %p:\n", pr);
+ db_printf(" jid = %d\n", pr->pr_id);
+ db_printf(" name = %s\n", pr->pr_name);
+ db_printf(" ref = %d\n", pr->pr_ref);
+ db_printf(" uref = %d\n", pr->pr_uref);
+ db_printf(" path = %s\n", pr->pr_path);
+ db_printf(" cpuset = %d\n", pr->pr_cpuset
+ ? pr->pr_cpuset->cs_id : -1);
+ db_printf(" root = %p\n", pr->pr_root);
+ db_printf(" securelevel = %d\n", pr->pr_securelevel);
+ db_printf(" flags = %x", pr->pr_flags);
+ if (pr->pr_flags & PR_PERSIST)
+ db_printf(" persist");
+ db_printf("\n");
+ db_printf(" host.hostname = %s\n", pr->pr_host);
#ifdef INET
- for (i=0; i < pr->pr_ip4s; i++) {
- ia.s_addr = pr->pr_ip4[i].s_addr;
- db_printf("%6s %s\n", "", inet_ntoa(ia));
- }
+ db_printf(" ip4s = %d\n", pr->pr_ip4s);
+ for (ii = 0; ii < pr->pr_ip4s; ii++)
+ db_printf(" %s %s\n",
+ ii == 0 ? "ip4 =" : " ",
+ inet_ntoa(pr->pr_ip4[ii]));
#endif
#ifdef INET6
- for (i=0; i < pr->pr_ip6s; i++)
- db_printf("%6s %s\n",
- "", ip6_sprintf(ip6buf, &pr->pr_ip6[i]));
-#endif /* INET6 */
- if (db_pager_quit)
- break;
+ db_printf(" ip6s = %d\n", pr->pr_ip6s);
+ for (ii = 0; ii < pr->pr_ip6s; ii++)
+ db_printf(" %s %s\n",
+ ii == 0 ? "ip6 =" : " ",
+ ip6_sprintf(ip6buf, &pr->pr_ip6[ii]));
+#endif
+}
+
+DB_SHOW_COMMAND(prison, db_show_prison_command)
+{
+ struct prison *pr;
+
+ if (!have_addr) {
+ /* Show all prisons in the list. */
+ TAILQ_FOREACH(pr, &allprison, pr_list) {
+ db_show_prison(pr);
+ if (db_pager_quit)
+ break;
+ }
+ return;
}
+
+ /* Look for a prison with the ID and with references. */
+ TAILQ_FOREACH(pr, &allprison, pr_list)
+ if (pr->pr_id == addr && pr->pr_ref > 0)
+ break;
+ if (pr == NULL)
+ /* Look again, without requiring a reference. */
+ TAILQ_FOREACH(pr, &allprison, pr_list)
+ if (pr->pr_id == addr)
+ break;
+ if (pr == NULL)
+ /* Assume address points to a valid prison. */
+ pr = (struct prison *)addr;
+ db_show_prison(pr);
}
+
#endif /* DDB */
OpenPOWER on IntegriCloud