Blob Blame History Raw
From: Florian Westphal <fw@strlen.de>
Subject: ipv6: defrag: drop non-last frags smaller than min mtu
Patch-mainline: v4.19-rc1
Git-commit: 0ed4229b08c13c84a3c301a08defdc9e7f4467e6
References: CVE-2018-5391 bsc#1103097 bsc#1141054

don't bother with pathological cases, they only waste cycles.
IPv6 requires a minimum MTU of 1280 so we should never see fragments
smaller than this (except last frag).

v3: don't use awkward "-offset + len"
v2: drop IPv4 part, which added same check w. IPV4_MIN_MTU (68).
    There were concerns that there could be even smaller frags
    generated by intermediate nodes, e.g. on radio networks.

Cc: Peter Oskolkov <posk@google.com>
Cc: Eric Dumazet <edumazet@google.com>
Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Michal Kubecek <mkubecek@suse.cz>

SLE12-SP2-LTSS: this commit was eventually reverted in upstream because it
breaks conformance with some USGv6 tests but only after frag queues were
reworked to use rbtrees rather than linear lists. As such change would not
be feasible to backport, let's weaken the sanity checks to allow fragments
of size 640 (to cover potential implementation trying to use equal sized
fragments) and short first fragment (to cover potential implementation
which would send the "remainder" as first fragment rather than last). Also
introduce net.ipv6.ip6frag_strict_short which (if set to non-zero value)
disables the length check completely (at the expense of being more
vulnerable to FragmentSmack type attacks).
---
 include/net/net_namespace.h             |  3 +++
 net/ipv6/netfilter/nf_conntrack_reasm.c |  8 ++++++++
 net/ipv6/reassembly.c                   | 18 ++++++++++++++++++
 3 files changed, 29 insertions(+)

--- a/include/net/net_namespace.h
+++ b/include/net/net_namespace.h
@@ -149,6 +149,9 @@ struct net {
 #endif
 	struct sock		*diag_nlsk;
 	atomic_t		fnhe_genid;
+#ifndef __GENKSYMS__
+	int			ip6frag_strict_short;
+#endif
 };
 
 #include <linux/seq_file_net.h>
--- a/net/ipv6/netfilter/nf_conntrack_reasm.c
+++ b/net/ipv6/netfilter/nf_conntrack_reasm.c
@@ -589,6 +589,14 @@ int nf_ct_frag6_gather(struct net *net, struct sk_buff *skb, u32 user)
 	hdr = ipv6_hdr(skb);
 	fhdr = (struct frag_hdr *)skb_transport_header(skb);
 
+	if (skb->len - skb_network_offset(skb) < IPV6_MIN_MTU / 2 &&
+	    fhdr->frag_off & htons(IP6_MF) &&
+	    fhdr->frag_off & htons(IP6_OFFSET) &&
+	    !net->ip6frag_strict_short) {
+		pr_debug("fragment too short\n");
+		return -EINVAL;
+	}
+
 	skb_orphan(skb);
 	fq = fq_find(net, fhdr->identification, user, &hdr->saddr, &hdr->daddr,
 		     skb->dev ? skb->dev->ifindex : 0, ip6_frag_ecn(hdr));
--- a/net/ipv6/reassembly.c
+++ b/net/ipv6/reassembly.c
@@ -558,6 +558,12 @@ static int ipv6_frag_rcv(struct sk_buff *skb)
 		return 1;
 	}
 
+	if (skb->len - skb_network_offset(skb) < IPV6_MIN_MTU / 2 &&
+	    fhdr->frag_off & htons(IP6_MF) &&
+	    fhdr->frag_off & htons(IP6_OFFSET) &&
+	    !net->ip6frag_strict_short)
+		goto fail_hdr;
+
 	fq = fq_find(net, fhdr->identification, &hdr->saddr, &hdr->daddr,
 		     skb->dev ? skb->dev->ifindex : 0, ip6_frag_ecn(hdr));
 	if (fq) {
@@ -616,6 +622,13 @@ static struct ctl_table ip6_frags_ns_ctl_table[] = {
 		.mode		= 0644,
 		.proc_handler	= proc_dointvec_jiffies,
 	},
+	{
+		.procname	= "ip6frag_strict_short",
+		.data		= &init_net.ip6frag_strict_short,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= proc_dointvec_jiffies,
+	},
 	{ }
 };
 
@@ -649,6 +662,7 @@ static int __net_init ip6_frags_ns_sysctl_register(struct net *net)
 		table[1].data = &net->ipv6.frags.low_thresh;
 		table[1].extra2 = &net->ipv6.frags.high_thresh;
 		table[2].data = &net->ipv6.frags.timeout;
+		table[3].data = &net->ip6frag_strict_short;
 
 		/* Don't export sysctls to unprivileged users */
 		if (net->user_ns != &init_user_ns)
@@ -717,6 +731,10 @@ static int __net_init ipv6_frags_init_net(struct net *net)
 	net->ipv6.frags.high_thresh = IPV6_FRAG_HIGH_THRESH;
 	net->ipv6.frags.low_thresh = IPV6_FRAG_LOW_THRESH;
 	net->ipv6.frags.timeout = IPV6_FRAG_TIMEOUT;
+	if (net_eq(net, &init_net))
+		net->ip6frag_strict_short = 0;
+	else
+		net->ip6frag_strict_short = init_net.ip6frag_strict_short;
 
 	inet_frags_init_net(&net->ipv6.frags);