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

netfilter: reject: don't send icmp error if csum is invalid

tcp resets are never emitted if the packet that triggers the
reject/reset has an invalid checksum.

For icmp error responses there was no such check.
It allows to distinguish icmp response generated via

iptables -I INPUT -p udp --dport 42 -j REJECT

and those emitted by network stack (won't respond if csum is invalid,
REJECT does).

Arguably its possible to avoid this by using conntrack and only
using REJECT with -m conntrack NEW/RELATED.

However, this doesn't work when connection tracking is not in use
or when using nf_conntrack_checksum=0.

Furthermore, sending errors in response to invalid csums doesn't make
much sense so just add similar test as in nf_send_reset.

Validate csum if needed and only send the response if it is ok.

Reference: http://bugzilla.redhat.com/show_bug.cgi?id=1169829
Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>

authored by

Florian Westphal and committed by
Pablo Neira Ayuso
ee586bbc b898441f

+76 -25
+1 -5
include/net/netfilter/ipv4/nf_reject.h
··· 5 5 #include <net/ip.h> 6 6 #include <net/icmp.h> 7 7 8 - static inline void nf_send_unreach(struct sk_buff *skb_in, int code) 9 - { 10 - icmp_send(skb_in, ICMP_DEST_UNREACH, code, 0); 11 - } 12 - 8 + void nf_send_unreach(struct sk_buff *skb_in, int code, int hook); 13 9 void nf_send_reset(struct sk_buff *oldskb, int hook); 14 10 15 11 const struct tcphdr *nf_reject_ip_tcphdr_get(struct sk_buff *oldskb,
+2 -9
include/net/netfilter/ipv6/nf_reject.h
··· 3 3 4 4 #include <linux/icmpv6.h> 5 5 6 - static inline void 7 - nf_send_unreach6(struct net *net, struct sk_buff *skb_in, unsigned char code, 8 - unsigned int hooknum) 9 - { 10 - if (hooknum == NF_INET_LOCAL_OUT && skb_in->dev == NULL) 11 - skb_in->dev = net->loopback_dev; 12 - 13 - icmpv6_send(skb_in, ICMPV6_DEST_UNREACH, code, 0); 14 - } 6 + void nf_send_unreach6(struct net *net, struct sk_buff *skb_in, unsigned char code, 7 + unsigned int hooknum); 15 8 16 9 void nf_send_reset6(struct net *net, struct sk_buff *oldskb, int hook); 17 10
+9 -8
net/ipv4/netfilter/ipt_REJECT.c
··· 34 34 reject_tg(struct sk_buff *skb, const struct xt_action_param *par) 35 35 { 36 36 const struct ipt_reject_info *reject = par->targinfo; 37 + int hook = par->hooknum; 37 38 38 39 switch (reject->with) { 39 40 case IPT_ICMP_NET_UNREACHABLE: 40 - nf_send_unreach(skb, ICMP_NET_UNREACH); 41 + nf_send_unreach(skb, ICMP_NET_UNREACH, hook); 41 42 break; 42 43 case IPT_ICMP_HOST_UNREACHABLE: 43 - nf_send_unreach(skb, ICMP_HOST_UNREACH); 44 + nf_send_unreach(skb, ICMP_HOST_UNREACH, hook); 44 45 break; 45 46 case IPT_ICMP_PROT_UNREACHABLE: 46 - nf_send_unreach(skb, ICMP_PROT_UNREACH); 47 + nf_send_unreach(skb, ICMP_PROT_UNREACH, hook); 47 48 break; 48 49 case IPT_ICMP_PORT_UNREACHABLE: 49 - nf_send_unreach(skb, ICMP_PORT_UNREACH); 50 + nf_send_unreach(skb, ICMP_PORT_UNREACH, hook); 50 51 break; 51 52 case IPT_ICMP_NET_PROHIBITED: 52 - nf_send_unreach(skb, ICMP_NET_ANO); 53 + nf_send_unreach(skb, ICMP_NET_ANO, hook); 53 54 break; 54 55 case IPT_ICMP_HOST_PROHIBITED: 55 - nf_send_unreach(skb, ICMP_HOST_ANO); 56 + nf_send_unreach(skb, ICMP_HOST_ANO, hook); 56 57 break; 57 58 case IPT_ICMP_ADMIN_PROHIBITED: 58 - nf_send_unreach(skb, ICMP_PKT_FILTERED); 59 + nf_send_unreach(skb, ICMP_PKT_FILTERED, hook); 59 60 break; 60 61 case IPT_TCP_RESET: 61 - nf_send_reset(skb, par->hooknum); 62 + nf_send_reset(skb, hook); 62 63 case IPT_ICMP_ECHOREPLY: 63 64 /* Doesn't happen. */ 64 65 break;
+23
net/ipv4/netfilter/nf_reject_ipv4.c
··· 164 164 } 165 165 EXPORT_SYMBOL_GPL(nf_send_reset); 166 166 167 + void nf_send_unreach(struct sk_buff *skb_in, int code, int hook) 168 + { 169 + struct iphdr *iph = ip_hdr(skb_in); 170 + u8 proto; 171 + 172 + if (skb_in->csum_bad || iph->frag_off & htons(IP_OFFSET)) 173 + return; 174 + 175 + if (skb_csum_unnecessary(skb_in)) { 176 + icmp_send(skb_in, ICMP_DEST_UNREACH, code, 0); 177 + return; 178 + } 179 + 180 + if (iph->protocol == IPPROTO_TCP || iph->protocol == IPPROTO_UDP) 181 + proto = iph->protocol; 182 + else 183 + proto = 0; 184 + 185 + if (nf_ip_checksum(skb_in, hook, ip_hdrlen(skb_in), proto) == 0) 186 + icmp_send(skb_in, ICMP_DEST_UNREACH, code, 0); 187 + } 188 + EXPORT_SYMBOL_GPL(nf_send_unreach); 189 + 167 190 MODULE_LICENSE("GPL");
+2 -1
net/ipv4/netfilter/nft_reject_ipv4.c
··· 27 27 28 28 switch (priv->type) { 29 29 case NFT_REJECT_ICMP_UNREACH: 30 - nf_send_unreach(pkt->skb, priv->icmp_code); 30 + nf_send_unreach(pkt->skb, priv->icmp_code, 31 + pkt->ops->hooknum); 31 32 break; 32 33 case NFT_REJECT_TCP_RST: 33 34 nf_send_reset(pkt->skb, pkt->ops->hooknum);
+35
net/ipv6/netfilter/nf_reject_ipv6.c
··· 208 208 } 209 209 EXPORT_SYMBOL_GPL(nf_send_reset6); 210 210 211 + static bool reject6_csum_ok(struct sk_buff *skb, int hook) 212 + { 213 + const struct ipv6hdr *ip6h = ipv6_hdr(skb); 214 + int thoff; 215 + __be16 fo; 216 + u8 proto; 217 + 218 + if (skb->csum_bad) 219 + return false; 220 + 221 + if (skb_csum_unnecessary(skb)) 222 + return true; 223 + 224 + proto = ip6h->nexthdr; 225 + thoff = ipv6_skip_exthdr(skb, ((u8*)(ip6h+1) - skb->data), &proto, &fo); 226 + 227 + if (thoff < 0 || thoff >= skb->len || (fo & htons(~0x7)) != 0) 228 + return false; 229 + 230 + return nf_ip6_checksum(skb, hook, thoff, proto) == 0; 231 + } 232 + 233 + void nf_send_unreach6(struct net *net, struct sk_buff *skb_in, 234 + unsigned char code, unsigned int hooknum) 235 + { 236 + if (!reject6_csum_ok(skb_in, hooknum)) 237 + return; 238 + 239 + if (hooknum == NF_INET_LOCAL_OUT && skb_in->dev == NULL) 240 + skb_in->dev = net->loopback_dev; 241 + 242 + icmpv6_send(skb_in, ICMPV6_DEST_UNREACH, code, 0); 243 + } 244 + EXPORT_SYMBOL_GPL(nf_send_unreach6); 245 + 211 246 MODULE_LICENSE("GPL");
+4 -2
net/netfilter/nft_reject_inet.c
··· 28 28 case NFPROTO_IPV4: 29 29 switch (priv->type) { 30 30 case NFT_REJECT_ICMP_UNREACH: 31 - nf_send_unreach(pkt->skb, priv->icmp_code); 31 + nf_send_unreach(pkt->skb, priv->icmp_code, 32 + pkt->ops->hooknum); 32 33 break; 33 34 case NFT_REJECT_TCP_RST: 34 35 nf_send_reset(pkt->skb, pkt->ops->hooknum); 35 36 break; 36 37 case NFT_REJECT_ICMPX_UNREACH: 37 38 nf_send_unreach(pkt->skb, 38 - nft_reject_icmp_code(priv->icmp_code)); 39 + nft_reject_icmp_code(priv->icmp_code), 40 + pkt->ops->hooknum); 39 41 break; 40 42 } 41 43 break;