l2tp: fix race in pppol2tp_release with session object destroy
[pandora-kernel.git] / net / l2tp / l2tp_ppp.c
index 54e249e..62d7e71 100644 (file)
@@ -463,10 +463,28 @@ abort:
  * Session (and tunnel control) socket create/destroy.
  *****************************************************************************/
 
+static void pppol2tp_put_sk(struct rcu_head *head)
+{
+       struct pppol2tp_session *ps;
+
+       ps = container_of(head, typeof(*ps), rcu);
+       sock_put(ps->__sk);
+}
+
 /* Called by l2tp_core when a session socket is being closed.
  */
 static void pppol2tp_session_close(struct l2tp_session *session)
 {
+       struct pppol2tp_session *ps;
+
+       ps = l2tp_session_priv(session);
+       mutex_lock(&ps->sk_lock);
+       ps->__sk = rcu_dereference_protected(ps->sk,
+                                            lockdep_is_held(&ps->sk_lock));
+       RCU_INIT_POINTER(ps->sk, NULL);
+       if (ps->__sk)
+               call_rcu(&ps->rcu, pppol2tp_put_sk);
+       mutex_unlock(&ps->sk_lock);
 }
 
 /* Really kill the session socket. (Called from sock_put() if
@@ -490,14 +508,6 @@ out:
        return;
 }
 
-static void pppol2tp_put_sk(struct rcu_head *head)
-{
-       struct pppol2tp_session *ps;
-
-       ps = container_of(head, typeof(*ps), rcu);
-       sock_put(ps->__sk);
-}
-
 /* Called when the PPPoX socket (session) is closed.
  */
 static int pppol2tp_release(struct socket *sock)
@@ -521,26 +531,17 @@ static int pppol2tp_release(struct socket *sock)
        sock_orphan(sk);
        sock->sk = NULL;
 
+       /* If the socket is associated with a session,
+        * l2tp_session_delete will call pppol2tp_session_close which
+        * will drop the session's ref on the socket.
+        */
        session = pppol2tp_sock_to_session(sk);
-
-       if (session != NULL) {
-               struct pppol2tp_session *ps;
-
+       if (session) {
                l2tp_session_delete(session);
-
-               ps = l2tp_session_priv(session);
-               mutex_lock(&ps->sk_lock);
-               ps->__sk = rcu_dereference_protected(ps->sk,
-                                                    lockdep_is_held(&ps->sk_lock));
-               RCU_INIT_POINTER(ps->sk, NULL);
-               mutex_unlock(&ps->sk_lock);
-               call_rcu(&ps->rcu, pppol2tp_put_sk);
-
-               /* Rely on the sock_put() call at the end of the function for
-                * dropping the reference held by pppol2tp_sock_to_session().
-                * The last reference will be dropped by pppol2tp_put_sk().
-                */
+               /* drop the ref obtained by pppol2tp_sock_to_session */
+               sock_put(sk);
        }
+
        skb_queue_purge(&sk->sk_receive_queue);
        skb_queue_purge(&sk->sk_write_queue);
 
@@ -828,6 +829,7 @@ static int pppol2tp_connect(struct socket *sock, struct sockaddr *uservaddr,
 
 out_no_ppp:
        /* This is how we get the session context from the socket. */
+       sock_hold(sk);
        sk->sk_user_data = session;
        rcu_assign_pointer(ps->sk, sk);
        mutex_unlock(&ps->sk_lock);