sctp: fix ASCONF list handling
[pandora-kernel.git] / net / sctp / socket.c
index fc63664..24e88af 100644 (file)
@@ -1539,8 +1539,10 @@ SCTP_STATIC void sctp_close(struct sock *sk, long timeout)
 
        /* Supposedly, no process has access to the socket, but
         * the net layers still may.
 
        /* Supposedly, no process has access to the socket, but
         * the net layers still may.
+        * Also, sctp_destroy_sock() needs to be called with addr_wq_lock
+        * held and that should be grabbed before socket lock.
         */
         */
-       sctp_local_bh_disable();
+       spin_lock_bh(&sctp_globals.addr_wq_lock);
        sctp_bh_lock_sock(sk);
 
        /* Hold the sock, since sk_common_release() will put sock_put()
        sctp_bh_lock_sock(sk);
 
        /* Hold the sock, since sk_common_release() will put sock_put()
@@ -1550,7 +1552,7 @@ SCTP_STATIC void sctp_close(struct sock *sk, long timeout)
        sk_common_release(sk);
 
        sctp_bh_unlock_sock(sk);
        sk_common_release(sk);
 
        sctp_bh_unlock_sock(sk);
-       sctp_local_bh_enable();
+       spin_unlock_bh(&sctp_globals.addr_wq_lock);
 
        sock_put(sk);
 
 
        sock_put(sk);
 
@@ -3499,6 +3501,7 @@ static int sctp_setsockopt_auto_asconf(struct sock *sk, char __user *optval,
        if ((val && sp->do_auto_asconf) || (!val && !sp->do_auto_asconf))
                return 0;
 
        if ((val && sp->do_auto_asconf) || (!val && !sp->do_auto_asconf))
                return 0;
 
+       spin_lock_bh(&sctp_globals.addr_wq_lock);
        if (val == 0 && sp->do_auto_asconf) {
                list_del(&sp->auto_asconf_list);
                sp->do_auto_asconf = 0;
        if (val == 0 && sp->do_auto_asconf) {
                list_del(&sp->auto_asconf_list);
                sp->do_auto_asconf = 0;
@@ -3507,6 +3510,7 @@ static int sctp_setsockopt_auto_asconf(struct sock *sk, char __user *optval,
                    &sctp_auto_asconf_splist);
                sp->do_auto_asconf = 1;
        }
                    &sctp_auto_asconf_splist);
                sp->do_auto_asconf = 1;
        }
+       spin_unlock_bh(&sctp_globals.addr_wq_lock);
        return 0;
 }
 
        return 0;
 }
 
@@ -3942,18 +3946,28 @@ SCTP_STATIC int sctp_init_sock(struct sock *sk)
        local_bh_disable();
        percpu_counter_inc(&sctp_sockets_allocated);
        sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
        local_bh_disable();
        percpu_counter_inc(&sctp_sockets_allocated);
        sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
+
+       /* Nothing can fail after this block, otherwise
+        * sctp_destroy_sock() will be called without addr_wq_lock held
+        */
        if (sctp_default_auto_asconf) {
        if (sctp_default_auto_asconf) {
+               spin_lock(&sctp_globals.addr_wq_lock);
                list_add_tail(&sp->auto_asconf_list,
                    &sctp_auto_asconf_splist);
                sp->do_auto_asconf = 1;
                list_add_tail(&sp->auto_asconf_list,
                    &sctp_auto_asconf_splist);
                sp->do_auto_asconf = 1;
-       } else
+               spin_unlock(&sctp_globals.addr_wq_lock);
+       } else {
                sp->do_auto_asconf = 0;
                sp->do_auto_asconf = 0;
+       }
+
        local_bh_enable();
 
        return 0;
 }
 
        local_bh_enable();
 
        return 0;
 }
 
-/* Cleanup any SCTP per socket resources.  */
+/* Cleanup any SCTP per socket resources. Must be called with
+ * sctp_globals.addr_wq_lock held if sp->do_auto_asconf is true
+ */
 SCTP_STATIC void sctp_destroy_sock(struct sock *sk)
 {
        struct sctp_sock *sp;
 SCTP_STATIC void sctp_destroy_sock(struct sock *sk)
 {
        struct sctp_sock *sp;
@@ -6719,6 +6733,19 @@ void sctp_copy_sock(struct sock *newsk, struct sock *sk,
        newinet->mc_list = NULL;
 }
 
        newinet->mc_list = NULL;
 }
 
+static inline void sctp_copy_descendant(struct sock *sk_to,
+                                       const struct sock *sk_from)
+{
+       int ancestor_size = sizeof(struct inet_sock) +
+                           sizeof(struct sctp_sock) -
+                           offsetof(struct sctp_sock, auto_asconf_list);
+
+       if (sk_from->sk_family == PF_INET6)
+               ancestor_size += sizeof(struct ipv6_pinfo);
+
+       __inet_sk_copy_descendant(sk_to, sk_from, ancestor_size);
+}
+
 /* Populate the fields of the newsk from the oldsk and migrate the assoc
  * and its messages to the newsk.
  */
 /* Populate the fields of the newsk from the oldsk and migrate the assoc
  * and its messages to the newsk.
  */
@@ -6733,7 +6760,6 @@ static void sctp_sock_migrate(struct sock *oldsk, struct sock *newsk,
        struct sk_buff *skb, *tmp;
        struct sctp_ulpevent *event;
        struct sctp_bind_hashbucket *head;
        struct sk_buff *skb, *tmp;
        struct sctp_ulpevent *event;
        struct sctp_bind_hashbucket *head;
-       struct list_head tmplist;
 
        /* Migrate socket buffer sizes and all the socket level options to the
         * new socket.
 
        /* Migrate socket buffer sizes and all the socket level options to the
         * new socket.
@@ -6741,12 +6767,7 @@ static void sctp_sock_migrate(struct sock *oldsk, struct sock *newsk,
        newsk->sk_sndbuf = oldsk->sk_sndbuf;
        newsk->sk_rcvbuf = oldsk->sk_rcvbuf;
        /* Brute force copy old sctp opt. */
        newsk->sk_sndbuf = oldsk->sk_sndbuf;
        newsk->sk_rcvbuf = oldsk->sk_rcvbuf;
        /* Brute force copy old sctp opt. */
-       if (oldsp->do_auto_asconf) {
-               memcpy(&tmplist, &newsp->auto_asconf_list, sizeof(tmplist));
-               inet_sk_copy_descendant(newsk, oldsk);
-               memcpy(&newsp->auto_asconf_list, &tmplist, sizeof(tmplist));
-       } else
-               inet_sk_copy_descendant(newsk, oldsk);
+       sctp_copy_descendant(newsk, oldsk);
 
        /* Restore the ep value that was overwritten with the above structure
         * copy.
 
        /* Restore the ep value that was overwritten with the above structure
         * copy.