netfilter-devel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH nf v3] netfilter: x_tables: perform more sanity tests on rule set
@ 2016-03-22 17:02 Florian Westphal
  2016-03-22 17:02 ` [PATCH 1/5] netfilter: x_tables: validate e->target_offset early Florian Westphal
                   ` (4 more replies)
  0 siblings, 5 replies; 13+ messages in thread
From: Florian Westphal @ 2016-03-22 17:02 UTC (permalink / raw)
  To: netfilter-devel

3rd iteration.

In addition to the problem reported by Ben Hawkes this also adds
a few checks to better validate ->next_offset and the target.

I checked that ip(6)tables-restore still works w. simple rulesets.

The reproducer doesn't work anymore w. patch #4 applied.


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH 1/5] netfilter: x_tables: validate e->target_offset early
  2016-03-22 17:02 [PATCH nf v3] netfilter: x_tables: perform more sanity tests on rule set Florian Westphal
@ 2016-03-22 17:02 ` Florian Westphal
  2016-03-24 20:17   ` Pablo Neira Ayuso
  2016-03-22 17:02 ` [PATCH 2/5] netfilter: x_tables: make sure e->next_offset covers remaining blob size Florian Westphal
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 13+ messages in thread
From: Florian Westphal @ 2016-03-22 17:02 UTC (permalink / raw)
  To: netfilter-devel; +Cc: Florian Westphal

We should check that e->target_offset is sane before
mark_source_chains gets called since it will fetch the target entry
for loop detection.

Signed-off-by: Florian Westphal <fw@strlen.de>
---
 net/ipv4/netfilter/arp_tables.c | 17 ++++++++---------
 net/ipv4/netfilter/ip_tables.c  | 17 ++++++++---------
 net/ipv6/netfilter/ip6_tables.c | 17 ++++++++---------
 3 files changed, 24 insertions(+), 27 deletions(-)

diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c
index bf08192..830bbe8 100644
--- a/net/ipv4/netfilter/arp_tables.c
+++ b/net/ipv4/netfilter/arp_tables.c
@@ -474,14 +474,12 @@ next:
 	return 1;
 }
 
-static inline int check_entry(const struct arpt_entry *e, const char *name)
+static inline int check_entry(const struct arpt_entry *e)
 {
 	const struct xt_entry_target *t;
 
-	if (!arp_checkentry(&e->arp)) {
-		duprintf("arp_tables: arp check failed %p %s.\n", e, name);
+	if (!arp_checkentry(&e->arp))
 		return -EINVAL;
-	}
 
 	if (e->target_offset + sizeof(struct xt_entry_target) > e->next_offset)
 		return -EINVAL;
@@ -522,10 +520,6 @@ find_check_entry(struct arpt_entry *e, const char *name, unsigned int size)
 	struct xt_target *target;
 	int ret;
 
-	ret = check_entry(e, name);
-	if (ret)
-		return ret;
-
 	e->counters.pcnt = xt_percpu_counter_alloc();
 	if (IS_ERR_VALUE(e->counters.pcnt))
 		return -ENOMEM;
@@ -576,6 +570,7 @@ static inline int check_entry_size_and_hooks(struct arpt_entry *e,
 					     unsigned int valid_hooks)
 {
 	unsigned int h;
+	int err;
 
 	if ((unsigned long)e % __alignof__(struct arpt_entry) != 0 ||
 	    (unsigned char *)e + sizeof(struct arpt_entry) >= limit) {
@@ -590,6 +585,10 @@ static inline int check_entry_size_and_hooks(struct arpt_entry *e,
 		return -EINVAL;
 	}
 
+	err = check_entry(e);
+	if (err)
+		return err;
+
 	/* Check hooks & underflows */
 	for (h = 0; h < NF_ARP_NUMHOOKS; h++) {
 		if (!(valid_hooks & (1 << h)))
@@ -1246,7 +1245,7 @@ check_compat_entry_size_and_hooks(struct compat_arpt_entry *e,
 	}
 
 	/* For purposes of check_entry casting the compat entry is fine */
-	ret = check_entry((struct arpt_entry *)e, name);
+	ret = check_entry((struct arpt_entry *)e);
 	if (ret)
 		return ret;
 
diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c
index e53f8d6..1d72a3c 100644
--- a/net/ipv4/netfilter/ip_tables.c
+++ b/net/ipv4/netfilter/ip_tables.c
@@ -569,14 +569,12 @@ static void cleanup_match(struct xt_entry_match *m, struct net *net)
 }
 
 static int
-check_entry(const struct ipt_entry *e, const char *name)
+check_entry(const struct ipt_entry *e)
 {
 	const struct xt_entry_target *t;
 
-	if (!ip_checkentry(&e->ip)) {
-		duprintf("ip check failed %p %s.\n", e, name);
+	if (!ip_checkentry(&e->ip))
 		return -EINVAL;
-	}
 
 	if (e->target_offset + sizeof(struct xt_entry_target) >
 	    e->next_offset)
@@ -666,10 +664,6 @@ find_check_entry(struct ipt_entry *e, struct net *net, const char *name,
 	struct xt_mtchk_param mtpar;
 	struct xt_entry_match *ematch;
 
-	ret = check_entry(e, name);
-	if (ret)
-		return ret;
-
 	e->counters.pcnt = xt_percpu_counter_alloc();
 	if (IS_ERR_VALUE(e->counters.pcnt))
 		return -ENOMEM;
@@ -741,6 +735,7 @@ check_entry_size_and_hooks(struct ipt_entry *e,
 			   unsigned int valid_hooks)
 {
 	unsigned int h;
+	int err;
 
 	if ((unsigned long)e % __alignof__(struct ipt_entry) != 0 ||
 	    (unsigned char *)e + sizeof(struct ipt_entry) >= limit) {
@@ -755,6 +750,10 @@ check_entry_size_and_hooks(struct ipt_entry *e,
 		return -EINVAL;
 	}
 
+	err = check_entry(e);
+	if (err)
+		return err;
+
 	/* Check hooks & underflows */
 	for (h = 0; h < NF_INET_NUMHOOKS; h++) {
 		if (!(valid_hooks & (1 << h)))
@@ -1506,7 +1505,7 @@ check_compat_entry_size_and_hooks(struct compat_ipt_entry *e,
 	}
 
 	/* For purposes of check_entry casting the compat entry is fine */
-	ret = check_entry((struct ipt_entry *)e, name);
+	ret = check_entry((struct ipt_entry *)e);
 	if (ret)
 		return ret;
 
diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_tables.c
index 84f9baf..26a5ad1 100644
--- a/net/ipv6/netfilter/ip6_tables.c
+++ b/net/ipv6/netfilter/ip6_tables.c
@@ -581,14 +581,12 @@ static void cleanup_match(struct xt_entry_match *m, struct net *net)
 }
 
 static int
-check_entry(const struct ip6t_entry *e, const char *name)
+check_entry(const struct ip6t_entry *e)
 {
 	const struct xt_entry_target *t;
 
-	if (!ip6_checkentry(&e->ipv6)) {
-		duprintf("ip_tables: ip check failed %p %s.\n", e, name);
+	if (!ip6_checkentry(&e->ipv6))
 		return -EINVAL;
-	}
 
 	if (e->target_offset + sizeof(struct xt_entry_target) >
 	    e->next_offset)
@@ -679,10 +677,6 @@ find_check_entry(struct ip6t_entry *e, struct net *net, const char *name,
 	struct xt_mtchk_param mtpar;
 	struct xt_entry_match *ematch;
 
-	ret = check_entry(e, name);
-	if (ret)
-		return ret;
-
 	e->counters.pcnt = xt_percpu_counter_alloc();
 	if (IS_ERR_VALUE(e->counters.pcnt))
 		return -ENOMEM;
@@ -753,6 +747,7 @@ check_entry_size_and_hooks(struct ip6t_entry *e,
 			   unsigned int valid_hooks)
 {
 	unsigned int h;
+	int err;
 
 	if ((unsigned long)e % __alignof__(struct ip6t_entry) != 0 ||
 	    (unsigned char *)e + sizeof(struct ip6t_entry) >= limit) {
@@ -767,6 +762,10 @@ check_entry_size_and_hooks(struct ip6t_entry *e,
 		return -EINVAL;
 	}
 
+	err = check_entry(e);
+	if (err)
+		return err;
+
 	/* Check hooks & underflows */
 	for (h = 0; h < NF_INET_NUMHOOKS; h++) {
 		if (!(valid_hooks & (1 << h)))
@@ -1518,7 +1517,7 @@ check_compat_entry_size_and_hooks(struct compat_ip6t_entry *e,
 	}
 
 	/* For purposes of check_entry casting the compat entry is fine */
-	ret = check_entry((struct ip6t_entry *)e, name);
+	ret = check_entry((struct ip6t_entry *)e);
 	if (ret)
 		return ret;
 
-- 
2.4.10


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 2/5] netfilter: x_tables: make sure e->next_offset covers remaining blob size
  2016-03-22 17:02 [PATCH nf v3] netfilter: x_tables: perform more sanity tests on rule set Florian Westphal
  2016-03-22 17:02 ` [PATCH 1/5] netfilter: x_tables: validate e->target_offset early Florian Westphal
@ 2016-03-22 17:02 ` Florian Westphal
  2016-03-24 20:18   ` Pablo Neira Ayuso
  2016-03-22 17:02 ` [PATCH 3/5] netfilter: x_tables: add and use xt_check_entry_target Florian Westphal
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 13+ messages in thread
From: Florian Westphal @ 2016-03-22 17:02 UTC (permalink / raw)
  To: netfilter-devel; +Cc: Florian Westphal

Otherwise this function may read data beyond the ruleset blob.

Signed-off-by: Florian Westphal <fw@strlen.de>
---
 net/ipv4/netfilter/arp_tables.c | 6 ++++--
 net/ipv4/netfilter/ip_tables.c  | 6 ++++--
 net/ipv6/netfilter/ip6_tables.c | 6 ++++--
 3 files changed, 12 insertions(+), 6 deletions(-)

diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c
index 830bbe8..51d4fe5 100644
--- a/net/ipv4/netfilter/arp_tables.c
+++ b/net/ipv4/netfilter/arp_tables.c
@@ -573,7 +573,8 @@ static inline int check_entry_size_and_hooks(struct arpt_entry *e,
 	int err;
 
 	if ((unsigned long)e % __alignof__(struct arpt_entry) != 0 ||
-	    (unsigned char *)e + sizeof(struct arpt_entry) >= limit) {
+	    (unsigned char *)e + sizeof(struct arpt_entry) >= limit ||
+	    (unsigned char *)e + e->next_offset > limit) {
 		duprintf("Bad offset %p\n", e);
 		return -EINVAL;
 	}
@@ -1232,7 +1233,8 @@ check_compat_entry_size_and_hooks(struct compat_arpt_entry *e,
 
 	duprintf("check_compat_entry_size_and_hooks %p\n", e);
 	if ((unsigned long)e % __alignof__(struct compat_arpt_entry) != 0 ||
-	    (unsigned char *)e + sizeof(struct compat_arpt_entry) >= limit) {
+	    (unsigned char *)e + sizeof(struct compat_arpt_entry) >= limit ||
+	    (unsigned char *)e + e->next_offset > limit) {
 		duprintf("Bad offset %p, limit = %p\n", e, limit);
 		return -EINVAL;
 	}
diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c
index 1d72a3c..fb7694e 100644
--- a/net/ipv4/netfilter/ip_tables.c
+++ b/net/ipv4/netfilter/ip_tables.c
@@ -738,7 +738,8 @@ check_entry_size_and_hooks(struct ipt_entry *e,
 	int err;
 
 	if ((unsigned long)e % __alignof__(struct ipt_entry) != 0 ||
-	    (unsigned char *)e + sizeof(struct ipt_entry) >= limit) {
+	    (unsigned char *)e + sizeof(struct ipt_entry) >= limit ||
+	    (unsigned char *)e + e->next_offset > limit) {
 		duprintf("Bad offset %p\n", e);
 		return -EINVAL;
 	}
@@ -1492,7 +1493,8 @@ check_compat_entry_size_and_hooks(struct compat_ipt_entry *e,
 
 	duprintf("check_compat_entry_size_and_hooks %p\n", e);
 	if ((unsigned long)e % __alignof__(struct compat_ipt_entry) != 0 ||
-	    (unsigned char *)e + sizeof(struct compat_ipt_entry) >= limit) {
+	    (unsigned char *)e + sizeof(struct compat_ipt_entry) >= limit ||
+	    (unsigned char *)e + e->next_offset > limit) {
 		duprintf("Bad offset %p, limit = %p\n", e, limit);
 		return -EINVAL;
 	}
diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_tables.c
index 26a5ad1..b248528f 100644
--- a/net/ipv6/netfilter/ip6_tables.c
+++ b/net/ipv6/netfilter/ip6_tables.c
@@ -750,7 +750,8 @@ check_entry_size_and_hooks(struct ip6t_entry *e,
 	int err;
 
 	if ((unsigned long)e % __alignof__(struct ip6t_entry) != 0 ||
-	    (unsigned char *)e + sizeof(struct ip6t_entry) >= limit) {
+	    (unsigned char *)e + sizeof(struct ip6t_entry) >= limit ||
+	    (unsigned char *)e + e->next_offset > limit) {
 		duprintf("Bad offset %p\n", e);
 		return -EINVAL;
 	}
@@ -1504,7 +1505,8 @@ check_compat_entry_size_and_hooks(struct compat_ip6t_entry *e,
 
 	duprintf("check_compat_entry_size_and_hooks %p\n", e);
 	if ((unsigned long)e % __alignof__(struct compat_ip6t_entry) != 0 ||
-	    (unsigned char *)e + sizeof(struct compat_ip6t_entry) >= limit) {
+	    (unsigned char *)e + sizeof(struct compat_ip6t_entry) >= limit ||
+	    (unsigned char *)e + e->next_offset > limit) {
 		duprintf("Bad offset %p, limit = %p\n", e, limit);
 		return -EINVAL;
 	}
-- 
2.4.10


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 3/5] netfilter: x_tables: add and use xt_check_entry_target
  2016-03-22 17:02 [PATCH nf v3] netfilter: x_tables: perform more sanity tests on rule set Florian Westphal
  2016-03-22 17:02 ` [PATCH 1/5] netfilter: x_tables: validate e->target_offset early Florian Westphal
  2016-03-22 17:02 ` [PATCH 2/5] netfilter: x_tables: make sure e->next_offset covers remaining blob size Florian Westphal
@ 2016-03-22 17:02 ` Florian Westphal
  2016-03-24 20:18   ` Pablo Neira Ayuso
  2016-03-25 11:45   ` Florian Westphal
  2016-03-22 17:02 ` [PATCH 4/5] netfilter: x_tables: fix unconditional helper Florian Westphal
  2016-03-22 17:02 ` [PATCH 5/5] netfilter: x_tables: don't move to non-existant next rule Florian Westphal
  4 siblings, 2 replies; 13+ messages in thread
From: Florian Westphal @ 2016-03-22 17:02 UTC (permalink / raw)
  To: netfilter-devel; +Cc: Florian Westphal

We have targets and standard targets -- the latter carries a verdict, so we
must check the standard size as well here -- later functions access t->verdict
which otherwise can point after the blob end.

Spotted with UBSAN.

Signed-off-by: Florian Westphal <fw@strlen.de>
---
 include/linux/netfilter/x_tables.h |  3 +++
 net/ipv4/netfilter/arp_tables.c    | 11 +----------
 net/ipv4/netfilter/ip_tables.c     | 12 +-----------
 net/ipv6/netfilter/ip6_tables.c    | 12 +-----------
 net/netfilter/x_tables.c           | 22 ++++++++++++++++++++++
 5 files changed, 28 insertions(+), 32 deletions(-)

diff --git a/include/linux/netfilter/x_tables.h b/include/linux/netfilter/x_tables.h
index 80a305b..9e7153e 100644
--- a/include/linux/netfilter/x_tables.h
+++ b/include/linux/netfilter/x_tables.h
@@ -413,6 +413,9 @@ xt_get_per_cpu_counter(struct xt_counters *cnt, unsigned int cpu)
 
 struct nf_hook_ops *xt_hook_ops_alloc(const struct xt_table *, nf_hookfn *);
 
+int xt_check_entry_target(const void *base, unsigned int target_offset,
+			  unsigned int next_offset);
+
 #ifdef CONFIG_COMPAT
 #include <net/compat.h>
 
diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c
index 51d4fe5..59a9610 100644
--- a/net/ipv4/netfilter/arp_tables.c
+++ b/net/ipv4/netfilter/arp_tables.c
@@ -476,19 +476,10 @@ next:
 
 static inline int check_entry(const struct arpt_entry *e)
 {
-	const struct xt_entry_target *t;
-
 	if (!arp_checkentry(&e->arp))
 		return -EINVAL;
 
-	if (e->target_offset + sizeof(struct xt_entry_target) > e->next_offset)
-		return -EINVAL;
-
-	t = arpt_get_target_c(e);
-	if (e->target_offset + t->u.target_size > e->next_offset)
-		return -EINVAL;
-
-	return 0;
+	return xt_check_entry_target(e, e->target_offset, e->next_offset);
 }
 
 static inline int check_target(struct arpt_entry *e, const char *name)
diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c
index fb7694e..1acc6ee 100644
--- a/net/ipv4/netfilter/ip_tables.c
+++ b/net/ipv4/netfilter/ip_tables.c
@@ -571,20 +571,10 @@ static void cleanup_match(struct xt_entry_match *m, struct net *net)
 static int
 check_entry(const struct ipt_entry *e)
 {
-	const struct xt_entry_target *t;
-
 	if (!ip_checkentry(&e->ip))
 		return -EINVAL;
 
-	if (e->target_offset + sizeof(struct xt_entry_target) >
-	    e->next_offset)
-		return -EINVAL;
-
-	t = ipt_get_target_c(e);
-	if (e->target_offset + t->u.target_size > e->next_offset)
-		return -EINVAL;
-
-	return 0;
+	return xt_check_entry_target(e, e->target_offset, e->next_offset);
 }
 
 static int
diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_tables.c
index b248528f..29e2113 100644
--- a/net/ipv6/netfilter/ip6_tables.c
+++ b/net/ipv6/netfilter/ip6_tables.c
@@ -583,20 +583,10 @@ static void cleanup_match(struct xt_entry_match *m, struct net *net)
 static int
 check_entry(const struct ip6t_entry *e)
 {
-	const struct xt_entry_target *t;
-
 	if (!ip6_checkentry(&e->ipv6))
 		return -EINVAL;
 
-	if (e->target_offset + sizeof(struct xt_entry_target) >
-	    e->next_offset)
-		return -EINVAL;
-
-	t = ip6t_get_target_c(e);
-	if (e->target_offset + t->u.target_size > e->next_offset)
-		return -EINVAL;
-
-	return 0;
+	return xt_check_entry_target(e, e->target_offset, e->next_offset);
 }
 
 static int check_match(struct xt_entry_match *m, struct xt_mtchk_param *par)
diff --git a/net/netfilter/x_tables.c b/net/netfilter/x_tables.c
index 582c9cf..c10f9b3 100644
--- a/net/netfilter/x_tables.c
+++ b/net/netfilter/x_tables.c
@@ -541,6 +541,28 @@ int xt_compat_match_to_user(const struct xt_entry_match *m,
 EXPORT_SYMBOL_GPL(xt_compat_match_to_user);
 #endif /* CONFIG_COMPAT */
 
+/* check that arp/ip/ip6t_entry target_offset is sane */
+int xt_check_entry_target(const void *base, unsigned int target_offset,
+			  unsigned int next_offset)
+{
+	const struct xt_entry_target *t;
+	const char *e = base;
+
+	if (target_offset + sizeof(struct xt_entry_target) > next_offset)
+		return -EINVAL;
+
+	t = (void *) (e + target_offset);
+	if (target_offset + t->u.target_size > next_offset)
+		return -EINVAL;
+
+	if (strcmp(t->u.user.name, XT_STANDARD_TARGET) == 0 &&
+	    target_offset + sizeof(struct xt_standard_target) > next_offset)
+		return -EINVAL;
+
+	return 0;
+}
+EXPORT_SYMBOL(xt_check_entry_target);
+
 int xt_check_target(struct xt_tgchk_param *par,
 		    unsigned int size, u_int8_t proto, bool inv_proto)
 {
-- 
2.4.10


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 4/5] netfilter: x_tables: fix unconditional helper
  2016-03-22 17:02 [PATCH nf v3] netfilter: x_tables: perform more sanity tests on rule set Florian Westphal
                   ` (2 preceding siblings ...)
  2016-03-22 17:02 ` [PATCH 3/5] netfilter: x_tables: add and use xt_check_entry_target Florian Westphal
@ 2016-03-22 17:02 ` Florian Westphal
  2016-03-24 20:18   ` Pablo Neira Ayuso
  2016-03-22 17:02 ` [PATCH 5/5] netfilter: x_tables: don't move to non-existant next rule Florian Westphal
  4 siblings, 1 reply; 13+ messages in thread
From: Florian Westphal @ 2016-03-22 17:02 UTC (permalink / raw)
  To: netfilter-devel; +Cc: Florian Westphal

Ben Hawkes says:

 In the mark_source_chains function (net/ipv4/netfilter/ip_tables.c) it
 is possible for a user-supplied ipt_entry structure to have a large
 next_offset field. This field is not bounds checked prior to writing a
 counter value at the supplied offset.

Problem is that mark_source_chains should not have been called --
the rule doesn't have a next entry, so its supposed to return
an absolute verdict of either ACCEPT or DROP.

However, the function conditional() doesn't work as the name implies.
It only checks that the rule is using wildcard address matching.

However, an unconditional rule must also not be using any matches
(no -m args).

The underflow validator only checked the addresses, therefore
passing the 'unconditional absolute verdict' test, while
mark_source_chains also tested for presence of matches, and thus
proceeeded to the next (not-existent) rule.

Unify this so that all the callers have same idea of 'unconditional rule'.

Reported-by: Ben Hawkes <hawkes@google.com>
Signed-off-by: Florian Westphal <fw@strlen.de>
---
 net/ipv4/netfilter/arp_tables.c | 18 +++++++++---------
 net/ipv4/netfilter/ip_tables.c  | 23 +++++++++++------------
 net/ipv6/netfilter/ip6_tables.c | 23 +++++++++++------------
 3 files changed, 31 insertions(+), 33 deletions(-)

diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c
index 59a9610..a2002ff 100644
--- a/net/ipv4/netfilter/arp_tables.c
+++ b/net/ipv4/netfilter/arp_tables.c
@@ -359,11 +359,12 @@ unsigned int arpt_do_table(struct sk_buff *skb,
 }
 
 /* All zeroes == unconditional rule. */
-static inline bool unconditional(const struct arpt_arp *arp)
+static inline bool unconditional(const struct arpt_entry *e)
 {
 	static const struct arpt_arp uncond;
 
-	return memcmp(arp, &uncond, sizeof(uncond)) == 0;
+	return e->target_offset == sizeof(struct arpt_entry) &&
+	       memcmp(&e->arp, &uncond, sizeof(uncond)) == 0;
 }
 
 /* Figures out from what hook each rule can be called: returns 0 if
@@ -402,11 +403,10 @@ static int mark_source_chains(const struct xt_table_info *newinfo,
 				|= ((1 << hook) | (1 << NF_ARP_NUMHOOKS));
 
 			/* Unconditional return/END. */
-			if ((e->target_offset == sizeof(struct arpt_entry) &&
+			if ((unconditional(e) &&
 			     (strcmp(t->target.u.user.name,
 				     XT_STANDARD_TARGET) == 0) &&
-			     t->verdict < 0 && unconditional(&e->arp)) ||
-			    visited) {
+			     t->verdict < 0) || visited) {
 				unsigned int oldpos, size;
 
 				if ((strcmp(t->target.u.user.name,
@@ -542,7 +542,7 @@ static bool check_underflow(const struct arpt_entry *e)
 	const struct xt_entry_target *t;
 	unsigned int verdict;
 
-	if (!unconditional(&e->arp))
+	if (!unconditional(e))
 		return false;
 	t = arpt_get_target_c(e);
 	if (strcmp(t->u.user.name, XT_STANDARD_TARGET) != 0)
@@ -589,9 +589,9 @@ static inline int check_entry_size_and_hooks(struct arpt_entry *e,
 			newinfo->hook_entry[h] = hook_entries[h];
 		if ((unsigned char *)e - base == underflows[h]) {
 			if (!check_underflow(e)) {
-				pr_err("Underflows must be unconditional and "
-				       "use the STANDARD target with "
-				       "ACCEPT/DROP\n");
+				pr_debug("Underflows must be unconditional and "
+					 "use the STANDARD target with "
+					 "ACCEPT/DROP\n");
 				return -EINVAL;
 			}
 			newinfo->underflow[h] = underflows[h];
diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c
index 1acc6ee..45b1d97 100644
--- a/net/ipv4/netfilter/ip_tables.c
+++ b/net/ipv4/netfilter/ip_tables.c
@@ -168,11 +168,12 @@ get_entry(const void *base, unsigned int offset)
 
 /* All zeroes == unconditional rule. */
 /* Mildly perf critical (only if packet tracing is on) */
-static inline bool unconditional(const struct ipt_ip *ip)
+static inline bool unconditional(const struct ipt_entry *e)
 {
 	static const struct ipt_ip uncond;
 
-	return memcmp(ip, &uncond, sizeof(uncond)) == 0;
+	return e->target_offset == sizeof(struct ipt_entry) &&
+	       memcmp(&e->ip, &uncond, sizeof(uncond)) == 0;
 #undef FWINV
 }
 
@@ -229,11 +230,10 @@ get_chainname_rulenum(const struct ipt_entry *s, const struct ipt_entry *e,
 	} else if (s == e) {
 		(*rulenum)++;
 
-		if (s->target_offset == sizeof(struct ipt_entry) &&
+		if (unconditional(s) &&
 		    strcmp(t->target.u.kernel.target->name,
 			   XT_STANDARD_TARGET) == 0 &&
-		   t->verdict < 0 &&
-		   unconditional(&s->ip)) {
+		   t->verdict < 0) {
 			/* Tail of chains: STANDARD target (return/policy) */
 			*comment = *chainname == hookname
 				? comments[NF_IP_TRACE_COMMENT_POLICY]
@@ -476,11 +476,10 @@ mark_source_chains(const struct xt_table_info *newinfo,
 			e->comefrom |= ((1 << hook) | (1 << NF_INET_NUMHOOKS));
 
 			/* Unconditional return/END. */
-			if ((e->target_offset == sizeof(struct ipt_entry) &&
+			if ((unconditional(e) &&
 			     (strcmp(t->target.u.user.name,
 				     XT_STANDARD_TARGET) == 0) &&
-			     t->verdict < 0 && unconditional(&e->ip)) ||
-			    visited) {
+			     t->verdict < 0) || visited) {
 				unsigned int oldpos, size;
 
 				if ((strcmp(t->target.u.user.name,
@@ -705,7 +704,7 @@ static bool check_underflow(const struct ipt_entry *e)
 	const struct xt_entry_target *t;
 	unsigned int verdict;
 
-	if (!unconditional(&e->ip))
+	if (!unconditional(e))
 		return false;
 	t = ipt_get_target_c(e);
 	if (strcmp(t->u.user.name, XT_STANDARD_TARGET) != 0)
@@ -753,9 +752,9 @@ check_entry_size_and_hooks(struct ipt_entry *e,
 			newinfo->hook_entry[h] = hook_entries[h];
 		if ((unsigned char *)e - base == underflows[h]) {
 			if (!check_underflow(e)) {
-				pr_err("Underflows must be unconditional and "
-				       "use the STANDARD target with "
-				       "ACCEPT/DROP\n");
+				pr_debug("Underflows must be unconditional and "
+					 "use the STANDARD target with "
+					 "ACCEPT/DROP\n");
 				return -EINVAL;
 			}
 			newinfo->underflow[h] = underflows[h];
diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_tables.c
index 29e2113..85c0942 100644
--- a/net/ipv6/netfilter/ip6_tables.c
+++ b/net/ipv6/netfilter/ip6_tables.c
@@ -198,11 +198,12 @@ get_entry(const void *base, unsigned int offset)
 
 /* All zeroes == unconditional rule. */
 /* Mildly perf critical (only if packet tracing is on) */
-static inline bool unconditional(const struct ip6t_ip6 *ipv6)
+static inline bool unconditional(const struct ip6t_entry *e)
 {
 	static const struct ip6t_ip6 uncond;
 
-	return memcmp(ipv6, &uncond, sizeof(uncond)) == 0;
+	return e->target_offset == sizeof(struct ip6t_entry) &&
+	       memcmp(&e->ipv6, &uncond, sizeof(uncond)) == 0;
 }
 
 static inline const struct xt_entry_target *
@@ -258,11 +259,10 @@ get_chainname_rulenum(const struct ip6t_entry *s, const struct ip6t_entry *e,
 	} else if (s == e) {
 		(*rulenum)++;
 
-		if (s->target_offset == sizeof(struct ip6t_entry) &&
+		if (unconditional(s) &&
 		    strcmp(t->target.u.kernel.target->name,
 			   XT_STANDARD_TARGET) == 0 &&
-		    t->verdict < 0 &&
-		    unconditional(&s->ipv6)) {
+		    t->verdict < 0) {
 			/* Tail of chains: STANDARD target (return/policy) */
 			*comment = *chainname == hookname
 				? comments[NF_IP6_TRACE_COMMENT_POLICY]
@@ -488,11 +488,10 @@ mark_source_chains(const struct xt_table_info *newinfo,
 			e->comefrom |= ((1 << hook) | (1 << NF_INET_NUMHOOKS));
 
 			/* Unconditional return/END. */
-			if ((e->target_offset == sizeof(struct ip6t_entry) &&
+			if ((unconditional(e) &&
 			     (strcmp(t->target.u.user.name,
 				     XT_STANDARD_TARGET) == 0) &&
-			     t->verdict < 0 &&
-			     unconditional(&e->ipv6)) || visited) {
+			     t->verdict < 0) || visited) {
 				unsigned int oldpos, size;
 
 				if ((strcmp(t->target.u.user.name,
@@ -717,7 +716,7 @@ static bool check_underflow(const struct ip6t_entry *e)
 	const struct xt_entry_target *t;
 	unsigned int verdict;
 
-	if (!unconditional(&e->ipv6))
+	if (!unconditional(e))
 		return false;
 	t = ip6t_get_target_c(e);
 	if (strcmp(t->u.user.name, XT_STANDARD_TARGET) != 0)
@@ -765,9 +764,9 @@ check_entry_size_and_hooks(struct ip6t_entry *e,
 			newinfo->hook_entry[h] = hook_entries[h];
 		if ((unsigned char *)e - base == underflows[h]) {
 			if (!check_underflow(e)) {
-				pr_err("Underflows must be unconditional and "
-				       "use the STANDARD target with "
-				       "ACCEPT/DROP\n");
+				pr_debug("Underflows must be unconditional and "
+					 "use the STANDARD target with "
+					 "ACCEPT/DROP\n");
 				return -EINVAL;
 			}
 			newinfo->underflow[h] = underflows[h];
-- 
2.4.10


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 5/5] netfilter: x_tables: don't move to non-existant next rule
  2016-03-22 17:02 [PATCH nf v3] netfilter: x_tables: perform more sanity tests on rule set Florian Westphal
                   ` (3 preceding siblings ...)
  2016-03-22 17:02 ` [PATCH 4/5] netfilter: x_tables: fix unconditional helper Florian Westphal
@ 2016-03-22 17:02 ` Florian Westphal
  2016-03-22 17:22   ` Pablo Neira Ayuso
  4 siblings, 1 reply; 13+ messages in thread
From: Florian Westphal @ 2016-03-22 17:02 UTC (permalink / raw)
  To: netfilter-devel; +Cc: Florian Westphal

Ben Hawkes reported an out-of-bounds write in mark_source_chains().
This was caused by improper underflow check -- we should have bailed
earlier.

The underflow check has been fixed in the preceeding change
("netfilter: x_tables: fix unconditional helper").

Just to be safe also add checks to mark_source_chains() in case we have other
bugs that would cause such a condition.

Signed-off-by: Florian Westphal <fw@strlen.de>
---
 net/ipv4/netfilter/arp_tables.c | 8 +++++---
 net/ipv4/netfilter/ip_tables.c  | 4 ++++
 net/ipv6/netfilter/ip6_tables.c | 4 ++++
 3 files changed, 13 insertions(+), 3 deletions(-)

diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c
index a2002ff..13266f4 100644
--- a/net/ipv4/netfilter/arp_tables.c
+++ b/net/ipv4/netfilter/arp_tables.c
@@ -439,6 +439,8 @@ static int mark_source_chains(const struct xt_table_info *newinfo,
 				size = e->next_offset;
 				e = (struct arpt_entry *)
 					(entry0 + pos + size);
+				if (pos + size >= newinfo->size)
+					return 0;
 				e->counters.pcnt = pos;
 				pos += size;
 			} else {
@@ -461,6 +463,8 @@ static int mark_source_chains(const struct xt_table_info *newinfo,
 				} else {
 					/* ... this is a fallthru */
 					newpos = pos + e->next_offset;
+					if (newpos >= newinfo->size)
+						return 0;
 				}
 				e = (struct arpt_entry *)
 					(entry0 + newpos);
@@ -682,10 +686,8 @@ static int translate_table(struct xt_table_info *newinfo, void *entry0,
 		}
 	}
 
-	if (!mark_source_chains(newinfo, repl->valid_hooks, entry0)) {
-		duprintf("Looping hook\n");
+	if (!mark_source_chains(newinfo, repl->valid_hooks, entry0))
 		return -ELOOP;
-	}
 
 	/* Finally, each sanity check must pass */
 	i = 0;
diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c
index 45b1d97..c4836f0 100644
--- a/net/ipv4/netfilter/ip_tables.c
+++ b/net/ipv4/netfilter/ip_tables.c
@@ -520,6 +520,8 @@ mark_source_chains(const struct xt_table_info *newinfo,
 				size = e->next_offset;
 				e = (struct ipt_entry *)
 					(entry0 + pos + size);
+				if (WARN_ON(pos + size >= newinfo->size))
+					return 0;
 				e->counters.pcnt = pos;
 				pos += size;
 			} else {
@@ -541,6 +543,8 @@ mark_source_chains(const struct xt_table_info *newinfo,
 				} else {
 					/* ... this is a fallthru */
 					newpos = pos + e->next_offset;
+					if (WARN_ON(newpos >= newinfo->size))
+						return 0;
 				}
 				e = (struct ipt_entry *)
 					(entry0 + newpos);
diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_tables.c
index 85c0942..ab7cdbf 100644
--- a/net/ipv6/netfilter/ip6_tables.c
+++ b/net/ipv6/netfilter/ip6_tables.c
@@ -532,6 +532,8 @@ mark_source_chains(const struct xt_table_info *newinfo,
 				size = e->next_offset;
 				e = (struct ip6t_entry *)
 					(entry0 + pos + size);
+				if (pos + size >= newinfo->size)
+					return 0;
 				e->counters.pcnt = pos;
 				pos += size;
 			} else {
@@ -553,6 +555,8 @@ mark_source_chains(const struct xt_table_info *newinfo,
 				} else {
 					/* ... this is a fallthru */
 					newpos = pos + e->next_offset;
+					if (newpos >= newinfo->size)
+						return 0;
 				}
 				e = (struct ip6t_entry *)
 					(entry0 + newpos);
-- 
2.4.10


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH 5/5] netfilter: x_tables: don't move to non-existant next rule
  2016-03-22 17:02 ` [PATCH 5/5] netfilter: x_tables: don't move to non-existant next rule Florian Westphal
@ 2016-03-22 17:22   ` Pablo Neira Ayuso
  0 siblings, 0 replies; 13+ messages in thread
From: Pablo Neira Ayuso @ 2016-03-22 17:22 UTC (permalink / raw)
  To: Florian Westphal; +Cc: netfilter-devel

On Tue, Mar 22, 2016 at 06:02:53PM +0100, Florian Westphal wrote:
> Ben Hawkes reported an out-of-bounds write in mark_source_chains().
> This was caused by improper underflow check -- we should have bailed
> earlier.
> 
> The underflow check has been fixed in the preceeding change
> ("netfilter: x_tables: fix unconditional helper").
> 
> Just to be safe also add checks to mark_source_chains() in case we have other
> bugs that would cause such a condition.
> 
> Signed-off-by: Florian Westphal <fw@strlen.de>
> ---
>  net/ipv4/netfilter/arp_tables.c | 8 +++++---
>  net/ipv4/netfilter/ip_tables.c  | 4 ++++
>  net/ipv6/netfilter/ip6_tables.c | 4 ++++
>  3 files changed, 13 insertions(+), 3 deletions(-)
> 
> diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c
> index a2002ff..13266f4 100644
> --- a/net/ipv4/netfilter/arp_tables.c
> +++ b/net/ipv4/netfilter/arp_tables.c
> @@ -439,6 +439,8 @@ static int mark_source_chains(const struct xt_table_info *newinfo,
>  				size = e->next_offset;
>  				e = (struct arpt_entry *)
>  					(entry0 + pos + size);
> +				if (pos + size >= newinfo->size)
> +					return 0;
>  				e->counters.pcnt = pos;
>  				pos += size;
>  			} else {
> @@ -461,6 +463,8 @@ static int mark_source_chains(const struct xt_table_info *newinfo,
>  				} else {
>  					/* ... this is a fallthru */
>  					newpos = pos + e->next_offset;
> +					if (newpos >= newinfo->size)
> +						return 0;
>  				}
>  				e = (struct arpt_entry *)
>  					(entry0 + newpos);
> @@ -682,10 +686,8 @@ static int translate_table(struct xt_table_info *newinfo, void *entry0,
>  		}
>  	}
>  
> -	if (!mark_source_chains(newinfo, repl->valid_hooks, entry0)) {
> -		duprintf("Looping hook\n");
> +	if (!mark_source_chains(newinfo, repl->valid_hooks, entry0))
>  		return -ELOOP;
> -	}
>  
>  	/* Finally, each sanity check must pass */
>  	i = 0;
> diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c
> index 45b1d97..c4836f0 100644
> --- a/net/ipv4/netfilter/ip_tables.c
> +++ b/net/ipv4/netfilter/ip_tables.c
> @@ -520,6 +520,8 @@ mark_source_chains(const struct xt_table_info *newinfo,
>  				size = e->next_offset;
>  				e = (struct ipt_entry *)
>  					(entry0 + pos + size);
> +				if (WARN_ON(pos + size >= newinfo->size))
> +					return 0;

This got WARN_ON(), but not in other spots.

I'll place 1 to 4 in the nf tree, then I suggest we take a little bit
more time to follow up to validate that we can actually trigger from
all possible corners.

Thanks Florian!

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 1/5] netfilter: x_tables: validate e->target_offset early
  2016-03-22 17:02 ` [PATCH 1/5] netfilter: x_tables: validate e->target_offset early Florian Westphal
@ 2016-03-24 20:17   ` Pablo Neira Ayuso
  0 siblings, 0 replies; 13+ messages in thread
From: Pablo Neira Ayuso @ 2016-03-24 20:17 UTC (permalink / raw)
  To: Florian Westphal; +Cc: netfilter-devel

On Tue, Mar 22, 2016 at 06:02:49PM +0100, Florian Westphal wrote:
> We should check that e->target_offset is sane before
> mark_source_chains gets called since it will fetch the target entry
> for loop detection.

Applied, thanks Florian.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 2/5] netfilter: x_tables: make sure e->next_offset covers remaining blob size
  2016-03-22 17:02 ` [PATCH 2/5] netfilter: x_tables: make sure e->next_offset covers remaining blob size Florian Westphal
@ 2016-03-24 20:18   ` Pablo Neira Ayuso
  0 siblings, 0 replies; 13+ messages in thread
From: Pablo Neira Ayuso @ 2016-03-24 20:18 UTC (permalink / raw)
  To: Florian Westphal; +Cc: netfilter-devel

On Tue, Mar 22, 2016 at 06:02:50PM +0100, Florian Westphal wrote:
> Otherwise this function may read data beyond the ruleset blob.

Also applied, thanks.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 3/5] netfilter: x_tables: add and use xt_check_entry_target
  2016-03-22 17:02 ` [PATCH 3/5] netfilter: x_tables: add and use xt_check_entry_target Florian Westphal
@ 2016-03-24 20:18   ` Pablo Neira Ayuso
  2016-03-25 11:45   ` Florian Westphal
  1 sibling, 0 replies; 13+ messages in thread
From: Pablo Neira Ayuso @ 2016-03-24 20:18 UTC (permalink / raw)
  To: Florian Westphal; +Cc: netfilter-devel

On Tue, Mar 22, 2016 at 06:02:51PM +0100, Florian Westphal wrote:
> We have targets and standard targets -- the latter carries a verdict, so we
> must check the standard size as well here -- later functions access t->verdict
> which otherwise can point after the blob end.

Applied to nf, thanks.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 4/5] netfilter: x_tables: fix unconditional helper
  2016-03-22 17:02 ` [PATCH 4/5] netfilter: x_tables: fix unconditional helper Florian Westphal
@ 2016-03-24 20:18   ` Pablo Neira Ayuso
  0 siblings, 0 replies; 13+ messages in thread
From: Pablo Neira Ayuso @ 2016-03-24 20:18 UTC (permalink / raw)
  To: Florian Westphal; +Cc: netfilter-devel

On Tue, Mar 22, 2016 at 06:02:52PM +0100, Florian Westphal wrote:
> Ben Hawkes says:
> 
>  In the mark_source_chains function (net/ipv4/netfilter/ip_tables.c) it
>  is possible for a user-supplied ipt_entry structure to have a large
>  next_offset field. This field is not bounds checked prior to writing a
>  counter value at the supplied offset.
> 
> Problem is that mark_source_chains should not have been called --
> the rule doesn't have a next entry, so its supposed to return
> an absolute verdict of either ACCEPT or DROP.
> 
> However, the function conditional() doesn't work as the name implies.
> It only checks that the rule is using wildcard address matching.
> 
> However, an unconditional rule must also not be using any matches
> (no -m args).
> 
> The underflow validator only checked the addresses, therefore
> passing the 'unconditional absolute verdict' test, while
> mark_source_chains also tested for presence of matches, and thus
> proceeeded to the next (not-existent) rule.
> 
> Unify this so that all the callers have same idea of 'unconditional rule'.

Applied, thanks Florian.


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 3/5] netfilter: x_tables: add and use xt_check_entry_target
  2016-03-22 17:02 ` [PATCH 3/5] netfilter: x_tables: add and use xt_check_entry_target Florian Westphal
  2016-03-24 20:18   ` Pablo Neira Ayuso
@ 2016-03-25 11:45   ` Florian Westphal
  2016-03-25 14:27     ` Pablo Neira Ayuso
  1 sibling, 1 reply; 13+ messages in thread
From: Florian Westphal @ 2016-03-25 11:45 UTC (permalink / raw)
  To: Florian Westphal; +Cc: netfilter-devel

Florian Westphal <fw@strlen.de> wrote:
> +	if (strcmp(t->u.user.name, XT_STANDARD_TARGET) == 0 &&
> +	    target_offset + sizeof(struct xt_standard_target) > next_offset)
> +		return -EINVAL;

The last hunk broke CONFIG_COMPAT, it calls into check_entry() and
casts compat_Xt_entry to Xt_entry, but for compat case
xt_standard_target is larger than what a 32bit userspace sends us.

For now I'd suggest to not pass this to stable, I'll  rework
the compat handlers next week to fix this properly.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 3/5] netfilter: x_tables: add and use xt_check_entry_target
  2016-03-25 11:45   ` Florian Westphal
@ 2016-03-25 14:27     ` Pablo Neira Ayuso
  0 siblings, 0 replies; 13+ messages in thread
From: Pablo Neira Ayuso @ 2016-03-25 14:27 UTC (permalink / raw)
  To: Florian Westphal; +Cc: netfilter-devel

On Fri, Mar 25, 2016 at 12:45:53PM +0100, Florian Westphal wrote:
> Florian Westphal <fw@strlen.de> wrote:
> > +	if (strcmp(t->u.user.name, XT_STANDARD_TARGET) == 0 &&
> > +	    target_offset + sizeof(struct xt_standard_target) > next_offset)
> > +		return -EINVAL;
> 
> The last hunk broke CONFIG_COMPAT, it calls into check_entry() and
> casts compat_Xt_entry to Xt_entry, but for compat case
> xt_standard_target is larger than what a 32bit userspace sends us.
> 
> For now I'd suggest to not pass this to stable, I'll  rework
> the compat handlers next week to fix this properly.

OK, I'll pull this out from the nf tree and rebase.

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2016-03-25 14:27 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-03-22 17:02 [PATCH nf v3] netfilter: x_tables: perform more sanity tests on rule set Florian Westphal
2016-03-22 17:02 ` [PATCH 1/5] netfilter: x_tables: validate e->target_offset early Florian Westphal
2016-03-24 20:17   ` Pablo Neira Ayuso
2016-03-22 17:02 ` [PATCH 2/5] netfilter: x_tables: make sure e->next_offset covers remaining blob size Florian Westphal
2016-03-24 20:18   ` Pablo Neira Ayuso
2016-03-22 17:02 ` [PATCH 3/5] netfilter: x_tables: add and use xt_check_entry_target Florian Westphal
2016-03-24 20:18   ` Pablo Neira Ayuso
2016-03-25 11:45   ` Florian Westphal
2016-03-25 14:27     ` Pablo Neira Ayuso
2016-03-22 17:02 ` [PATCH 4/5] netfilter: x_tables: fix unconditional helper Florian Westphal
2016-03-24 20:18   ` Pablo Neira Ayuso
2016-03-22 17:02 ` [PATCH 5/5] netfilter: x_tables: don't move to non-existant next rule Florian Westphal
2016-03-22 17:22   ` Pablo Neira Ayuso

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).