Vulnerability: A certain Red Hat patch to the sctp_sock_migrate function in net/sctp/socket.c in the Linux kernel before 2.6.21, as used in Red Hat Enterprise Linux (RHEL) 5, allows remote attackers to cause a denial of service (NULL pointer dereference and OOPS) via a crafted SCTP packet.
Vulnerability ID: CVE-2011-2482
CWE: CWE-476
Severity score: 6.9
Project: https://github.com/torvalds/linux
Published: 2013-06-08
File: net/sctp/socket.c
Commit message: [SCTP]: Fix assertion (!atomic_read(&sk->sk_rmem_alloc)) failed message

In current implementation, LKSCTP does receive buffer accounting for
data in sctp_receive_queue and pd_lobby. However, LKSCTP don't do
accounting for data in frag_list when data is fragmented. In addition,
LKSCTP doesn't do accounting for data in reasm and lobby queue in
structure sctp_ulpq.
When there are date in these queue, assertion failed message is printed
in inet_sock_destruct because sk_rmem_alloc of oldsk does not become 0
when socket is destroyed.

Signed-off-by: Tsutomu Fujii <t-fujii@nb.jp.nec.com>
Signed-off-by: Vlad Yasevich <vladislav.yasevich@hp.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Commit author: Tsutomu Fujii

Vulnerable version (before fix):
 void sctp_wait_for_close(struct sock *sk, long timeout) 	finish_wait(sk->sk_sleep, &wait); }  /* 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, 	sctp_skb_for_each(skb, &oldsk->sk_receive_queue, tmp) { 		event = sctp_skb2event(skb); 		if (event->asoc == assoc) { 			sctp_sock_rfree(skb); 			__skb_unlink(skb, &oldsk->sk_receive_queue); 			__skb_queue_tail(&newsk->sk_receive_queue, skb); 			sctp_skb_set_owner_r(skb, newsk); 		} 	}  static void sctp_sock_migrate(struct sock *oldsk, struct sock *newsk, 		sctp_skb_for_each(skb, &oldsp->pd_lobby, tmp) { 			event = sctp_skb2event(skb); 			if (event->asoc == assoc) { 				sctp_sock_rfree(skb); 				__skb_unlink(skb, &oldsp->pd_lobby); 				__skb_queue_tail(queue, skb); 				sctp_skb_set_owner_r(skb, newsk); 			} 		}  static void sctp_sock_migrate(struct sock *oldsk, struct sock *newsk,  	}  	/* Set the type of socket to indicate that it is peeled off from the 	 * original UDP-style socket or created with the accept() call on a 	 * TCP-style socket..

Fixed version (after fix):
 void sctp_wait_for_close(struct sock *sk, long timeout) 	finish_wait(sk->sk_sleep, &wait); }  static void sctp_sock_rfree_frag(struct sk_buff *skb) { 	struct sk_buff *frag;  	if (!skb->data_len) 		goto done;  	/* Don't forget the fragments. */ 	for (frag = skb_shinfo(skb)->frag_list; frag; frag = frag->next) 		sctp_sock_rfree_frag(frag);  done: 	sctp_sock_rfree(skb); }  static void sctp_skb_set_owner_r_frag(struct sk_buff *skb, struct sock *sk) { 	struct sk_buff *frag;  	if (!skb->data_len) 		goto done;  	/* Don't forget the fragments. */ 	for (frag = skb_shinfo(skb)->frag_list; frag; frag = frag->next) 		sctp_skb_set_owner_r_frag(frag, sk);  done: 	sctp_skb_set_owner_r(skb, sk); }  /* 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, 	sctp_skb_for_each(skb, &oldsk->sk_receive_queue, tmp) { 		event = sctp_skb2event(skb); 		if (event->asoc == assoc) { 			sctp_sock_rfree_frag(skb); 			__skb_unlink(skb, &oldsk->sk_receive_queue); 			__skb_queue_tail(&newsk->sk_receive_queue, skb); 			sctp_skb_set_owner_r_frag(skb, newsk); 		} 	}  static void sctp_sock_migrate(struct sock *oldsk, struct sock *newsk, 		sctp_skb_for_each(skb, &oldsp->pd_lobby, tmp) { 			event = sctp_skb2event(skb); 			if (event->asoc == assoc) { 				sctp_sock_rfree_frag(skb); 				__skb_unlink(skb, &oldsp->pd_lobby); 				__skb_queue_tail(queue, skb); 				sctp_skb_set_owner_r_frag(skb, newsk); 			} 		}  static void sctp_sock_migrate(struct sock *oldsk, struct sock *newsk,  	}  	sctp_skb_for_each(skb, &assoc->ulpq.reasm, tmp) { 		sctp_sock_rfree_frag(skb); 		sctp_skb_set_owner_r_frag(skb, newsk); 	}  	sctp_skb_for_each(skb, &assoc->ulpq.lobby, tmp) { 		sctp_sock_rfree_frag(skb); 		sctp_skb_set_owner_r_frag(skb, newsk); 	}  	/* Set the type of socket to indicate that it is peeled off from the 	 * original UDP-style socket or created with the accept() call on a 	 * TCP-style socket..

Patch:
patch: @@ -5638,6 +5638,36 @@ void sctp_wait_for_close(struct sock *sk, long timeout)
 	finish_wait(sk->sk_sleep, &wait);
 }
 
+static void sctp_sock_rfree_frag(struct sk_buff *skb)
+{
+	struct sk_buff *frag;
+
+	if (!skb->data_len)
+		goto done;
+
+	/* Don't forget the fragments. */
+	for (frag = skb_shinfo(skb)->frag_list; frag; frag = frag->next)
+		sctp_sock_rfree_frag(frag);
+
+done:
+	sctp_sock_rfree(skb);
+}
+
+static void sctp_skb_set_owner_r_frag(struct sk_buff *skb, struct sock *sk)
+{
+	struct sk_buff *frag;
+
+	if (!skb->data_len)
+		goto done;
+
+	/* Don't forget the fragments. */
+	for (frag = skb_shinfo(skb)->frag_list; frag; frag = frag->next)
+		sctp_skb_set_owner_r_frag(frag, sk);
+
+done:
+	sctp_skb_set_owner_r(skb, sk);
+}
+
 /* Populate the fields of the newsk from the oldsk and migrate the assoc
  * and its messages to the newsk.
  */
@@ -5692,10 +5722,10 @@ static void sctp_sock_migrate(struct sock *oldsk, struct sock *newsk,
 	sctp_skb_for_each(skb, &oldsk->sk_receive_queue, tmp) {
 		event = sctp_skb2event(skb);
 		if (event->asoc == assoc) {
-			sctp_sock_rfree(skb);
+			sctp_sock_rfree_frag(skb);
 			__skb_unlink(skb, &oldsk->sk_receive_queue);
 			__skb_queue_tail(&newsk->sk_receive_queue, skb);
-			sctp_skb_set_owner_r(skb, newsk);
+			sctp_skb_set_owner_r_frag(skb, newsk);
 		}
 	}
 
@@ -5723,10 +5753,10 @@ static void sctp_sock_migrate(struct sock *oldsk, struct sock *newsk,
 		sctp_skb_for_each(skb, &oldsp->pd_lobby, tmp) {
 			event = sctp_skb2event(skb);
 			if (event->asoc == assoc) {
-				sctp_sock_rfree(skb);
+				sctp_sock_rfree_frag(skb);
 				__skb_unlink(skb, &oldsp->pd_lobby);
 				__skb_queue_tail(queue, skb);
-				sctp_skb_set_owner_r(skb, newsk);
+				sctp_skb_set_owner_r_frag(skb, newsk);
 			}
 		}
 
@@ -5738,6 +5768,16 @@ static void sctp_sock_migrate(struct sock *oldsk, struct sock *newsk,
 
 	}
 
+	sctp_skb_for_each(skb, &assoc->ulpq.reasm, tmp) {
+		sctp_sock_rfree_frag(skb);
+		sctp_skb_set_owner_r_frag(skb, newsk);
+	}
+
+	sctp_skb_for_each(skb, &assoc->ulpq.lobby, tmp) {
+		sctp_sock_rfree_frag(skb);
+		sctp_skb_set_owner_r_frag(skb, newsk);
+	}
+
 	/* Set the type of socket to indicate that it is peeled off from the
 	 * original UDP-style socket or created with the accept() call on a
 	 * TCP-style socket..
path: net/sctp/socket.c