summaryrefslogtreecommitdiffstats
path: root/sys/kern
diff options
context:
space:
mode:
authormarkj <markj@FreeBSD.org>2016-09-02 00:14:28 +0000
committermarkj <markj@FreeBSD.org>2016-09-02 00:14:28 +0000
commita80af232a432914d0c8e6a0c45184d0efcbf1a98 (patch)
tree2dbfbfd4ea4c0bdeb3e429a1c59472bb0a31aa02 /sys/kern
parenta79616dbee6ff950597905ba262f9d753ad202d3 (diff)
downloadFreeBSD-src-a80af232a432914d0c8e6a0c45184d0efcbf1a98.zip
FreeBSD-src-a80af232a432914d0c8e6a0c45184d0efcbf1a98.tar.gz
MFC r285522:
Fix cleanup race between unp_dispose and unp_gc. This change modifies the original commit to avoid changing the domain_dispose KPI. Tested by: Oliver Pinter
Diffstat (limited to 'sys/kern')
-rw-r--r--sys/kern/uipc_socket.c29
-rw-r--r--sys/kern/uipc_usrreq.c31
2 files changed, 45 insertions, 15 deletions
diff --git a/sys/kern/uipc_socket.c b/sys/kern/uipc_socket.c
index 23acdf7..eb83617 100644
--- a/sys/kern/uipc_socket.c
+++ b/sys/kern/uipc_socket.c
@@ -741,8 +741,12 @@ sofree(struct socket *so)
ACCEPT_UNLOCK();
VNET_SO_ASSERT(so);
- if (pr->pr_flags & PR_RIGHTS && pr->pr_domain->dom_dispose != NULL)
- (*pr->pr_domain->dom_dispose)(so->so_rcv.sb_mb);
+ if (pr->pr_flags & PR_RIGHTS && pr->pr_domain->dom_dispose != NULL) {
+ if (pr->pr_domain->dom_family == AF_LOCAL)
+ unp_dispose_so(so);
+ else
+ (*pr->pr_domain->dom_dispose)(so->so_rcv.sb_mb);
+ }
if (pr->pr_usrreqs->pru_detach != NULL)
(*pr->pr_usrreqs->pru_detach)(so);
@@ -2290,7 +2294,7 @@ sorflush(struct socket *so)
{
struct sockbuf *sb = &so->so_rcv;
struct protosw *pr = so->so_proto;
- struct sockbuf asb;
+ struct socket aso;
VNET_SO_ASSERT(so);
@@ -2315,8 +2319,9 @@ sorflush(struct socket *so)
* and mutex data unchanged.
*/
SOCKBUF_LOCK(sb);
- bzero(&asb, offsetof(struct sockbuf, sb_startzero));
- bcopy(&sb->sb_startzero, &asb.sb_startzero,
+ bzero(&aso, sizeof(aso));
+ aso.so_pcb = so->so_pcb;
+ bcopy(&sb->sb_startzero, &aso.so_rcv.sb_startzero,
sizeof(*sb) - offsetof(struct sockbuf, sb_startzero));
bzero(&sb->sb_startzero,
sizeof(*sb) - offsetof(struct sockbuf, sb_startzero));
@@ -2324,12 +2329,16 @@ sorflush(struct socket *so)
sbunlock(sb);
/*
- * Dispose of special rights and flush the socket buffer. Don't call
- * any unsafe routines (that rely on locks being initialized) on asb.
+ * Dispose of special rights and flush the copied socket. Don't call
+ * any unsafe routines (that rely on locks being initialized) on aso.
*/
- if (pr->pr_flags & PR_RIGHTS && pr->pr_domain->dom_dispose != NULL)
- (*pr->pr_domain->dom_dispose)(asb.sb_mb);
- sbrelease_internal(&asb, so);
+ if (pr->pr_flags & PR_RIGHTS && pr->pr_domain->dom_dispose != NULL) {
+ if (pr->pr_domain->dom_family == AF_LOCAL)
+ unp_dispose_so(&aso);
+ else
+ (*pr->pr_domain->dom_dispose)(aso.so_rcv.sb_mb);
+ }
+ sbrelease_internal(&aso.so_rcv, so);
}
/*
diff --git a/sys/kern/uipc_usrreq.c b/sys/kern/uipc_usrreq.c
index ed9817c..80d701f 100644
--- a/sys/kern/uipc_usrreq.c
+++ b/sys/kern/uipc_usrreq.c
@@ -2200,15 +2200,19 @@ unp_gc_process(struct unpcb *unp)
* Mark all sockets we reference with RIGHTS.
*/
so = unp->unp_socket;
- SOCKBUF_LOCK(&so->so_rcv);
- unp_scan(so->so_rcv.sb_mb, unp_accessable);
- SOCKBUF_UNLOCK(&so->so_rcv);
+ if ((unp->unp_gcflag & UNPGC_IGNORE_RIGHTS) == 0) {
+ SOCKBUF_LOCK(&so->so_rcv);
+ unp_scan(so->so_rcv.sb_mb, unp_accessable);
+ SOCKBUF_UNLOCK(&so->so_rcv);
+ }
/*
* Mark all sockets in our accept queue.
*/
ACCEPT_LOCK();
TAILQ_FOREACH(soa, &so->so_comp, so_list) {
+ if ((sotounpcb(soa)->unp_gcflag & UNPGC_IGNORE_RIGHTS) != 0)
+ continue;
SOCKBUF_LOCK(&soa->so_rcv);
unp_scan(soa->so_rcv.sb_mb, unp_accessable);
SOCKBUF_UNLOCK(&soa->so_rcv);
@@ -2238,11 +2242,13 @@ unp_gc(__unused void *arg, int pending)
unp_taskcount++;
UNP_LIST_LOCK();
/*
- * First clear all gc flags from previous runs.
+ * First clear all gc flags from previous runs, apart from
+ * UNPGC_IGNORE_RIGHTS.
*/
for (head = heads; *head != NULL; head++)
LIST_FOREACH(unp, *head, unp_link)
- unp->unp_gcflag = 0;
+ unp->unp_gcflag =
+ (unp->unp_gcflag & UNPGC_IGNORE_RIGHTS);
/*
* Scan marking all reachable sockets with UNPGC_REF. Once a socket
@@ -2319,6 +2325,21 @@ unp_dispose(struct mbuf *m)
unp_scan(m, unp_freerights);
}
+/*
+ * Synchronize against unp_gc, which can trip over data as we are freeing it.
+ */
+void
+unp_dispose_so(struct socket *so)
+{
+ struct unpcb *unp;
+
+ unp = sotounpcb(so);
+ UNP_LIST_LOCK();
+ unp->unp_gcflag |= UNPGC_IGNORE_RIGHTS;
+ UNP_LIST_UNLOCK();
+ unp_dispose(so->so_rcv.sb_mb);
+}
+
static void
unp_scan(struct mbuf *m0, void (*op)(struct filedescent **, int))
{
OpenPOWER on IntegriCloud