net: unix: fix use-after-free
Subsystems (3)
v3 cover net: unix: fix use-after-free
Hi,
These patches are against mainline, I can re-base to net-next, just let me know.
They have been tested against: https://lkml.org/lkml/2015/9/13/195,
which causes the use-after-free quite quickly and here:
https://lkml.org/lkml/2015/10/2/693.
Thanks,
-Jason
v3:
-beef up memory barrier comments in 3/3 (Peter Zijlstra) -clean up unix_dgram_writable() function in 3/3 (Joe Perches)
Jason Baron (3):
net: unix: fix use-after-free in unix_dgram_poll()
net: unix: Convert gc_flags to flags
net: unix: optimize wakeups in unix_dgram_recvmsg()
include/net/af_unix.h | 4 +- net/unix/af_unix.c | 117 +++++++++++++++++++++++++++++++++++++++----------- net/unix/garbage.c | 12 +++--- 3 files changed, 101 insertions(+), 32 deletions(-)
Signature · 1 line
2.6.1
PATCH v3 1/3 net: unix: fix use-after-free in unix_dgram_poll()
The unix_dgram_poll() routine calls sock_poll_wait() not only for the wait queue associated with the socket s that we are poll'ing against, but also calls sock_poll_wait() for a remote peer socket p, if it is connected. Thus, if we call poll()/select()/epoll() for the socket s, there are then a couple of code paths in which the remote peer socket p and its associated peer_wait queue can be freed before poll()/select()/epoll() have a chance to remove themselves from the remote peer socket.
The way that remote peer socket can be freed are:
1. If s calls connect() to a connect to a new socket other than p, it will drop its reference on p, and thus a close() on p will free it.
2. If we call close on p(), then a subsequent sendmsg() from s, will drop the final reference to p, allowing it to be freed.
Address this issue, by reverting unix_dgram_poll() to only register with the wait queue associated with s and register a callback with the remote peer socket on connect() that will wake up the wait queue associated with s. If scenarios 1 or 2 occur above we then simply remove the callback from the remote peer. This then presents the expected semantics to poll()/select()/ epoll().
I've implemented this for sock-type, SOCK_RAW, SOCK_DGRAM, and SOCK_SEQPACKET but not for SOCK_STREAM, since SOCK_STREAM does not use unix_dgram_poll().
Introduced in commit ec0d215f9420 ("af_unix: fix 'poll for write'/connected DGRAM sockets").
--- include/net/af_unix.h | 1 + net/unix/af_unix.c | 32 +++++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-)
include/net/af_unix.h +1 -0
@@ -62,6 +62,7 @@ struct unix_sock {#define UNIX_GC_CANDIDATE 0#define UNIX_GC_MAYBE_CYCLE 1struct socket_wq peer_wq; +wait_queue_t wait; };#define unix_sk (__sk) ((struct unix_sock *)__sk)
net/unix/af_unix.c +31 -1
@@ -420,6 +420,9 @@ static void unix_release_sock(struct sock *sk, int embrion) skpair =unix_peer (sk);if (skpair !=NULL ) { +if (sk->sk_type !=SOCK_STREAM ) +remove_wait_queue (&unix_sk (skpair)->peer_wait, + &u->wait);if (sk->sk_type ==SOCK_STREAM || sk->sk_type ==SOCK_SEQPACKET ) {unix_state_lock (skpair);/* No more writes */
@@ -636,6 +639,16 @@ static struct proto unix_proto = {*/ static struct lock_class_key af_unix_sk_receive_queue_lock_key; +static int peer_wake (wait_queue_t *wait,unsigned mode,int sync,void *key) +{ +struct unix_sock *u; + + u = container_of(wait, struct unix_sock, wait); +wake_up_interruptible_sync_poll (sk_sleep (&u->sk), key); + +return 0 ; +} +static struct sock *unix_create1 (struct net *net,struct socket *sock,int kern) {struct sock *sk =NULL ;
@@ -664,6 +677,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->wait, peer_wake);unix_insert_socket (unix_sockets_unbound (sk), sk); out:if (sk ==NULL )
@@ -1030,7 +1044,11 @@ restart:*/ if (unix_peer (sk)) {struct sock *old_peer =unix_peer (sk); + +remove_wait_queue (&unix_sk (old_peer)->peer_wait, + &unix_sk (sk)->wait);unix_peer (sk) = other; +add_wait_queue (&unix_sk (other)->peer_wait, &unix_sk (sk)->wait);unix_state_double_unlock (sk, other);if (other != old_peer)
@@ -1038,8 +1056,12 @@ restart:sock_put (old_peer); }else {unix_peer (sk) = other; +add_wait_queue (&unix_sk (other)->peer_wait, &unix_sk (sk)->wait);unix_state_double_unlock (sk, other); } +/* New remote may have created write space for us */ +wake_up_interruptible_sync_poll (sk_sleep (sk), +POLLOUT |POLLWRNORM |POLLWRBAND );return 0 ; out_unlock:
@@ -1194,6 +1216,8 @@ restart:sock_hold (sk);unix_peer (newsk) = sk; +if (sk->sk_type ==SOCK_SEQPACKET ) +add_wait_queue (&unix_sk (sk)->peer_wait, &unix_sk (newsk)->wait); newsk->sk_state =TCP_ESTABLISHED ; newsk->sk_type = sk->sk_type;init_peercred (newsk);
@@ -1220,6 +1244,8 @@ restart:smp_mb__after_atomic ();/* sock_hold() does an atomic_inc() */ unix_peer (sk) = newsk; +if (sk->sk_type ==SOCK_SEQPACKET ) +add_wait_queue (&unix_sk (newsk)->peer_wait, &unix_sk (sk)->wait);unix_state_unlock (sk);
@@ -1254,6 +1280,10 @@ static int unix_socketpair(struct socket *socka, struct socket *sockb)sock_hold (skb);unix_peer (ska) = skb;unix_peer (skb) = ska; +if (ska->sk_type !=SOCK_STREAM ) { +add_wait_queue (&unix_sk (ska)->peer_wait, &unix_sk (skb)->wait); +add_wait_queue (&unix_sk (skb)->peer_wait, &unix_sk (ska)->wait); + }init_peercred (ska);init_peercred (skb);
@@ -1565,6 +1595,7 @@ restart:unix_state_lock (sk);if (unix_peer (sk) == other) {unix_peer (sk) =NULL ; +remove_wait_queue (&unix_sk (other)->peer_wait, &u->wait);unix_state_unlock (sk);unix_dgram_disconnected (sk, other);
@@ -2441,7 +2472,6 @@ static unsigned int unix_dgram_poll(struct file *file, struct socket *sock, 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 ; }
Signature · 1 line
2.6.1
PATCH v3 2/3 net: unix: Convert gc_flags to flags
In reply to Jason BaronConvert gc_flags to flags in perparation for the subsequent patch, which will make use of a flag bit for a non-gc purpose.
--- include/net/af_unix.h | 2 +- net/unix/garbage.c | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-)
include/net/af_unix.h +1 -1
@@ -58,7 +58,7 @@ struct unix_sock {atomic_long_t inflight;spinlock_t lock;unsigned char recursion_level; -unsigned long gc_flags; +unsigned long flags;#define UNIX_GC_CANDIDATE 0#define UNIX_GC_MAYBE_CYCLE 1struct socket_wq peer_wq;
net/unix/garbage.c +6 -6
@@ -179,7 +179,7 @@ static void scan_inflight(struct sock *x, void (*func)(struct unix_sock *),* have been added to the queues after * starting the garbage collection */ -if (test_bit (UNIX_GC_CANDIDATE , &u->gc_flags)) { +if (test_bit (UNIX_GC_CANDIDATE , &u->flags)) { hit = true;func (u);
@@ -246,7 +246,7 @@ static void inc_inflight_move_tail(struct unix_sock *u)* of the list, so that it's checked even if it was already * passed over */ -if (test_bit (UNIX_GC_MAYBE_CYCLE , &u->gc_flags)) +if (test_bit (UNIX_GC_MAYBE_CYCLE , &u->flags))list_move_tail (&u->link, &gc_candidates); }
@@ -305,8 +305,8 @@ void unix_gc(void)BUG_ON (total_refs < inflight_refs);if (total_refs == inflight_refs) {list_move_tail (&u->link, &gc_candidates); -__set_bit (UNIX_GC_CANDIDATE , &u->gc_flags); -__set_bit (UNIX_GC_MAYBE_CYCLE , &u->gc_flags); +__set_bit (UNIX_GC_CANDIDATE , &u->flags); +__set_bit (UNIX_GC_MAYBE_CYCLE , &u->flags); } }
@@ -332,7 +332,7 @@ void unix_gc(void)if (atomic_long_read (&u->inflight) >0 ) {list_move_tail (&u->link, ¬_cycle_list); -__clear_bit (UNIX_GC_MAYBE_CYCLE , &u->gc_flags); +__clear_bit (UNIX_GC_MAYBE_CYCLE , &u->flags);scan_children (&u->sk, inc_inflight_move_tail,NULL ); } }
@@ -343,7 +343,7 @@ void unix_gc(void)*/ while (!list_empty (¬_cycle_list)) { u = list_entry(not_cycle_list.next, struct unix_sock, link); -__clear_bit (UNIX_GC_CANDIDATE , &u->gc_flags); +__clear_bit (UNIX_GC_CANDIDATE , &u->flags);list_move_tail (&u->link, &gc_inflight_list); }
Signature · 1 line
2.6.1
PATCH v3 3/3 net: unix: optimize wakeups in unix_dgram_recvmsg()
In reply to Jason BaronNow that connect() permanently registers a callback routine, we can induce extra overhead in unix_dgram_recvmsg(), which unconditionally wakes up its peer_wait queue on every receive. This patch makes the wakeup there conditional on there being waiters.
--- include/net/af_unix.h | 1 + net/unix/af_unix.c | 85 ++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 62 insertions(+), 24 deletions(-)
include/net/af_unix.h +1 -0
@@ -61,6 +61,7 @@ struct unix_sock {unsigned long flags;#define UNIX_GC_CANDIDATE 0#define UNIX_GC_MAYBE_CYCLE 1 +#define UNIX_NOSPACE 2struct socket_wq peer_wq;wait_queue_t wait; };
net/unix/af_unix.c +61 -24
130 lines not on this page · raw message
Signature · 1 line
2.6.1