Thread (15 messages) 15 messages, 3 authors, 2016-03-02
STALE3739d

[PATCH net] sctp: do sanity checks before migrating the asoc

From: Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>
Date: 2016-01-15 21:41:01
Also in: linux-sctp
Subsystem: networking [general], sctp protocol, the rest · Maintainers: "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Marcelo Ricardo Leitner, Xin Long, Linus Torvalds

On Fri, Jan 15, 2016 at 08:11:03PM +0100, Dmitry Vyukov wrote:
On Fri, Jan 15, 2016 at 7:46 PM, Marcelo Ricardo Leitner
[off-list ref] wrote:
quoted
On Wed, Dec 30, 2015 at 09:42:27PM +0100, Dmitry Vyukov wrote:
quoted
Hello,

The following program leads to a leak of two sock objects:
...
quoted
On commit 8513342170278468bac126640a5d2d12ffbff106 (Dec 28).
I'm afraid I cannot reproduce this one?
I enabled dynprintk at sctp_destroy_sock and it does print twice when I
run this test app.
Also added debugs to check association lifetime, and then it was
destroyed. Same for endpoint.

Checking with trace-cmd, both calls to sctp_close() resulted in
sctp_destroy_sock() being called.

As for sock_hold/put, they are matched too.

Ideas? Log is below for double checking

Hummm... I can reproduce it pretty reliably.

[  197.459024] kmemleak: 11 new suspected memory leaks (see
/sys/kernel/debug/kmemleak)
[  307.494874] kmemleak: 409 new suspected memory leaks (see
/sys/kernel/debug/kmemleak)
[  549.784022] kmemleak: 125 new suspected memory leaks (see
/sys/kernel/debug/kmemleak)

I double checked via /proc/slabinfo:

SCTPv6              4373   4420   2368   13    8 : tunables    0    0
  0 : slabdata    340    340      0

SCTPv6 starts with almost 0, but grows infinitely while I run the
program in a loop.

Here is my SCTP related configs:

CONFIG_IP_SCTP=y
CONFIG_NET_SCTPPROBE=y
CONFIG_SCTP_DBG_OBJCNT=y
# CONFIG_SCTP_DEFAULT_COOKIE_HMAC_MD5 is not set
# CONFIG_SCTP_DEFAULT_COOKIE_HMAC_SHA1 is not set
CONFIG_SCTP_DEFAULT_COOKIE_HMAC_NONE=y
# CONFIG_SCTP_COOKIE_HMAC_MD5 is not set
# CONFIG_SCTP_COOKIE_HMAC_SHA1 is not set

I am on commit 67990608c8b95d2b8ccc29932376ae73d5818727 and I don't
seem to have any sctp-related changes on top.
Ok, now I can. Enabled slub debugs, now I cannot see calls to
sctp_destroy_sock. I see to sctp_close, but not to sctp_destroy_sock.

And SCTPv6 grew by 2 sockets after the execution.

Further checking, it's a race within SCTP asoc migration:
thread 0                thread 1
- app creates a sock
                        - sends a packet to itself
			  - sctp will create an asoc and do implicit
			    handshake
			  - send the packet
- listen()
- accept() is called and
  that asoc is migrated
                 - packet is delivered
                   - skb->destructor is called, BUT:

(note that if accept() is called after packet is delivered and skb is freed, it
doesn't happen)

static void sctp_wfree(struct sk_buff *skb)
{
        struct sctp_chunk *chunk = skb_shinfo(skb)->destructor_arg;
        struct sctp_association *asoc = chunk->asoc;
        struct sock *sk = asoc->base.sk;
...
        atomic_sub(sizeof(struct sctp_chunk), &sk->sk_wmem_alloc);

and it's pointing to the new socket already. So one socket gets a leak
on sk_wmem_alloc and another gets a negative value:
--- a/net/sctp/socket.c
+++ b/net/sctp/socket.c
@@ -1537,12 +1537,14 @@ static void sctp_close(struct sock *sk, long timeout)
        /* Hold the sock, since sk_common_release() will put sock_put()
         * and we have just a little more cleanup.
         */
+       printk("%s sock_hold %p\n", __func__, sk);
        sock_hold(sk);
        sk_common_release(sk);

        bh_unlock_sock(sk);
        spin_unlock_bh(&net->sctp.addr_wq_lock);

+       printk("%s sock_put %p %d %d\n", __func__, sk, atomic_read(&sk->sk_refcnt), atomic_read(&sk->sk_wmem_alloc));
        sock_put(sk);

        SCTP_DBG_OBJCNT_DEC(sock);

gave me:

[   99.456944] sctp_close sock_hold ffff880137df8940
...
[   99.457337] sctp_close sock_put ffff880137df8940 1 -247
[   99.458313] sctp_close sock_hold ffff880137dfef00
...
[   99.458383] sctp_close sock_put ffff880137dfef00 1 249

That's why the socket is not freed..


---8<---

As reported by Dmitry, we cannot migrate asocs that have skbs in tx
queue because they have the destructor callback pointing to the asoc,
but which will point to a different socket if we migrate the asoc in
between the packet sent and packet release.

This patch implements proper error handling for sctp_sock_migrate and
this first sanity check.

Reported-by: Dmitry Vyukov <dvyukov@google.com>
Signed-off-by: Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>
---
 net/sctp/socket.c | 31 ++++++++++++++++++++++++-------
 1 file changed, 24 insertions(+), 7 deletions(-)
diff --git a/net/sctp/socket.c b/net/sctp/socket.c
index 9bb80ec4c08f..5a22a6cfb699 100644
--- a/net/sctp/socket.c
+++ b/net/sctp/socket.c
@@ -99,8 +99,8 @@ static int sctp_send_asconf(struct sctp_association *asoc,
 			    struct sctp_chunk *chunk);
 static int sctp_do_bind(struct sock *, union sctp_addr *, int);
 static int sctp_autobind(struct sock *sk);
-static void sctp_sock_migrate(struct sock *, struct sock *,
-			      struct sctp_association *, sctp_socket_type_t);
+static int sctp_sock_migrate(struct sock *, struct sock *,
+			     struct sctp_association *, sctp_socket_type_t);
 
 static int sctp_memory_pressure;
 static atomic_long_t sctp_memory_allocated;
@@ -3929,7 +3929,11 @@ static struct sock *sctp_accept(struct sock *sk, int flags, int *err)
 	/* Populate the fields of the newsk from the oldsk and migrate the
 	 * asoc to the newsk.
 	 */
-	sctp_sock_migrate(sk, newsk, asoc, SCTP_SOCKET_TCP);
+	error = sctp_sock_migrate(sk, newsk, asoc, SCTP_SOCKET_TCP);
+	if (error) {
+		sk_common_release(newsk);
+		newsk = NULL;
+	}
 
 out:
 	release_sock(sk);
@@ -4436,10 +4440,16 @@ int sctp_do_peeloff(struct sock *sk, sctp_assoc_t id, struct socket **sockp)
 	/* Populate the fields of the newsk from the oldsk and migrate the
 	 * asoc to the newsk.
 	 */
-	sctp_sock_migrate(sk, sock->sk, asoc, SCTP_SOCKET_UDP_HIGH_BANDWIDTH);
+	err = sctp_sock_migrate(sk, sock->sk, asoc,
+				SCTP_SOCKET_UDP_HIGH_BANDWIDTH);
+	if (err) {
+		sk_common_release(sock->sk);
+		goto out;
+	}
 
 	*sockp = sock;
 
+out:
 	return err;
 }
 EXPORT_SYMBOL(sctp_do_peeloff);
@@ -7217,9 +7227,9 @@ static inline void sctp_copy_descendant(struct sock *sk_to,
 /* Populate the fields of the newsk from the oldsk and migrate the assoc
  * and its messages to the newsk.
  */
-static void sctp_sock_migrate(struct sock *oldsk, struct sock *newsk,
-			      struct sctp_association *assoc,
-			      sctp_socket_type_t type)
+static int sctp_sock_migrate(struct sock *oldsk, struct sock *newsk,
+			     struct sctp_association *assoc,
+			     sctp_socket_type_t type)
 {
 	struct sctp_sock *oldsp = sctp_sk(oldsk);
 	struct sctp_sock *newsp = sctp_sk(newsk);
@@ -7229,6 +7239,12 @@ static void sctp_sock_migrate(struct sock *oldsk, struct sock *newsk,
 	struct sctp_ulpevent *event;
 	struct sctp_bind_hashbucket *head;
 
+	/* We cannot migrate asocs that have skbs tied to it otherwise
+	 * its destructor will update the wrong socket
+	 */
+	if (assoc->sndbuf_used)
+		return -EBUSY;
+
 	/* Migrate socket buffer sizes and all the socket level options to the
 	 * new socket.
 	 */
@@ -7343,6 +7359,8 @@ static void sctp_sock_migrate(struct sock *oldsk, struct sock *newsk,
 
 	newsk->sk_state = SCTP_SS_ESTABLISHED;
 	release_sock(newsk);
+
+	return 0;
 }
 
 
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help