]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
sctp: Fix a race between ICMP protocol unreachable and connect()
authorVlad Yasevich <vladislav.yasevich@hp.com>
Thu, 6 May 2010 07:56:07 +0000 (00:56 -0700)
committerGreg Kroah-Hartman <gregkh@suse.de>
Mon, 21 Mar 2011 19:44:33 +0000 (12:44 -0700)
commit 50b5d6ad63821cea324a5a7a19854d4de1a0a819 upstream.

ICMP protocol unreachable handling completely disregarded
the fact that the user may have locked the socket.  It proceeded
to destroy the association, even though the user may have
held the lock and had a ref on the association.  This resulted
in the following:

Attempt to release alive inet socket f6afcc00

=========================
[ BUG: held lock freed! ]
-------------------------
somenu/2672 is freeing memory f6afcc00-f6afcfff, with a lock still held
there!
 (sk_lock-AF_INET){+.+.+.}, at: [<c122098a>] sctp_connect+0x13/0x4c
1 lock held by somenu/2672:
 #0:  (sk_lock-AF_INET){+.+.+.}, at: [<c122098a>] sctp_connect+0x13/0x4c

stack backtrace:
Pid: 2672, comm: somenu Not tainted 2.6.32-telco #55
Call Trace:
 [<c1232266>] ? printk+0xf/0x11
 [<c1038553>] debug_check_no_locks_freed+0xce/0xff
 [<c10620b4>] kmem_cache_free+0x21/0x66
 [<c1185f25>] __sk_free+0x9d/0xab
 [<c1185f9c>] sk_free+0x1c/0x1e
 [<c1216e38>] sctp_association_put+0x32/0x89
 [<c1220865>] __sctp_connect+0x36d/0x3f4
 [<c122098a>] ? sctp_connect+0x13/0x4c
 [<c102d073>] ? autoremove_wake_function+0x0/0x33
 [<c12209a8>] sctp_connect+0x31/0x4c
 [<c11d1e80>] inet_dgram_connect+0x4b/0x55
 [<c11834fa>] sys_connect+0x54/0x71
 [<c103a3a2>] ? lock_release_non_nested+0x88/0x239
 [<c1054026>] ? might_fault+0x42/0x7c
 [<c1054026>] ? might_fault+0x42/0x7c
 [<c11847ab>] sys_socketcall+0x6d/0x178
 [<c10da994>] ? trace_hardirqs_on_thunk+0xc/0x10
 [<c1002959>] syscall_call+0x7/0xb

This was because the sctp_wait_for_connect() would aqcure the socket
lock and then proceed to release the last reference count on the
association, thus cause the fully destruction path to finish freeing
the socket.

The simplest solution is to start a very short timer in case the socket
is owned by user.  When the timer expires, we can do some verification
and be able to do the release properly.

Signed-off-by: Vlad Yasevich <vladislav.yasevich@hp.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
include/net/sctp/sm.h
include/net/sctp/structs.h
net/sctp/input.c
net/sctp/sm_sideeffect.c
net/sctp/transport.c

index 851c813adb3ac2a144cbb0b98582331629a5c385..61d73e37d5436138d743372a48aecad3191526fe 100644 (file)
@@ -279,6 +279,7 @@ int sctp_do_sm(sctp_event_t event_type, sctp_subtype_t subtype,
 /* 2nd level prototypes */
 void sctp_generate_t3_rtx_event(unsigned long peer);
 void sctp_generate_heartbeat_event(unsigned long peer);
+void sctp_generate_proto_unreach_event(unsigned long peer);
 
 void sctp_ootb_pkt_free(struct sctp_packet *);
 
index 597f8e27aaf6dfccffa354ae4219c164a1bd078e..219043a67bf7ab6073eba985e80ebb8ca206f2c1 100644 (file)
@@ -1010,6 +1010,9 @@ struct sctp_transport {
        /* Heartbeat timer is per destination. */
        struct timer_list hb_timer;
 
+       /* Timer to handle ICMP proto unreachable envets */
+       struct timer_list proto_unreach_timer;
+
        /* Since we're using per-destination retransmission timers
         * (see above), we're also using per-destination "transmitted"
         * queues.  This probably ought to be a private struct
index 3d74b264ea2256442eb336e1e333269b8cdbe128..eaacf76c2005d1c51454981fd40bf3235d3a6fe5 100644 (file)
@@ -439,11 +439,25 @@ void sctp_icmp_proto_unreachable(struct sock *sk,
 {
        SCTP_DEBUG_PRINTK("%s\n",  __func__);
 
-       sctp_do_sm(SCTP_EVENT_T_OTHER,
-                  SCTP_ST_OTHER(SCTP_EVENT_ICMP_PROTO_UNREACH),
-                  asoc->state, asoc->ep, asoc, t,
-                  GFP_ATOMIC);
+       if (sock_owned_by_user(sk)) {
+               if (timer_pending(&t->proto_unreach_timer))
+                       return;
+               else {
+                       if (!mod_timer(&t->proto_unreach_timer,
+                                               jiffies + (HZ/20)))
+                               sctp_association_hold(asoc);
+               }
 
+       } else {
+               if (timer_pending(&t->proto_unreach_timer) &&
+                   del_timer(&t->proto_unreach_timer))
+                       sctp_association_put(asoc);
+
+               sctp_do_sm(SCTP_EVENT_T_OTHER,
+                          SCTP_ST_OTHER(SCTP_EVENT_ICMP_PROTO_UNREACH),
+                          asoc->state, asoc->ep, asoc, t,
+                          GFP_ATOMIC);
+       }
 }
 
 /* Common lookup code for icmp/icmpv6 error handler. */
index 42bbb24105502888a507ab2dfd7d005981e4d342..90a53671a08f9e15f533d0b257506e3b49d041f9 100644 (file)
@@ -396,6 +396,41 @@ out_unlock:
        sctp_transport_put(transport);
 }
 
+/* Handle the timeout of the ICMP protocol unreachable timer.  Trigger
+ * the correct state machine transition that will close the association.
+ */
+void sctp_generate_proto_unreach_event(unsigned long data)
+{
+       struct sctp_transport *transport = (struct sctp_transport *) data;
+       struct sctp_association *asoc = transport->asoc;
+
+       sctp_bh_lock_sock(asoc->base.sk);
+       if (sock_owned_by_user(asoc->base.sk)) {
+               SCTP_DEBUG_PRINTK("%s:Sock is busy.\n", __func__);
+
+               /* Try again later.  */
+               if (!mod_timer(&transport->proto_unreach_timer,
+                               jiffies + (HZ/20)))
+                       sctp_association_hold(asoc);
+               goto out_unlock;
+       }
+
+       /* Is this structure just waiting around for us to actually
+        * get destroyed?
+        */
+       if (asoc->base.dead)
+               goto out_unlock;
+
+       sctp_do_sm(SCTP_EVENT_T_OTHER,
+                  SCTP_ST_OTHER(SCTP_EVENT_ICMP_PROTO_UNREACH),
+                  asoc->state, asoc->ep, asoc, transport, GFP_ATOMIC);
+
+out_unlock:
+       sctp_bh_unlock_sock(asoc->base.sk);
+       sctp_association_put(asoc);
+}
+
+
 /* Inject a SACK Timeout event into the state machine.  */
 static void sctp_generate_sack_event(unsigned long data)
 {
index b827d21dbe54451582c61a4758b7063e794cfd7d..943dbf4450fe6cf7aea4e5c0951a58f75182b35c 100644 (file)
@@ -107,6 +107,8 @@ static struct sctp_transport *sctp_transport_init(struct sctp_transport *peer,
                        (unsigned long)peer);
        setup_timer(&peer->hb_timer, sctp_generate_heartbeat_event,
                        (unsigned long)peer);
+       setup_timer(&peer->proto_unreach_timer,
+                   sctp_generate_proto_unreach_event, (unsigned long)peer);
 
        /* Initialize the 64-bit random nonce sent with heartbeat. */
        get_random_bytes(&peer->hb_nonce, sizeof(peer->hb_nonce));