Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ee586bbc authored by Florian Westphal's avatar Florian Westphal Committed by Pablo Neira Ayuso
Browse files

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: default avatarFlorian Westphal <fw@strlen.de>
Signed-off-by: default avatarPablo Neira Ayuso <pablo@netfilter.org>
parent b898441f
Loading
Loading
Loading
Loading
+1 −5
Original line number Diff line number Diff line
@@ -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,
+2 −9
Original line number Diff line number Diff line
@@ -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);

+9 −8
Original line number Diff line number Diff line
@@ -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;
+23 −0
Original line number Diff line number Diff line
@@ -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");
+2 −1
Original line number Diff line number Diff line
@@ -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);
Loading