netfilter: reject: don't send icmp error if csum is invalid
authorFlorian Westphal <fw@strlen.de>
Mon, 16 Feb 2015 17:54:04 +0000 (18:54 +0100)
committerPablo Neira Ayuso <pablo@netfilter.org>
Tue, 3 Mar 2015 01:10:35 +0000 (02:10 +0100)
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>
include/net/netfilter/ipv4/nf_reject.h
include/net/netfilter/ipv6/nf_reject.h
net/ipv4/netfilter/ipt_REJECT.c
net/ipv4/netfilter/nf_reject_ipv4.c
net/ipv4/netfilter/nft_reject_ipv4.c
net/ipv6/netfilter/nf_reject_ipv6.c
net/netfilter/nft_reject_inet.c

index 03e928a552290f57b050c4ce7dbe6592d02102e4..864127573c3209f7a039cfb96fd53a80ec8e08d8 100644 (file)
@@ -5,11 +5,7 @@
 #include <net/ip.h>
 #include <net/icmp.h>
 
-static inline void nf_send_unreach(struct sk_buff *skb_in, int code)
-{
-       icmp_send(skb_in, ICMP_DEST_UNREACH, code, 0);
-}
-
+void nf_send_unreach(struct sk_buff *skb_in, int code, int hook);
 void nf_send_reset(struct sk_buff *oldskb, int hook);
 
 const struct tcphdr *nf_reject_ip_tcphdr_get(struct sk_buff *oldskb,
index 23216d48abf9ddb79aa188cbf55e0a032e4f9e78..0ae445d3f21794d51a16d00f2de5f69b9305cd74 100644 (file)
@@ -3,15 +3,8 @@
 
 #include <linux/icmpv6.h>
 
-static inline void
-nf_send_unreach6(struct net *net, struct sk_buff *skb_in, unsigned char code,
-            unsigned int hooknum)
-{
-       if (hooknum == NF_INET_LOCAL_OUT && skb_in->dev == NULL)
-               skb_in->dev = net->loopback_dev;
-
-       icmpv6_send(skb_in, ICMPV6_DEST_UNREACH, code, 0);
-}
+void nf_send_unreach6(struct net *net, struct sk_buff *skb_in, unsigned char code,
+                     unsigned int hooknum);
 
 void nf_send_reset6(struct net *net, struct sk_buff *oldskb, int hook);
 
index 8f48f5517e336e61e3457379744fffd792816fcb..87907d4bd259a725cfeea6e4cbccb77be7995972 100644 (file)
@@ -34,31 +34,32 @@ static unsigned int
 reject_tg(struct sk_buff *skb, const struct xt_action_param *par)
 {
        const struct ipt_reject_info *reject = par->targinfo;
+       int hook = par->hooknum;
 
        switch (reject->with) {
        case IPT_ICMP_NET_UNREACHABLE:
-               nf_send_unreach(skb, ICMP_NET_UNREACH);
+               nf_send_unreach(skb, ICMP_NET_UNREACH, hook);
                break;
        case IPT_ICMP_HOST_UNREACHABLE:
-               nf_send_unreach(skb, ICMP_HOST_UNREACH);
+               nf_send_unreach(skb, ICMP_HOST_UNREACH, hook);
                break;
        case IPT_ICMP_PROT_UNREACHABLE:
-               nf_send_unreach(skb, ICMP_PROT_UNREACH);
+               nf_send_unreach(skb, ICMP_PROT_UNREACH, hook);
                break;
        case IPT_ICMP_PORT_UNREACHABLE:
-               nf_send_unreach(skb, ICMP_PORT_UNREACH);
+               nf_send_unreach(skb, ICMP_PORT_UNREACH, hook);
                break;
        case IPT_ICMP_NET_PROHIBITED:
-               nf_send_unreach(skb, ICMP_NET_ANO);
+               nf_send_unreach(skb, ICMP_NET_ANO, hook);
                break;
        case IPT_ICMP_HOST_PROHIBITED:
-               nf_send_unreach(skb, ICMP_HOST_ANO);
+               nf_send_unreach(skb, ICMP_HOST_ANO, hook);
                break;
        case IPT_ICMP_ADMIN_PROHIBITED:
-               nf_send_unreach(skb, ICMP_PKT_FILTERED);
+               nf_send_unreach(skb, ICMP_PKT_FILTERED, hook);
                break;
        case IPT_TCP_RESET:
-               nf_send_reset(skb, par->hooknum);
+               nf_send_reset(skb, hook);
        case IPT_ICMP_ECHOREPLY:
                /* Doesn't happen. */
                break;
index 536da7bc598ae9321224bb16c8d800571a163153..b7405eb7f1ef566570f8f5d6cd4aa20a591bb40a 100644 (file)
@@ -164,4 +164,27 @@ void nf_send_reset(struct sk_buff *oldskb, int hook)
 }
 EXPORT_SYMBOL_GPL(nf_send_reset);
 
+void nf_send_unreach(struct sk_buff *skb_in, int code, int hook)
+{
+       struct iphdr *iph = ip_hdr(skb_in);
+       u8 proto;
+
+       if (skb_in->csum_bad || iph->frag_off & htons(IP_OFFSET))
+               return;
+
+       if (skb_csum_unnecessary(skb_in)) {
+               icmp_send(skb_in, ICMP_DEST_UNREACH, code, 0);
+               return;
+       }
+
+       if (iph->protocol == IPPROTO_TCP || iph->protocol == IPPROTO_UDP)
+               proto = iph->protocol;
+       else
+               proto = 0;
+
+       if (nf_ip_checksum(skb_in, hook, ip_hdrlen(skb_in), proto) == 0)
+               icmp_send(skb_in, ICMP_DEST_UNREACH, code, 0);
+}
+EXPORT_SYMBOL_GPL(nf_send_unreach);
+
 MODULE_LICENSE("GPL");
index d729542bd1b7901c174c62996b13110517a49032..16a5d4d73d7565807490074d9f4df6f0ab90b297 100644 (file)
@@ -27,7 +27,8 @@ static void nft_reject_ipv4_eval(const struct nft_expr *expr,
 
        switch (priv->type) {
        case NFT_REJECT_ICMP_UNREACH:
-               nf_send_unreach(pkt->skb, priv->icmp_code);
+               nf_send_unreach(pkt->skb, priv->icmp_code,
+                               pkt->ops->hooknum);
                break;
        case NFT_REJECT_TCP_RST:
                nf_send_reset(pkt->skb, pkt->ops->hooknum);
index d05b36440e8be341e3b22cc172acc05962c8a37a..68e0bb4db1bf4bee9988ff410bc0942c4ca0749f 100644 (file)
@@ -208,4 +208,39 @@ void nf_send_reset6(struct net *net, struct sk_buff *oldskb, int hook)
 }
 EXPORT_SYMBOL_GPL(nf_send_reset6);
 
+static bool reject6_csum_ok(struct sk_buff *skb, int hook)
+{
+       const struct ipv6hdr *ip6h = ipv6_hdr(skb);
+       int thoff;
+       __be16 fo;
+       u8 proto;
+
+       if (skb->csum_bad)
+               return false;
+
+       if (skb_csum_unnecessary(skb))
+               return true;
+
+       proto = ip6h->nexthdr;
+       thoff = ipv6_skip_exthdr(skb, ((u8*)(ip6h+1) - skb->data), &proto, &fo);
+
+       if (thoff < 0 || thoff >= skb->len || (fo & htons(~0x7)) != 0)
+               return false;
+
+       return nf_ip6_checksum(skb, hook, thoff, proto) == 0;
+}
+
+void nf_send_unreach6(struct net *net, struct sk_buff *skb_in,
+                     unsigned char code, unsigned int hooknum)
+{
+       if (!reject6_csum_ok(skb_in, hooknum))
+               return;
+
+       if (hooknum == NF_INET_LOCAL_OUT && skb_in->dev == NULL)
+               skb_in->dev = net->loopback_dev;
+
+       icmpv6_send(skb_in, ICMPV6_DEST_UNREACH, code, 0);
+}
+EXPORT_SYMBOL_GPL(nf_send_unreach6);
+
 MODULE_LICENSE("GPL");
index 7b5f9d58680ad0ebca03c13e4a6f43037283bdc1..92877114aff4634b99c0c14d74d7043487bf31cc 100644 (file)
@@ -28,14 +28,16 @@ static void nft_reject_inet_eval(const struct nft_expr *expr,
        case NFPROTO_IPV4:
                switch (priv->type) {
                case NFT_REJECT_ICMP_UNREACH:
-                       nf_send_unreach(pkt->skb, priv->icmp_code);
+                       nf_send_unreach(pkt->skb, priv->icmp_code,
+                                       pkt->ops->hooknum);
                        break;
                case NFT_REJECT_TCP_RST:
                        nf_send_reset(pkt->skb, pkt->ops->hooknum);
                        break;
                case NFT_REJECT_ICMPX_UNREACH:
                        nf_send_unreach(pkt->skb,
-                                       nft_reject_icmp_code(priv->icmp_code));
+                                       nft_reject_icmp_code(priv->icmp_code),
+                                       pkt->ops->hooknum);
                        break;
                }
                break;