[linux] 01/03: unix: avoid use-after-free in ep_remove_wait_queue (CVE-2013-7446)

debian-kernel at lists.debian.org debian-kernel at lists.debian.org
Tue Dec 1 03:08:36 UTC 2015


This is an automated email from the git hooks/post-receive script.

benh pushed a commit to branch squeeze-security
in repository linux.

commit beb9304f8cb42762199d09ea45a65540c3211cda
Author: Ben Hutchings <ben at decadent.org.uk>
Date:   Tue Dec 1 03:05:24 2015 +0000

    unix: avoid use-after-free in ep_remove_wait_queue (CVE-2013-7446)
---
 debian/changelog                                   |   1 +
 ...id-use-after-free-in-ep_remove_wait_queue.patch | 325 +++++++++++++++++++++
 .../patches/debian/af_unix-avoid-abi-changes.patch |  26 ++
 debian/patches/series/48squeeze17                  |   4 +
 4 files changed, 356 insertions(+)

diff --git a/debian/changelog b/debian/changelog
index f701352..e0e0bcb 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -43,6 +43,7 @@ linux-2.6 (2.6.32-48squeeze17) UNRELEASED; urgency=medium
     - splice: sendfile() at once fails for big files
     - security: add cred argument to security_capable()
     - pagemap: hide physical addresses from non-privileged users
+  * unix: avoid use-after-free in ep_remove_wait_queue (CVE-2013-7446)
 
  -- Ben Hutchings <ben at decadent.org.uk>  Sun, 08 Nov 2015 13:41:21 +0000
 
diff --git a/debian/patches/bugfix/all/unix-avoid-use-after-free-in-ep_remove_wait_queue.patch b/debian/patches/bugfix/all/unix-avoid-use-after-free-in-ep_remove_wait_queue.patch
new file mode 100644
index 0000000..4154367
--- /dev/null
+++ b/debian/patches/bugfix/all/unix-avoid-use-after-free-in-ep_remove_wait_queue.patch
@@ -0,0 +1,325 @@
+From: Rainer Weikusat <rweikusat at mobileactivedefense.com>
+Date: Fri, 20 Nov 2015 22:07:23 +0000
+Subject: unix: avoid use-after-free in ep_remove_wait_queue
+Origin: https://git.kernel.org/cgit/linux/kernel/git/davem/net.git/commit?id=7d267278a9ece963d77eefec61630223fce08c6c
+
+Rainer Weikusat <rweikusat at mobileactivedefense.com> writes:
+An AF_UNIX datagram socket being the client in an n:1 association with
+some server socket is only allowed to send messages to the server if the
+receive queue of this socket contains at most sk_max_ack_backlog
+datagrams. This implies that prospective writers might be forced to go
+to sleep despite none of the message presently enqueued on the server
+receive queue were sent by them. In order to ensure that these will be
+woken up once space becomes again available, the present unix_dgram_poll
+routine does a second sock_poll_wait call with the peer_wait wait queue
+of the server socket as queue argument (unix_dgram_recvmsg does a wake
+up on this queue after a datagram was received). This is inherently
+problematic because the server socket is only guaranteed to remain alive
+for as long as the client still holds a reference to it. In case the
+connection is dissolved via connect or by the dead peer detection logic
+in unix_dgram_sendmsg, the server socket may be freed despite "the
+polling mechanism" (in particular, epoll) still has a pointer to the
+corresponding peer_wait queue. There's no way to forcibly deregister a
+wait queue with epoll.
+
+Based on an idea by Jason Baron, the patch below changes the code such
+that a wait_queue_t belonging to the client socket is enqueued on the
+peer_wait queue of the server whenever the peer receive queue full
+condition is detected by either a sendmsg or a poll. A wake up on the
+peer queue is then relayed to the ordinary wait queue of the client
+socket via wake function. The connection to the peer wait queue is again
+dissolved if either a wake up is about to be relayed or the client
+socket reconnects or a dead peer is detected or the client socket is
+itself closed. This enables removing the second sock_poll_wait from
+unix_dgram_poll, thus avoiding the use-after-free, while still ensuring
+that no blocked writer sleeps forever.
+
+Signed-off-by: Rainer Weikusat <rweikusat at mobileactivedefense.com>
+Fixes: ec0d215f9420 ("af_unix: fix 'poll for write'/connected DGRAM sockets")
+Reviewed-by: Jason Baron <jbaron at akamai.com>
+Signed-off-by: David S. Miller <davem at davemloft.net>
+[bwh: Backported to 2.6.32:
+ - Access sk_sleep directly, not through sk_sleep() function
+ - Adjust context]
+---
+--- a/include/net/af_unix.h
++++ b/include/net/af_unix.h
+@@ -59,6 +59,7 @@ struct unix_sock {
+ 	unsigned int		gc_maybe_cycle : 1;
+ 	unsigned char		recursion_level;
+         wait_queue_head_t       peer_wait;
++	wait_queue_t		peer_wake;
+ };
+ #define unix_sk(__sk) ((struct unix_sock *)__sk)
+ 
+--- a/net/unix/af_unix.c
++++ b/net/unix/af_unix.c
+@@ -306,6 +306,118 @@ found:
+ 	return s;
+ }
+ 
++/* Support code for asymmetrically connected dgram sockets
++ *
++ * If a datagram socket is connected to a socket not itself connected
++ * to the first socket (eg, /dev/log), clients may only enqueue more
++ * messages if the present receive queue of the server socket is not
++ * "too large". This means there's a second writeability condition
++ * poll and sendmsg need to test. The dgram recv code will do a wake
++ * up on the peer_wait wait queue of a socket upon reception of a
++ * datagram which needs to be propagated to sleeping would-be writers
++ * since these might not have sent anything so far. This can't be
++ * accomplished via poll_wait because the lifetime of the server
++ * socket might be less than that of its clients if these break their
++ * association with it or if the server socket is closed while clients
++ * are still connected to it and there's no way to inform "a polling
++ * implementation" that it should let go of a certain wait queue
++ *
++ * In order to propagate a wake up, a wait_queue_t of the client
++ * socket is enqueued on the peer_wait queue of the server socket
++ * whose wake function does a wake_up on the ordinary client socket
++ * wait queue. This connection is established whenever a write (or
++ * poll for write) hit the flow control condition and broken when the
++ * association to the server socket is dissolved or after a wake up
++ * was relayed.
++ */
++
++static int unix_dgram_peer_wake_relay(wait_queue_t *q, unsigned mode, int flags,
++				      void *key)
++{
++	struct unix_sock *u;
++	wait_queue_head_t *u_sleep;
++
++	u = container_of(q, struct unix_sock, peer_wake);
++
++	__remove_wait_queue(&unix_sk(u->peer_wake.private)->peer_wait,
++			    q);
++	u->peer_wake.private = NULL;
++
++	/* relaying can only happen while the wq still exists */
++	u_sleep = u->sk.sk_sleep;
++	if (u_sleep)
++		wake_up_interruptible_poll(u_sleep, key);
++
++	return 0;
++}
++
++static int unix_dgram_peer_wake_connect(struct sock *sk, struct sock *other)
++{
++	struct unix_sock *u, *u_other;
++	int rc;
++
++	u = unix_sk(sk);
++	u_other = unix_sk(other);
++	rc = 0;
++	spin_lock(&u_other->peer_wait.lock);
++
++	if (!u->peer_wake.private) {
++		u->peer_wake.private = other;
++		__add_wait_queue(&u_other->peer_wait, &u->peer_wake);
++
++		rc = 1;
++	}
++
++	spin_unlock(&u_other->peer_wait.lock);
++	return rc;
++}
++
++static void unix_dgram_peer_wake_disconnect(struct sock *sk,
++					    struct sock *other)
++{
++	struct unix_sock *u, *u_other;
++
++	u = unix_sk(sk);
++	u_other = unix_sk(other);
++	spin_lock(&u_other->peer_wait.lock);
++
++	if (u->peer_wake.private == other) {
++		__remove_wait_queue(&u_other->peer_wait, &u->peer_wake);
++		u->peer_wake.private = NULL;
++	}
++
++	spin_unlock(&u_other->peer_wait.lock);
++}
++
++static void unix_dgram_peer_wake_disconnect_wakeup(struct sock *sk,
++						   struct sock *other)
++{
++	unix_dgram_peer_wake_disconnect(sk, other);
++	wake_up_interruptible_poll(sk->sk_sleep,
++				   POLLOUT |
++				   POLLWRNORM |
++				   POLLWRBAND);
++}
++
++/* preconditions:
++ *	- unix_peer(sk) == other
++ *	- association is stable
++ */
++static int unix_dgram_peer_wake_me(struct sock *sk, struct sock *other)
++{
++	int connected;
++
++	connected = unix_dgram_peer_wake_connect(sk, other);
++
++	if (unix_recvq_full(other))
++		return 1;
++
++	if (connected)
++		unix_dgram_peer_wake_disconnect(sk, other);
++
++	return 0;
++}
++
+ static inline int unix_writable(struct sock *sk)
+ {
+ 	return (atomic_read(&sk->sk_wmem_alloc) << 2) <= sk->sk_sndbuf;
+@@ -410,6 +522,8 @@ static void unix_release_sock(struct sock *sk, int embrion)
+ 			sk_wake_async(skpair, SOCK_WAKE_WAITD, POLL_HUP);
+ 			read_unlock(&skpair->sk_callback_lock);
+ 		}
++
++		unix_dgram_peer_wake_disconnect(sk, skpair);
+ 		sock_put(skpair); /* It may now die */
+ 		unix_peer(sk) = NULL;
+ 	}
+@@ -609,6 +723,7 @@ static struct sock *unix_create1(struct net *net, struct socket *sock, int kern)
+ 	INIT_LIST_HEAD(&u->link);
+ 	mutex_init(&u->readlock); /* single task reading lock */
+ 	init_waitqueue_head(&u->peer_wait);
++	init_waitqueue_func_entry(&u->peer_wake, unix_dgram_peer_wake_relay);
+ 	unix_insert_socket(unix_sockets_unbound, sk);
+ out:
+ 	if (sk == NULL)
+@@ -987,6 +1102,8 @@ restart:
+ 	if (unix_peer(sk)) {
+ 		struct sock *old_peer = unix_peer(sk);
+ 		unix_peer(sk) = other;
++		unix_dgram_peer_wake_disconnect_wakeup(sk, old_peer);
++
+ 		unix_state_double_unlock(sk, other);
+ 
+ 		if (other != old_peer)
+@@ -1385,6 +1502,7 @@ static int unix_dgram_sendmsg(struct socket *sock, struct msghdr *msg,
+ 	long timeo;
+ 	struct scm_cookie tmp_scm;
+ 	int max_level = 0;
++	int sk_locked;
+ 
+ 	if (NULL == siocb->scm)
+ 		siocb->scm = &tmp_scm;
+@@ -1450,12 +1568,14 @@ restart:
+ 			goto out_free;
+ 	}
+ 
++	sk_locked = 0;
+ 	unix_state_lock(other);
++restart_locked:
+ 	err = -EPERM;
+ 	if (!unix_may_send(sk, other))
+ 		goto out_unlock;
+ 
+-	if (sock_flag(other, SOCK_DEAD)) {
++	if (unlikely(sock_flag(other, SOCK_DEAD))) {
+ 		/*
+ 		 *	Check with 1003.1g - what should
+ 		 *	datagram error
+@@ -1463,10 +1582,14 @@ restart:
+ 		unix_state_unlock(other);
+ 		sock_put(other);
+ 
++		if (!sk_locked)
++			unix_state_lock(sk);
++
+ 		err = 0;
+-		unix_state_lock(sk);
+ 		if (unix_peer(sk) == other) {
+ 			unix_peer(sk) = NULL;
++			unix_dgram_peer_wake_disconnect_wakeup(sk, other);
++
+ 			unix_state_unlock(sk);
+ 
+ 			unix_dgram_disconnected(sk, other);
+@@ -1492,21 +1615,38 @@ restart:
+ 			goto out_unlock;
+ 	}
+ 
+-	if (unix_peer(other) != sk && unix_recvq_full(other)) {
+-		if (!timeo) {
+-			err = -EAGAIN;
+-			goto out_unlock;
++	if (unlikely(unix_peer(other) != sk && unix_recvq_full(other))) {
++		if (timeo) {
++			timeo = unix_wait_for_peer(other, timeo);
++
++			err = sock_intr_errno(timeo);
++			if (signal_pending(current))
++				goto out_free;
++
++			goto restart;
+ 		}
+ 
+-		timeo = unix_wait_for_peer(other, timeo);
++		if (!sk_locked) {
++			unix_state_unlock(other);
++			unix_state_double_lock(sk, other);
++		}
+ 
+-		err = sock_intr_errno(timeo);
+-		if (signal_pending(current))
+-			goto out_free;
++		if (unix_peer(sk) != other ||
++		    unix_dgram_peer_wake_me(sk, other)) {
++			err = -EAGAIN;
++			sk_locked = 1;
++			goto out_unlock;
++		}
+ 
+-		goto restart;
++		if (!sk_locked) {
++			sk_locked = 1;
++			goto restart_locked;
++		}
+ 	}
+ 
++	if (unlikely(sk_locked))
++		unix_state_unlock(sk);
++
+ 	skb_queue_tail(&other->sk_receive_queue, skb);
+ 	if (max_level > unix_sk(other)->recursion_level)
+ 		unix_sk(other)->recursion_level = max_level;
+@@ -1517,6 +1658,8 @@ restart:
+ 	return len;
+ 
+ out_unlock:
++	if (sk_locked)
++		unix_state_unlock(sk);
+ 	unix_state_unlock(other);
+ out_free:
+ 	kfree_skb(skb);
+@@ -2103,17 +2245,15 @@ static unsigned int unix_dgram_poll(struct file *file, struct socket *sock,
+ 	/* writable? */
+ 	writable = unix_writable(sk);
+ 	if (writable) {
+-		other = unix_peer_get(sk);
+-		if (other) {
+-			if (unix_peer(other) != sk) {
+-				sock_poll_wait(file, &unix_sk(other)->peer_wait,
+-					  wait);
+-				if (unix_recvq_full(other))
+-					writable = 0;
+-			}
+-
+-			sock_put(other);
+-		}
++		unix_state_lock(sk);
++
++		other = unix_peer(sk);
++		if (other && unix_peer(other) != sk &&
++		    unix_recvq_full(other) &&
++		    unix_dgram_peer_wake_me(sk, other))
++			writable = 0;
++
++		unix_state_unlock(sk);
+ 	}
+ 
+ 	if (writable)
diff --git a/debian/patches/debian/af_unix-avoid-abi-changes.patch b/debian/patches/debian/af_unix-avoid-abi-changes.patch
new file mode 100644
index 0000000..25a0305
--- /dev/null
+++ b/debian/patches/debian/af_unix-avoid-abi-changes.patch
@@ -0,0 +1,26 @@
+From: Ben Hutchings <ben at decadent.org.uk>
+Date: Tue, 01 Dec 2015 02:21:58 +0000
+Subject: af_unix: Avoid ABI changes
+
+struct unix_sock is only allocated in af_unix so it's safe to add new
+members at the end.
+
+- Move recursion_level to the end of the structure.
+- Hide recursion_level and peer_wake from genksyms.
+
+---
+--- a/include/net/af_unix.h
++++ b/include/net/af_unix.h
+@@ -57,9 +57,11 @@ struct unix_sock {
+         spinlock_t		lock;
+ 	unsigned int		gc_candidate : 1;
+ 	unsigned int		gc_maybe_cycle : 1;
+-	unsigned char		recursion_level;
+         wait_queue_head_t       peer_wait;
++#ifndef __GENKSYSMS__
++	unsigned char		recursion_level;
+ 	wait_queue_t		peer_wake;
++#endif
+ };
+ #define unix_sk(__sk) ((struct unix_sock *)__sk)
+ 
diff --git a/debian/patches/series/48squeeze17 b/debian/patches/series/48squeeze17
index 321a8cd..a7b6ffe 100644
--- a/debian/patches/series/48squeeze17
+++ b/debian/patches/series/48squeeze17
@@ -30,3 +30,7 @@
 + bugfix/all/pagemap-close-races-with-suid-execve-2.patch
 + bugfix/all/proc-map-report-errors-sanely.patch
 + bugfix/all/proc-fix-oops-on-invalid-proc-pid-maps-access.patch
+
+- debian/af_unix-Avoid-ABI-change-from-introduction-of-recursion-limit.patch
++ bugfix/all/unix-avoid-use-after-free-in-ep_remove_wait_queue.patch
++ debian/af_unix-avoid-abi-changes.patch

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/kernel/linux.git



More information about the Kernel-svn-changes mailing list