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.c908
1 files changed, 848 insertions, 60 deletions
diff --git a/sys/kern/kern_jail.c b/sys/kern/kern_jail.c
index aae9178..b453d58 100644
--- a/sys/kern/kern_jail.c
+++ b/sys/kern/kern_jail.c
@@ -1,5 +1,7 @@
/*-
- * Copyright (c) 1999 Poul-Henning Kamp. All rights reserved.
+ * Copyright (c) 1999 Poul-Henning Kamp.
+ * Copyright (c) 2008 Bjoern A. Zeeb.
+ * All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -26,6 +28,9 @@
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
+#include "opt_ddb.h"
+#include "opt_inet.h"
+#include "opt_inet6.h"
#include "opt_mac.h"
#include <sys/param.h>
@@ -54,6 +59,12 @@ __FBSDID("$FreeBSD$");
#include <sys/osd.h>
#include <net/if.h>
#include <netinet/in.h>
+#ifdef DDB
+#include <ddb/ddb.h>
+#ifdef INET6
+#include <netinet6/in6_var.h>
+#endif /* INET6 */
+#endif /* DDB */
#include <security/mac/mac_framework.h>
@@ -70,7 +81,7 @@ SYSCTL_INT(_security_jail, OID_AUTO, set_hostname_allowed, CTLFLAG_RW,
int jail_socket_unixiproute_only = 1;
SYSCTL_INT(_security_jail, OID_AUTO, socket_unixiproute_only, CTLFLAG_RW,
&jail_socket_unixiproute_only, 0,
- "Processes in jail are limited to creating UNIX/IPv4/route sockets only");
+ "Processes in jail are limited to creating UNIX/IP/route sockets only");
int jail_sysvipc_allowed = 0;
SYSCTL_INT(_security_jail, OID_AUTO, sysvipc_allowed, CTLFLAG_RW,
@@ -97,6 +108,11 @@ SYSCTL_INT(_security_jail, OID_AUTO, mount_allowed, CTLFLAG_RW,
&jail_mount_allowed, 0,
"Processes in jail can mount/unmount jail-friendly file systems");
+int jail_max_af_ips = 255;
+SYSCTL_INT(_security_jail, OID_AUTO, jail_max_af_ips, CTLFLAG_RW,
+ &jail_max_af_ips, 0,
+ "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;
@@ -106,6 +122,12 @@ int prisoncount = 0;
static void init_prison(void *);
static void prison_complete(void *context, int pending);
static int sysctl_jail_list(SYSCTL_HANDLER_ARGS);
+#ifdef INET
+static int _prison_check_ip4(struct prison *, struct in_addr *);
+#endif
+#ifdef INET6
+static int _prison_check_ip6(struct prison *, struct in6_addr *);
+#endif
static void
init_prison(void *data __unused)
@@ -117,6 +139,276 @@ init_prison(void *data __unused)
SYSINIT(prison, SI_SUB_INTRINSIC, SI_ORDER_ANY, init_prison, NULL);
+#ifdef INET
+static int
+qcmp_v4(const void *ip1, const void *ip2)
+{
+ in_addr_t iaa, iab;
+
+ /*
+ * We need to compare in HBO here to get the list sorted as expected
+ * by the result of the code. Sorting NBO addresses gives you
+ * interesting results. If you do not understand, do not try.
+ */
+ iaa = ntohl(((const struct in_addr *)ip1)->s_addr);
+ iab = ntohl(((const struct in_addr *)ip2)->s_addr);
+
+ /*
+ * Do not simply return the difference of the two numbers, the int is
+ * not wide enough.
+ */
+ if (iaa > iab)
+ return (1);
+ else if (iaa < iab)
+ return (-1);
+ else
+ return (0);
+}
+#endif
+
+#ifdef INET6
+static int
+qcmp_v6(const void *ip1, const void *ip2)
+{
+ const struct in6_addr *ia6a, *ia6b;
+ int i, rc;
+
+ ia6a = (const struct in6_addr *)ip1;
+ ia6b = (const struct in6_addr *)ip2;
+
+ rc = 0;
+ for (i=0; rc == 0 && i < sizeof(struct in6_addr); i++) {
+ if (ia6a->s6_addr[i] > ia6b->s6_addr[i])
+ rc = 1;
+ else if (ia6a->s6_addr[i] < ia6b->s6_addr[i])
+ rc = -1;
+ }
+ return (rc);
+}
+#endif
+
+#if defined(INET) || defined(INET6)
+static int
+prison_check_conflicting_ips(struct prison *p)
+{
+ struct prison *pr;
+ int i;
+
+ sx_assert(&allprison_lock, SX_LOCKED);
+
+ if (p->pr_ip4s == 0 && p->pr_ip6s == 0)
+ return (0);
+
+ 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;
+
+ /*
+ * 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.
+ */
+#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]))
+ return (EINVAL);
+ }
+ }
+#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]))
+ return (EINVAL);
+ }
+ }
+#endif
+ }
+
+ return (0);
+}
+
+static int
+jail_copyin_ips(struct jail *j)
+{
+#ifdef INET
+ struct in_addr *ip4;
+#endif
+#ifdef INET6
+ struct in6_addr *ip6;
+#endif
+ int error, i;
+
+ /*
+ * 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.
+ *
+ * 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.
+ */
+#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);
+
+ /*
+ * 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)) {
+ error = EINVAL;
+ goto e_free_ip;
+ }
+ if ((i+1) < j->ip4s &&
+ (ip4[0].s_addr == ip4[i+1].s_addr ||
+ ip4[i].s_addr == ip4[i+1].s_addr)) {
+ error = EINVAL;
+ goto e_free_ip;
+ }
+ }
+
+ j->ip4 = ip4;
+ }
+#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 = EINVAL;
+ goto e_free_ip;
+ }
+ if ((i+1) < j->ip6s &&
+ (IN6_ARE_ADDR_EQUAL(&ip6[0], &ip6[i+1]) ||
+ IN6_ARE_ADDR_EQUAL(&ip6[i], &ip6[i+1]))) {
+ error = EINVAL;
+ goto e_free_ip;
+ }
+ }
+
+ j->ip6 = ip6;
+ }
+#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 */
+
+static int
+jail_handle_ips(struct jail *j)
+{
+#if defined(INET) || defined(INET6)
+ int error;
+#endif
+
+ /*
+ * Finish conversion for older versions, copyin and setup IPs.
+ */
+ switch (j->version) {
+ case 0:
+ {
+#ifdef INET
+ /* FreeBSD single IPv4 jails. */
+ struct in_addr *ip4;
+
+ 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);
+
+ /*
+ * Jail version 0 still used HBO for the IPv4 address.
+ */
+ ip4->s_addr = htonl(j->ip4s);
+ j->ip4s = 1;
+ j->ip4 = ip4;
+ break;
+#else
+ return (EINVAL);
+#endif
+ }
+
+ case 1:
+ /*
+ * 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.
+ */
+ return (EINVAL);
+
+ case 2: /* JAIL_API_VERSION */
+ /* FreeBSD multi-IPv4/IPv6,noIP jails. */
+#if defined(INET) || defined(INET6)
+#ifdef INET
+ if (j->ip4s > jail_max_af_ips)
+ return (EINVAL);
+#else
+ if (j->ip4s != 0)
+ return (EINVAL);
+#endif
+#ifdef INET6
+ if (j->ip6s > jail_max_af_ips)
+ return (EINVAL);
+#else
+ if (j->ip6s != 0)
+ return (EINVAL);
+#endif
+ error = jail_copyin_ips(j);
+ if (error)
+ return (error);
+#endif
+ break;
+
+ default:
+ /* Sci-Fi jails are not supported, sorry. */
+ return (EINVAL);
+ }
+
+ return (0);
+}
+
+
/*
* struct jail_args {
* struct jail *jail;
@@ -125,22 +417,72 @@ SYSINIT(prison, SI_SUB_INTRINSIC, SI_ORDER_ANY, init_prison, NULL);
int
jail(struct thread *td, struct jail_args *uap)
{
+ uint32_t version;
+ int error;
+ struct jail j;
+
+ error = copyin(uap->jail, &version, sizeof(uint32_t));
+ if (error)
+ return (error);
+
+ switch (version) {
+ case 0:
+ /* FreeBSD single IPv4 jails. */
+ {
+ 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 1:
+ /*
+ * 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. */
+ error = copyin(uap->jail, &j, sizeof(struct jail));
+ if (error)
+ return (error);
+ break;
+
+ default:
+ /* Sci-Fi jails are not supported, sorry. */
+ return (EINVAL);
+ }
+ return (kern_jail(td, &j));
+}
+
+int
+kern_jail(struct thread *td, struct jail *j)
+{
struct nameidata nd;
struct prison *pr, *tpr;
- struct jail j;
struct jail_attach_args jaa;
int vfslocked, error, tryprid;
- error = copyin(uap->jail, &j, sizeof(j));
+ KASSERT(j != NULL, ("%s: j is NULL", __func__));
+
+ /* Handle addresses - convert old structs, copyin, check IPs. */
+ error = jail_handle_ips(j);
if (error)
return (error);
- if (j.version != 0)
- return (EINVAL);
+ /* 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), 0);
+ 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,
@@ -153,16 +495,50 @@ jail(struct thread *td, struct jail_args *uap)
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), 0);
+ error = copyinstr(j->hostname, &pr->pr_host, sizeof(pr->pr_host), NULL);
if (error)
goto e_dropvnref;
- pr->pr_ip = j.ip_number;
+ if (j->jailname != NULL) {
+ error = copyinstr(j->jailname, &pr->pr_name,
+ sizeof(pr->pr_name), NULL);
+ if (error)
+ goto e_dropvnref;
+ }
+ if (j->ip4s > 0) {
+ pr->pr_ip4 = j->ip4;
+ pr->pr_ip4s = j->ip4s;
+ }
+#ifdef INET6
+ if (j->ip6s > 0) {
+ pr->pr_ip6 = j->ip6;
+ pr->pr_ip6s = j->ip6s;
+ }
+#endif
pr->pr_linux = NULL;
pr->pr_securelevel = securelevel;
bzero(&pr->pr_osd, sizeof(pr->pr_osd));
- /* Determine next pr_id and add prison to allprison list. */
+ /*
+ * 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;
+
+ /* Allocate a dedicated cpuset for each jail. */
+ error = cpuset_create_root(td, &pr->pr_cpuset);
+ if (error)
+ goto e_dropvnref;
+
sx_xlock(&allprison_lock);
+ /* Make sure we cannot run into problems with ambiguous bind()ings. */
+ error = prison_check_conflicting_ips(pr);
+ if (error) {
+ sx_xunlock(&allprison_lock);
+ goto e_dropcpuset;
+ }
+
+ /* Determine next pr_id and add prison to allprison list. */
tryprid = lastprid + 1;
if (tryprid == JAIL_MAX)
tryprid = 1;
@@ -173,7 +549,7 @@ next:
if (tryprid == JAIL_MAX) {
sx_xunlock(&allprison_lock);
error = EAGAIN;
- goto e_dropvnref;
+ goto e_dropcpuset;
}
goto next;
}
@@ -196,6 +572,8 @@ e_dropprref:
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);
@@ -203,6 +581,12 @@ e_dropvnref:
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);
}
@@ -238,10 +622,27 @@ jail_attach(struct thread *td, struct jail_attach_args *uap)
sx_sunlock(&allprison_lock);
return (EINVAL);
}
+
+ /*
+ * Do not allow a process to attach to a prison that is not
+ * considered to be "ALIVE".
+ */
+ if (pr->pr_state != PRISON_STATE_ALIVE) {
+ mtx_unlock(&pr->pr_mtx);
+ sx_sunlock(&allprison_lock);
+ return (EINVAL);
+ }
pr->pr_ref++;
mtx_unlock(&pr->pr_mtx);
sx_sunlock(&allprison_lock);
+ /*
+ * Reparent the newly attached process to this jail.
+ */
+ error = cpuset_setproc_update_set(p, pr->pr_cpuset);
+ if (error)
+ goto e_unref;
+
vfslocked = VFS_LOCK_GIANT(pr->pr_root->v_mount);
vn_lock(pr->pr_root, LK_EXCLUSIVE | LK_RETRY);
if ((error = change_dir(pr->pr_root, td)) != 0)
@@ -261,12 +662,14 @@ 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:
VOP_UNLOCK(pr->pr_root, 0);
VFS_UNLOCK_GIANT(vfslocked);
+e_unref:
mtx_lock(&pr->pr_mtx);
pr->pr_ref--;
mtx_unlock(&pr->pr_mtx);
@@ -331,6 +734,8 @@ prison_complete(void *context, int pending)
prisoncount--;
sx_xunlock(&allprison_lock);
+ cpuset_rel(pr->pr_cpuset);
+
/* Free all OSD associated to this jail. */
osd_jail_exit(pr);
@@ -339,8 +744,13 @@ prison_complete(void *context, int pending)
VFS_UNLOCK_GIANT(vfslocked);
mtx_destroy(&pr->pr_mtx);
- if (pr->pr_linux != NULL)
- free(pr->pr_linux, M_PRISON);
+ free(pr->pr_linux, M_PRISON);
+#ifdef INET6
+ free(pr->pr_ip6, M_PRISON);
+#endif
+#ifdef INET
+ free(pr->pr_ip4, M_PRISON);
+#endif
free(pr, M_PRISON);
}
@@ -363,79 +773,364 @@ prison_hold(struct prison *pr)
mtx_unlock(&pr->pr_mtx);
}
-u_int32_t
-prison_getip(struct ucred *cred)
+void
+prison_proc_hold(struct prison *pr)
{
- return (cred->cr_prison->pr_ip);
+ mtx_lock(&pr->pr_mtx);
+ KASSERT(pr->pr_state == PRISON_STATE_ALIVE,
+ ("Cannot add a process to a non-alive prison (id=%d).", pr->pr_id));
+ pr->pr_nprocs++;
+ mtx_unlock(&pr->pr_mtx);
}
+void
+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 (id=%d).", pr->pr_id));
+ pr->pr_nprocs--;
+ if (pr->pr_nprocs == 0)
+ pr->pr_state = PRISON_STATE_DYING;
+ mtx_unlock(&pr->pr_mtx);
+}
+
+
+#ifdef INET
+/*
+ * Pass back primary IPv4 address of this jail.
+ *
+ * If not jailed return success but do not alter the address. Caller has to
+ * make sure to intialize it correctly (INADDR_ANY).
+ *
+ * Returns 0 on success, 1 on error. Address returned in NBO.
+ */
int
-prison_ip(struct ucred *cred, int flag, u_int32_t *ip)
+prison_getip4(struct ucred *cred, struct in_addr *ia)
{
- u_int32_t tmp;
+
+ KASSERT(cred != NULL, ("%s: cred is NULL", __func__));
+ KASSERT(ia != NULL, ("%s: ia is NULL", __func__));
if (!jailed(cred))
+ /* Do not change address passed in. */
return (0);
- if (flag)
- tmp = *ip;
- else
- tmp = ntohl(*ip);
- if (tmp == INADDR_ANY) {
- if (flag)
- *ip = cred->cr_prison->pr_ip;
- else
- *ip = htonl(cred->cr_prison->pr_ip);
+
+ if (cred->cr_prison->pr_ip4 == NULL)
+ return (1);
+
+ ia->s_addr = cred->cr_prison->pr_ip4[0].s_addr;
+ return (0);
+}
+
+/*
+ * Make sure our (source) address is set to something meaningful to this
+ * jail.
+ *
+ * Returns 0 on success, 1 on error. Address passed in in NBO and returned
+ * in NBO.
+ */
+int
+prison_local_ip4(struct ucred *cred, struct in_addr *ia)
+{
+ struct in_addr ia0;
+
+ 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)
+ return (1);
+
+ ia0.s_addr = ntohl(ia->s_addr);
+ if (ia0.s_addr == INADDR_LOOPBACK) {
+ ia->s_addr = cred->cr_prison->pr_ip4[0].s_addr;
return (0);
}
- if (tmp == INADDR_LOOPBACK) {
- if (flag)
- *ip = cred->cr_prison->pr_ip;
- else
- *ip = htonl(cred->cr_prison->pr_ip);
+
+ /*
+ * In case there is only 1 IPv4 address, bind directly.
+ */
+ if (ia0.s_addr == INADDR_ANY && cred->cr_prison->pr_ip4s == 1) {
+ ia->s_addr = cred->cr_prison->pr_ip4[0].s_addr;
return (0);
}
- if (cred->cr_prison->pr_ip != tmp)
+
+ if (ia0.s_addr == INADDR_ANY || prison_check_ip4(cred, ia))
+ return (0);
+
+ return (1);
+}
+
+/*
+ * Rewrite destination address in case we will connect to loopback address.
+ *
+ * Returns 0 on success, 1 on error. Address passed in in NBO and returned
+ * in NBO.
+ */
+int
+prison_remote_ip4(struct ucred *cred, struct in_addr *ia)
+{
+
+ 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)
return (1);
+ if (ntohl(ia->s_addr) == INADDR_LOOPBACK) {
+ ia->s_addr = cred->cr_prison->pr_ip4[0].s_addr;
+ return (0);
+ }
+
+ /*
+ * Return success because nothing had to be changed.
+ */
return (0);
}
-void
-prison_remote_ip(struct ucred *cred, int flag, u_int32_t *ip)
+/*
+ * Check if given address belongs to the jail referenced by cred.
+ *
+ * Returns 1 if address belongs to jail, 0 if not. Address passed in in NBO.
+ */
+static int
+_prison_check_ip4(struct prison *pr, struct in_addr *ia)
{
- u_int32_t tmp;
+ int i, a, z, d;
+
+ if (pr->pr_ip4 == NULL)
+ return (0);
+
+ /*
+ * Check the primary IP.
+ */
+ if (pr->pr_ip4[0].s_addr == ia->s_addr)
+ return (1);
+
+ /*
+ * All the other IPs are sorted so we can do a binary search.
+ */
+ a = 0;
+ z = pr->pr_ip4s - 2;
+ while (a <= z) {
+ i = (a + z) / 2;
+ d = qcmp_v4(&pr->pr_ip4[i+1], ia);
+ if (d > 0)
+ z = i - 1;
+ else if (d < 0)
+ a = i + 1;
+ else
+ return (1);
+ }
+ return (0);
+}
+
+int
+prison_check_ip4(struct ucred *cred, struct in_addr *ia)
+{
+
+ KASSERT(cred != NULL, ("%s: cred is NULL", __func__));
+ KASSERT(ia != NULL, ("%s: ia is NULL", __func__));
if (!jailed(cred))
- return;
- if (flag)
- tmp = *ip;
- else
- tmp = ntohl(*ip);
- if (tmp == INADDR_LOOPBACK) {
- if (flag)
- *ip = cred->cr_prison->pr_ip;
+ return (1);
+
+ return (_prison_check_ip4(cred->cr_prison, ia));
+}
+#endif
+
+#ifdef INET6
+/*
+ * Pass back primary IPv6 address for this jail.
+ *
+ * If not jailed return success but do not alter the address. Caller has to
+ * make sure to intialize it correctly (IN6ADDR_ANY_INIT).
+ *
+ * Returns 0 on success, 1 on error.
+ */
+int
+prison_getip6(struct ucred *cred, struct in6_addr *ia6)
+{
+
+ 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)
+ return (1);
+ bcopy(&cred->cr_prison->pr_ip6[0], ia6, sizeof(struct in6_addr));
+ return (0);
+}
+
+/*
+ * Make sure our (source) address is set to something meaningful to this jail.
+ *
+ * v6only should be set based on (inp->inp_flags & IN6P_IPV6_V6ONLY != 0)
+ * when needed while binding.
+ *
+ * Returns 0 on success, 1 on error.
+ */
+int
+prison_local_ip6(struct ucred *cred, struct in6_addr *ia6, int v6only)
+{
+
+ 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)
+ return (1);
+ if (IN6_IS_ADDR_LOOPBACK(ia6)) {
+ bcopy(&cred->cr_prison->pr_ip6[0], ia6,
+ sizeof(struct in6_addr));
+ return (0);
+ }
+
+ /*
+ * In case there is only 1 IPv6 address, and v6only is true, then
+ * bind directly.
+ */
+ if (v6only != 0 && IN6_IS_ADDR_UNSPECIFIED(ia6) &&
+ cred->cr_prison->pr_ip6s == 1) {
+ bcopy(&cred->cr_prison->pr_ip6[0], ia6,
+ sizeof(struct in6_addr));
+ return (0);
+ }
+ if (IN6_IS_ADDR_UNSPECIFIED(ia6) || prison_check_ip6(cred, ia6))
+ return (0);
+ return (1);
+}
+
+/*
+ * Rewrite destination address in case we will connect to loopback address.
+ *
+ * Returns 0 on success, 1 on error.
+ */
+int
+prison_remote_ip6(struct ucred *cred, struct in6_addr *ia6)
+{
+
+ 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)
+ return (1);
+ if (IN6_IS_ADDR_LOOPBACK(ia6)) {
+ bcopy(&cred->cr_prison->pr_ip6[0], ia6,
+ sizeof(struct in6_addr));
+ return (0);
+ }
+
+ /*
+ * Return success because nothing had to be changed.
+ */
+ return (0);
+}
+
+/*
+ * Check if given address belongs to the jail referenced by cred.
+ *
+ * Returns 1 if address belongs to jail, 0 if not.
+ */
+static int
+_prison_check_ip6(struct prison *pr, struct in6_addr *ia6)
+{
+ int i, a, z, d;
+
+ if (pr->pr_ip6 == NULL)
+ return (0);
+
+ /*
+ * Check the primary IP.
+ */
+ if (IN6_ARE_ADDR_EQUAL(&pr->pr_ip6[0], ia6))
+ return (1);
+
+ /*
+ * All the other IPs are sorted so we can do a binary search.
+ */
+ a = 0;
+ z = pr->pr_ip6s - 2;
+ while (a <= z) {
+ i = (a + z) / 2;
+ d = qcmp_v6(&pr->pr_ip6[i+1], ia6);
+ if (d > 0)
+ z = i - 1;
+ else if (d < 0)
+ a = i + 1;
else
- *ip = htonl(cred->cr_prison->pr_ip);
- return;
+ return (1);
}
- return;
+ return (0);
}
int
+prison_check_ip6(struct ucred *cred, struct in6_addr *ia6)
+{
+
+ KASSERT(cred != NULL, ("%s: cred is NULL", __func__));
+ KASSERT(ia6 != NULL, ("%s: ia6 is NULL", __func__));
+
+ if (!jailed(cred))
+ return (1);
+
+ return (_prison_check_ip6(cred->cr_prison, ia6));
+}
+#endif
+
+/*
+ * Check if given address belongs to the jail referenced by cred (wrapper to
+ * prison_check_ip[46]).
+ *
+ * Returns 1 if address belongs to jail, 0 if not. IPv4 Address passed in in
+ * NBO.
+ */
+int
prison_if(struct ucred *cred, struct sockaddr *sa)
{
+#ifdef INET
struct sockaddr_in *sai;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 *sai6;
+#endif
int ok;
- sai = (struct sockaddr_in *)sa;
- if ((sai->sin_family != AF_INET) && jail_socket_unixiproute_only)
- ok = 1;
- else if (sai->sin_family != AF_INET)
- ok = 0;
- else if (cred->cr_prison->pr_ip != ntohl(sai->sin_addr.s_addr))
- ok = 1;
- else
- ok = 0;
+ KASSERT(cred != NULL, ("%s: cred is NULL", __func__));
+ KASSERT(sa != NULL, ("%s: sa is NULL", __func__));
+
+ ok = 0;
+ switch(sa->sa_family)
+ {
+#ifdef INET
+ case AF_INET:
+ sai = (struct sockaddr_in *)sa;
+ if (prison_check_ip4(cred, &sai->sin_addr))
+ ok = 1;
+ break;
+
+#endif
+#ifdef INET6
+ case AF_INET6:
+ sai6 = (struct sockaddr_in6 *)sa;
+ if (prison_check_ip6(cred, (struct in6_addr *)&sai6->sin6_addr))
+ ok = 1;
+ break;
+
+#endif
+ default:
+ if (!jail_socket_unixiproute_only)
+ ok = 1;
+ }
return (ok);
}
@@ -655,6 +1350,7 @@ prison_priv_check(struct ucred *cred, int priv)
* processes in the same jail. Likewise for signalling.
*/
case PRIV_SCHED_DIFFCRED:
+ case PRIV_SCHED_CPUSET:
case PRIV_SIGNAL_DIFFCRED:
case PRIV_SIGNAL_SUGID:
@@ -764,6 +1460,8 @@ sysctl_jail_list(SYSCTL_HANDLER_ARGS)
{
struct xprison *xp, *sxp;
struct prison *pr;
+ char *p;
+ size_t len;
int count, error;
if (jailed(req->td->td_ucred))
@@ -775,21 +1473,54 @@ sysctl_jail_list(SYSCTL_HANDLER_ARGS)
return (0);
}
- sxp = xp = malloc(sizeof(*xp) * count, M_TEMP, M_WAITOK | M_ZERO);
+ len = sizeof(*xp) * count;
+ LIST_FOREACH(pr, &allprison, pr_list) {
+#ifdef INET
+ len += pr->pr_ip4s * sizeof(struct in_addr);
+#endif
+#ifdef INET6
+ len += pr->pr_ip6s * sizeof(struct in6_addr);
+#endif
+ }
+
+ sxp = xp = malloc(len, M_TEMP, M_WAITOK | M_ZERO);
LIST_FOREACH(pr, &allprison, pr_list) {
xp->pr_version = XPRISON_VERSION;
xp->pr_id = pr->pr_id;
- xp->pr_ip = pr->pr_ip;
+ xp->pr_state = pr->pr_state;
+ xp->pr_cpusetid = pr->pr_cpuset->cs_id;
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);
- xp++;
+#ifdef INET
+ xp->pr_ip4s = pr->pr_ip4s;
+#endif
+#ifdef INET6
+ xp->pr_ip6s = pr->pr_ip6s;
+#endif
+ p = (char *)(xp + 1);
+#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));
+ }
+#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));
+ }
+#endif
+ xp = (struct xprison *)p;
}
sx_sunlock(&allprison_lock);
- error = SYSCTL_OUT(req, sxp, sizeof(*sxp) * count);
+ error = SYSCTL_OUT(req, sxp, len);
free(sxp, M_TEMP);
return (error);
}
@@ -809,3 +1540,60 @@ sysctl_jail_jailed(SYSCTL_HANDLER_ARGS)
}
SYSCTL_PROC(_security_jail, OID_AUTO, jailed, CTLTYPE_INT | CTLFLAG_RD,
NULL, 0, sysctl_jail_jailed, "I", "Process in jail?");
+
+#ifdef DDB
+DB_SHOW_COMMAND(jails, db_show_jails)
+{
+ struct prison *pr;
+#ifdef INET
+ struct in_addr ia;
+#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 != NULL) ? pr->pr_name : "", state);
+ db_printf("%6s %-6d\n",
+ "", pr->pr_cpuset->cs_id);
+#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));
+ }
+#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;
+ }
+}
+#endif /* DDB */
OpenPOWER on IntegriCloud