Vulnerability: Use-after-free vulnerability in the skb_segment function in net/core/skbuff.c in the Linux kernel through 3.13.6 allows attackers to obtain sensitive information from kernel memory by leveraging the absence of a certain orphaning operation.
Vulnerability ID: CVE-2014-0131
CWE: CWE-416
Severity score: 2.9
Project: https://github.com/torvalds/linux
Published: 2014-03-24
File: net/core/skbuff.c
Commit message: skbuff: skb_segment: orphan frags before copying

skb_segment copies frags around, so we need
to copy them carefully to avoid accessing
user memory after reporting completion to userspace
through a callback.

skb_segment doesn't normally happen on datapath:
TSO needs to be disabled - so disabling zero copy
in this case does not look like a big deal.

Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
Acked-by: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
Commit author: Michael S. Tsirkin

Vulnerable version (before fix):
 struct sk_buff *skb_segment(struct sk_buff *head_skb, 	skb_frag_t *frag = skb_shinfo(head_skb)->frags; 	unsigned int mss = skb_shinfo(head_skb)->gso_size; 	unsigned int doffset = head_skb->data - skb_mac_header(head_skb); 	unsigned int offset = doffset; 	unsigned int tnl_hlen = skb_tnl_header_len(head_skb); 	unsigned int headroom; struct sk_buff *skb_segment(struct sk_buff *head_skb, 			i = 0; 			nfrags = skb_shinfo(list_skb)->nr_frags; 			frag = skb_shinfo(list_skb)->frags; 			pos += skb_headlen(list_skb);  			while (pos < offset + len) { struct sk_buff *skb_segment(struct sk_buff *head_skb, 				i = 0; 				nfrags = skb_shinfo(list_skb)->nr_frags; 				frag = skb_shinfo(list_skb)->frags;  				BUG_ON(!nfrags);  struct sk_buff *skb_segment(struct sk_buff *head_skb, 				goto err; 			}  			*nskb_frag = *frag; 			__skb_frag_ref(nskb_frag); 			size = skb_frag_size(nskb_frag);

Fixed version (after fix):
 struct sk_buff *skb_segment(struct sk_buff *head_skb, 	skb_frag_t *frag = skb_shinfo(head_skb)->frags; 	unsigned int mss = skb_shinfo(head_skb)->gso_size; 	unsigned int doffset = head_skb->data - skb_mac_header(head_skb); 	struct sk_buff *frag_skb = head_skb; 	unsigned int offset = doffset; 	unsigned int tnl_hlen = skb_tnl_header_len(head_skb); 	unsigned int headroom; struct sk_buff *skb_segment(struct sk_buff *head_skb, 			i = 0; 			nfrags = skb_shinfo(list_skb)->nr_frags; 			frag = skb_shinfo(list_skb)->frags; 			frag_skb = list_skb; 			pos += skb_headlen(list_skb);  			while (pos < offset + len) { struct sk_buff *skb_segment(struct sk_buff *head_skb, 				i = 0; 				nfrags = skb_shinfo(list_skb)->nr_frags; 				frag = skb_shinfo(list_skb)->frags; 				frag_skb = list_skb;  				BUG_ON(!nfrags);  struct sk_buff *skb_segment(struct sk_buff *head_skb, 				goto err; 			}  			if (unlikely(skb_orphan_frags(frag_skb, GFP_ATOMIC))) 				goto err;  			*nskb_frag = *frag; 			__skb_frag_ref(nskb_frag); 			size = skb_frag_size(nskb_frag);

Patch:
patch: @@ -2854,6 +2854,7 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
 	skb_frag_t *frag = skb_shinfo(head_skb)->frags;
 	unsigned int mss = skb_shinfo(head_skb)->gso_size;
 	unsigned int doffset = head_skb->data - skb_mac_header(head_skb);
+	struct sk_buff *frag_skb = head_skb;
 	unsigned int offset = doffset;
 	unsigned int tnl_hlen = skb_tnl_header_len(head_skb);
 	unsigned int headroom;
@@ -2898,6 +2899,7 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
 			i = 0;
 			nfrags = skb_shinfo(list_skb)->nr_frags;
 			frag = skb_shinfo(list_skb)->frags;
+			frag_skb = list_skb;
 			pos += skb_headlen(list_skb);
 
 			while (pos < offset + len) {
@@ -2985,6 +2987,7 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
 				i = 0;
 				nfrags = skb_shinfo(list_skb)->nr_frags;
 				frag = skb_shinfo(list_skb)->frags;
+				frag_skb = list_skb;
 
 				BUG_ON(!nfrags);
 
@@ -2999,6 +3002,9 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
 				goto err;
 			}
 
+			if (unlikely(skb_orphan_frags(frag_skb, GFP_ATOMIC)))
+				goto err;
+
 			*nskb_frag = *frag;
 			__skb_frag_ref(nskb_frag);
 			size = skb_frag_size(nskb_frag);
path: net/core/skbuff.c