diff --git a/include/net/ip.h b/include/net/ip.h index 7e7db511c9630..01c7ed96585ee 100644 --- a/include/net/ip.h +++ b/include/net/ip.h @@ -74,6 +74,13 @@ static inline unsigned int ip_hdrlen(const struct sk_buff *skb) return ip_hdr(skb)->ihl * 4; } +static inline int ip_check_csum_start(const struct sk_buff *skb) +{ + if (unlikely(skb->csum_start < skb->network_header + ip_hdrlen(skb))) + return -EINVAL; + return 0; +} + struct ipcm_cookie { struct sockcm_cookie sockc; __be32 addr; diff --git a/net/bridge/netfilter/nf_conntrack_bridge.c b/net/bridge/netfilter/nf_conntrack_bridge.c index 6482de4d87509..10127b7588efa 100644 --- a/net/bridge/netfilter/nf_conntrack_bridge.c +++ b/net/bridge/netfilter/nf_conntrack_bridge.c @@ -40,9 +40,13 @@ static int nf_br_ip_fragment(struct net *net, struct sock *sk, int err = 0; /* for offloaded checksums cleanup checksum before fragmentation */ - if (skb->ip_summed == CHECKSUM_PARTIAL && - (err = skb_checksum_help(skb))) - goto blackhole; + if (skb->ip_summed == CHECKSUM_PARTIAL) { + err = ip_check_csum_start(skb); + if (!err) + err = skb_checksum_help(skb); + if (err) + goto blackhole; + } iph = ip_hdr(skb); diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c index 7625bcc856155..780d0a6e50958 100644 --- a/net/ipv4/ip_output.c +++ b/net/ipv4/ip_output.c @@ -779,9 +779,13 @@ int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb, int err = 0; /* for offloaded checksums cleanup checksum before fragmentation */ - if (skb->ip_summed == CHECKSUM_PARTIAL && - (err = skb_checksum_help(skb))) - goto fail; + if (skb->ip_summed == CHECKSUM_PARTIAL) { + err = ip_check_csum_start(skb); + if (!err) + err = skb_checksum_help(skb); + if (err) + goto fail; + } /* * Point into the IP datagram header. diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c index 3e244c4db0f59..c0955dd79e322 100644 --- a/net/netfilter/nfnetlink_queue.c +++ b/net/netfilter/nfnetlink_queue.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -607,6 +608,12 @@ static int nfqnl_put_bridge(struct nf_queue_entry *entry, struct sk_buff *skb) static int nf_queue_checksum_help(struct sk_buff *entskb) { + if (entskb->protocol == htons(ETH_P_IP)) { + if (!pskb_network_may_pull(entskb, sizeof(struct iphdr)) || + ip_check_csum_start(entskb)) + return -EINVAL; + } + if (skb_csum_is_sctp(entskb)) return skb_crc32c_csum_help(entskb); diff --git a/net/netfilter/xt_CHECKSUM.c b/net/netfilter/xt_CHECKSUM.c index 9d99f5a3d1764..1fb0f8118404d 100644 --- a/net/netfilter/xt_CHECKSUM.c +++ b/net/netfilter/xt_CHECKSUM.c @@ -15,6 +15,7 @@ #include #include +#include MODULE_LICENSE("GPL"); MODULE_AUTHOR("Michael S. Tsirkin "); @@ -25,8 +26,11 @@ MODULE_ALIAS("ip6t_CHECKSUM"); static unsigned int checksum_tg(struct sk_buff *skb, const struct xt_action_param *par) { - if (skb->ip_summed == CHECKSUM_PARTIAL && !skb_is_gso(skb)) + if (skb->ip_summed == CHECKSUM_PARTIAL && !skb_is_gso(skb)) { + if (xt_family(par) == NFPROTO_IPV4 && ip_check_csum_start(skb)) + return NF_DROP; skb_checksum_help(skb); + } return XT_CONTINUE; } diff --git a/net/xfrm/xfrm_output.c b/net/xfrm/xfrm_output.c index 71bfbb6760f7e..15469d664f5f9 100644 --- a/net/xfrm/xfrm_output.c +++ b/net/xfrm/xfrm_output.c @@ -816,16 +816,22 @@ int xfrm_output(struct sock *sk, struct sk_buff *skb) } if (skb->ip_summed == CHECKSUM_PARTIAL) { - err = skb_checksum_help(skb); - if (err) { - XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR); - kfree_skb(skb); - return err; + if (skb->protocol == htons(ETH_P_IP)) { + err = ip_check_csum_start(skb); + if (err) + goto error; } + err = skb_checksum_help(skb); + if (err) + goto error; } out: return xfrm_output2(net, sk, skb); +error: + XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR); + kfree_skb(skb); + return err; } EXPORT_SYMBOL_GPL(xfrm_output);