Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

net: add recursion limit to GRO

Currently, GRO can do unlimited recursion through the gro_receive
handlers. This was fixed for tunneling protocols by limiting tunnel GRO
to one level with encap_mark, but both VLAN and TEB still have this
problem. Thus, the kernel is vulnerable to a stack overflow, if we
receive a packet composed entirely of VLAN headers.

This patch adds a recursion counter to the GRO layer to prevent stack
overflow. When a gro_receive function hits the recursion limit, GRO is
aborted for this skb and it is processed normally. This recursion
counter is put in the GRO CB, but could be turned into a percpu counter
if we run out of space in the CB.

Thanks to Vladimír Beneš <vbenes@redhat.com> for the initial bug report.

Fixes: CVE-2016-7039
Fixes: 9b174d88c257 ("net: Add Transparent Ethernet Bridging GRO support.")
Fixes: 66e5133f19e9 ("vlan: Add GRO support for non hardware accelerated vlan")
Signed-off-by: Sabrina Dubroca <sd@queasysnail.net>
Reviewed-by: Jiri Benc <jbenc@redhat.com>
Acked-by: Hannes Frederic Sowa <hannes@stressinduktion.org>
Acked-by: Tom Herbert <tom@herbertland.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Sabrina Dubroca and committed by
David S. Miller
fcd91dd4 7aa8e63f

+49 -11
+1 -1
drivers/net/geneve.c
··· 453 453 454 454 skb_gro_pull(skb, gh_len); 455 455 skb_gro_postpull_rcsum(skb, gh, gh_len); 456 - pp = ptype->callbacks.gro_receive(head, skb); 456 + pp = call_gro_receive(ptype->callbacks.gro_receive, head, skb); 457 457 flush = 0; 458 458 459 459 out_unlock:
+1 -1
drivers/net/vxlan.c
··· 583 583 } 584 584 } 585 585 586 - pp = eth_gro_receive(head, skb); 586 + pp = call_gro_receive(eth_gro_receive, head, skb); 587 587 flush = 0; 588 588 589 589 out:
+38 -1
include/linux/netdevice.h
··· 2169 2169 /* Used to determine if flush_id can be ignored */ 2170 2170 u8 is_atomic:1; 2171 2171 2172 - /* 5 bit hole */ 2172 + /* Number of gro_receive callbacks this packet already went through */ 2173 + u8 recursion_counter:4; 2174 + 2175 + /* 1 bit hole */ 2173 2176 2174 2177 /* used to support CHECKSUM_COMPLETE for tunneling protocols */ 2175 2178 __wsum csum; ··· 2182 2179 }; 2183 2180 2184 2181 #define NAPI_GRO_CB(skb) ((struct napi_gro_cb *)(skb)->cb) 2182 + 2183 + #define GRO_RECURSION_LIMIT 15 2184 + static inline int gro_recursion_inc_test(struct sk_buff *skb) 2185 + { 2186 + return ++NAPI_GRO_CB(skb)->recursion_counter == GRO_RECURSION_LIMIT; 2187 + } 2188 + 2189 + typedef struct sk_buff **(*gro_receive_t)(struct sk_buff **, struct sk_buff *); 2190 + static inline struct sk_buff **call_gro_receive(gro_receive_t cb, 2191 + struct sk_buff **head, 2192 + struct sk_buff *skb) 2193 + { 2194 + if (unlikely(gro_recursion_inc_test(skb))) { 2195 + NAPI_GRO_CB(skb)->flush |= 1; 2196 + return NULL; 2197 + } 2198 + 2199 + return cb(head, skb); 2200 + } 2201 + 2202 + typedef struct sk_buff **(*gro_receive_sk_t)(struct sock *, struct sk_buff **, 2203 + struct sk_buff *); 2204 + static inline struct sk_buff **call_gro_receive_sk(gro_receive_sk_t cb, 2205 + struct sock *sk, 2206 + struct sk_buff **head, 2207 + struct sk_buff *skb) 2208 + { 2209 + if (unlikely(gro_recursion_inc_test(skb))) { 2210 + NAPI_GRO_CB(skb)->flush |= 1; 2211 + return NULL; 2212 + } 2213 + 2214 + return cb(sk, head, skb); 2215 + } 2185 2216 2186 2217 struct packet_type { 2187 2218 __be16 type; /* This is really htons(ether_type). */
+1 -1
net/8021q/vlan.c
··· 664 664 665 665 skb_gro_pull(skb, sizeof(*vhdr)); 666 666 skb_gro_postpull_rcsum(skb, vhdr, sizeof(*vhdr)); 667 - pp = ptype->callbacks.gro_receive(head, skb); 667 + pp = call_gro_receive(ptype->callbacks.gro_receive, head, skb); 668 668 669 669 out_unlock: 670 670 rcu_read_unlock();
+1
net/core/dev.c
··· 4511 4511 NAPI_GRO_CB(skb)->flush = 0; 4512 4512 NAPI_GRO_CB(skb)->free = 0; 4513 4513 NAPI_GRO_CB(skb)->encap_mark = 0; 4514 + NAPI_GRO_CB(skb)->recursion_counter = 0; 4514 4515 NAPI_GRO_CB(skb)->is_fou = 0; 4515 4516 NAPI_GRO_CB(skb)->is_atomic = 1; 4516 4517 NAPI_GRO_CB(skb)->gro_remcsum_start = 0;
+1 -1
net/ethernet/eth.c
··· 439 439 440 440 skb_gro_pull(skb, sizeof(*eh)); 441 441 skb_gro_postpull_rcsum(skb, eh, sizeof(*eh)); 442 - pp = ptype->callbacks.gro_receive(head, skb); 442 + pp = call_gro_receive(ptype->callbacks.gro_receive, head, skb); 443 443 444 444 out_unlock: 445 445 rcu_read_unlock();
+1 -1
net/ipv4/af_inet.c
··· 1391 1391 skb_gro_pull(skb, sizeof(*iph)); 1392 1392 skb_set_transport_header(skb, skb_gro_offset(skb)); 1393 1393 1394 - pp = ops->callbacks.gro_receive(head, skb); 1394 + pp = call_gro_receive(ops->callbacks.gro_receive, head, skb); 1395 1395 1396 1396 out_unlock: 1397 1397 rcu_read_unlock();
+2 -2
net/ipv4/fou.c
··· 249 249 if (!ops || !ops->callbacks.gro_receive) 250 250 goto out_unlock; 251 251 252 - pp = ops->callbacks.gro_receive(head, skb); 252 + pp = call_gro_receive(ops->callbacks.gro_receive, head, skb); 253 253 254 254 out_unlock: 255 255 rcu_read_unlock(); ··· 441 441 if (WARN_ON_ONCE(!ops || !ops->callbacks.gro_receive)) 442 442 goto out_unlock; 443 443 444 - pp = ops->callbacks.gro_receive(head, skb); 444 + pp = call_gro_receive(ops->callbacks.gro_receive, head, skb); 445 445 flush = 0; 446 446 447 447 out_unlock:
+1 -1
net/ipv4/gre_offload.c
··· 229 229 /* Adjusted NAPI_GRO_CB(skb)->csum after skb_gro_pull()*/ 230 230 skb_gro_postpull_rcsum(skb, greh, grehlen); 231 231 232 - pp = ptype->callbacks.gro_receive(head, skb); 232 + pp = call_gro_receive(ptype->callbacks.gro_receive, head, skb); 233 233 flush = 0; 234 234 235 235 out_unlock:
+1 -1
net/ipv4/udp_offload.c
··· 295 295 296 296 skb_gro_pull(skb, sizeof(struct udphdr)); /* pull encapsulating udp header */ 297 297 skb_gro_postpull_rcsum(skb, uh, sizeof(struct udphdr)); 298 - pp = udp_sk(sk)->gro_receive(sk, head, skb); 298 + pp = call_gro_receive_sk(udp_sk(sk)->gro_receive, sk, head, skb); 299 299 300 300 out_unlock: 301 301 rcu_read_unlock();
+1 -1
net/ipv6/ip6_offload.c
··· 246 246 247 247 skb_gro_postpull_rcsum(skb, iph, nlen); 248 248 249 - pp = ops->callbacks.gro_receive(head, skb); 249 + pp = call_gro_receive(ops->callbacks.gro_receive, head, skb); 250 250 251 251 out_unlock: 252 252 rcu_read_unlock();