diff options
author | Willem de Bruijn <willemb@google.com> | 2018-12-30 17:24:36 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2019-01-01 15:05:02 -0500 |
commit | cb9f1b783850b14cbd7f87d061d784a666dfba1f (patch) | |
tree | 652c378230eed5add180c8fec9787db540b32e97 | |
parent | 8c76e77f9069f10505c08e02646c3ee11ad79038 (diff) |
ip: validate header length on virtual device xmit
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 76c0ddd8c3a6 ("ip6_tunnel: be careful when accessing the
inner header") and commit ccfec9e5cb2d ("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 <syzkaller@googlegroups.com>
Signed-off-by: Willem de Bruijn <willemb@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | include/net/ip_tunnels.h | 20 | ||||
-rw-r--r-- | net/ipv4/ip_gre.c | 9 | ||||
-rw-r--r-- | net/ipv4/ip_tunnel.c | 9 | ||||
-rw-r--r-- | net/ipv4/ip_vti.c | 12 | ||||
-rw-r--r-- | net/ipv6/ip6_gre.c | 10 | ||||
-rw-r--r-- | net/ipv6/ip6_tunnel.c | 10 | ||||
-rw-r--r-- | net/ipv6/ip6_vti.c | 8 | ||||
-rw-r--r-- | net/ipv6/ip6mr.c | 17 | ||||
-rw-r--r-- | net/ipv6/sit.c | 3 |
9 files changed, 66 insertions, 32 deletions
diff --git a/include/net/ip_tunnels.h b/include/net/ip_tunnels.h index cbcf35ce1b14..34f019650941 100644 --- a/include/net/ip_tunnels.h +++ b/include/net/ip_tunnels.h | |||
@@ -308,6 +308,26 @@ int ip_tunnel_encap_del_ops(const struct ip_tunnel_encap_ops *op, | |||
308 | int ip_tunnel_encap_setup(struct ip_tunnel *t, | 308 | int ip_tunnel_encap_setup(struct ip_tunnel *t, |
309 | struct ip_tunnel_encap *ipencap); | 309 | struct ip_tunnel_encap *ipencap); |
310 | 310 | ||
311 | static inline bool pskb_inet_may_pull(struct sk_buff *skb) | ||
312 | { | ||
313 | int nhlen; | ||
314 | |||
315 | switch (skb->protocol) { | ||
316 | #if IS_ENABLED(CONFIG_IPV6) | ||
317 | case htons(ETH_P_IPV6): | ||
318 | nhlen = sizeof(struct ipv6hdr); | ||
319 | break; | ||
320 | #endif | ||
321 | case htons(ETH_P_IP): | ||
322 | nhlen = sizeof(struct iphdr); | ||
323 | break; | ||
324 | default: | ||
325 | nhlen = 0; | ||
326 | } | ||
327 | |||
328 | return pskb_network_may_pull(skb, nhlen); | ||
329 | } | ||
330 | |||
311 | static inline int ip_encap_hlen(struct ip_tunnel_encap *e) | 331 | static inline int ip_encap_hlen(struct ip_tunnel_encap *e) |
312 | { | 332 | { |
313 | const struct ip_tunnel_encap_ops *ops; | 333 | const struct ip_tunnel_encap_ops *ops; |
diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c index c7a7bd58a23c..d1d09f3e5f9e 100644 --- a/net/ipv4/ip_gre.c +++ b/net/ipv4/ip_gre.c | |||
@@ -676,6 +676,9 @@ static netdev_tx_t ipgre_xmit(struct sk_buff *skb, | |||
676 | struct ip_tunnel *tunnel = netdev_priv(dev); | 676 | struct ip_tunnel *tunnel = netdev_priv(dev); |
677 | const struct iphdr *tnl_params; | 677 | const struct iphdr *tnl_params; |
678 | 678 | ||
679 | if (!pskb_inet_may_pull(skb)) | ||
680 | goto free_skb; | ||
681 | |||
679 | if (tunnel->collect_md) { | 682 | if (tunnel->collect_md) { |
680 | gre_fb_xmit(skb, dev, skb->protocol); | 683 | gre_fb_xmit(skb, dev, skb->protocol); |
681 | return NETDEV_TX_OK; | 684 | return NETDEV_TX_OK; |
@@ -719,6 +722,9 @@ static netdev_tx_t erspan_xmit(struct sk_buff *skb, | |||
719 | struct ip_tunnel *tunnel = netdev_priv(dev); | 722 | struct ip_tunnel *tunnel = netdev_priv(dev); |
720 | bool truncate = false; | 723 | bool truncate = false; |
721 | 724 | ||
725 | if (!pskb_inet_may_pull(skb)) | ||
726 | goto free_skb; | ||
727 | |||
722 | if (tunnel->collect_md) { | 728 | if (tunnel->collect_md) { |
723 | erspan_fb_xmit(skb, dev, skb->protocol); | 729 | erspan_fb_xmit(skb, dev, skb->protocol); |
724 | return NETDEV_TX_OK; | 730 | return NETDEV_TX_OK; |
@@ -762,6 +768,9 @@ static netdev_tx_t gre_tap_xmit(struct sk_buff *skb, | |||
762 | { | 768 | { |
763 | struct ip_tunnel *tunnel = netdev_priv(dev); | 769 | struct ip_tunnel *tunnel = netdev_priv(dev); |
764 | 770 | ||
771 | if (!pskb_inet_may_pull(skb)) | ||
772 | goto free_skb; | ||
773 | |||
765 | if (tunnel->collect_md) { | 774 | if (tunnel->collect_md) { |
766 | gre_fb_xmit(skb, dev, htons(ETH_P_TEB)); | 775 | gre_fb_xmit(skb, dev, htons(ETH_P_TEB)); |
767 | return NETDEV_TX_OK; | 776 | return NETDEV_TX_OK; |
diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c index 284a22154b4e..c4f5602308ed 100644 --- a/net/ipv4/ip_tunnel.c +++ b/net/ipv4/ip_tunnel.c | |||
@@ -627,7 +627,6 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev, | |||
627 | const struct iphdr *tnl_params, u8 protocol) | 627 | const struct iphdr *tnl_params, u8 protocol) |
628 | { | 628 | { |
629 | struct ip_tunnel *tunnel = netdev_priv(dev); | 629 | struct ip_tunnel *tunnel = netdev_priv(dev); |
630 | unsigned int inner_nhdr_len = 0; | ||
631 | const struct iphdr *inner_iph; | 630 | const struct iphdr *inner_iph; |
632 | struct flowi4 fl4; | 631 | struct flowi4 fl4; |
633 | u8 tos, ttl; | 632 | u8 tos, ttl; |
@@ -637,14 +636,6 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev, | |||
637 | __be32 dst; | 636 | __be32 dst; |
638 | bool connected; | 637 | bool connected; |
639 | 638 | ||
640 | /* ensure we can access the inner net header, for several users below */ | ||
641 | if (skb->protocol == htons(ETH_P_IP)) | ||
642 | inner_nhdr_len = sizeof(struct iphdr); | ||
643 | else if (skb->protocol == htons(ETH_P_IPV6)) | ||
644 | inner_nhdr_len = sizeof(struct ipv6hdr); | ||
645 | if (unlikely(!pskb_may_pull(skb, inner_nhdr_len))) | ||
646 | goto tx_error; | ||
647 | |||
648 | inner_iph = (const struct iphdr *)skb_inner_network_header(skb); | 639 | inner_iph = (const struct iphdr *)skb_inner_network_header(skb); |
649 | connected = (tunnel->parms.iph.daddr != 0); | 640 | connected = (tunnel->parms.iph.daddr != 0); |
650 | 641 | ||
diff --git a/net/ipv4/ip_vti.c b/net/ipv4/ip_vti.c index de31b302d69c..d7b43e700023 100644 --- a/net/ipv4/ip_vti.c +++ b/net/ipv4/ip_vti.c | |||
@@ -241,6 +241,9 @@ static netdev_tx_t vti_tunnel_xmit(struct sk_buff *skb, struct net_device *dev) | |||
241 | struct ip_tunnel *tunnel = netdev_priv(dev); | 241 | struct ip_tunnel *tunnel = netdev_priv(dev); |
242 | struct flowi fl; | 242 | struct flowi fl; |
243 | 243 | ||
244 | if (!pskb_inet_may_pull(skb)) | ||
245 | goto tx_err; | ||
246 | |||
244 | memset(&fl, 0, sizeof(fl)); | 247 | memset(&fl, 0, sizeof(fl)); |
245 | 248 | ||
246 | switch (skb->protocol) { | 249 | switch (skb->protocol) { |
@@ -253,15 +256,18 @@ static netdev_tx_t vti_tunnel_xmit(struct sk_buff *skb, struct net_device *dev) | |||
253 | memset(IP6CB(skb), 0, sizeof(*IP6CB(skb))); | 256 | memset(IP6CB(skb), 0, sizeof(*IP6CB(skb))); |
254 | break; | 257 | break; |
255 | default: | 258 | default: |
256 | dev->stats.tx_errors++; | 259 | goto tx_err; |
257 | dev_kfree_skb(skb); | ||
258 | return NETDEV_TX_OK; | ||
259 | } | 260 | } |
260 | 261 | ||
261 | /* override mark with tunnel output key */ | 262 | /* override mark with tunnel output key */ |
262 | fl.flowi_mark = be32_to_cpu(tunnel->parms.o_key); | 263 | fl.flowi_mark = be32_to_cpu(tunnel->parms.o_key); |
263 | 264 | ||
264 | return vti_xmit(skb, dev, &fl); | 265 | return vti_xmit(skb, dev, &fl); |
266 | |||
267 | tx_err: | ||
268 | dev->stats.tx_errors++; | ||
269 | kfree_skb(skb); | ||
270 | return NETDEV_TX_OK; | ||
265 | } | 271 | } |
266 | 272 | ||
267 | static int vti4_err(struct sk_buff *skb, u32 info) | 273 | static int vti4_err(struct sk_buff *skb, u32 info) |
diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c index 229e55c99021..09d0826742f8 100644 --- a/net/ipv6/ip6_gre.c +++ b/net/ipv6/ip6_gre.c | |||
@@ -881,6 +881,9 @@ static netdev_tx_t ip6gre_tunnel_xmit(struct sk_buff *skb, | |||
881 | struct net_device_stats *stats = &t->dev->stats; | 881 | struct net_device_stats *stats = &t->dev->stats; |
882 | int ret; | 882 | int ret; |
883 | 883 | ||
884 | if (!pskb_inet_may_pull(skb)) | ||
885 | goto tx_err; | ||
886 | |||
884 | if (!ip6_tnl_xmit_ctl(t, &t->parms.laddr, &t->parms.raddr)) | 887 | if (!ip6_tnl_xmit_ctl(t, &t->parms.laddr, &t->parms.raddr)) |
885 | goto tx_err; | 888 | goto tx_err; |
886 | 889 | ||
@@ -923,6 +926,9 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb, | |||
923 | int nhoff; | 926 | int nhoff; |
924 | int thoff; | 927 | int thoff; |
925 | 928 | ||
929 | if (!pskb_inet_may_pull(skb)) | ||
930 | goto tx_err; | ||
931 | |||
926 | if (!ip6_tnl_xmit_ctl(t, &t->parms.laddr, &t->parms.raddr)) | 932 | if (!ip6_tnl_xmit_ctl(t, &t->parms.laddr, &t->parms.raddr)) |
927 | goto tx_err; | 933 | goto tx_err; |
928 | 934 | ||
@@ -995,8 +1001,6 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb, | |||
995 | goto tx_err; | 1001 | goto tx_err; |
996 | } | 1002 | } |
997 | } else { | 1003 | } else { |
998 | struct ipv6hdr *ipv6h = ipv6_hdr(skb); | ||
999 | |||
1000 | switch (skb->protocol) { | 1004 | switch (skb->protocol) { |
1001 | case htons(ETH_P_IP): | 1005 | case htons(ETH_P_IP): |
1002 | memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt)); | 1006 | memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt)); |
@@ -1004,7 +1008,7 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb, | |||
1004 | &dsfield, &encap_limit); | 1008 | &dsfield, &encap_limit); |
1005 | break; | 1009 | break; |
1006 | case htons(ETH_P_IPV6): | 1010 | case htons(ETH_P_IPV6): |
1007 | if (ipv6_addr_equal(&t->parms.raddr, &ipv6h->saddr)) | 1011 | if (ipv6_addr_equal(&t->parms.raddr, &ipv6_hdr(skb)->saddr)) |
1008 | goto tx_err; | 1012 | goto tx_err; |
1009 | if (prepare_ip6gre_xmit_ipv6(skb, dev, &fl6, | 1013 | if (prepare_ip6gre_xmit_ipv6(skb, dev, &fl6, |
1010 | &dsfield, &encap_limit)) | 1014 | &dsfield, &encap_limit)) |
diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c index 99179b9c8384..0c6403cf8b52 100644 --- a/net/ipv6/ip6_tunnel.c +++ b/net/ipv6/ip6_tunnel.c | |||
@@ -1243,10 +1243,6 @@ ip4ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev) | |||
1243 | u8 tproto; | 1243 | u8 tproto; |
1244 | int err; | 1244 | int err; |
1245 | 1245 | ||
1246 | /* ensure we can access the full inner ip header */ | ||
1247 | if (!pskb_may_pull(skb, sizeof(struct iphdr))) | ||
1248 | return -1; | ||
1249 | |||
1250 | iph = ip_hdr(skb); | 1246 | iph = ip_hdr(skb); |
1251 | memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt)); | 1247 | memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt)); |
1252 | 1248 | ||
@@ -1321,9 +1317,6 @@ ip6ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev) | |||
1321 | u8 tproto; | 1317 | u8 tproto; |
1322 | int err; | 1318 | int err; |
1323 | 1319 | ||
1324 | if (unlikely(!pskb_may_pull(skb, sizeof(*ipv6h)))) | ||
1325 | return -1; | ||
1326 | |||
1327 | ipv6h = ipv6_hdr(skb); | 1320 | ipv6h = ipv6_hdr(skb); |
1328 | tproto = READ_ONCE(t->parms.proto); | 1321 | tproto = READ_ONCE(t->parms.proto); |
1329 | if ((tproto != IPPROTO_IPV6 && tproto != 0) || | 1322 | if ((tproto != IPPROTO_IPV6 && tproto != 0) || |
@@ -1405,6 +1398,9 @@ ip6_tnl_start_xmit(struct sk_buff *skb, struct net_device *dev) | |||
1405 | struct net_device_stats *stats = &t->dev->stats; | 1398 | struct net_device_stats *stats = &t->dev->stats; |
1406 | int ret; | 1399 | int ret; |
1407 | 1400 | ||
1401 | if (!pskb_inet_may_pull(skb)) | ||
1402 | goto tx_err; | ||
1403 | |||
1408 | switch (skb->protocol) { | 1404 | switch (skb->protocol) { |
1409 | case htons(ETH_P_IP): | 1405 | case htons(ETH_P_IP): |
1410 | ret = ip4ip6_tnl_xmit(skb, dev); | 1406 | ret = ip4ip6_tnl_xmit(skb, dev); |
diff --git a/net/ipv6/ip6_vti.c b/net/ipv6/ip6_vti.c index 706fe42e4928..8b6eefff2f7e 100644 --- a/net/ipv6/ip6_vti.c +++ b/net/ipv6/ip6_vti.c | |||
@@ -522,18 +522,18 @@ vti6_tnl_xmit(struct sk_buff *skb, struct net_device *dev) | |||
522 | { | 522 | { |
523 | struct ip6_tnl *t = netdev_priv(dev); | 523 | struct ip6_tnl *t = netdev_priv(dev); |
524 | struct net_device_stats *stats = &t->dev->stats; | 524 | struct net_device_stats *stats = &t->dev->stats; |
525 | struct ipv6hdr *ipv6h; | ||
526 | struct flowi fl; | 525 | struct flowi fl; |
527 | int ret; | 526 | int ret; |
528 | 527 | ||
528 | if (!pskb_inet_may_pull(skb)) | ||
529 | goto tx_err; | ||
530 | |||
529 | memset(&fl, 0, sizeof(fl)); | 531 | memset(&fl, 0, sizeof(fl)); |
530 | 532 | ||
531 | switch (skb->protocol) { | 533 | switch (skb->protocol) { |
532 | case htons(ETH_P_IPV6): | 534 | case htons(ETH_P_IPV6): |
533 | ipv6h = ipv6_hdr(skb); | ||
534 | |||
535 | if ((t->parms.proto != IPPROTO_IPV6 && t->parms.proto != 0) || | 535 | if ((t->parms.proto != IPPROTO_IPV6 && t->parms.proto != 0) || |
536 | vti6_addr_conflict(t, ipv6h)) | 536 | vti6_addr_conflict(t, ipv6_hdr(skb))) |
537 | goto tx_err; | 537 | goto tx_err; |
538 | 538 | ||
539 | xfrm_decode_session(skb, &fl, AF_INET6); | 539 | xfrm_decode_session(skb, &fl, AF_INET6); |
diff --git a/net/ipv6/ip6mr.c b/net/ipv6/ip6mr.c index 8276f1224f16..30337b38274b 100644 --- a/net/ipv6/ip6mr.c +++ b/net/ipv6/ip6mr.c | |||
@@ -51,6 +51,7 @@ | |||
51 | #include <linux/export.h> | 51 | #include <linux/export.h> |
52 | #include <net/ip6_checksum.h> | 52 | #include <net/ip6_checksum.h> |
53 | #include <linux/netconf.h> | 53 | #include <linux/netconf.h> |
54 | #include <net/ip_tunnels.h> | ||
54 | 55 | ||
55 | #include <linux/nospec.h> | 56 | #include <linux/nospec.h> |
56 | 57 | ||
@@ -599,13 +600,12 @@ static netdev_tx_t reg_vif_xmit(struct sk_buff *skb, | |||
599 | .flowi6_iif = skb->skb_iif ? : LOOPBACK_IFINDEX, | 600 | .flowi6_iif = skb->skb_iif ? : LOOPBACK_IFINDEX, |
600 | .flowi6_mark = skb->mark, | 601 | .flowi6_mark = skb->mark, |
601 | }; | 602 | }; |
602 | int err; | ||
603 | 603 | ||
604 | err = ip6mr_fib_lookup(net, &fl6, &mrt); | 604 | if (!pskb_inet_may_pull(skb)) |
605 | if (err < 0) { | 605 | goto tx_err; |
606 | kfree_skb(skb); | 606 | |
607 | return err; | 607 | if (ip6mr_fib_lookup(net, &fl6, &mrt) < 0) |
608 | } | 608 | goto tx_err; |
609 | 609 | ||
610 | read_lock(&mrt_lock); | 610 | read_lock(&mrt_lock); |
611 | dev->stats.tx_bytes += skb->len; | 611 | dev->stats.tx_bytes += skb->len; |
@@ -614,6 +614,11 @@ static netdev_tx_t reg_vif_xmit(struct sk_buff *skb, | |||
614 | read_unlock(&mrt_lock); | 614 | read_unlock(&mrt_lock); |
615 | kfree_skb(skb); | 615 | kfree_skb(skb); |
616 | return NETDEV_TX_OK; | 616 | return NETDEV_TX_OK; |
617 | |||
618 | tx_err: | ||
619 | dev->stats.tx_errors++; | ||
620 | kfree_skb(skb); | ||
621 | return NETDEV_TX_OK; | ||
617 | } | 622 | } |
618 | 623 | ||
619 | static int reg_vif_get_iflink(const struct net_device *dev) | 624 | static int reg_vif_get_iflink(const struct net_device *dev) |
diff --git a/net/ipv6/sit.c b/net/ipv6/sit.c index 51c9f75f34b9..1e03305c0549 100644 --- a/net/ipv6/sit.c +++ b/net/ipv6/sit.c | |||
@@ -1021,6 +1021,9 @@ tx_error: | |||
1021 | static netdev_tx_t sit_tunnel_xmit(struct sk_buff *skb, | 1021 | static netdev_tx_t sit_tunnel_xmit(struct sk_buff *skb, |
1022 | struct net_device *dev) | 1022 | struct net_device *dev) |
1023 | { | 1023 | { |
1024 | if (!pskb_inet_may_pull(skb)) | ||
1025 | goto tx_err; | ||
1026 | |||
1024 | switch (skb->protocol) { | 1027 | switch (skb->protocol) { |
1025 | case htons(ETH_P_IP): | 1028 | case htons(ETH_P_IP): |
1026 | sit_tunnel_xmit__(skb, dev, IPPROTO_IPIP); | 1029 | sit_tunnel_xmit__(skb, dev, IPPROTO_IPIP); |