soclose() & so->so_upcall() = race?
Andre Oppermann
andre at freebsd.org
Fri Mar 7 19:08:01 UTC 2008
Robert Watson wrote:
>
> On Fri, 7 Mar 2008, Alexander Motin wrote:
>
>> As I can see so_upcall() callback is called with SOCKBUF_MTX unlocked.
>> It means that SB_UPCALL flag can be removed during call and socket can
>> be closed and deallocated with soclose() while callback is running. Am
>> I right or I have missed something? How in that situation socket
>> pointer protected from being used after free?
>
>
> There are known problems with so_upcall and locking, including this
> one. The other problems include:
>
> - The locking condition on entering the upcall depends on the invocation
> point
> and is inconsistent.
>
> - The protection of the upcall field and flag are inconsistent.
>
> - Consumers of so_upcall, such as socket accept filters, don't properly
> respect the locking environment they run in.
>
> - Some (all) accept filters produce nastily convoluted stack traces
> involving
> recursion across really odd combinations of sockets and protocols. For
> example, you can see soisdisconnected() calling soisconnected().
>
> Some of this is inherent to the design of accept filters and so_upcall
> and follows from using a single function pointer for many different
> cases. I have an 8.x todo list item to try and figure out how to make
> so_upcall and accept filters rational in the context of fine-grained
> locking, but I've not yet reached that point on my todo list. I think
> we can continue to incrementally hack so_upcall and accept filters to
> fix many races, but the reality is that we need a more coherent model
> for dealing with accept filters. One idea that Colin Percival (I think)
> suggested is that we separate socket upcalls from accept filters, and
> that accept filters consistent of a predicate for completion rather than
> directly invoking socket state transitions. I've not explored the
> implications, but think it might well be a good idea to avoid the weird
> stack traces.
I've experimented with some changes to sowakeup() to better formalize it.
Code diff below. Observations noted in the comments.
--
Andre
Index: uipc_sockbuf.c
===================================================================
RCS file: /home/ncvs/src/sys/kern/uipc_sockbuf.c,v
retrieving revision 1.165
diff -u -p -r1.165 uipc_sockbuf.c
--- uipc_sockbuf.c 6 Sep 2006 21:59:36 -0000 1.165
+++ uipc_sockbuf.c 25 Feb 2007 15:22:32 -0000
@@ -173,11 +173,12 @@ sowakeup(struct socket *so, struct sockb
SOCKBUF_LOCK_ASSERT(sb);
- selwakeuppri(&sb->sb_sel, PSOCK);
+#if 1
+ selwakeuppri(&sb->sb_sel, PSOCK); /* removes thread from sleepq */
sb->sb_flags &= ~SB_SEL;
if (sb->sb_flags & SB_WAIT) {
sb->sb_flags &= ~SB_WAIT;
- wakeup(&sb->sb_cc);
+ wakeup(&sb->sb_cc); /* removes thread from sleepq too!? */
}
KNOTE_LOCKED(&sb->sb_sel.si_note, 0);
SOCKBUF_UNLOCK(sb);
@@ -188,6 +189,46 @@ sowakeup(struct socket *so, struct sockb
if (sb->sb_flags & SB_AIO)
aio_swake(so, sb);
mtx_assert(SOCKBUF_MTX(sb), MA_NOTOWNED);
+#else
+ /* Only wakeup if above low water mark. */
+ if (so->so_error == 0 && sb->sb_lowat < sbspace(sb))
+ return;
+ /* First run any upcalls which may process or modify the data. */
+ if (sb->sb_flags & SB_UPCALL) {
+ /*
+ * Upcall tells us whether to do a wakeup or not.
+ * Need a flag on the socket to tell select/poll and kqueue
+ * to ignore socket buffer until cleared.
+ * Maybe we should defer the upcall to a worker thread using
+ * task queue?
+ */
+ if ((*so->so_upcall)(so, sb, so->so_upcallarg, M_DONTWAIT)) {
+ sb->sb_flags |= SB_IGNORE;
+ SOCKBUF_UNLOCK(sb);
+ return; /* don't wakeup, more work! */
+ }
+ }
+ /* KQueue notifications. */
+ KNOTE_LOCKED(&sb->sb_sel.si_note, 0); /* issues wakeup */
+ /* Someone doing select/poll? */
+ if (sb->sb_flags & SB_SEL) {
+ selwakeuppri(&sb->sb_sel, PSOCK); /* removes thread from sleepq */
+ sb->sb_flags &= ~SB_SEL;
+ }
+ /* Someone waiting blocked on socket buffer. */
+ if (sb->sb_flags & SB_WAIT) {
+ wakeup(&sb->sb_cc); /* removes thread from sleepq */
+ sb->sb_flags &= ~SB_WAIT;
+ }
+ /* Async IO notificatins. */
+ if (sb->sb_flags & SB_AIO)
+ aio_swake(so, sb);
+ /* Socket buffer won't be accessed anymore. */
+ SOCKBUF_UNLOCK(sb);
+ /* Send SIGIO or SIGURG to thread. XXX: Is this still needed? */
+ if ((so->so_state & SS_ASYNC) && so->so_sigio != NULL)
+ pgsigio(&so->so_sigio, SIGIO, 0);
+#endif
}
/*
More information about the freebsd-hackers
mailing list