From: Neil Horman <nhorman@redhat.com> Date: Mon, 3 Jan 2011 19:44:19 -0500 Subject: [net] sctp: fix panic from bad socket lock on icmp error Message-id: <20110103194419.GB13951@hmsreliant.think-freely.org> Patchwork-id: 4534 O-Subject: [kernel team] [RHEL 5.7 PATCH] sctp: Fix panic resulting from bad locking of socket on icmp error (bz 665477) Bugzilla: 665477 CVE: CVE-2010-4526 RH-Acked-by: David S. Miller <davem@redhat.com> RH-Acked-by: Herbert Xu <herbert.xu@redhat.com> RH-Acked-by: Eugene Teo <eugene@redhat.com> Hey- Backport of upstream commit 50b5d6ad63821cea324a5a7a19854d4de1a0a819, which fixes an sctp panic resulting from freeing an association on a potentially locked socket when an icmp dest unreach error is received. Tested by myself to fix the problem. Brew Status: https://brewweb.devel.redhat.com/taskinfo?taskID=3003869 Signed-off-by: Jarod Wilson <jarod@redhat.com> diff --git a/include/net/sctp/sm.h b/include/net/sctp/sm.h index c483e9a..a382d73 100644 --- a/include/net/sctp/sm.h +++ b/include/net/sctp/sm.h @@ -268,6 +268,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 *); diff --git a/include/net/sctp/structs.h b/include/net/sctp/structs.h index a0a6196..70f9157 100644 --- a/include/net/sctp/structs.h +++ b/include/net/sctp/structs.h @@ -938,6 +938,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 diff --git a/net/sctp/input.c b/net/sctp/input.c index 2060bbe..2a4ba84 100644 --- a/net/sctp/input.c +++ b/net/sctp/input.c @@ -423,11 +423,25 @@ void sctp_icmp_proto_unreachable(struct sock *sk, { SCTP_DEBUG_PRINTK("%s\n", __FUNCTION__); - 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. */ diff --git a/net/sctp/sm_sideeffect.c b/net/sctp/sm_sideeffect.c index bf17c73..d436a00 100644 --- a/net/sctp/sm_sideeffect.c +++ b/net/sctp/sm_sideeffect.c @@ -388,6 +388,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) { diff --git a/net/sctp/transport.c b/net/sctp/transport.c index 2763aa9..1d49698 100644 --- a/net/sctp/transport.c +++ b/net/sctp/transport.c @@ -109,6 +109,10 @@ static struct sctp_transport *sctp_transport_init(struct sctp_transport *peer, peer->hb_timer.function = sctp_generate_heartbeat_event; peer->hb_timer.data = (unsigned long)peer; + init_timer(&peer->proto_unreach_timer); + peer->proto_unreach_timer.function = sctp_generate_proto_unreach_event; + peer->proto_unreach_timer.data = (unsigned long)peer; + /* Initialize the 64-bit random nonce sent with heartbeat. */ get_random_bytes(&peer->hb_nonce, sizeof(peer->hb_nonce));