Skip to content

Commit 50b5d6a

Browse files
Vlad Yasevichdavem330
authored andcommitted
sctp: Fix a race between ICMP protocol unreachable and connect()
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 Freescale#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 <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent 6ec8256 commit 50b5d6a

File tree

5 files changed

+59
-4
lines changed

5 files changed

+59
-4
lines changed

include/net/sctp/sm.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ int sctp_do_sm(sctp_event_t event_type, sctp_subtype_t subtype,
279279
/* 2nd level prototypes */
280280
void sctp_generate_t3_rtx_event(unsigned long peer);
281281
void sctp_generate_heartbeat_event(unsigned long peer);
282+
void sctp_generate_proto_unreach_event(unsigned long peer);
282283

283284
void sctp_ootb_pkt_free(struct sctp_packet *);
284285

include/net/sctp/structs.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,6 +1010,9 @@ struct sctp_transport {
10101010
/* Heartbeat timer is per destination. */
10111011
struct timer_list hb_timer;
10121012

1013+
/* Timer to handle ICMP proto unreachable envets */
1014+
struct timer_list proto_unreach_timer;
1015+
10131016
/* Since we're using per-destination retransmission timers
10141017
* (see above), we're also using per-destination "transmitted"
10151018
* queues. This probably ought to be a private struct

net/sctp/input.c

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -440,11 +440,25 @@ void sctp_icmp_proto_unreachable(struct sock *sk,
440440
{
441441
SCTP_DEBUG_PRINTK("%s\n", __func__);
442442

443-
sctp_do_sm(SCTP_EVENT_T_OTHER,
444-
SCTP_ST_OTHER(SCTP_EVENT_ICMP_PROTO_UNREACH),
445-
asoc->state, asoc->ep, asoc, t,
446-
GFP_ATOMIC);
443+
if (sock_owned_by_user(sk)) {
444+
if (timer_pending(&t->proto_unreach_timer))
445+
return;
446+
else {
447+
if (!mod_timer(&t->proto_unreach_timer,
448+
jiffies + (HZ/20)))
449+
sctp_association_hold(asoc);
450+
}
451+
452+
} else {
453+
if (timer_pending(&t->proto_unreach_timer) &&
454+
del_timer(&t->proto_unreach_timer))
455+
sctp_association_put(asoc);
447456

457+
sctp_do_sm(SCTP_EVENT_T_OTHER,
458+
SCTP_ST_OTHER(SCTP_EVENT_ICMP_PROTO_UNREACH),
459+
asoc->state, asoc->ep, asoc, t,
460+
GFP_ATOMIC);
461+
}
448462
}
449463

450464
/* Common lookup code for icmp/icmpv6 error handler. */

net/sctp/sm_sideeffect.c

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,41 @@ void sctp_generate_heartbeat_event(unsigned long data)
397397
sctp_transport_put(transport);
398398
}
399399

400+
/* Handle the timeout of the ICMP protocol unreachable timer. Trigger
401+
* the correct state machine transition that will close the association.
402+
*/
403+
void sctp_generate_proto_unreach_event(unsigned long data)
404+
{
405+
struct sctp_transport *transport = (struct sctp_transport *) data;
406+
struct sctp_association *asoc = transport->asoc;
407+
408+
sctp_bh_lock_sock(asoc->base.sk);
409+
if (sock_owned_by_user(asoc->base.sk)) {
410+
SCTP_DEBUG_PRINTK("%s:Sock is busy.\n", __func__);
411+
412+
/* Try again later. */
413+
if (!mod_timer(&transport->proto_unreach_timer,
414+
jiffies + (HZ/20)))
415+
sctp_association_hold(asoc);
416+
goto out_unlock;
417+
}
418+
419+
/* Is this structure just waiting around for us to actually
420+
* get destroyed?
421+
*/
422+
if (asoc->base.dead)
423+
goto out_unlock;
424+
425+
sctp_do_sm(SCTP_EVENT_T_OTHER,
426+
SCTP_ST_OTHER(SCTP_EVENT_ICMP_PROTO_UNREACH),
427+
asoc->state, asoc->ep, asoc, transport, GFP_ATOMIC);
428+
429+
out_unlock:
430+
sctp_bh_unlock_sock(asoc->base.sk);
431+
sctp_association_put(asoc);
432+
}
433+
434+
400435
/* Inject a SACK Timeout event into the state machine. */
401436
static void sctp_generate_sack_event(unsigned long data)
402437
{

net/sctp/transport.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ static struct sctp_transport *sctp_transport_init(struct sctp_transport *peer,
108108
(unsigned long)peer);
109109
setup_timer(&peer->hb_timer, sctp_generate_heartbeat_event,
110110
(unsigned long)peer);
111+
setup_timer(&peer->proto_unreach_timer,
112+
sctp_generate_proto_unreach_event, (unsigned long)peer);
111113

112114
/* Initialize the 64-bit random nonce sent with heartbeat. */
113115
get_random_bytes(&peer->hb_nonce, sizeof(peer->hb_nonce));

0 commit comments

Comments
 (0)