Gitweb:     
http://git.kernel.org/git/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=278a3de5abc7901805689a66340b5af9882b4f9a
Commit:     278a3de5abc7901805689a66340b5af9882b4f9a
Parent:     007a880d627aee0e854e793099bb33d0c1130678
Author:     David S. Miller <[EMAIL PROTECTED]>
AuthorDate: Thu May 31 15:19:20 2007 -0700
Committer:  David S. Miller <[EMAIL PROTECTED]>
CommitDate: Sun Jun 3 18:08:42 2007 -0700

    [AF_UNIX]: Fix datagram connect race causing an OOPS.
    
    Based upon an excellent bug report and initial patch by
    Frederik Deweerdt.
    
    The UNIX datagram connect code blindly dereferences other->sk_socket
    via the call down to the security_unix_may_send() function.
    
    Without locking 'other' that pointer can go NULL via unix_release_sock()
    which does sock_orphan() which also marks the socket SOCK_DEAD.
    
    So we have to lock both 'sk' and 'other' yet avoid all kinds of
    potential deadlocks (connect to self is OK for datagram sockets and it
    is possible for two datagram sockets to perform a simultaneous connect
    to each other).  So what we do is have a "double lock" function similar
    to how we handle this situation in other areas of the kernel.  We take
    the lock of the socket pointer with the smallest address first in
    order to avoid ABBA style deadlocks.
    
    Once we have them both locked, we check to see if SOCK_DEAD is set
    for 'other' and if so, drop everything and retry the lookup.
    
    Signed-off-by: David S. Miller <[EMAIL PROTECTED]>
---
 net/unix/af_unix.c |   43 ++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 38 insertions(+), 5 deletions(-)

diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 453ede8..87c794d 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -858,6 +858,31 @@ out_mknod_parent:
        goto out_up;
 }
 
+static void unix_state_double_lock(struct sock *sk1, struct sock *sk2)
+{
+       if (unlikely(sk1 == sk2) || !sk2) {
+               unix_state_lock(sk1);
+               return;
+       }
+       if (sk1 < sk2) {
+               unix_state_lock(sk1);
+               unix_state_lock_nested(sk2);
+       } else {
+               unix_state_lock(sk2);
+               unix_state_lock_nested(sk1);
+       }
+}
+
+static void unix_state_double_unlock(struct sock *sk1, struct sock *sk2)
+{
+       if (unlikely(sk1 == sk2) || !sk2) {
+               unix_state_unlock(sk1);
+               return;
+       }
+       unix_state_unlock(sk1);
+       unix_state_unlock(sk2);
+}
+
 static int unix_dgram_connect(struct socket *sock, struct sockaddr *addr,
                              int alen, int flags)
 {
@@ -877,11 +902,19 @@ static int unix_dgram_connect(struct socket *sock, struct 
sockaddr *addr,
                    !unix_sk(sk)->addr && (err = unix_autobind(sock)) != 0)
                        goto out;
 
+restart:
                other=unix_find_other(sunaddr, alen, sock->type, hash, &err);
                if (!other)
                        goto out;
 
-               unix_state_lock(sk);
+               unix_state_double_lock(sk, other);
+
+               /* Apparently VFS overslept socket death. Retry. */
+               if (sock_flag(other, SOCK_DEAD)) {
+                       unix_state_double_unlock(sk, other);
+                       sock_put(other);
+                       goto restart;
+               }
 
                err = -EPERM;
                if (!unix_may_send(sk, other))
@@ -896,7 +929,7 @@ static int unix_dgram_connect(struct socket *sock, struct 
sockaddr *addr,
                 *      1003.1g breaking connected state with AF_UNSPEC
                 */
                other = NULL;
-               unix_state_lock(sk);
+               unix_state_double_lock(sk, other);
        }
 
        /*
@@ -905,19 +938,19 @@ static int unix_dgram_connect(struct socket *sock, struct 
sockaddr *addr,
        if (unix_peer(sk)) {
                struct sock *old_peer = unix_peer(sk);
                unix_peer(sk)=other;
-               unix_state_unlock(sk);
+               unix_state_double_unlock(sk, other);
 
                if (other != old_peer)
                        unix_dgram_disconnected(sk, old_peer);
                sock_put(old_peer);
        } else {
                unix_peer(sk)=other;
-               unix_state_unlock(sk);
+               unix_state_double_unlock(sk, other);
        }
        return 0;
 
 out_unlock:
-       unix_state_unlock(sk);
+       unix_state_double_unlock(sk, other);
        sock_put(other);
 out:
        return err;
-
To unsubscribe from this list: send the line "unsubscribe git-commits-head" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to