Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > linux.kernel > #1238603 > unrolled thread
| Started by | Jason Baron <jbaron@akamai.com> |
|---|---|
| First post | 2015-10-02 22:50 +0200 |
| Last post | 2015-10-05 20:00 +0200 |
| Articles | 10 — 4 participants |
Back to article view | Back to linux.kernel
[PATCH v2 0/3] af_unix: fix use-after-free Jason Baron <jbaron@akamai.com> - 2015-10-02 22:50 +0200
[PATCH v2 2/3] af_unix: Convert gc_flags to flags Jason Baron <jbaron@akamai.com> - 2015-10-02 22:50 +0200
[PATCH v2 1/3] unix: fix use-after-free in unix_dgram_poll() Jason Baron <jbaron@akamai.com> - 2015-10-02 22:50 +0200
Re: [PATCH v2 1/3] unix: fix use-after-free in unix_dgram_poll() Mathias Krause <minipli@googlemail.com> - 2015-10-03 07:50 +0200
Re: [PATCH v2 1/3] unix: fix use-after-free in unix_dgram_poll() Rainer Weikusat <rweikusat@mobileactivedefense.com> - 2015-10-03 19:10 +0200
Re: [PATCH v2 1/3] unix: fix use-after-free in unix_dgram_poll() Rainer Weikusat <rweikusat@mobileactivedefense.com> - 2015-10-04 19:50 +0200
Re: [PATCH v2 1/3] unix: fix use-after-free in unix_dgram_poll() Rainer Weikusat <rweikusat@mobileactivedefense.com> - 2015-10-05 18:40 +0200
Re: [PATCH v2 1/3] unix: fix use-after-free in unix_dgram_poll() Eric Dumazet <eric.dumazet@gmail.com> - 2015-10-05 19:00 +0200
Re: [PATCH v2 1/3] unix: fix use-after-free in unix_dgram_poll() Rainer Weikusat <rweikusat@mobileactivedefense.com> - 2015-10-05 19:30 +0200
Re: [PATCH v2 1/3] unix: fix use-after-free in unix_dgram_poll() Jason Baron <jbaron@akamai.com> - 2015-10-05 20:00 +0200
| From | Jason Baron <jbaron@akamai.com> |
|---|---|
| Date | 2015-10-02 22:50 +0200 |
| Subject | [PATCH v2 0/3] af_unix: fix use-after-free |
| Message-ID | <qffrc-5YK-5@gated-at.bofh.it> |
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 Jason Baron (3): unix: fix use-after-free in unix_dgram_poll() af_unix: Convert gc_flags to flags af_unix: optimize the unix_dgram_recvmsg() include/net/af_unix.h | 4 +- net/unix/af_unix.c | 104 ++++++++++++++++++++++++++++++++++++++------------ net/unix/garbage.c | 12 +++--- 3 files changed, 88 insertions(+), 32 deletions(-) -- 1.8.2.rc2 -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/
[toc] | [next] | [standalone]
| From | Jason Baron <jbaron@akamai.com> |
|---|---|
| Date | 2015-10-02 22:50 +0200 |
| Subject | [PATCH v2 2/3] af_unix: Convert gc_flags to flags |
| Message-ID | <qffrc-5YK-15@gated-at.bofh.it> |
| In reply to | #1238603 |
Convert gc_flags to flags in preparation for the subsequent patch, which will
make use of a flag bit for a non-gc purpose.
Signed-off-by: Jason Baron <jbaron@akamai.com>
---
include/net/af_unix.h | 2 +-
net/unix/garbage.c | 12 ++++++------
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/include/net/af_unix.h b/include/net/af_unix.h
index 9698aff..6a4a345 100644
--- a/include/net/af_unix.h
+++ b/include/net/af_unix.h
@@ -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 1
struct socket_wq peer_wq;
diff --git a/net/unix/garbage.c b/net/unix/garbage.c
index a73a226..39794d9 100644
--- a/net/unix/garbage.c
+++ b/net/unix/garbage.c
@@ -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);
}
--
1.8.2.rc2
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
[toc] | [prev] | [next] | [standalone]
| From | Jason Baron <jbaron@akamai.com> |
|---|---|
| Date | 2015-10-02 22:50 +0200 |
| Subject | [PATCH v2 1/3] unix: fix use-after-free in unix_dgram_poll() |
| Message-ID | <qffrc-5YK-21@gated-at.bofh.it> |
| In reply to | #1238603 |
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().
Signed-off-by: Jason Baron <jbaron@akamai.com>
---
include/net/af_unix.h | 1 +
net/unix/af_unix.c | 32 +++++++++++++++++++++++++++++++-
2 files changed, 32 insertions(+), 1 deletion(-)
diff --git a/include/net/af_unix.h b/include/net/af_unix.h
index 4a167b3..9698aff 100644
--- a/include/net/af_unix.h
+++ b/include/net/af_unix.h
@@ -62,6 +62,7 @@ struct unix_sock {
#define UNIX_GC_CANDIDATE 0
#define UNIX_GC_MAYBE_CYCLE 1
struct socket_wq peer_wq;
+ wait_queue_t wait;
};
#define unix_sk(__sk) ((struct unix_sock *)__sk)
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 03ee4d3..f789423 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -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;
}
--
1.8.2.rc2
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
[toc] | [prev] | [next] | [standalone]
| From | Mathias Krause <minipli@googlemail.com> |
|---|---|
| Date | 2015-10-03 07:50 +0200 |
| Subject | Re: [PATCH v2 1/3] unix: fix use-after-free in unix_dgram_poll() |
| Message-ID | <qfnRL-1aO-5@gated-at.bofh.it> |
| In reply to | #1238607 |
On 2 October 2015 at 22:43, Jason Baron <jbaron@akamai.com> wrote:
> 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().
>
> Signed-off-by: Jason Baron <jbaron@akamai.com>
> ---
> include/net/af_unix.h | 1 +
> net/unix/af_unix.c | 32 +++++++++++++++++++++++++++++++-
> 2 files changed, 32 insertions(+), 1 deletion(-)
>
> diff --git a/include/net/af_unix.h b/include/net/af_unix.h
> index 4a167b3..9698aff 100644
> --- a/include/net/af_unix.h
> +++ b/include/net/af_unix.h
> @@ -62,6 +62,7 @@ struct unix_sock {
> #define UNIX_GC_CANDIDATE 0
> #define UNIX_GC_MAYBE_CYCLE 1
> struct socket_wq peer_wq;
> + wait_queue_t wait;
> };
> #define unix_sk(__sk) ((struct unix_sock *)__sk)
>
> diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
> index 03ee4d3..f789423 100644
> --- a/net/unix/af_unix.c
> +++ b/net/unix/af_unix.c
> @@ -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;
> }
> --
> 1.8.2.rc2
>
My reproducer runs on this patch for more than 3 days now without
triggering anything anymore.
Tested-by: Mathias Krause <minipli@googlemail.com>
Thanks Jason!
Regards,
Mathias
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
[toc] | [prev] | [next] | [standalone]
| From | Rainer Weikusat <rweikusat@mobileactivedefense.com> |
|---|---|
| Date | 2015-10-03 19:10 +0200 |
| Subject | Re: [PATCH v2 1/3] unix: fix use-after-free in unix_dgram_poll() |
| Message-ID | <qfytQ-86k-23@gated-at.bofh.it> |
| In reply to | #1238735 |
Mathias Krause <minipli@googlemail.com> writes: > On 2 October 2015 at 22:43, Jason Baron <jbaron@akamai.com> wrote: >> 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 [useless full-quote removed] > My reproducer runs on this patch for more than 3 days now without > triggering anything anymore. Since the behaviour of your program is random, using it to "test" anything doesn't really provide any insight: It could have been executing the same codepath which doesn't happen to trigger any problems for all of these three days. Nobody can tell. -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/
[toc] | [prev] | [next] | [standalone]
| From | Rainer Weikusat <rweikusat@mobileactivedefense.com> |
|---|---|
| Date | 2015-10-04 19:50 +0200 |
| Subject | Re: [PATCH v2 1/3] unix: fix use-after-free in unix_dgram_poll() |
| Message-ID | <qfVA6-7jd-11@gated-at.bofh.it> |
| In reply to | #1238960 |
Rainer Weikusat <rweikusat@mobileactivedefense.com> writes:
> Mathias Krause <minipli@googlemail.com> writes:
>> On 2 October 2015 at 22:43, Jason Baron <jbaron@akamai.com> wrote:
>>> 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
>
> [useless full-quote removed]
>
>> My reproducer runs on this patch for more than 3 days now without
>> triggering anything anymore.
>
> Since the behaviour of your program is random, using it to "test"
> anything doesn't really provide any insight: It could have been
> executing the same codepath which doesn't happen to trigger any problems
> for all of these three days. Nobody can tell.
Since this "strangely" seems to have been lost in the thread: Here's the
test program showing that the reconnect while in epoll actually causes a
problem (at least I think so):
--------
#include <fcntl.h>
#include <pthread.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/epoll.h>
#include <signal.h>
#include <unistd.h>
static int sk, tg0, tg1;
static void *epoller(void *unused)
{
struct epoll_event epev;
int epfd;
epfd = epoll_create(1);
if (epfd == -1) exit(0);
epev.events = EPOLLOUT;
epoll_ctl(epfd, EPOLL_CTL_ADD, sk, &epev);
epoll_wait(epfd, &epev, 1, 5000);
close(sk);
execl("./a.out", "./a.out", (void *)0);
return NULL;
}
int main(void)
{
struct sockaddr_un sun;
pthread_t tid;
int rc;
sun.sun_family = AF_UNIX;
tg0 = socket(AF_UNIX, SOCK_DGRAM, 0);
strncpy(sun.sun_path, "/tmp/tg0", sizeof(sun.sun_path));
unlink(sun.sun_path);
bind(tg0, (struct sockaddr *)&sun, sizeof(sun));
tg1 = socket(AF_UNIX, SOCK_DGRAM, 0);
strncpy(sun.sun_path, "/tmp/tg1", sizeof(sun.sun_path));
unlink(sun.sun_path);
bind(tg1, (struct sockaddr *)&sun, sizeof(sun));
sk = socket(AF_UNIX, SOCK_DGRAM, 0);
connect(sk, (struct sockaddr *)&sun, sizeof(sun));
fcntl(sk, F_SETFL, fcntl(sk, F_GETFL) | O_NONBLOCK);
while ((rc = write(sk, "bla", 3)) != -1);
pthread_create(&tid, NULL, epoller, NULL);
usleep(5);
strncpy(sun.sun_path, "/tmp/tg0", sizeof(sun.sun_path));
connect(sk, (struct sockaddr *)&sun, sizeof(sun));
close(tg1);
pause();
return 0;
}
----------
And here the other demonstrating the poller not being woken up despite
it could write something:
----------
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/poll.h>
#include <sys/wait.h>
#include <unistd.h>
int main(void)
{
struct sockaddr_un sun;
struct pollfd pfd;
int tg, sk0, sk1, rc;
char buf[16];
sun.sun_family = AF_UNIX;
tg = socket(AF_UNIX, SOCK_DGRAM, 0);
strncpy(sun.sun_path, "/tmp/tg", sizeof(sun.sun_path));
unlink(sun.sun_path);
bind(tg, (struct sockaddr *)&sun, sizeof(sun));
sk0 = socket(AF_UNIX, SOCK_DGRAM, 0);
connect(sk0, (struct sockaddr *)&sun, sizeof(sun));
sk1 = socket(AF_UNIX, SOCK_DGRAM, 0);
connect(sk1, (struct sockaddr *)&sun, sizeof(sun));
fcntl(sk0, F_SETFL, fcntl(sk0, F_GETFL) | O_NONBLOCK);
fcntl(sk1, F_SETFL, fcntl(sk1, F_GETFL) | O_NONBLOCK);
while (write(sk0, "bla", 3) != -1);
if (fork() == 0) {
pfd.fd = sk1;
pfd.events = POLLOUT;
rc = poll(&pfd, 1, -1);
_exit(0);
}
sleep(3);
read(tg, buf, sizeof(buf));
wait(&rc);
return 0;
}
-----------
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
[toc] | [prev] | [next] | [standalone]
| From | Rainer Weikusat <rweikusat@mobileactivedefense.com> |
|---|---|
| Date | 2015-10-05 18:40 +0200 |
| Subject | Re: [PATCH v2 1/3] unix: fix use-after-free in unix_dgram_poll() |
| Message-ID | <qggXU-4mO-21@gated-at.bofh.it> |
| In reply to | #1238607 |
Jason Baron <jbaron@akamai.com> writes:
> 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.
Here's a more simple idea which _might_ work. The underlying problem
seems to be that the second sock_poll_wait introduces a covert reference
to the peer socket which isn't accounted for. The basic idea behind this
is to execute an additional sock_hold for the peer whenever the
sock_poll_wait is called for it and store it (the struct sock *) in a
new struct unix_sock member. Upon entering unix_dgram_poll, if the
member is not NULL, it's cleared and a sock_put for its former value is
done. The 'poll peer not NULL -> sock_put it' code is also added to the
destructor, although I'm unsure if this is really necessary. The patch
below also includes the additional SOCK_DEAD test suggested by Martin as
that seems generally sensible to me.
NB: This has survived both Martin's and my test programs for a number
of executions/ longer periods of time than was common before without
generating list corruption warnings. The patch below is against 'my'
3.2.54 and is here provided as a suggestion in the hope that it will be
useful for someting, not as patch submission, as I spent less time
thinking through this than I should ideally have but despite of this,
it's another 2.5 hours of my life spent on something completely
different than what I should be working on at the moment.
--------------
diff -pru linux-2-6/include/net/af_unix.h linux-2-6.p/include/net/af_unix.h
--- linux-2-6/include/net/af_unix.h 2014-01-20 21:52:53.000000000 +0000
+++ linux-2-6.p/include/net/af_unix.h 2015-10-05 15:11:20.270958297 +0100
@@ -50,6 +50,7 @@ struct unix_sock {
struct vfsmount *mnt;
struct mutex readlock;
struct sock *peer;
+ struct sock *poll_peer;
struct sock *other;
struct list_head link;
atomic_long_t inflight;
diff -pru linux-2-6/net/unix/af_unix.c linux-2-6.p/net/unix/af_unix.c
--- linux-2-6/net/unix/af_unix.c 2014-01-22 16:51:52.000000000 +0000
+++ linux-2-6.p/net/unix/af_unix.c 2015-10-05 17:05:28.358273567 +0100
@@ -361,6 +361,9 @@ static void unix_sock_destructor(struct
if (u->addr)
unix_release_addr(u->addr);
+ if (u->poll_peer)
+ sock_put(u->poll_peer);
+
atomic_long_dec(&unix_nr_socks);
local_bh_disable();
sock_prot_inuse_add(sock_net(sk), sk->sk_prot, -1);
@@ -625,6 +628,7 @@ static struct sock *unix_create1(struct
u = unix_sk(sk);
u->dentry = NULL;
u->mnt = NULL;
+ u->poll_peer = NULL;
spin_lock_init(&u->lock);
atomic_long_set(&u->inflight, 0);
INIT_LIST_HEAD(&u->link);
@@ -2135,8 +2139,16 @@ static unsigned int unix_poll(struct fil
static unsigned int unix_dgram_poll(struct file *file, struct socket *sock,
poll_table *wait)
{
- struct sock *sk = sock->sk, *other;
- unsigned int mask, writable;
+ struct sock *sk = sock->sk, *other, *pp;
+ struct unix_sock *u;
+ unsigned int mask, writable, dead;
+
+ u = unix_sk(sk);
+ pp = u->poll_peer;
+ if (pp) {
+ u->poll_peer = NULL;
+ sock_put(pp);
+ }
sock_poll_wait(file, sk_sleep(sk), wait);
mask = 0;
@@ -2170,7 +2182,20 @@ static unsigned int unix_dgram_poll(stru
other = unix_peer_get(sk);
if (other) {
if (unix_peer(other) != sk) {
- sock_poll_wait(file, &unix_sk(other)->peer_wait, wait);
+ unix_state_lock(other);
+
+ dead = sock_flag(other, SOCK_DEAD);
+ if (!dead)
+ sock_poll_wait(file, &unix_sk(other)->peer_wait,
+ wait);
+
+ unix_state_unlock(other);
+
+ if (!dead) {
+ u->poll_peer = other;
+ sock_hold(other);
+ }
+
if (unix_recvq_full(other))
writable = 0;
}
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
[toc] | [prev] | [next] | [standalone]
| From | Eric Dumazet <eric.dumazet@gmail.com> |
|---|---|
| Date | 2015-10-05 19:00 +0200 |
| Subject | Re: [PATCH v2 1/3] unix: fix use-after-free in unix_dgram_poll() |
| Message-ID | <qghhg-4Jy-19@gated-at.bofh.it> |
| In reply to | #1239735 |
On Mon, 2015-10-05 at 17:31 +0100, Rainer Weikusat wrote:
> atomic_long_set(&u->inflight, 0);
> INIT_LIST_HEAD(&u->link);
> @@ -2135,8 +2139,16 @@ static unsigned int unix_poll(struct fil
> static unsigned int unix_dgram_poll(struct file *file, struct socket *sock,
> poll_table *wait)
> {
> - struct sock *sk = sock->sk, *other;
> - unsigned int mask, writable;
> + struct sock *sk = sock->sk, *other, *pp;
> + struct unix_sock *u;
> + unsigned int mask, writable, dead;
> +
> + u = unix_sk(sk);
> + pp = u->poll_peer;
> + if (pp) {
> + u->poll_peer = NULL;
> + sock_put(pp);
> + }
This looks racy.
Multiple threads could use poll() at the same time,
and you would have too many sock_put()
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
[toc] | [prev] | [next] | [standalone]
| From | Rainer Weikusat <rweikusat@mobileactivedefense.com> |
|---|---|
| Date | 2015-10-05 19:30 +0200 |
| Subject | Re: [PATCH v2 1/3] unix: fix use-after-free in unix_dgram_poll() |
| Message-ID | <qghKh-5wB-13@gated-at.bofh.it> |
| In reply to | #1239750 |
Eric Dumazet <eric.dumazet@gmail.com> writes:
> On Mon, 2015-10-05 at 17:31 +0100, Rainer Weikusat wrote:
>
>> atomic_long_set(&u->inflight, 0);
>> INIT_LIST_HEAD(&u->link);
>> @@ -2135,8 +2139,16 @@ static unsigned int unix_poll(struct fil
>> static unsigned int unix_dgram_poll(struct file *file, struct socket *sock,
>> poll_table *wait)
>> {
>> - struct sock *sk = sock->sk, *other;
>> - unsigned int mask, writable;
>> + struct sock *sk = sock->sk, *other, *pp;
>> + struct unix_sock *u;
>> + unsigned int mask, writable, dead;
>> +
>> + u = unix_sk(sk);
>> + pp = u->poll_peer;
>> + if (pp) {
>> + u->poll_peer = NULL;
>> + sock_put(pp);
>> + }
>
>
> This looks racy.
> Multiple threads could use poll() at the same time,
> and you would have too many sock_put()
That's one of the reasons why I wrote "might work": The use of a single
structure member without any locking for the sock_poll_wait suggests
that this is taken care of in some other way, as does the absence of any
comment about that in the 'public' LDDs ("Linux Device Drivers"),
however, I don't really know if this is true. If not, this simple idea
can't work.
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
[toc] | [prev] | [next] | [standalone]
| From | Jason Baron <jbaron@akamai.com> |
|---|---|
| Date | 2015-10-05 20:00 +0200 |
| Subject | Re: [PATCH v2 1/3] unix: fix use-after-free in unix_dgram_poll() |
| Message-ID | <qgidm-64H-37@gated-at.bofh.it> |
| In reply to | #1239735 |
On 10/05/2015 12:31 PM, Rainer Weikusat wrote:
> Jason Baron <jbaron@akamai.com> writes:
>> 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.
>
> Here's a more simple idea which _might_ work. The underlying problem
> seems to be that the second sock_poll_wait introduces a covert reference
> to the peer socket which isn't accounted for. The basic idea behind this
> is to execute an additional sock_hold for the peer whenever the
> sock_poll_wait is called for it and store it (the struct sock *) in a
> new struct unix_sock member. Upon entering unix_dgram_poll, if the
> member is not NULL, it's cleared and a sock_put for its former value is
> done. The 'poll peer not NULL -> sock_put it' code is also added to the
> destructor, although I'm unsure if this is really necessary. The patch
> below also includes the additional SOCK_DEAD test suggested by Martin as
> that seems generally sensible to me.
>
> NB: This has survived both Martin's and my test programs for a number
> of executions/ longer periods of time than was common before without
> generating list corruption warnings. The patch below is against 'my'
> 3.2.54 and is here provided as a suggestion in the hope that it will be
> useful for someting, not as patch submission, as I spent less time
> thinking through this than I should ideally have but despite of this,
> it's another 2.5 hours of my life spent on something completely
> different than what I should be working on at the moment.
>
> --------------
> diff -pru linux-2-6/include/net/af_unix.h linux-2-6.p/include/net/af_unix.h
> --- linux-2-6/include/net/af_unix.h 2014-01-20 21:52:53.000000000 +0000
> +++ linux-2-6.p/include/net/af_unix.h 2015-10-05 15:11:20.270958297 +0100
> @@ -50,6 +50,7 @@ struct unix_sock {
> struct vfsmount *mnt;
> struct mutex readlock;
> struct sock *peer;
> + struct sock *poll_peer;
> struct sock *other;
> struct list_head link;
> atomic_long_t inflight;
> diff -pru linux-2-6/net/unix/af_unix.c linux-2-6.p/net/unix/af_unix.c
> --- linux-2-6/net/unix/af_unix.c 2014-01-22 16:51:52.000000000 +0000
> +++ linux-2-6.p/net/unix/af_unix.c 2015-10-05 17:05:28.358273567 +0100
> @@ -361,6 +361,9 @@ static void unix_sock_destructor(struct
> if (u->addr)
> unix_release_addr(u->addr);
>
> + if (u->poll_peer)
> + sock_put(u->poll_peer);
> +
> atomic_long_dec(&unix_nr_socks);
> local_bh_disable();
> sock_prot_inuse_add(sock_net(sk), sk->sk_prot, -1);
> @@ -625,6 +628,7 @@ static struct sock *unix_create1(struct
> u = unix_sk(sk);
> u->dentry = NULL;
> u->mnt = NULL;
> + u->poll_peer = NULL;
> spin_lock_init(&u->lock);
> atomic_long_set(&u->inflight, 0);
> INIT_LIST_HEAD(&u->link);
> @@ -2135,8 +2139,16 @@ static unsigned int unix_poll(struct fil
> static unsigned int unix_dgram_poll(struct file *file, struct socket *sock,
> poll_table *wait)
> {
> - struct sock *sk = sock->sk, *other;
> - unsigned int mask, writable;
> + struct sock *sk = sock->sk, *other, *pp;
> + struct unix_sock *u;
> + unsigned int mask, writable, dead;
> +
> + u = unix_sk(sk);
> + pp = u->poll_peer;
> + if (pp) {
> + u->poll_peer = NULL;
> + sock_put(pp);
> + }
>
> sock_poll_wait(file, sk_sleep(sk), wait);
> mask = 0;
> @@ -2170,7 +2182,20 @@ static unsigned int unix_dgram_poll(stru
> other = unix_peer_get(sk);
> if (other) {
> if (unix_peer(other) != sk) {
> - sock_poll_wait(file, &unix_sk(other)->peer_wait, wait);
> + unix_state_lock(other);
> +
> + dead = sock_flag(other, SOCK_DEAD);
> + if (!dead)
> + sock_poll_wait(file, &unix_sk(other)->peer_wait,
> + wait);
> +
> + unix_state_unlock(other);
> +
> + if (!dead) {
> + u->poll_peer = other;
> + sock_hold(other);
> + }
> +
> if (unix_recvq_full(other))
> writable = 0;
> }
>
Interesting - will this work for the test case you supplied where we are in
epoll_wait() and another thread does a connect() to a new target? In that
case, even if we issue a wakeup to the epoll thread, its not going to have
a non-NULL poll_table, so it wouldn't be added to the new target. IE
the first test case here:
https://lkml.org/lkml/2015/10/4/154
Only a re-add or modify using epoll_ctl() will re-register with a non-NULL
poll_table.
Thanks,
-Jason
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
[toc] | [prev] | [standalone]
Back to top | Article view | linux.kernel
csiph-web