aboutsummaryrefslogtreecommitdiffstats
path: root/net/unix
diff options
context:
space:
mode:
authorDavid S. Miller <davem@sunset.davemloft.net>2007-05-31 18:19:20 -0400
committerDavid S. Miller <davem@sunset.davemloft.net>2007-06-03 21:08:42 -0400
commit278a3de5abc7901805689a66340b5af9882b4f9a (patch)
treec18ffd5992fb38e3a6322b220fb56a1da6e5aa77 /net/unix
parent007a880d627aee0e854e793099bb33d0c1130678 (diff)
[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 <davem@davemloft.net>
Diffstat (limited to 'net/unix')
-rw-r--r--net/unix/af_unix.c43
1 files changed, 38 insertions, 5 deletions
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 453ede86a65..87c794d8fa2 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -858,6 +858,31 @@ out_mknod_parent:
858 goto out_up; 858 goto out_up;
859} 859}
860 860
861static void unix_state_double_lock(struct sock *sk1, struct sock *sk2)
862{
863 if (unlikely(sk1 == sk2) || !sk2) {
864 unix_state_lock(sk1);
865 return;
866 }
867 if (sk1 < sk2) {
868 unix_state_lock(sk1);
869 unix_state_lock_nested(sk2);
870 } else {
871 unix_state_lock(sk2);
872 unix_state_lock_nested(sk1);
873 }
874}
875
876static void unix_state_double_unlock(struct sock *sk1, struct sock *sk2)
877{
878 if (unlikely(sk1 == sk2) || !sk2) {
879 unix_state_unlock(sk1);
880 return;
881 }
882 unix_state_unlock(sk1);
883 unix_state_unlock(sk2);
884}
885
861static int unix_dgram_connect(struct socket *sock, struct sockaddr *addr, 886static int unix_dgram_connect(struct socket *sock, struct sockaddr *addr,
862 int alen, int flags) 887 int alen, int flags)
863{ 888{
@@ -877,11 +902,19 @@ static int unix_dgram_connect(struct socket *sock, struct sockaddr *addr,
877 !unix_sk(sk)->addr && (err = unix_autobind(sock)) != 0) 902 !unix_sk(sk)->addr && (err = unix_autobind(sock)) != 0)
878 goto out; 903 goto out;
879 904
905restart:
880 other=unix_find_other(sunaddr, alen, sock->type, hash, &err); 906 other=unix_find_other(sunaddr, alen, sock->type, hash, &err);
881 if (!other) 907 if (!other)
882 goto out; 908 goto out;
883 909
884 unix_state_lock(sk); 910 unix_state_double_lock(sk, other);
911
912 /* Apparently VFS overslept socket death. Retry. */
913 if (sock_flag(other, SOCK_DEAD)) {
914 unix_state_double_unlock(sk, other);
915 sock_put(other);
916 goto restart;
917 }
885 918
886 err = -EPERM; 919 err = -EPERM;
887 if (!unix_may_send(sk, other)) 920 if (!unix_may_send(sk, other))
@@ -896,7 +929,7 @@ static int unix_dgram_connect(struct socket *sock, struct sockaddr *addr,
896 * 1003.1g breaking connected state with AF_UNSPEC 929 * 1003.1g breaking connected state with AF_UNSPEC
897 */ 930 */
898 other = NULL; 931 other = NULL;
899 unix_state_lock(sk); 932 unix_state_double_lock(sk, other);
900 } 933 }
901 934
902 /* 935 /*
@@ -905,19 +938,19 @@ static int unix_dgram_connect(struct socket *sock, struct sockaddr *addr,
905 if (unix_peer(sk)) { 938 if (unix_peer(sk)) {
906 struct sock *old_peer = unix_peer(sk); 939 struct sock *old_peer = unix_peer(sk);
907 unix_peer(sk)=other; 940 unix_peer(sk)=other;
908 unix_state_unlock(sk); 941 unix_state_double_unlock(sk, other);
909 942
910 if (other != old_peer) 943 if (other != old_peer)
911 unix_dgram_disconnected(sk, old_peer); 944 unix_dgram_disconnected(sk, old_peer);
912 sock_put(old_peer); 945 sock_put(old_peer);
913 } else { 946 } else {
914 unix_peer(sk)=other; 947 unix_peer(sk)=other;
915 unix_state_unlock(sk); 948 unix_state_double_unlock(sk, other);
916 } 949 }
917 return 0; 950 return 0;
918 951
919out_unlock: 952out_unlock:
920 unix_state_unlock(sk); 953 unix_state_double_unlock(sk, other);
921 sock_put(other); 954 sock_put(other);
922out: 955out:
923 return err; 956 return err;