diff options
Diffstat (limited to 'sys/kern/kern_jail.c')
-rw-r--r-- | sys/kern/kern_jail.c | 908 |
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 */ |