From: Jason Baron <jbaron@akamai.com> Date: 2015-10-09 04:16:24
Hi,
These patches are against mainline, I can re-base to net-next, please
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
v4:
-set UNIX_NOSPACE only if the peer socket has receive space
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 | 124 ++++++++++++++++++++++++++++++++++++++++----------
net/unix/garbage.c | 12 ++---
3 files changed, 108 insertions(+), 32 deletions(-)
--
2.6.1
From: Jason Baron <jbaron@akamai.com> Date: 2015-10-09 04:16:31
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").
Tested-by: Mathias Krause <redacted>
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(-)
@@ -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 = {*/staticstructlock_class_keyaf_unix_sk_receive_queue_lock_key;+staticintpeer_wake(wait_queue_t*wait,unsignedmode,intsync,void*key)+{+structunix_sock*u;++u=container_of(wait,structunix_sock,wait);+wake_up_interruptible_sync_poll(sk_sleep(&u->sk),key);++return0;+}+staticstructsock*unix_create1(structnet*net,structsocket*sock,intkern){structsock*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)
@@ -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);return0;out_unlock:
@@ -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);
From: Jason Baron <jbaron@akamai.com> Date: 2015-10-09 04:16:35
Convert gc_flags to flags in perparation 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(-)
From: Jason Baron <jbaron@akamai.com> Date: 2015-10-09 04:16:53
Now 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.
Tested using: http://www.spinics.net/lists/netdev/msg145533.html
Signed-off-by: Jason Baron <jbaron@akamai.com>
---
include/net/af_unix.h | 1 +
net/unix/af_unix.c | 92 +++++++++++++++++++++++++++++++++++++--------------
2 files changed, 69 insertions(+), 24 deletions(-)
@@ -1079,6 +1079,12 @@ static long unix_wait_for_peer(struct sock *other, long timeo)prepare_to_wait_exclusive(&u->peer_wait,&wait,TASK_INTERRUPTIBLE);+set_bit(UNIX_NOSPACE,&u->flags);+/* Ensure that we either see space in the peer sk_receive_queue via the+*unix_recvq_full()checkbelow,orwereceiveawakeupwhenit+*empties.Pairswiththembinunix_dgram_recvmsg().+*/+smp_mb__after_atomic();sched=!sock_flag(other,SOCK_DEAD)&&!(other->sk_shutdown&RCV_SHUTDOWN)&&unix_recvq_full(other);
@@ -1623,17 +1629,27 @@ restart:if(unix_peer(other)!=sk&&unix_recvq_full(other)){if(!timeo){-err=-EAGAIN;-gotoout_unlock;-}--timeo=unix_wait_for_peer(other,timeo);+set_bit(UNIX_NOSPACE,&unix_sk(other)->flags);+/* Ensure that we either see space in the peer+*sk_receive_queueviatheunix_recvq_full()check+*below,orwereceiveawakeupwhenitempties.This+*makessurethatepollETtriggerscorrectly.Pairs+*withthembinunix_dgram_recvmsg().+*/+smp_mb__after_atomic();+if(unix_recvq_full(other)){+err=-EAGAIN;+gotoout_unlock;+}+}else{+timeo=unix_wait_for_peer(other,timeo);-err=sock_intr_errno(timeo);-if(signal_pending(current))-gotoout_free;+err=sock_intr_errno(timeo);+if(signal_pending(current))+gotoout_free;-gotorestart;+gotorestart;+}}if(sock_flag(other,SOCK_RCVTSTAMP))
@@ -1939,8 +1955,19 @@ static int unix_dgram_recvmsg(struct socket *sock, struct msghdr *msg,gotoout_unlock;}-wake_up_interruptible_sync_poll(&u->peer_wait,-POLLOUT|POLLWRNORM|POLLWRBAND);+/* Ensure that waiters on our sk->sk_receive_queue draining that check+*viaunix_recvq_full()eitherseespaceinthequeueorgetawakeup+*below.sk->sk_receive_queueisreduecebythe__skb_recv_datagram()+*callabove.Pairswiththembinunix_dgram_sendmsg(),+*unix_dgram_poll(),andunix_wait_for_peer().+*/+smp_mb();+if(test_bit(UNIX_NOSPACE,&u->flags)){+clear_bit(UNIX_NOSPACE,&u->flags);+wake_up_interruptible_sync_poll(&u->peer_wait,+POLLOUT|POLLWRNORM|+POLLWRBAND);+}if(msg->msg_name)unix_copy_addr(msg,skb->sk);
@@ -2468,20 +2509,23 @@ static unsigned int unix_dgram_poll(struct file *file, struct socket *sock,if(!(poll_requested_events(wait)&(POLLWRBAND|POLLWRNORM|POLLOUT)))returnmask;-writable=unix_writable(sk);other=unix_peer_get(sk);-if(other){-if(unix_peer(other)!=sk){-if(unix_recvq_full(other))-writable=0;-}-sock_put(other);-}--if(writable)+if(unix_dgram_writable(sk,other,&other_nospace)){mask|=POLLOUT|POLLWRNORM|POLLWRBAND;-else+}else{set_bit(SOCK_ASYNC_NOSPACE,&sk->sk_socket->flags);+if(other_nospace)+set_bit(UNIX_NOSPACE,&unix_sk(other)->flags);+/* Ensure that we either see space in the peer sk_receive_queue+*viatheunix_recvq_full()checkbelow,orwereceiveawakeup+*whenitempties.Pairswiththembinunix_dgram_recvmsg().+*/+smp_mb__after_atomic();+if(unix_dgram_writable(sk,other,&other_nospace))+mask|=POLLOUT|POLLWRNORM|POLLWRBAND;+}+if(other)+sock_put(other);returnmask;}
From: kbuild test robot <hidden> Date: 2015-10-09 04:31:04
Hi Jason,
[auto build test ERROR on v4.3-rc3 -- if it's inappropriate base, please ignore]
config: x86_64-randconfig-i0-201540 (attached as .config)
reproduce:
# save the attached .config to linux build tree
make ARCH=x86_64
All errors (new ones prefixed by >>):
net/unix/af_unix.c: In function 'unix_dgram_writable':
quoted
net/unix/af_unix.c:2465:3: error: 'other_full' undeclared (first use in this function)
*other_full = false;
^
net/unix/af_unix.c:2465:3: note: each undeclared identifier is reported only once for each function it appears in
vim +/other_full +2465 net/unix/af_unix.c
2459 return mask;
2460 }
2461
2462 static bool unix_dgram_writable(struct sock *sk, struct sock *other,
2463 bool *other_nospace)
2464 {
2465 *other_full = false;
2466
2467 if (other && unix_peer(other) != sk && unix_recvq_full(other)) {
2468 *other_full = true;
---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation
From: Hannes Frederic Sowa <hidden> Date: 2015-10-09 14:38:34
Hi,
Jason Baron [off-list ref] 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.
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").
Tested-by: Mathias Krause <redacted>
Signed-off-by: Jason Baron <jbaron@akamai.com>
While I think this approach works, I haven't seen where the current code
leaks a reference. Assignment to unix_peer(sk) in general take spin_lock
and increment refcount. Are there bugs at the two places you referred
to?
Is an easier fix just to use atomic_inc_not_zero(&sk->sk_refcnt) in
unix_peer_get() which could also help other places?
Thanks,
Hannes
From: Jason Baron <jbaron@akamai.com> Date: 2015-10-09 15:12:24
On 10/09/2015 12:29 AM, kbuild test robot wrote:
Hi Jason,
[auto build test ERROR on v4.3-rc3 -- if it's inappropriate base, please ignore]
config: x86_64-randconfig-i0-201540 (attached as .config)
reproduce:
# save the attached .config to linux build tree
make ARCH=x86_64
All errors (new ones prefixed by >>):
net/unix/af_unix.c: In function 'unix_dgram_writable':
quoted
quoted
net/unix/af_unix.c:2465:3: error: 'other_full' undeclared (first use in this function)
*other_full = false;
^
net/unix/af_unix.c:2465:3: note: each undeclared identifier is reported only once for each function it appears in
Forgot to refresh this patch before sending. The one that I tested with
is below.
Thanks,
-Jason
Now 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.
Tested using: http://www.spinics.net/lists/netdev/msg145533.html
Signed-off-by: Jason Baron <jbaron@akamai.com>
---
include/net/af_unix.h | 1 +
net/unix/af_unix.c | 92 +++++++++++++++++++++++++++++++++++++--------------
2 files changed, 69 insertions(+), 24 deletions(-)
@@ -1079,6 +1079,12 @@ static long unix_wait_for_peer(struct sock *other, long timeo)prepare_to_wait_exclusive(&u->peer_wait,&wait,TASK_INTERRUPTIBLE);+set_bit(UNIX_NOSPACE,&u->flags);+/* Ensure that we either see space in the peer sk_receive_queue via the+*unix_recvq_full()checkbelow,orwereceiveawakeupwhenit+*empties.Pairswiththembinunix_dgram_recvmsg().+*/+smp_mb__after_atomic();sched=!sock_flag(other,SOCK_DEAD)&&!(other->sk_shutdown&RCV_SHUTDOWN)&&unix_recvq_full(other);
@@ -1623,17 +1629,27 @@ restart:if(unix_peer(other)!=sk&&unix_recvq_full(other)){if(!timeo){-err=-EAGAIN;-gotoout_unlock;-}--timeo=unix_wait_for_peer(other,timeo);+set_bit(UNIX_NOSPACE,&unix_sk(other)->flags);+/* Ensure that we either see space in the peer+*sk_receive_queueviatheunix_recvq_full()check+*below,orwereceiveawakeupwhenitempties.This+*makessurethatepollETtriggerscorrectly.Pairs+*withthembinunix_dgram_recvmsg().+*/+smp_mb__after_atomic();+if(unix_recvq_full(other)){+err=-EAGAIN;+gotoout_unlock;+}+}else{+timeo=unix_wait_for_peer(other,timeo);-err=sock_intr_errno(timeo);-if(signal_pending(current))-gotoout_free;+err=sock_intr_errno(timeo);+if(signal_pending(current))+gotoout_free;-gotorestart;+gotorestart;+}}if(sock_flag(other,SOCK_RCVTSTAMP))
@@ -1939,8 +1955,19 @@ static int unix_dgram_recvmsg(struct socket *sock, struct msghdr *msg,gotoout_unlock;}-wake_up_interruptible_sync_poll(&u->peer_wait,-POLLOUT|POLLWRNORM|POLLWRBAND);+/* Ensure that waiters on our sk->sk_receive_queue draining that check+*viaunix_recvq_full()eitherseespaceinthequeueorgetawakeup+*below.sk->sk_receive_queueisreduecebythe__skb_recv_datagram()+*callabove.Pairswiththembinunix_dgram_sendmsg(),+*unix_dgram_poll(),andunix_wait_for_peer().+*/+smp_mb();+if(test_bit(UNIX_NOSPACE,&u->flags)){+clear_bit(UNIX_NOSPACE,&u->flags);+wake_up_interruptible_sync_poll(&u->peer_wait,+POLLOUT|POLLWRNORM|+POLLWRBAND);+}if(msg->msg_name)unix_copy_addr(msg,skb->sk);
@@ -2468,20 +2509,23 @@ static unsigned int unix_dgram_poll(struct file *file, struct socket *sock,if(!(poll_requested_events(wait)&(POLLWRBAND|POLLWRNORM|POLLOUT)))returnmask;-writable=unix_writable(sk);other=unix_peer_get(sk);-if(other){-if(unix_peer(other)!=sk){-if(unix_recvq_full(other))-writable=0;-}-sock_put(other);-}--if(writable)+if(unix_dgram_writable(sk,other,&other_nospace)){mask|=POLLOUT|POLLWRNORM|POLLWRBAND;-else+}else{set_bit(SOCK_ASYNC_NOSPACE,&sk->sk_socket->flags);+if(other_nospace)+set_bit(UNIX_NOSPACE,&unix_sk(other)->flags);+/* Ensure that we either see space in the peer sk_receive_queue+*viatheunix_recvq_full()checkbelow,orwereceiveawakeup+*whenitempties.Pairswiththembinunix_dgram_recvmsg().+*/+smp_mb__after_atomic();+if(unix_dgram_writable(sk,other,&other_nospace))+mask|=POLLOUT|POLLWRNORM|POLLWRBAND;+}+if(other)+sock_put(other);returnmask;}
I'd like to understand how patches that don't even compile can be
"tested"?
net/unix/af_unix.c: In function ‘unix_dgram_writable’:
net/unix/af_unix.c:2480:3: error: ‘other_full’ undeclared (first use in this function)
net/unix/af_unix.c:2480:3: note: each undeclared identifier is reported only once for each function it appears in
Could you explain how that works, I'm having a hard time understanding
this?
Also please address Hannes's feedback, thanks.
From: Jason Baron <jbaron@akamai.com> Date: 2015-10-12 19:42:00
On 10/09/2015 10:38 AM, Hannes Frederic Sowa wrote:
Hi,
Jason Baron [off-list ref] writes:
quoted
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").
Tested-by: Mathias Krause <redacted>
Signed-off-by: Jason Baron <jbaron@akamai.com>
While I think this approach works, I haven't seen where the current code
leaks a reference. Assignment to unix_peer(sk) in general take spin_lock
and increment refcount. Are there bugs at the two places you referred
to?
Is an easier fix just to use atomic_inc_not_zero(&sk->sk_refcnt) in
unix_peer_get() which could also help other places?
Hi,
So we could potentially inc the refcnt on the remote peer such that the
remote peer does not free before the socket that has connected to it.
However, then the socket that has taken the reference against the peer
socket has to potentially record a number of remote sockets (all the ones
that it has connected to over its lifetime), and then drop all of their
refcnt's when it finally closes.
The reason for this is that with the current code when we do
poll()/select()/epoll() on a socket with a peer socket, those calls
take reference on the peer socket. Specifically, they record the remote
peer whead, such that they can remove their callbacks when they return.
So its not safe to just drop a reference on the remote peer when it
closes because their might be outstanding poll()/select()/epoll()
references pending.
Normally, poll()/select()/epoll() are waiting on a whead associated
directly with the fd/file that they are waiting for.
The other point here is that the way this patch structures things is
that when the socket connects to a new remote and hence disconnects from
an existing remote, POLLOUT events will continue to be correctly
delivered. That was not possible with the current structure of things
b/c there was no way to inform poll to re-register with the remote peer
whead. So, that means that the first test case here now works:
https://lkml.org/lkml/2015/10/4/154
Whereas with the old code test case would just hang for ever.
So yes there is a bit of code churn here, but I think it moves the
code-base in a direction that not only solves this issue, but corrects
additional poll() behaviors as well.
Thanks,
-Jason
I'd like to understand how patches that don't even compile can be
"tested"?
net/unix/af_unix.c: In function ‘unix_dgram_writable’:
net/unix/af_unix.c:2480:3: error: ‘other_full’ undeclared (first use in this function)
net/unix/af_unix.c:2480:3: note: each undeclared identifier is reported only once for each function it appears in
Could you explain how that works, I'm having a hard time understanding
this?
Traveling this week, so responses a bit delayed.
Yes, I screwed up the posting. I had some outstanding code in my
local tree to make it compile, but I failed to refresh my patch series
with this outstanding code before mailing it out. So what I tested/built
was not quite what I mailed out.
As soon as I noticed this issue in patch 3/3 I re-posted it here:
http://marc.info/?l=linux-netdev&m=144440355808472&w=2
in an attempt to avoid this confusion. I'm happy to re-post the series
or whatever makes things easiest for you.
From: Hannes Frederic Sowa <hidden> Date: 2015-10-13 11:42:51
Hello,
On Mon, Oct 12, 2015, at 21:41, Jason Baron wrote:
On 10/09/2015 10:38 AM, Hannes Frederic Sowa wrote:
quoted
Hi,
Jason Baron [off-list ref] writes:
quoted
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").
Tested-by: Mathias Krause <redacted>
Signed-off-by: Jason Baron <jbaron@akamai.com>
While I think this approach works, I haven't seen where the current code
leaks a reference. Assignment to unix_peer(sk) in general take spin_lock
and increment refcount. Are there bugs at the two places you referred
to?
Is an easier fix just to use atomic_inc_not_zero(&sk->sk_refcnt) in
unix_peer_get() which could also help other places?
Hi,
So we could potentially inc the refcnt on the remote peer such that the
remote peer does not free before the socket that has connected to it.
However, then the socket that has taken the reference against the peer
socket has to potentially record a number of remote sockets (all the ones
that it has connected to over its lifetime), and then drop all of their
refcnt's when it finally closes.
The reason for this is that with the current code when we do
poll()/select()/epoll() on a socket with a peer socket, those calls
take reference on the peer socket. Specifically, they record the remote
peer whead, such that they can remove their callbacks when they return.
So its not safe to just drop a reference on the remote peer when it
closes because their might be outstanding poll()/select()/epoll()
references pending.
Thanks for the explanation, it was very helpful. The eventpoll
infrastructure seems not to be easily able to handle these kind of
socket cross references easily, I understand.
Normally, poll()/select()/epoll() are waiting on a whead associated
directly with the fd/file that they are waiting for.
Exactly. The reference count is implicit by the current process to
handle the filedescriptor and deregister the wait heads during program
tear-down or close. So a sock_poll_wait call to a foreign socket's wait
queue will confuse this subsystem.
The other point here is that the way this patch structures things is
that when the socket connects to a new remote and hence disconnects from
an existing remote, POLLOUT events will continue to be correctly
delivered. That was not possible with the current structure of things
b/c there was no way to inform poll to re-register with the remote peer
whead. So, that means that the first test case here now works:
https://lkml.org/lkml/2015/10/4/154
Whereas with the old code test case would just hang for ever.
So yes there is a bit of code churn here, but I think it moves the
code-base in a direction that not only solves this issue, but corrects
additional poll() behaviors as well.
Agreed, the new semantics make sense to me and are an improvement.
Thanks,
Hannes