Skip to content
This repository has been archived by the owner on Dec 14, 2022. It is now read-only.

Commit

Permalink
sctp: use the right sk after waking up from wait_buf sleep
Browse files Browse the repository at this point in the history
[ Upstream commit cea0cc8 ]

Commit dfcb9f4 ("sctp: deny peeloff operation on asocs with threads
sleeping on it") fixed the race between peeloff and wait sndbuf by
checking waitqueue_active(&asoc->wait) in sctp_do_peeloff().

But it actually doesn't work, as even if waitqueue_active returns false
the waiting sndbuf thread may still not yet hold sk lock. After asoc is
peeled off, sk is not asoc->base.sk any more, then to hold the old sk
lock couldn't make assoc safe to access.

This patch is to fix this by changing to hold the new sk lock if sk is
not asoc->base.sk, meanwhile, also set the sk in sctp_sendmsg with the
new sk.

With this fix, there is no more race between peeloff and waitbuf, the
check 'waitqueue_active' in sctp_do_peeloff can be removed.

Thanks Marcelo and Neil for making this clear.

v1->v2:
  fix it by changing to lock the new sock instead of adding a flag in asoc.

Suggested-by: Neil Horman <[email protected]>
Signed-off-by: Xin Long <[email protected]>
Acked-by: Neil Horman <[email protected]>
Signed-off-by: David S. Miller <[email protected]>
Signed-off-by: Sasha Levin <[email protected]>
Signed-off-by: Greg Kroah-Hartman <[email protected]>
  • Loading branch information
lxin authored and gregkh committed Dec 14, 2017
1 parent 1158ecd commit 9904da5
Showing 1 changed file with 11 additions and 10 deletions.
21 changes: 11 additions & 10 deletions net/sctp/socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@
/* Forward declarations for internal helper functions. */
static int sctp_writeable(struct sock *sk);
static void sctp_wfree(struct sk_buff *skb);
static int sctp_wait_for_sndbuf(struct sctp_association *, long *timeo_p,
size_t msg_len);
static int sctp_wait_for_sndbuf(struct sctp_association *asoc, long *timeo_p,
size_t msg_len, struct sock **orig_sk);
static int sctp_wait_for_packet(struct sock *sk, int *err, long *timeo_p);
static int sctp_wait_for_connect(struct sctp_association *, long *timeo_p);
static int sctp_wait_for_accept(struct sock *sk, long timeo);
Expand Down Expand Up @@ -1957,7 +1957,8 @@ static int sctp_sendmsg(struct sock *sk, struct msghdr *msg, size_t msg_len)

timeo = sock_sndtimeo(sk, msg->msg_flags & MSG_DONTWAIT);
if (!sctp_wspace(asoc)) {
err = sctp_wait_for_sndbuf(asoc, &timeo, msg_len);
/* sk can be changed by peel off when waiting for buf. */
err = sctp_wait_for_sndbuf(asoc, &timeo, msg_len, &sk);
if (err) {
if (err == -ESRCH) {
/* asoc is already dead. */
Expand Down Expand Up @@ -4777,12 +4778,6 @@ int sctp_do_peeloff(struct sock *sk, sctp_assoc_t id, struct socket **sockp)
if (!asoc)
return -EINVAL;

/* If there is a thread waiting on more sndbuf space for
* sending on this asoc, it cannot be peeled.
*/
if (waitqueue_active(&asoc->wait))
return -EBUSY;

/* An association cannot be branched off from an already peeled-off
* socket, nor is this supported for tcp style sockets.
*/
Expand Down Expand Up @@ -7446,7 +7441,7 @@ void sctp_sock_rfree(struct sk_buff *skb)

/* Helper function to wait for space in the sndbuf. */
static int sctp_wait_for_sndbuf(struct sctp_association *asoc, long *timeo_p,
size_t msg_len)
size_t msg_len, struct sock **orig_sk)
{
struct sock *sk = asoc->base.sk;
int err = 0;
Expand Down Expand Up @@ -7480,11 +7475,17 @@ static int sctp_wait_for_sndbuf(struct sctp_association *asoc, long *timeo_p,
release_sock(sk);
current_timeo = schedule_timeout(current_timeo);
lock_sock(sk);
if (sk != asoc->base.sk) {
release_sock(sk);
sk = asoc->base.sk;
lock_sock(sk);
}

*timeo_p = current_timeo;
}

out:
*orig_sk = sk;
finish_wait(&asoc->wait, &wait);

/* Release the association's refcnt. */
Expand Down

0 comments on commit 9904da5

Please sign in to comment.