diff mbox

[SRU,P/T/U/V/W] UBUNTU: SAUCE: (noup) netfilter: x_tables: don't rely on well-behaving userspace

Message ID 1457642646-17454-1-git-send-email-chris.j.arges@canonical.com
State New
Headers show

Commit Message

Chris J Arges March 10, 2016, 8:44 p.m. UTC
From: Florian Westphal <fw@strlen.de>

BugLink: http://bugs.launchpad.net/bugs/1555338

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 xt_entry_foreach() macro stops iterating once e->next_offset
is out of bounds, assuming this is the last entry.

With malformed data thats not necessarily the case so we can
write outside of allocated area later as we might not have walked the
entire blob.

Fix this by simplifying mark_source_chains -- it already has to check
if nextoff is in range to catch invalid jumps, so just do the check
when we move to a next entry as well.

Also, check that the offset meets the xtables_entry alignment.

Reported-by: Ben Hawkes <hawkes@google.com>
Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Chris J Arges <chris.j.arges@canonical.com>
---
 net/ipv4/netfilter/arp_tables.c | 25 +++++++++++++++++--------
 net/ipv4/netfilter/ip_tables.c  | 24 +++++++++++++++++-------
 net/ipv6/netfilter/ip6_tables.c | 24 +++++++++++++++++-------
 3 files changed, 51 insertions(+), 22 deletions(-)

Comments

Brad Figg March 10, 2016, 8:49 p.m. UTC | #1
On Thu, Mar 10, 2016 at 02:44:06PM -0600, Chris J Arges wrote:
> From: Florian Westphal <fw@strlen.de>
> 
> BugLink: http://bugs.launchpad.net/bugs/1555338
> 
> 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 xt_entry_foreach() macro stops iterating once e->next_offset
> is out of bounds, assuming this is the last entry.
> 
> With malformed data thats not necessarily the case so we can
> write outside of allocated area later as we might not have walked the
> entire blob.
> 
> Fix this by simplifying mark_source_chains -- it already has to check
> if nextoff is in range to catch invalid jumps, so just do the check
> when we move to a next entry as well.
> 
> Also, check that the offset meets the xtables_entry alignment.
> 
> Reported-by: Ben Hawkes <hawkes@google.com>
> Signed-off-by: Florian Westphal <fw@strlen.de>
> Signed-off-by: Chris J Arges <chris.j.arges@canonical.com>
> ---
>  net/ipv4/netfilter/arp_tables.c | 25 +++++++++++++++++--------
>  net/ipv4/netfilter/ip_tables.c  | 24 +++++++++++++++++-------
>  net/ipv6/netfilter/ip6_tables.c | 24 +++++++++++++++++-------
>  3 files changed, 51 insertions(+), 22 deletions(-)
> 
> diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c
> index 59da7cd..88334b1 100644
> --- a/net/ipv4/netfilter/arp_tables.c
> +++ b/net/ipv4/netfilter/arp_tables.c
> @@ -362,6 +362,17 @@ static inline bool unconditional(const struct arpt_arp *arp)
>  	return memcmp(arp, &uncond, sizeof(uncond)) == 0;
>  }
>  
> +static bool next_offset_ok(const struct xt_table_info *t, unsigned int newpos)
> +{
> +	if (newpos > t->size - sizeof(struct arpt_entry))
> +		return false;
> +
> +	if (newpos % __alignof__(struct arpt_entry) != 0)
> +		return false;
> +
> +	return true;
> +}
> +
>  /* Figures out from what hook each rule can be called: returns 0 if
>   * there are loops.  Puts hook bitmask in comefrom.
>   */
> @@ -433,6 +444,8 @@ static int mark_source_chains(const struct xt_table_info *newinfo,
>  
>  				/* Move along one */
>  				size = e->next_offset;
> +				if (!next_offset_ok(newinfo, pos + size))
> +					return 0;
>  				e = (struct arpt_entry *)
>  					(entry0 + pos + size);
>  				e->counters.pcnt = pos;
> @@ -443,14 +456,6 @@ static int mark_source_chains(const struct xt_table_info *newinfo,
>  				if (strcmp(t->target.u.user.name,
>  					   XT_STANDARD_TARGET) == 0 &&
>  				    newpos >= 0) {
> -					if (newpos > newinfo->size -
> -						sizeof(struct arpt_entry)) {
> -						duprintf("mark_source_chains: "
> -							"bad verdict (%i)\n",
> -								newpos);
> -						return 0;
> -					}
> -
>  					/* This a jump; chase it. */
>  					duprintf("Jump rule %u -> %u\n",
>  						 pos, newpos);
> @@ -458,6 +463,10 @@ static int mark_source_chains(const struct xt_table_info *newinfo,
>  					/* ... this is a fallthru */
>  					newpos = pos + e->next_offset;
>  				}
> +
> +				if (!next_offset_ok(newinfo, newpos))
> +					return 0;
> +
>  				e = (struct arpt_entry *)
>  					(entry0 + newpos);
>  				e->counters.pcnt = pos;
> diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c
> index 718dfbd..ee17b65 100644
> --- a/net/ipv4/netfilter/ip_tables.c
> +++ b/net/ipv4/netfilter/ip_tables.c
> @@ -439,6 +439,17 @@ ipt_do_table(struct sk_buff *skb,
>  #endif
>  }
>  
> +static bool next_offset_ok(const struct xt_table_info *t, unsigned int newpos)
> +{
> +	if (newpos > t->size - sizeof(struct ipt_entry))
> +		return false;
> +
> +	if (newpos % __alignof__(struct ipt_entry) != 0)
> +		return false;
> +
> +	return true;
> +}
> +
>  /* Figures out from what hook each rule can be called: returns 0 if
>     there are loops.  Puts hook bitmask in comefrom. */
>  static int
> @@ -515,6 +526,8 @@ mark_source_chains(const struct xt_table_info *newinfo,
>  
>  				/* Move along one */
>  				size = e->next_offset;
> +				if (!next_offset_ok(newinfo, pos + size))
> +					return 0;
>  				e = (struct ipt_entry *)
>  					(entry0 + pos + size);
>  				e->counters.pcnt = pos;
> @@ -525,13 +538,6 @@ mark_source_chains(const struct xt_table_info *newinfo,
>  				if (strcmp(t->target.u.user.name,
>  					   XT_STANDARD_TARGET) == 0 &&
>  				    newpos >= 0) {
> -					if (newpos > newinfo->size -
> -						sizeof(struct ipt_entry)) {
> -						duprintf("mark_source_chains: "
> -							"bad verdict (%i)\n",
> -								newpos);
> -						return 0;
> -					}
>  					/* This a jump; chase it. */
>  					duprintf("Jump rule %u -> %u\n",
>  						 pos, newpos);
> @@ -539,6 +545,10 @@ mark_source_chains(const struct xt_table_info *newinfo,
>  					/* ... this is a fallthru */
>  					newpos = pos + e->next_offset;
>  				}
> +
> +				if (!next_offset_ok(newinfo, newpos))
> +					return 0;
> +
>  				e = (struct ipt_entry *)
>  					(entry0 + newpos);
>  				e->counters.pcnt = pos;
> diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_tables.c
> index 710238f..5b4b008 100644
> --- a/net/ipv6/netfilter/ip6_tables.c
> +++ b/net/ipv6/netfilter/ip6_tables.c
> @@ -449,6 +449,17 @@ ip6t_do_table(struct sk_buff *skb,
>  #endif
>  }
>  
> +static bool next_offset_ok(const struct xt_table_info *t, unsigned int newpos)
> +{
> +	if (newpos > t->size - sizeof(struct ip6t_entry))
> +		return false;
> +
> +	if (newpos % __alignof__(struct ip6t_entry) != 0)
> +		return false;
> +
> +	return true;
> +}
> +
>  /* Figures out from what hook each rule can be called: returns 0 if
>     there are loops.  Puts hook bitmask in comefrom. */
>  static int
> @@ -525,6 +536,8 @@ mark_source_chains(const struct xt_table_info *newinfo,
>  
>  				/* Move along one */
>  				size = e->next_offset;
> +				if (!next_offset_ok(newinfo, pos + size))
> +					return 0;
>  				e = (struct ip6t_entry *)
>  					(entry0 + pos + size);
>  				e->counters.pcnt = pos;
> @@ -535,13 +548,6 @@ mark_source_chains(const struct xt_table_info *newinfo,
>  				if (strcmp(t->target.u.user.name,
>  					   XT_STANDARD_TARGET) == 0 &&
>  				    newpos >= 0) {
> -					if (newpos > newinfo->size -
> -						sizeof(struct ip6t_entry)) {
> -						duprintf("mark_source_chains: "
> -							"bad verdict (%i)\n",
> -								newpos);
> -						return 0;
> -					}
>  					/* This a jump; chase it. */
>  					duprintf("Jump rule %u -> %u\n",
>  						 pos, newpos);
> @@ -549,6 +555,10 @@ mark_source_chains(const struct xt_table_info *newinfo,
>  					/* ... this is a fallthru */
>  					newpos = pos + e->next_offset;
>  				}
> +
> +				if (!next_offset_ok(newinfo, newpos))
> +					return 0;
> +
>  				e = (struct ip6t_entry *)
>  					(entry0 + newpos);
>  				e->counters.pcnt = pos;
> -- 
> 2.7.0
> 
> 
> -- 
> kernel-team mailing list
> kernel-team@lists.ubuntu.com
> https://lists.ubuntu.com/mailman/listinfo/kernel-team

Looks good
Tim Gardner March 11, 2016, 1:43 a.m. UTC | #2
From IRC chatter I gather that you had a reproducer. Care to dump your
test method and results in the bug ?
diff mbox

Patch

diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c
index 59da7cd..88334b1 100644
--- a/net/ipv4/netfilter/arp_tables.c
+++ b/net/ipv4/netfilter/arp_tables.c
@@ -362,6 +362,17 @@  static inline bool unconditional(const struct arpt_arp *arp)
 	return memcmp(arp, &uncond, sizeof(uncond)) == 0;
 }
 
+static bool next_offset_ok(const struct xt_table_info *t, unsigned int newpos)
+{
+	if (newpos > t->size - sizeof(struct arpt_entry))
+		return false;
+
+	if (newpos % __alignof__(struct arpt_entry) != 0)
+		return false;
+
+	return true;
+}
+
 /* Figures out from what hook each rule can be called: returns 0 if
  * there are loops.  Puts hook bitmask in comefrom.
  */
@@ -433,6 +444,8 @@  static int mark_source_chains(const struct xt_table_info *newinfo,
 
 				/* Move along one */
 				size = e->next_offset;
+				if (!next_offset_ok(newinfo, pos + size))
+					return 0;
 				e = (struct arpt_entry *)
 					(entry0 + pos + size);
 				e->counters.pcnt = pos;
@@ -443,14 +456,6 @@  static int mark_source_chains(const struct xt_table_info *newinfo,
 				if (strcmp(t->target.u.user.name,
 					   XT_STANDARD_TARGET) == 0 &&
 				    newpos >= 0) {
-					if (newpos > newinfo->size -
-						sizeof(struct arpt_entry)) {
-						duprintf("mark_source_chains: "
-							"bad verdict (%i)\n",
-								newpos);
-						return 0;
-					}
-
 					/* This a jump; chase it. */
 					duprintf("Jump rule %u -> %u\n",
 						 pos, newpos);
@@ -458,6 +463,10 @@  static int mark_source_chains(const struct xt_table_info *newinfo,
 					/* ... this is a fallthru */
 					newpos = pos + e->next_offset;
 				}
+
+				if (!next_offset_ok(newinfo, newpos))
+					return 0;
+
 				e = (struct arpt_entry *)
 					(entry0 + newpos);
 				e->counters.pcnt = pos;
diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c
index 718dfbd..ee17b65 100644
--- a/net/ipv4/netfilter/ip_tables.c
+++ b/net/ipv4/netfilter/ip_tables.c
@@ -439,6 +439,17 @@  ipt_do_table(struct sk_buff *skb,
 #endif
 }
 
+static bool next_offset_ok(const struct xt_table_info *t, unsigned int newpos)
+{
+	if (newpos > t->size - sizeof(struct ipt_entry))
+		return false;
+
+	if (newpos % __alignof__(struct ipt_entry) != 0)
+		return false;
+
+	return true;
+}
+
 /* Figures out from what hook each rule can be called: returns 0 if
    there are loops.  Puts hook bitmask in comefrom. */
 static int
@@ -515,6 +526,8 @@  mark_source_chains(const struct xt_table_info *newinfo,
 
 				/* Move along one */
 				size = e->next_offset;
+				if (!next_offset_ok(newinfo, pos + size))
+					return 0;
 				e = (struct ipt_entry *)
 					(entry0 + pos + size);
 				e->counters.pcnt = pos;
@@ -525,13 +538,6 @@  mark_source_chains(const struct xt_table_info *newinfo,
 				if (strcmp(t->target.u.user.name,
 					   XT_STANDARD_TARGET) == 0 &&
 				    newpos >= 0) {
-					if (newpos > newinfo->size -
-						sizeof(struct ipt_entry)) {
-						duprintf("mark_source_chains: "
-							"bad verdict (%i)\n",
-								newpos);
-						return 0;
-					}
 					/* This a jump; chase it. */
 					duprintf("Jump rule %u -> %u\n",
 						 pos, newpos);
@@ -539,6 +545,10 @@  mark_source_chains(const struct xt_table_info *newinfo,
 					/* ... this is a fallthru */
 					newpos = pos + e->next_offset;
 				}
+
+				if (!next_offset_ok(newinfo, newpos))
+					return 0;
+
 				e = (struct ipt_entry *)
 					(entry0 + newpos);
 				e->counters.pcnt = pos;
diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_tables.c
index 710238f..5b4b008 100644
--- a/net/ipv6/netfilter/ip6_tables.c
+++ b/net/ipv6/netfilter/ip6_tables.c
@@ -449,6 +449,17 @@  ip6t_do_table(struct sk_buff *skb,
 #endif
 }
 
+static bool next_offset_ok(const struct xt_table_info *t, unsigned int newpos)
+{
+	if (newpos > t->size - sizeof(struct ip6t_entry))
+		return false;
+
+	if (newpos % __alignof__(struct ip6t_entry) != 0)
+		return false;
+
+	return true;
+}
+
 /* Figures out from what hook each rule can be called: returns 0 if
    there are loops.  Puts hook bitmask in comefrom. */
 static int
@@ -525,6 +536,8 @@  mark_source_chains(const struct xt_table_info *newinfo,
 
 				/* Move along one */
 				size = e->next_offset;
+				if (!next_offset_ok(newinfo, pos + size))
+					return 0;
 				e = (struct ip6t_entry *)
 					(entry0 + pos + size);
 				e->counters.pcnt = pos;
@@ -535,13 +548,6 @@  mark_source_chains(const struct xt_table_info *newinfo,
 				if (strcmp(t->target.u.user.name,
 					   XT_STANDARD_TARGET) == 0 &&
 				    newpos >= 0) {
-					if (newpos > newinfo->size -
-						sizeof(struct ip6t_entry)) {
-						duprintf("mark_source_chains: "
-							"bad verdict (%i)\n",
-								newpos);
-						return 0;
-					}
 					/* This a jump; chase it. */
 					duprintf("Jump rule %u -> %u\n",
 						 pos, newpos);
@@ -549,6 +555,10 @@  mark_source_chains(const struct xt_table_info *newinfo,
 					/* ... this is a fallthru */
 					newpos = pos + e->next_offset;
 				}
+
+				if (!next_offset_ok(newinfo, newpos))
+					return 0;
+
 				e = (struct ip6t_entry *)
 					(entry0 + newpos);
 				e->counters.pcnt = pos;