diff options
author | markj <markj@FreeBSD.org> | 2016-09-02 00:14:28 +0000 |
---|---|---|
committer | markj <markj@FreeBSD.org> | 2016-09-02 00:14:28 +0000 |
commit | a80af232a432914d0c8e6a0c45184d0efcbf1a98 (patch) | |
tree | 2dbfbfd4ea4c0bdeb3e429a1c59472bb0a31aa02 /sys/kern | |
parent | a79616dbee6ff950597905ba262f9d753ad202d3 (diff) | |
download | FreeBSD-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.c | 29 | ||||
-rw-r--r-- | sys/kern/uipc_usrreq.c | 31 |
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)) { |