Skip to content

Commit

Permalink
net: add recursion limit to GRO
Browse files Browse the repository at this point in the history
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š <[email protected]> for the initial bug report.

Fixes: CVE-2016-7039
Fixes: 9b174d8 ("net: Add Transparent Ethernet Bridging GRO support.")
Fixes: 66e5133 ("vlan: Add GRO support for non hardware accelerated vlan")
Signed-off-by: Sabrina Dubroca <[email protected]>
Reviewed-by: Jiri Benc <[email protected]>
Acked-by: Hannes Frederic Sowa <[email protected]>
Acked-by: Tom Herbert <[email protected]>
Signed-off-by: David S. Miller <[email protected]>
  • Loading branch information
qsn authored and davem330 committed Oct 20, 2016
1 parent 7aa8e63 commit fcd91dd
Show file tree
Hide file tree
Showing 11 changed files with 49 additions and 11 deletions.
2 changes: 1 addition & 1 deletion drivers/net/geneve.c
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ static struct sk_buff **geneve_gro_receive(struct sock *sk,

skb_gro_pull(skb, gh_len);
skb_gro_postpull_rcsum(skb, gh, gh_len);
pp = ptype->callbacks.gro_receive(head, skb);
pp = call_gro_receive(ptype->callbacks.gro_receive, head, skb);
flush = 0;

out_unlock:
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/vxlan.c
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ static struct sk_buff **vxlan_gro_receive(struct sock *sk,
}
}

pp = eth_gro_receive(head, skb);
pp = call_gro_receive(eth_gro_receive, head, skb);
flush = 0;

out:
Expand Down
39 changes: 38 additions & 1 deletion include/linux/netdevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -2169,7 +2169,10 @@ struct napi_gro_cb {
/* Used to determine if flush_id can be ignored */
u8 is_atomic:1;

/* 5 bit hole */
/* Number of gro_receive callbacks this packet already went through */
u8 recursion_counter:4;

/* 1 bit hole */

/* used to support CHECKSUM_COMPLETE for tunneling protocols */
__wsum csum;
Expand All @@ -2180,6 +2183,40 @@ struct napi_gro_cb {

#define NAPI_GRO_CB(skb) ((struct napi_gro_cb *)(skb)->cb)

#define GRO_RECURSION_LIMIT 15
static inline int gro_recursion_inc_test(struct sk_buff *skb)
{
return ++NAPI_GRO_CB(skb)->recursion_counter == GRO_RECURSION_LIMIT;
}

typedef struct sk_buff **(*gro_receive_t)(struct sk_buff **, struct sk_buff *);
static inline struct sk_buff **call_gro_receive(gro_receive_t cb,
struct sk_buff **head,
struct sk_buff *skb)
{
if (unlikely(gro_recursion_inc_test(skb))) {
NAPI_GRO_CB(skb)->flush |= 1;
return NULL;
}

return cb(head, skb);
}

typedef struct sk_buff **(*gro_receive_sk_t)(struct sock *, struct sk_buff **,
struct sk_buff *);
static inline struct sk_buff **call_gro_receive_sk(gro_receive_sk_t cb,
struct sock *sk,
struct sk_buff **head,
struct sk_buff *skb)
{
if (unlikely(gro_recursion_inc_test(skb))) {
NAPI_GRO_CB(skb)->flush |= 1;
return NULL;
}

return cb(sk, head, skb);
}

struct packet_type {
__be16 type; /* This is really htons(ether_type). */
struct net_device *dev; /* NULL is wildcarded here */
Expand Down
2 changes: 1 addition & 1 deletion net/8021q/vlan.c
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,7 @@ static struct sk_buff **vlan_gro_receive(struct sk_buff **head,

skb_gro_pull(skb, sizeof(*vhdr));
skb_gro_postpull_rcsum(skb, vhdr, sizeof(*vhdr));
pp = ptype->callbacks.gro_receive(head, skb);
pp = call_gro_receive(ptype->callbacks.gro_receive, head, skb);

out_unlock:
rcu_read_unlock();
Expand Down
1 change: 1 addition & 0 deletions net/core/dev.c
Original file line number Diff line number Diff line change
Expand Up @@ -4511,6 +4511,7 @@ static enum gro_result dev_gro_receive(struct napi_struct *napi, struct sk_buff
NAPI_GRO_CB(skb)->flush = 0;
NAPI_GRO_CB(skb)->free = 0;
NAPI_GRO_CB(skb)->encap_mark = 0;
NAPI_GRO_CB(skb)->recursion_counter = 0;
NAPI_GRO_CB(skb)->is_fou = 0;
NAPI_GRO_CB(skb)->is_atomic = 1;
NAPI_GRO_CB(skb)->gro_remcsum_start = 0;
Expand Down
2 changes: 1 addition & 1 deletion net/ethernet/eth.c
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ struct sk_buff **eth_gro_receive(struct sk_buff **head,

skb_gro_pull(skb, sizeof(*eh));
skb_gro_postpull_rcsum(skb, eh, sizeof(*eh));
pp = ptype->callbacks.gro_receive(head, skb);
pp = call_gro_receive(ptype->callbacks.gro_receive, head, skb);

out_unlock:
rcu_read_unlock();
Expand Down
2 changes: 1 addition & 1 deletion net/ipv4/af_inet.c
Original file line number Diff line number Diff line change
Expand Up @@ -1391,7 +1391,7 @@ struct sk_buff **inet_gro_receive(struct sk_buff **head, struct sk_buff *skb)
skb_gro_pull(skb, sizeof(*iph));
skb_set_transport_header(skb, skb_gro_offset(skb));

pp = ops->callbacks.gro_receive(head, skb);
pp = call_gro_receive(ops->callbacks.gro_receive, head, skb);

out_unlock:
rcu_read_unlock();
Expand Down
4 changes: 2 additions & 2 deletions net/ipv4/fou.c
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ static struct sk_buff **fou_gro_receive(struct sock *sk,
if (!ops || !ops->callbacks.gro_receive)
goto out_unlock;

pp = ops->callbacks.gro_receive(head, skb);
pp = call_gro_receive(ops->callbacks.gro_receive, head, skb);

out_unlock:
rcu_read_unlock();
Expand Down Expand Up @@ -441,7 +441,7 @@ static struct sk_buff **gue_gro_receive(struct sock *sk,
if (WARN_ON_ONCE(!ops || !ops->callbacks.gro_receive))
goto out_unlock;

pp = ops->callbacks.gro_receive(head, skb);
pp = call_gro_receive(ops->callbacks.gro_receive, head, skb);
flush = 0;

out_unlock:
Expand Down
2 changes: 1 addition & 1 deletion net/ipv4/gre_offload.c
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ static struct sk_buff **gre_gro_receive(struct sk_buff **head,
/* Adjusted NAPI_GRO_CB(skb)->csum after skb_gro_pull()*/
skb_gro_postpull_rcsum(skb, greh, grehlen);

pp = ptype->callbacks.gro_receive(head, skb);
pp = call_gro_receive(ptype->callbacks.gro_receive, head, skb);
flush = 0;

out_unlock:
Expand Down
2 changes: 1 addition & 1 deletion net/ipv4/udp_offload.c
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ struct sk_buff **udp_gro_receive(struct sk_buff **head, struct sk_buff *skb,

skb_gro_pull(skb, sizeof(struct udphdr)); /* pull encapsulating udp header */
skb_gro_postpull_rcsum(skb, uh, sizeof(struct udphdr));
pp = udp_sk(sk)->gro_receive(sk, head, skb);
pp = call_gro_receive_sk(udp_sk(sk)->gro_receive, sk, head, skb);

out_unlock:
rcu_read_unlock();
Expand Down
2 changes: 1 addition & 1 deletion net/ipv6/ip6_offload.c
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ static struct sk_buff **ipv6_gro_receive(struct sk_buff **head,

skb_gro_postpull_rcsum(skb, iph, nlen);

pp = ops->callbacks.gro_receive(head, skb);
pp = call_gro_receive(ops->callbacks.gro_receive, head, skb);

out_unlock:
rcu_read_unlock();
Expand Down

0 comments on commit fcd91dd

Please sign in to comment.