diff options
author | Philip Guenthe <guenther@cvs.openbsd.org> | 2012-10-17 05:07:56 +0000 |
---|---|---|
committer | Philip Guenthe <guenther@cvs.openbsd.org> | 2012-10-17 05:07:56 +0000 |
commit | 9338a56e6d0f1ca6861fe7439b48f45fbb7cb44a (patch) | |
tree | 4cf27452088c626416c6be7badd5b063de276ff1 /sys/kern | |
parent | 700ad4fc04948c9012504f7838d520923e169212 (diff) |
In sys_accept(), don't sleep between pulling the new socket from the
queue and calling soaccept(), so that the socket can't get torn down
by a TCP RST in the middle and trigger "panic: soaccept: !NOFDREF", as
seen by halex@
Analysis, original diff, and ok bluhm@
Diffstat (limited to 'sys/kern')
-rw-r--r-- | sys/kern/uipc_syscalls.c | 47 |
1 files changed, 28 insertions, 19 deletions
diff --git a/sys/kern/uipc_syscalls.c b/sys/kern/uipc_syscalls.c index a1407a71d46..1eb096cebd2 100644 --- a/sys/kern/uipc_syscalls.c +++ b/sys/kern/uipc_syscalls.c @@ -1,4 +1,4 @@ -/* $OpenBSD: uipc_syscalls.c,v 1.86 2012/05/06 20:25:27 matthew Exp $ */ +/* $OpenBSD: uipc_syscalls.c,v 1.87 2012/10/17 05:07:55 guenther Exp $ */ /* $NetBSD: uipc_syscalls.c,v 1.19 1996/02/09 19:00:48 christos Exp $ */ /* @@ -172,6 +172,7 @@ sys_accept(struct proc *p, void *v, register_t *retval) headfp = fp; s = splsoftnet(); head = fp->f_data; +redo: if ((head->so_options & SO_ACCEPTCONN) == 0) { error = EINVAL; goto bad; @@ -199,35 +200,44 @@ sys_accept(struct proc *p, void *v, register_t *retval) goto bad; } - /* - * At this point we know that there is at least one connection - * ready to be accepted. Remove it from the queue prior to - * allocating the file descriptor for it since falloc() may - * block allowing another process to accept the connection - * instead. - */ - so = TAILQ_FIRST(&head->so_q); - if (soqremque(so, 1) == 0) - panic("accept"); - /* Take note if socket was non-blocking. */ - nflag = (fp->f_flag & FNONBLOCK); + nflag = (headfp->f_flag & FNONBLOCK); fdplock(p->p_fd); error = falloc(p, &fp, &tmpfd); fdpunlock(p->p_fd); if (error != 0) { /* - * Probably ran out of file descriptors. Put the - * unaccepted connection back onto the queue and - * do another wakeup so some other process might - * have a chance at it. + * Probably ran out of file descriptors. Wakeup + * so some other process might have a chance at it. */ - soqinsque(head, so, 1); wakeup_one(&head->so_timeo); goto bad; } + nam = m_get(M_WAIT, MT_SONAME); + + /* + * Check whether the queue emptied while we slept: falloc() or + * m_get() may have blocked, allowing the connection to be reset + * or another thread or process to accept it. If so, start over. + */ + if (head->so_qlen == 0) { + m_freem(nam); + fdplock(p->p_fd); + fdremove(p->p_fd, tmpfd); + closef(fp, p); + fdpunlock(p->p_fd); + goto redo; + } + + /* + * Do not sleep after we have taken the socket out of the queue. + */ + so = TAILQ_FIRST(&head->so_q); + if (soqremque(so, 1) == 0) + panic("accept"); + /* connection has been removed from the listen queue */ KNOTE(&head->so_rcv.sb_sel.si_note, 0); @@ -235,7 +245,6 @@ sys_accept(struct proc *p, void *v, register_t *retval) fp->f_flag = FREAD | FWRITE | nflag; fp->f_ops = &socketops; fp->f_data = so; - nam = m_get(M_WAIT, MT_SONAME); error = soaccept(so, nam); if (!error && SCARG(uap, name)) { error = copyaddrout(p, nam, SCARG(uap, name), namelen, |