Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F136031804
D33339.id99721.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
16 KB
Referenced Files
None
Subscribers
None
D33339.id99721.diff
View Options
Index: sys/kern/kern_jail.c
===================================================================
--- sys/kern/kern_jail.c
+++ sys/kern/kern_jail.c
@@ -45,6 +45,7 @@
#include <sys/osd.h>
#include <sys/priv.h>
#include <sys/proc.h>
+#include <sys/epoch.h>
#include <sys/taskqueue.h>
#include <sys/fcntl.h>
#include <sys/jail.h>
@@ -508,6 +509,170 @@
return (error);
}
+/*
+ * Network address lists (pr_ip4, pr_ip6) allocation for jails. The addresses
+ * are accessed locklessly by the network stack, thus need to be protected by
+ * the network epoch.
+ */
+static void *
+prison_ip_alloc(const sa_family_t af, uint32_t cnt, int flags)
+{
+ void *mem;
+
+ mem = malloc(sizeof(struct epoch_context) + cnt *
+ (af == AF_INET ? sizeof(struct in_addr) : sizeof(struct in6_addr)),
+ M_PRISON, flags);
+ return (mem != NULL ? (epoch_context_t)mem + 1 : NULL);
+}
+
+static void
+prison_ip_free_deferred(epoch_context_t ctx)
+{
+
+ free(ctx, M_PRISON);
+}
+
+static void
+prison_ip_free(void *mem)
+{
+
+ if (mem != NULL)
+ NET_EPOCH_CALL(prison_ip_free_deferred,
+ (epoch_context_t)mem - 1);
+}
+
+static void
+prison_ip_set(struct prison *pr, const sa_family_t af, void *new,
+ uint32_t newcnt)
+{
+ uint32_t *cnt;
+ void **mem, *old;
+
+ mtx_assert(&pr->pr_mtx, MA_OWNED);
+
+ cnt = af == AF_INET ? &pr->pr_ip4s : &pr->pr_ip6s;
+ mem = af == AF_INET ? (void *)&pr->pr_ip4 : (void *)&pr->pr_ip6;
+
+ /*
+ * prison_check_ip[46]_locked first reads the count and then processes
+ * through the list. To prevent out of bounds read, we make sure
+ * the number read would be <= length of the list.
+ */
+ old = *mem;
+ if (newcnt >= *cnt) {
+ ck_pr_store_ptr(mem, new);
+ ck_pr_store_32(cnt, newcnt);
+ } else {
+ ck_pr_store_32(cnt, newcnt);
+ ck_pr_store_ptr(mem, new);
+ }
+ prison_ip_free(old);
+}
+
+/*
+ * Restrict a prison's IP address list with its parent's, possibly replacing
+ * it. Return true if the replacement buffer was used (or would have been).
+ */
+static bool
+prison_ip_restrict(struct prison *pr, const sa_family_t af, void *new)
+{
+ const unsigned ip_flag = af == AF_INET ? PR_IP4_USER : PR_IP6_USER;
+ const size_t size = af == AF_INET ?
+ sizeof(struct in_addr) : sizeof(struct in6_addr);
+ int (*const cmp)(const void *, const void *) = af == AF_INET ?
+ prison_qcmp_v4 : prison_qcmp_v6;
+ struct prison *ppr;
+ uint32_t ips;
+ bool alloced;
+
+#define PR_IPS(pr) (af == AF_INET ? (pr)->pr_ip4s : (pr)->pr_ip6s)
+#define PR_IP(pr, idx) (af == AF_INET ? \
+ (void *)(&(pr)->pr_ip4[idx]) : \
+ (void *)(&(pr)->pr_ip6[idx]))
+
+ mtx_assert(&pr->pr_mtx, MA_OWNED);
+
+ ppr = pr->pr_parent;
+ /*
+ * Due to epoch-synchronized access to the IP address lists we always
+ * allocate a new list even if the old one has enough space. We could
+ * atomically update an IPv4 address inside a list, but that would
+ * screw up sorting, and in case of IPv6 we can't even atomically write
+ * one.
+ */
+ ips = (pr->pr_flags & ip_flag) ? PR_IPS(pr) : PR_IPS(ppr);
+ if (ips == 0) {
+ prison_ip_set(pr, af, NULL, 0);
+ return (false);
+ }
+ if (new == NULL) {
+ new = prison_ip_alloc(af, ips, M_NOWAIT);
+ if (new == NULL)
+ return (true);
+ alloced = true;
+ } else
+ alloced = false;
+ if (!(pr->pr_flags & ip_flag)) {
+ /* This has no user settings, so just copy the parent's list. */
+ bcopy(PR_IP(ppr, 0), new, ips * size);
+ } else {
+ /* Remove addresses that aren't in the parent. */
+ void *p = new;
+ int i;
+
+ i = 0; /* index in PR_IP(pr) */
+ ips = 0; /* how many we copied to new */
+
+ for (int pi = 0; pi < PR_IPS(ppr); pi++)
+ if (cmp(PR_IP(pr, 0), PR_IP(ppr, pi)) == 0) {
+ /* Found our main address in parent. */
+ bcopy(PR_IP(pr, i), p, size);
+ p = (char *)p + size;
+ i++;
+ ips++;
+ break;
+ }
+ for (int pi = 1; i < PR_IPS(pr); ) {
+ /* Check against main, which is unsorted. */
+ if (cmp(PR_IP(pr, i), PR_IP(ppr, 0)) == 0) {
+ /* Matches parent's main address. */
+ bcopy(PR_IP(pr, i), p, size);
+ p = (char *)p + size;
+ i++;
+ ips++;
+ continue;
+ }
+ /* The rest are sorted. */
+ switch (pi >= PR_IPS(ppr) ? -1 :
+ cmp(PR_IP(pr, i), PR_IP(ppr, pi))) {
+ case -1:
+ i++;
+ break;
+ case 0:
+ bcopy(PR_IP(pr, i), p, size);
+ p = (char *)p + size;
+ i++;
+ pi++;
+ ips++;
+ break;
+ case 1:
+ pi++;
+ break;
+ }
+ }
+ if (ips == 0) {
+ if (alloced)
+ prison_ip_free(new);
+ new = NULL;
+ }
+ }
+ prison_ip_set(pr, af, new, ips);
+ return (new != NULL ? true : false);
+#undef PR_IPS
+#undef PR_IP
+}
+
+
int
kern_jail_set(struct thread *td, struct uio *optuio, int flags)
{
@@ -813,7 +978,7 @@
vfs_opterror(opts, "too many IPv4 addresses");
goto done_errmsg;
}
- ip4 = malloc(ip4s * sizeof(*ip4), M_PRISON, M_WAITOK);
+ ip4 = prison_ip_alloc(AF_INET, ip4s, M_WAITOK);
bcopy(op, ip4, ip4s * sizeof(*ip4));
/*
* IP addresses are all sorted but ip[0] to preserve
@@ -870,7 +1035,7 @@
vfs_opterror(opts, "too many IPv6 addresses");
goto done_errmsg;
}
- ip6 = malloc(ip6s * sizeof(*ip6), M_PRISON, M_WAITOK);
+ ip6 = prison_ip_alloc(AF_INET6, ip6s, M_WAITOK);
bcopy(op, ip6, ip6s * sizeof(*ip6));
if (ip6s > 1)
qsort(ip6 + 1, ip6s - 1, sizeof(*ip6),
@@ -1255,9 +1420,8 @@
pr->pr_flags |= ppr->pr_flags & PR_IP4;
if (ppr->pr_ip4 != NULL) {
pr->pr_ip4s = ppr->pr_ip4s;
- pr->pr_ip4 = malloc(pr->pr_ip4s *
- sizeof(struct in_addr), M_PRISON,
- M_WAITOK);
+ pr->pr_ip4 = prison_ip_alloc(AF_INET,
+ pr->pr_ip4s, M_WAITOK);
bcopy(ppr->pr_ip4, pr->pr_ip4,
pr->pr_ip4s * sizeof(*pr->pr_ip4));
}
@@ -1270,9 +1434,8 @@
pr->pr_flags |= ppr->pr_flags & PR_IP6;
if (ppr->pr_ip6 != NULL) {
pr->pr_ip6s = ppr->pr_ip6s;
- pr->pr_ip6 = malloc(pr->pr_ip6s *
- sizeof(struct in6_addr), M_PRISON,
- M_WAITOK);
+ pr->pr_ip6 = prison_ip_alloc(AF_INET6,
+ pr->pr_ip6s, M_WAITOK);
bcopy(ppr->pr_ip6, pr->pr_ip6,
pr->pr_ip6s * sizeof(*pr->pr_ip6));
}
@@ -1592,9 +1755,7 @@
redo_ip4 = 0;
if (pr_flags & PR_IP4_USER) {
pr->pr_flags |= PR_IP4;
- free(pr->pr_ip4, M_PRISON);
- pr->pr_ip4s = ip4s;
- pr->pr_ip4 = ip4;
+ prison_ip_set(pr, AF_INET, ip4, ip4s);
ip4 = NULL;
FOREACH_PRISON_DESCENDANT_LOCKED(pr, tpr, descend) {
#ifdef VIMAGE
@@ -1603,7 +1764,7 @@
continue;
}
#endif
- if (prison_restrict_ip4(tpr, NULL)) {
+ if (prison_ip_restrict(tpr, AF_INET, NULL)) {
redo_ip4 = 1;
descend = 0;
}
@@ -1614,9 +1775,7 @@
redo_ip6 = 0;
if (pr_flags & PR_IP6_USER) {
pr->pr_flags |= PR_IP6;
- free(pr->pr_ip6, M_PRISON);
- pr->pr_ip6s = ip6s;
- pr->pr_ip6 = ip6;
+ prison_ip_set(pr, AF_INET6, ip6, ip6s);
ip6 = NULL;
FOREACH_PRISON_DESCENDANT_LOCKED(pr, tpr, descend) {
#ifdef VIMAGE
@@ -1625,7 +1784,7 @@
continue;
}
#endif
- if (prison_restrict_ip6(tpr, NULL)) {
+ if (prison_ip_restrict(tpr, AF_INET6, NULL)) {
redo_ip6 = 1;
descend = 0;
}
@@ -1770,7 +1929,7 @@
#ifdef INET
while (redo_ip4) {
ip4s = pr->pr_ip4s;
- ip4 = malloc(ip4s * sizeof(*ip4), M_PRISON, M_WAITOK);
+ ip4 = prison_ip_alloc(AF_INET, ip4s, M_WAITOK);
mtx_lock(&pr->pr_mtx);
redo_ip4 = 0;
FOREACH_PRISON_DESCENDANT_LOCKED(pr, tpr, descend) {
@@ -1780,7 +1939,7 @@
continue;
}
#endif
- if (prison_restrict_ip4(tpr, ip4)) {
+ if (prison_ip_restrict(tpr, AF_INET, ip4)) {
if (ip4 != NULL)
ip4 = NULL;
else
@@ -1793,7 +1952,7 @@
#ifdef INET6
while (redo_ip6) {
ip6s = pr->pr_ip6s;
- ip6 = malloc(ip6s * sizeof(*ip6), M_PRISON, M_WAITOK);
+ ip6 = prison_ip_alloc(AF_INET6, ip6s, M_WAITOK);
mtx_lock(&pr->pr_mtx);
redo_ip6 = 0;
FOREACH_PRISON_DESCENDANT_LOCKED(pr, tpr, descend) {
@@ -1803,7 +1962,7 @@
continue;
}
#endif
- if (prison_restrict_ip6(tpr, ip6)) {
+ if (prison_ip_restrict(tpr, AF_INET6, ip6)) {
if (ip6 != NULL)
ip6 = NULL;
else
@@ -1889,10 +2048,10 @@
}
done_free:
#ifdef INET
- free(ip4, M_PRISON);
+ prison_ip_free(ip4);
#endif
#ifdef INET6
- free(ip6, M_PRISON);
+ prison_ip_free(ip6);
#endif
if (g_path != NULL)
free(g_path, M_TEMP);
@@ -2852,10 +3011,10 @@
vrele(rpr->pr_root);
mtx_destroy(&rpr->pr_mtx);
#ifdef INET
- free(rpr->pr_ip4, M_PRISON);
+ prison_ip_free(rpr->pr_ip4);
#endif
#ifdef INET6
- free(rpr->pr_ip6, M_PRISON);
+ prison_ip_free(rpr->pr_ip6);
#endif
if (rpr->pr_cpuset != NULL)
cpuset_rel(rpr->pr_cpuset);
Index: sys/netinet/in_jail.c
===================================================================
--- sys/netinet/in_jail.c
+++ sys/netinet/in_jail.c
@@ -43,6 +43,7 @@
#include <sys/osd.h>
#include <sys/priv.h>
#include <sys/proc.h>
+#include <sys/epoch.h>
#include <sys/taskqueue.h>
#include <sys/fcntl.h>
#include <sys/jail.h>
@@ -90,90 +91,6 @@
return (0);
}
-/*
- * Restrict a prison's IP address list with its parent's, possibly replacing
- * it. Return true if the replacement buffer was used (or would have been).
- */
-int
-prison_restrict_ip4(struct prison *pr, struct in_addr *newip4)
-{
- int ii, ij, used;
- struct prison *ppr;
-
- ppr = pr->pr_parent;
- if (!(pr->pr_flags & PR_IP4_USER)) {
- /* This has no user settings, so just copy the parent's list. */
- if (pr->pr_ip4s < ppr->pr_ip4s) {
- /*
- * There's no room for the parent's list. Use the
- * new list buffer, which is assumed to be big enough
- * (if it was passed). If there's no buffer, try to
- * allocate one.
- */
- used = 1;
- if (newip4 == NULL) {
- newip4 = malloc(ppr->pr_ip4s * sizeof(*newip4),
- M_PRISON, M_NOWAIT);
- if (newip4 != NULL)
- used = 0;
- }
- if (newip4 != NULL) {
- bcopy(ppr->pr_ip4, newip4,
- ppr->pr_ip4s * sizeof(*newip4));
- free(pr->pr_ip4, M_PRISON);
- pr->pr_ip4 = newip4;
- pr->pr_ip4s = ppr->pr_ip4s;
- }
- return (used);
- }
- pr->pr_ip4s = ppr->pr_ip4s;
- if (pr->pr_ip4s > 0)
- bcopy(ppr->pr_ip4, pr->pr_ip4,
- pr->pr_ip4s * sizeof(*newip4));
- else if (pr->pr_ip4 != NULL) {
- free(pr->pr_ip4, M_PRISON);
- pr->pr_ip4 = NULL;
- }
- } else if (pr->pr_ip4s > 0) {
- /* Remove addresses that aren't in the parent. */
- for (ij = 0; ij < ppr->pr_ip4s; ij++)
- if (pr->pr_ip4[0].s_addr == ppr->pr_ip4[ij].s_addr)
- break;
- if (ij < ppr->pr_ip4s)
- ii = 1;
- else {
- bcopy(pr->pr_ip4 + 1, pr->pr_ip4,
- --pr->pr_ip4s * sizeof(*pr->pr_ip4));
- ii = 0;
- }
- for (ij = 1; ii < pr->pr_ip4s; ) {
- if (pr->pr_ip4[ii].s_addr == ppr->pr_ip4[0].s_addr) {
- ii++;
- continue;
- }
- switch (ij >= ppr->pr_ip4s ? -1 :
- prison_qcmp_v4(&pr->pr_ip4[ii], &ppr->pr_ip4[ij])) {
- case -1:
- bcopy(pr->pr_ip4 + ii + 1, pr->pr_ip4 + ii,
- (--pr->pr_ip4s - ii) * sizeof(*pr->pr_ip4));
- break;
- case 0:
- ii++;
- ij++;
- break;
- case 1:
- ij++;
- break;
- }
- }
- if (pr->pr_ip4s == 0) {
- free(pr->pr_ip4, M_PRISON);
- pr->pr_ip4 = NULL;
- }
- }
- return (0);
-}
-
/*
* Pass back primary IPv4 address of this jail.
*
@@ -376,22 +293,33 @@
int
prison_check_ip4_locked(const struct prison *pr, const struct in_addr *ia)
{
- int i, a, z, d;
+ const struct in_addr *ip4;
+ int ip4s, i, a, z, d;
+
+ MPASS(mtx_owned(&pr->pr_mtx) || in_epoch(net_epoch_preempt));
+
+ if (!(pr->pr_flags & PR_IP4))
+ return (0);
+
+ ip4s = ck_pr_load_32(&pr->pr_ip4s);
+ ip4 = ck_pr_load_ptr(&pr->pr_ip4);
+ if (ip4 == NULL)
+ return (EAFNOSUPPORT);
/*
* Check the primary IP.
*/
- if (pr->pr_ip4[0].s_addr == ia->s_addr)
+ if (ip4[0].s_addr == ia->s_addr)
return (0);
/*
* All the other IPs are sorted so we can do a binary search.
*/
a = 0;
- z = pr->pr_ip4s - 2;
+ z = ip4s - 2;
while (a <= z) {
i = (a + z) / 2;
- d = prison_qcmp_v4(&pr->pr_ip4[i+1], ia);
+ d = prison_qcmp_v4(&ip4[i+1], ia);
if (d > 0)
z = i - 1;
else if (d < 0)
Index: sys/netinet6/in6_jail.c
===================================================================
--- sys/netinet6/in6_jail.c
+++ sys/netinet6/in6_jail.c
@@ -43,6 +43,7 @@
#include <sys/osd.h>
#include <sys/priv.h>
#include <sys/proc.h>
+#include <sys/epoch.h>
#include <sys/taskqueue.h>
#include <sys/fcntl.h>
#include <sys/jail.h>
@@ -84,88 +85,6 @@
return (rc);
}
-int
-prison_restrict_ip6(struct prison *pr, struct in6_addr *newip6)
-{
- int ii, ij, used;
- struct prison *ppr;
-
- ppr = pr->pr_parent;
- if (!(pr->pr_flags & PR_IP6_USER)) {
- /* This has no user settings, so just copy the parent's list. */
- if (pr->pr_ip6s < ppr->pr_ip6s) {
- /*
- * There's no room for the parent's list. Use the
- * new list buffer, which is assumed to be big enough
- * (if it was passed). If there's no buffer, try to
- * allocate one.
- */
- used = 1;
- if (newip6 == NULL) {
- newip6 = malloc(ppr->pr_ip6s * sizeof(*newip6),
- M_PRISON, M_NOWAIT);
- if (newip6 != NULL)
- used = 0;
- }
- if (newip6 != NULL) {
- bcopy(ppr->pr_ip6, newip6,
- ppr->pr_ip6s * sizeof(*newip6));
- free(pr->pr_ip6, M_PRISON);
- pr->pr_ip6 = newip6;
- pr->pr_ip6s = ppr->pr_ip6s;
- }
- return (used);
- }
- pr->pr_ip6s = ppr->pr_ip6s;
- if (pr->pr_ip6s > 0)
- bcopy(ppr->pr_ip6, pr->pr_ip6,
- pr->pr_ip6s * sizeof(*newip6));
- else if (pr->pr_ip6 != NULL) {
- free(pr->pr_ip6, M_PRISON);
- pr->pr_ip6 = NULL;
- }
- } else if (pr->pr_ip6s > 0) {
- /* Remove addresses that aren't in the parent. */
- for (ij = 0; ij < ppr->pr_ip6s; ij++)
- if (IN6_ARE_ADDR_EQUAL(&pr->pr_ip6[0],
- &ppr->pr_ip6[ij]))
- break;
- if (ij < ppr->pr_ip6s)
- ii = 1;
- else {
- bcopy(pr->pr_ip6 + 1, pr->pr_ip6,
- --pr->pr_ip6s * sizeof(*pr->pr_ip6));
- ii = 0;
- }
- for (ij = 1; ii < pr->pr_ip6s; ) {
- if (IN6_ARE_ADDR_EQUAL(&pr->pr_ip6[ii],
- &ppr->pr_ip6[0])) {
- ii++;
- continue;
- }
- switch (ij >= ppr->pr_ip6s ? -1 :
- prison_qcmp_v6(&pr->pr_ip6[ii], &ppr->pr_ip6[ij])) {
- case -1:
- bcopy(pr->pr_ip6 + ii + 1, pr->pr_ip6 + ii,
- (--pr->pr_ip6s - ii) * sizeof(*pr->pr_ip6));
- break;
- case 0:
- ii++;
- ij++;
- break;
- case 1:
- ij++;
- break;
- }
- }
- if (pr->pr_ip6s == 0) {
- free(pr->pr_ip6, M_PRISON);
- pr->pr_ip6 = NULL;
- }
- }
- return 0;
-}
-
/*
* Pass back primary IPv6 address for this jail.
*
@@ -362,22 +281,33 @@
int
prison_check_ip6_locked(const struct prison *pr, const struct in6_addr *ia6)
{
- int i, a, z, d;
+ const struct in6_addr *ip6;
+ int ip6s, i, a, z, d;
+
+ MPASS(mtx_owned(&pr->pr_mtx) || in_epoch(net_epoch_preempt));
+
+ if (!(pr->pr_flags & PR_IP6))
+ return (0);
+
+ ip6s = ck_pr_load_32(&pr->pr_ip6s);
+ ip6 = ck_pr_load_ptr(&pr->pr_ip6);
+ if (ip6 == NULL)
+ return (EAFNOSUPPORT);
/*
* Check the primary IP.
*/
- if (IN6_ARE_ADDR_EQUAL(&pr->pr_ip6[0], ia6))
+ if (IN6_ARE_ADDR_EQUAL(&ip6[0], ia6))
return (0);
/*
* All the other IPs are sorted so we can do a binary search.
*/
a = 0;
- z = pr->pr_ip6s - 2;
+ z = ip6s - 2;
while (a <= z) {
i = (a + z) / 2;
- d = prison_qcmp_v6(&pr->pr_ip6[i+1], ia6);
+ d = prison_qcmp_v6(&ip6[i+1], ia6);
if (d > 0)
z = i - 1;
else if (d < 0)
Index: sys/sys/jail.h
===================================================================
--- sys/sys/jail.h
+++ sys/sys/jail.h
@@ -177,8 +177,8 @@
struct cpuset *pr_cpuset; /* (p) cpuset */
struct vnet *pr_vnet; /* (c) network stack */
struct vnode *pr_root; /* (c) vnode to rdir */
- int pr_ip4s; /* (p) number of v4 IPs */
- int pr_ip6s; /* (p) number of v6 IPs */
+ uint32_t pr_ip4s; /* (p) number of v4 IPs */
+ uint32_t pr_ip6s; /* (p) number of v6 IPs */
struct in_addr *pr_ip4; /* (p) v4 IPs of jail */
struct in6_addr *pr_ip6; /* (p) v6 IPs of jail */
struct prison_racct *pr_prison_racct; /* (c) racct jail proxy */
@@ -439,7 +439,6 @@
int prison_check_ip4(const struct ucred *, const struct in_addr *);
int prison_check_ip4_locked(const struct prison *, const struct in_addr *);
int prison_saddrsel_ip4(struct ucred *, struct in_addr *);
-int prison_restrict_ip4(struct prison *, struct in_addr *);
int prison_qcmp_v4(const void *, const void *);
#ifdef INET6
int prison_equal_ip6(struct prison *, struct prison *);
@@ -449,7 +448,6 @@
int prison_check_ip6(const struct ucred *, const struct in6_addr *);
int prison_check_ip6_locked(const struct prison *, const struct in6_addr *);
int prison_saddrsel_ip6(struct ucred *, struct in6_addr *);
-int prison_restrict_ip6(struct prison *, struct in6_addr *);
int prison_qcmp_v6(const void *, const void *);
#endif
int prison_check_af(struct ucred *cred, int af);
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Nov 16, 6:24 AM (54 m, 7 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
25355578
Default Alt Text
D33339.id99721.diff (16 KB)
Attached To
Mode
D33339: jail: network epoch protection for IP address lists
Attached
Detach File
Event Timeline
Log In to Comment