Skip to content

Commit

Permalink
ip: validate header length on virtual device xmit
Browse files Browse the repository at this point in the history
KMSAN detected read beyond end of buffer in vti and sit devices when
passing truncated packets with PF_PACKET. The issue affects additional
ip tunnel devices.

Extend commit 76c0ddd ("ip6_tunnel: be careful when accessing the
inner header") and commit ccfec9e ("ip_tunnel: be careful when
accessing the inner header").

Move the check to a separate helper and call at the start of each
ndo_start_xmit function in net/ipv4 and net/ipv6.

Minor changes:
- convert dev_kfree_skb to kfree_skb on error path,
  as dev_kfree_skb calls consume_skb which is not for error paths.
- use pskb_network_may_pull even though that is pedantic here,
  as the same as pskb_may_pull for devices without llheaders.
- do not cache ipv6 hdrs if used only once
  (unsafe across pskb_may_pull, was more relevant to earlier patch)

Reported-by: syzbot <[email protected]>
Signed-off-by: Willem de Bruijn <[email protected]>
Signed-off-by: David S. Miller <[email protected]>
  • Loading branch information
wdebruij authored and davem330 committed Jan 1, 2019
1 parent 8c76e77 commit cb9f1b7
Show file tree
Hide file tree
Showing 9 changed files with 66 additions and 32 deletions.
20 changes: 20 additions & 0 deletions include/net/ip_tunnels.h
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,26 @@ int ip_tunnel_encap_del_ops(const struct ip_tunnel_encap_ops *op,
int ip_tunnel_encap_setup(struct ip_tunnel *t,
struct ip_tunnel_encap *ipencap);

static inline bool pskb_inet_may_pull(struct sk_buff *skb)
{
int nhlen;

switch (skb->protocol) {
#if IS_ENABLED(CONFIG_IPV6)
case htons(ETH_P_IPV6):
nhlen = sizeof(struct ipv6hdr);
break;
#endif
case htons(ETH_P_IP):
nhlen = sizeof(struct iphdr);
break;
default:
nhlen = 0;
}

return pskb_network_may_pull(skb, nhlen);
}

static inline int ip_encap_hlen(struct ip_tunnel_encap *e)
{
const struct ip_tunnel_encap_ops *ops;
Expand Down
9 changes: 9 additions & 0 deletions net/ipv4/ip_gre.c
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,9 @@ static netdev_tx_t ipgre_xmit(struct sk_buff *skb,
struct ip_tunnel *tunnel = netdev_priv(dev);
const struct iphdr *tnl_params;

if (!pskb_inet_may_pull(skb))
goto free_skb;

if (tunnel->collect_md) {
gre_fb_xmit(skb, dev, skb->protocol);
return NETDEV_TX_OK;
Expand Down Expand Up @@ -719,6 +722,9 @@ static netdev_tx_t erspan_xmit(struct sk_buff *skb,
struct ip_tunnel *tunnel = netdev_priv(dev);
bool truncate = false;

if (!pskb_inet_may_pull(skb))
goto free_skb;

if (tunnel->collect_md) {
erspan_fb_xmit(skb, dev, skb->protocol);
return NETDEV_TX_OK;
Expand Down Expand Up @@ -762,6 +768,9 @@ static netdev_tx_t gre_tap_xmit(struct sk_buff *skb,
{
struct ip_tunnel *tunnel = netdev_priv(dev);

if (!pskb_inet_may_pull(skb))
goto free_skb;

if (tunnel->collect_md) {
gre_fb_xmit(skb, dev, htons(ETH_P_TEB));
return NETDEV_TX_OK;
Expand Down
9 changes: 0 additions & 9 deletions net/ipv4/ip_tunnel.c
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,6 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
const struct iphdr *tnl_params, u8 protocol)
{
struct ip_tunnel *tunnel = netdev_priv(dev);
unsigned int inner_nhdr_len = 0;
const struct iphdr *inner_iph;
struct flowi4 fl4;
u8 tos, ttl;
Expand All @@ -637,14 +636,6 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
__be32 dst;
bool connected;

/* ensure we can access the inner net header, for several users below */
if (skb->protocol == htons(ETH_P_IP))
inner_nhdr_len = sizeof(struct iphdr);
else if (skb->protocol == htons(ETH_P_IPV6))
inner_nhdr_len = sizeof(struct ipv6hdr);
if (unlikely(!pskb_may_pull(skb, inner_nhdr_len)))
goto tx_error;

inner_iph = (const struct iphdr *)skb_inner_network_header(skb);
connected = (tunnel->parms.iph.daddr != 0);

Expand Down
12 changes: 9 additions & 3 deletions net/ipv4/ip_vti.c
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,9 @@ static netdev_tx_t vti_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
struct ip_tunnel *tunnel = netdev_priv(dev);
struct flowi fl;

if (!pskb_inet_may_pull(skb))
goto tx_err;

memset(&fl, 0, sizeof(fl));

switch (skb->protocol) {
Expand All @@ -253,15 +256,18 @@ static netdev_tx_t vti_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
memset(IP6CB(skb), 0, sizeof(*IP6CB(skb)));
break;
default:
dev->stats.tx_errors++;
dev_kfree_skb(skb);
return NETDEV_TX_OK;
goto tx_err;
}

/* override mark with tunnel output key */
fl.flowi_mark = be32_to_cpu(tunnel->parms.o_key);

return vti_xmit(skb, dev, &fl);

tx_err:
dev->stats.tx_errors++;
kfree_skb(skb);
return NETDEV_TX_OK;
}

static int vti4_err(struct sk_buff *skb, u32 info)
Expand Down
10 changes: 7 additions & 3 deletions net/ipv6/ip6_gre.c
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,9 @@ static netdev_tx_t ip6gre_tunnel_xmit(struct sk_buff *skb,
struct net_device_stats *stats = &t->dev->stats;
int ret;

if (!pskb_inet_may_pull(skb))
goto tx_err;

if (!ip6_tnl_xmit_ctl(t, &t->parms.laddr, &t->parms.raddr))
goto tx_err;

Expand Down Expand Up @@ -923,6 +926,9 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb,
int nhoff;
int thoff;

if (!pskb_inet_may_pull(skb))
goto tx_err;

if (!ip6_tnl_xmit_ctl(t, &t->parms.laddr, &t->parms.raddr))
goto tx_err;

Expand Down Expand Up @@ -995,16 +1001,14 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb,
goto tx_err;
}
} else {
struct ipv6hdr *ipv6h = ipv6_hdr(skb);

switch (skb->protocol) {
case htons(ETH_P_IP):
memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt));
prepare_ip6gre_xmit_ipv4(skb, dev, &fl6,
&dsfield, &encap_limit);
break;
case htons(ETH_P_IPV6):
if (ipv6_addr_equal(&t->parms.raddr, &ipv6h->saddr))
if (ipv6_addr_equal(&t->parms.raddr, &ipv6_hdr(skb)->saddr))
goto tx_err;
if (prepare_ip6gre_xmit_ipv6(skb, dev, &fl6,
&dsfield, &encap_limit))
Expand Down
10 changes: 3 additions & 7 deletions net/ipv6/ip6_tunnel.c
Original file line number Diff line number Diff line change
Expand Up @@ -1243,10 +1243,6 @@ ip4ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev)
u8 tproto;
int err;

/* ensure we can access the full inner ip header */
if (!pskb_may_pull(skb, sizeof(struct iphdr)))
return -1;

iph = ip_hdr(skb);
memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt));

Expand Down Expand Up @@ -1321,9 +1317,6 @@ ip6ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev)
u8 tproto;
int err;

if (unlikely(!pskb_may_pull(skb, sizeof(*ipv6h))))
return -1;

ipv6h = ipv6_hdr(skb);
tproto = READ_ONCE(t->parms.proto);
if ((tproto != IPPROTO_IPV6 && tproto != 0) ||
Expand Down Expand Up @@ -1405,6 +1398,9 @@ ip6_tnl_start_xmit(struct sk_buff *skb, struct net_device *dev)
struct net_device_stats *stats = &t->dev->stats;
int ret;

if (!pskb_inet_may_pull(skb))
goto tx_err;

switch (skb->protocol) {
case htons(ETH_P_IP):
ret = ip4ip6_tnl_xmit(skb, dev);
Expand Down
8 changes: 4 additions & 4 deletions net/ipv6/ip6_vti.c
Original file line number Diff line number Diff line change
Expand Up @@ -522,18 +522,18 @@ vti6_tnl_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct ip6_tnl *t = netdev_priv(dev);
struct net_device_stats *stats = &t->dev->stats;
struct ipv6hdr *ipv6h;
struct flowi fl;
int ret;

if (!pskb_inet_may_pull(skb))
goto tx_err;

memset(&fl, 0, sizeof(fl));

switch (skb->protocol) {
case htons(ETH_P_IPV6):
ipv6h = ipv6_hdr(skb);

if ((t->parms.proto != IPPROTO_IPV6 && t->parms.proto != 0) ||
vti6_addr_conflict(t, ipv6h))
vti6_addr_conflict(t, ipv6_hdr(skb)))
goto tx_err;

xfrm_decode_session(skb, &fl, AF_INET6);
Expand Down
17 changes: 11 additions & 6 deletions net/ipv6/ip6mr.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
#include <linux/export.h>
#include <net/ip6_checksum.h>
#include <linux/netconf.h>
#include <net/ip_tunnels.h>

#include <linux/nospec.h>

Expand Down Expand Up @@ -599,13 +600,12 @@ static netdev_tx_t reg_vif_xmit(struct sk_buff *skb,
.flowi6_iif = skb->skb_iif ? : LOOPBACK_IFINDEX,
.flowi6_mark = skb->mark,
};
int err;

err = ip6mr_fib_lookup(net, &fl6, &mrt);
if (err < 0) {
kfree_skb(skb);
return err;
}
if (!pskb_inet_may_pull(skb))
goto tx_err;

if (ip6mr_fib_lookup(net, &fl6, &mrt) < 0)
goto tx_err;

read_lock(&mrt_lock);
dev->stats.tx_bytes += skb->len;
Expand All @@ -614,6 +614,11 @@ static netdev_tx_t reg_vif_xmit(struct sk_buff *skb,
read_unlock(&mrt_lock);
kfree_skb(skb);
return NETDEV_TX_OK;

tx_err:
dev->stats.tx_errors++;
kfree_skb(skb);
return NETDEV_TX_OK;
}

static int reg_vif_get_iflink(const struct net_device *dev)
Expand Down
3 changes: 3 additions & 0 deletions net/ipv6/sit.c
Original file line number Diff line number Diff line change
Expand Up @@ -1021,6 +1021,9 @@ static netdev_tx_t sit_tunnel_xmit__(struct sk_buff *skb,
static netdev_tx_t sit_tunnel_xmit(struct sk_buff *skb,
struct net_device *dev)
{
if (!pskb_inet_may_pull(skb))
goto tx_err;

switch (skb->protocol) {
case htons(ETH_P_IP):
sit_tunnel_xmit__(skb, dev, IPPROTO_IPIP);
Expand Down

0 comments on commit cb9f1b7

Please sign in to comment.