diff options
author | Justin M. Forbes <jforbes@redhat.com> | 2016-06-28 13:00:03 -0500 |
---|---|---|
committer | Justin M. Forbes <jforbes@redhat.com> | 2016-06-28 13:00:03 -0500 |
commit | 9759d1d73d53e21471a45ccc49926c63b5e5b62a (patch) | |
tree | b17ce7f67283dd4ef76fa1baf04f4a134663b8ac | |
parent | c3a014b8cfa4ed6e7a07cbb6c95cb90e4b952288 (diff) | |
download | kernel-9759d1d73d53e21471a45ccc49926c63b5e5b62a.tar.gz kernel-9759d1d73d53e21471a45ccc49926c63b5e5b62a.tar.xz kernel-9759d1d73d53e21471a45ccc49926c63b5e5b62a.zip |
CVE-2016-4998 oob reads when processing IPT_SO_SET_REPLACE setsockopt (rhbz 1349886 1350316)
-rw-r--r-- | kernel.spec | 11 | ||||
-rw-r--r-- | netfilter-more-fixes.patch | 3107 | ||||
-rw-r--r-- | netfilter-x_tables-deal-with-bogus-nextoffset-values.patch | 150 |
3 files changed, 3114 insertions, 154 deletions
diff --git a/kernel.spec b/kernel.spec index e5b1c28ca..b8d6e8a6a 100644 --- a/kernel.spec +++ b/kernel.spec @@ -42,7 +42,7 @@ Summary: The Linux kernel # For non-released -rc kernels, this will be appended after the rcX and # gitX tags, so a 3 here would become part of release "0.rcX.gitX.3" # -%global baserelease 201 +%global baserelease 202 %global fedora_build %{baserelease} # base_sublevel is the kernel version we're starting with and patching @@ -620,9 +620,6 @@ Patch648: 0001-mm-CONFIG_NR_ZONES_EXTENDED.patch #CVE-2016-3135 rhbz 1317386 1317387 Patch664: netfilter-x_tables-check-for-size-overflow.patch -#CVE-2016-3134 rhbz 1317383 1317384 -Patch665: netfilter-x_tables-deal-with-bogus-nextoffset-values.patch - # CVE-2016-3672 rhbz 1324749 1324750 Patch689: x86-mm-32-Enable-full-randomization-on-i386-and-X86_.patch @@ -673,6 +670,9 @@ Patch728: hp-wmi-fix-wifi-cannot-be-hard-unblock.patch #CVE-2016-4998 rhbz 1349886 1350316 Patch729: CVE-2016-4998.patch +#CVE-2016-4998 rhbz 1349886 1350316 +Patch730: netfilter-more-fixes.patch + #CVE-2016-5829 rhbz 1350509 1350513 Patch826: HID-hiddev-validate-num_values-for-HIDIOCGUSAGES-HID.patch @@ -2200,6 +2200,9 @@ fi # # %changelog +* Tue Jun 28 2016 Justin M. Forbes <jforbes@fedoraproject.org> - 4.5.7-202 +- CVE-2016-4998 oob reads when processing IPT_SO_SET_REPLACE setsockopt (rhbz 1349886 1350316) + * Tue Jun 28 2016 Josh Boyer <jwboyer@fedoraproject.org> - CVE-2016-1237 missing check for permissions setting ACL (rhbz 1350845 1350847) - CVE-2016-5728 race condition in mic driver (rhbz 1350811 1350812) diff --git a/netfilter-more-fixes.patch b/netfilter-more-fixes.patch new file mode 100644 index 000000000..6695501f0 --- /dev/null +++ b/netfilter-more-fixes.patch @@ -0,0 +1,3107 @@ +From f24e230d257af1ad7476c6e81a8dc3127a74204e Mon Sep 17 00:00:00 2001 +From: Florian Westphal <fw@strlen.de> +Date: Fri, 1 Apr 2016 14:17:21 +0200 +Subject: netfilter: x_tables: don't move to non-existent next rule + +From: Florian Westphal <fw@strlen.de> + +commit f24e230d257af1ad7476c6e81a8dc3127a74204e upstream. + +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. + +Base chains enforce absolute verdict. + +User defined chains are supposed to end with an unconditional return, +xtables userspace adds them automatically. + +But if such return is missing we will move to non-existent next rule. + +Reported-by: Ben Hawkes <hawkes@google.com> +Signed-off-by: Florian Westphal <fw@strlen.de> +Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> +Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> + +--- + 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(-) + +--- a/net/ipv4/netfilter/arp_tables.c ++++ b/net/ipv4/netfilter/arp_tables.c +@@ -439,6 +439,8 @@ static int mark_source_chains(const stru + 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 stru + } else { + /* ... this is a fallthru */ + newpos = pos + e->next_offset; ++ if (newpos >= newinfo->size) ++ return 0; + } + e = (struct arpt_entry *) + (entry0 + newpos); +@@ -691,10 +695,8 @@ static int translate_table(struct xt_tab + } + } + +- 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; +--- 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 + size = e->next_offset; + e = (struct ipt_entry *) + (entry0 + pos + size); ++ if (pos + size >= newinfo->size) ++ return 0; + e->counters.pcnt = pos; + pos += size; + } else { +@@ -541,6 +543,8 @@ mark_source_chains(const struct xt_table + } else { + /* ... this is a fallthru */ + newpos = pos + e->next_offset; ++ if (newpos >= newinfo->size) ++ return 0; + } + e = (struct ipt_entry *) + (entry0 + newpos); +--- 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 + 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 + } else { + /* ... this is a fallthru */ + newpos = pos + e->next_offset; ++ if (newpos >= newinfo->size) ++ return 0; + } + e = (struct ip6t_entry *) + (entry0 + newpos); +From 36472341017529e2b12573093cc0f68719300997 Mon Sep 17 00:00:00 2001 +From: Florian Westphal <fw@strlen.de> +Date: Fri, 1 Apr 2016 14:17:22 +0200 +Subject: netfilter: x_tables: validate targets of jumps + +From: Florian Westphal <fw@strlen.de> + +commit 36472341017529e2b12573093cc0f68719300997 upstream. + +When we see a jump also check that the offset gets us to beginning of +a rule (an ipt_entry). + +The extra overhead is negible, even with absurd cases. + +300k custom rules, 300k jumps to 'next' user chain: +[ plus one jump from INPUT to first userchain ]: + +Before: +real 0m24.874s +user 0m7.532s +sys 0m16.076s + +After: +real 0m27.464s +user 0m7.436s +sys 0m18.840s + +Signed-off-by: Florian Westphal <fw@strlen.de> +Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> +Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> + +--- + net/ipv4/netfilter/arp_tables.c | 16 ++++++++++++++++ + net/ipv4/netfilter/ip_tables.c | 16 ++++++++++++++++ + net/ipv6/netfilter/ip6_tables.c | 16 ++++++++++++++++ + 3 files changed, 48 insertions(+) + +--- a/net/ipv4/netfilter/arp_tables.c ++++ b/net/ipv4/netfilter/arp_tables.c +@@ -367,6 +367,18 @@ static inline bool unconditional(const s + memcmp(&e->arp, &uncond, sizeof(uncond)) == 0; + } + ++static bool find_jump_target(const struct xt_table_info *t, ++ const struct arpt_entry *target) ++{ ++ struct arpt_entry *iter; ++ ++ xt_entry_foreach(iter, t->entries, t->size) { ++ if (iter == target) ++ return true; ++ } ++ return false; ++} ++ + /* Figures out from what hook each rule can be called: returns 0 if + * there are loops. Puts hook bitmask in comefrom. + */ +@@ -460,6 +472,10 @@ static int mark_source_chains(const stru + /* This a jump; chase it. */ + duprintf("Jump rule %u -> %u\n", + pos, newpos); ++ e = (struct arpt_entry *) ++ (entry0 + newpos); ++ if (!find_jump_target(newinfo, e)) ++ return 0; + } else { + /* ... this is a fallthru */ + newpos = pos + e->next_offset; +--- a/net/ipv4/netfilter/ip_tables.c ++++ b/net/ipv4/netfilter/ip_tables.c +@@ -443,6 +443,18 @@ ipt_do_table(struct sk_buff *skb, + #endif + } + ++static bool find_jump_target(const struct xt_table_info *t, ++ const struct ipt_entry *target) ++{ ++ struct ipt_entry *iter; ++ ++ xt_entry_foreach(iter, t->entries, t->size) { ++ if (iter == target) ++ return true; ++ } ++ return false; ++} ++ + /* Figures out from what hook each rule can be called: returns 0 if + there are loops. Puts hook bitmask in comefrom. */ + static int +@@ -540,6 +552,10 @@ mark_source_chains(const struct xt_table + /* This a jump; chase it. */ + duprintf("Jump rule %u -> %u\n", + pos, newpos); ++ e = (struct ipt_entry *) ++ (entry0 + newpos); ++ if (!find_jump_target(newinfo, e)) ++ return 0; + } else { + /* ... this is a fallthru */ + newpos = pos + e->next_offset; +--- a/net/ipv6/netfilter/ip6_tables.c ++++ b/net/ipv6/netfilter/ip6_tables.c +@@ -455,6 +455,18 @@ ip6t_do_table(struct sk_buff *skb, + #endif + } + ++static bool find_jump_target(const struct xt_table_info *t, ++ const struct ip6t_entry *target) ++{ ++ struct ip6t_entry *iter; ++ ++ xt_entry_foreach(iter, t->entries, t->size) { ++ if (iter == target) ++ return true; ++ } ++ return false; ++} ++ + /* Figures out from what hook each rule can be called: returns 0 if + there are loops. Puts hook bitmask in comefrom. */ + static int +@@ -552,6 +564,10 @@ mark_source_chains(const struct xt_table + /* This a jump; chase it. */ + duprintf("Jump rule %u -> %u\n", + pos, newpos); ++ e = (struct ip6t_entry *) ++ (entry0 + newpos); ++ if (!find_jump_target(newinfo, e)) ++ return 0; + } else { + /* ... this is a fallthru */ + newpos = pos + e->next_offset; +From 7d35812c3214afa5b37a675113555259cfd67b98 Mon Sep 17 00:00:00 2001 +From: Florian Westphal <fw@strlen.de> +Date: Fri, 1 Apr 2016 14:17:23 +0200 +Subject: netfilter: x_tables: add and use xt_check_entry_offsets + +From: Florian Westphal <fw@strlen.de> + +commit 7d35812c3214afa5b37a675113555259cfd67b98 upstream. + +Currently arp/ip and ip6tables each implement a short helper to check that +the target offset is large enough to hold one xt_entry_target struct and +that t->u.target_size fits within the current rule. + +Unfortunately these checks are not sufficient. + +To avoid adding new tests to all of ip/ip6/arptables move the current +checks into a helper, then extend this helper in followup patches. + +Signed-off-by: Florian Westphal <fw@strlen.de> +Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> +Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> + +--- + include/linux/netfilter/x_tables.h | 4 ++++ + 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 | 34 ++++++++++++++++++++++++++++++++++ + 5 files changed, 41 insertions(+), 32 deletions(-) + +--- a/include/linux/netfilter/x_tables.h ++++ b/include/linux/netfilter/x_tables.h +@@ -239,6 +239,10 @@ void xt_unregister_match(struct xt_match + int xt_register_matches(struct xt_match *match, unsigned int n); + void xt_unregister_matches(struct xt_match *match, unsigned int n); + ++int xt_check_entry_offsets(const void *base, ++ unsigned int target_offset, ++ unsigned int next_offset); ++ + int xt_check_match(struct xt_mtchk_param *, unsigned int size, u_int8_t proto, + bool inv_proto); + int xt_check_target(struct xt_tgchk_param *, unsigned int size, u_int8_t proto, +--- a/net/ipv4/netfilter/arp_tables.c ++++ b/net/ipv4/netfilter/arp_tables.c +@@ -496,19 +496,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_offsets(e, e->target_offset, e->next_offset); + } + + static inline int check_target(struct arpt_entry *e, const char *name) +--- a/net/ipv4/netfilter/ip_tables.c ++++ b/net/ipv4/netfilter/ip_tables.c +@@ -590,20 +590,10 @@ static void cleanup_match(struct xt_entr + 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_offsets(e, e->target_offset, e->next_offset); + } + + static int +--- a/net/ipv6/netfilter/ip6_tables.c ++++ b/net/ipv6/netfilter/ip6_tables.c +@@ -602,20 +602,10 @@ static void cleanup_match(struct xt_entr + 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_offsets(e, e->target_offset, e->next_offset); + } + + static int check_match(struct xt_entry_match *m, struct xt_mtchk_param *par) +--- a/net/netfilter/x_tables.c ++++ b/net/netfilter/x_tables.c +@@ -540,6 +540,40 @@ int xt_compat_match_to_user(const struct + EXPORT_SYMBOL_GPL(xt_compat_match_to_user); + #endif /* CONFIG_COMPAT */ + ++/** ++ * xt_check_entry_offsets - validate arp/ip/ip6t_entry ++ * ++ * @base: pointer to arp/ip/ip6t_entry ++ * @target_offset: the arp/ip/ip6_t->target_offset ++ * @next_offset: the arp/ip/ip6_t->next_offset ++ * ++ * validates that target_offset and next_offset are sane. ++ * ++ * The arp/ip/ip6t_entry structure @base must have passed following tests: ++ * - it must point to a valid memory location ++ * - base to base + next_offset must be accessible, i.e. not exceed allocated ++ * length. ++ * ++ * Return: 0 on success, negative errno on failure. ++ */ ++int xt_check_entry_offsets(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(*t) > next_offset) ++ return -EINVAL; ++ ++ t = (void *)(e + target_offset); ++ if (target_offset + t->u.target_size > next_offset) ++ return -EINVAL; ++ ++ return 0; ++} ++EXPORT_SYMBOL(xt_check_entry_offsets); ++ + int xt_check_target(struct xt_tgchk_param *par, + unsigned int size, u_int8_t proto, bool inv_proto) + { +From aa412ba225dd3bc36d404c28cdc3d674850d80d0 Mon Sep 17 00:00:00 2001 +From: Florian Westphal <fw@strlen.de> +Date: Fri, 1 Apr 2016 14:17:24 +0200 +Subject: netfilter: x_tables: kill check_entry helper + +From: Florian Westphal <fw@strlen.de> + +commit aa412ba225dd3bc36d404c28cdc3d674850d80d0 upstream. + +Once we add more sanity testing to xt_check_entry_offsets it +becomes relvant if we're expecting a 32bit 'config_compat' blob +or a normal one. + +Since we already have a lot of similar-named functions (check_entry, +compat_check_entry, find_and_check_entry, etc.) and the current +incarnation is short just fold its contents into the callers. + +Signed-off-by: Florian Westphal <fw@strlen.de> +Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> +Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> + +--- + net/ipv4/netfilter/arp_tables.c | 19 ++++++++----------- + net/ipv4/netfilter/ip_tables.c | 20 ++++++++------------ + net/ipv6/netfilter/ip6_tables.c | 20 ++++++++------------ + 3 files changed, 24 insertions(+), 35 deletions(-) + +--- a/net/ipv4/netfilter/arp_tables.c ++++ b/net/ipv4/netfilter/arp_tables.c +@@ -494,14 +494,6 @@ next: + return 1; + } + +-static inline int check_entry(const struct arpt_entry *e) +-{ +- if (!arp_checkentry(&e->arp)) +- return -EINVAL; +- +- return xt_check_entry_offsets(e, e->target_offset, e->next_offset); +-} +- + static inline int check_target(struct arpt_entry *e, const char *name) + { + struct xt_entry_target *t = arpt_get_target(e); +@@ -597,7 +589,10 @@ static inline int check_entry_size_and_h + return -EINVAL; + } + +- err = check_entry(e); ++ if (!arp_checkentry(&e->arp)) ++ return -EINVAL; ++ ++ err = xt_check_entry_offsets(e, e->target_offset, e->next_offset); + if (err) + return err; + +@@ -1255,8 +1250,10 @@ check_compat_entry_size_and_hooks(struct + return -EINVAL; + } + +- /* For purposes of check_entry casting the compat entry is fine */ +- ret = check_entry((struct arpt_entry *)e); ++ if (!arp_checkentry(&e->arp)) ++ return -EINVAL; ++ ++ ret = xt_check_entry_offsets(e, e->target_offset, e->next_offset); + if (ret) + return ret; + +--- a/net/ipv4/netfilter/ip_tables.c ++++ b/net/ipv4/netfilter/ip_tables.c +@@ -588,15 +588,6 @@ static void cleanup_match(struct xt_entr + } + + static int +-check_entry(const struct ipt_entry *e) +-{ +- if (!ip_checkentry(&e->ip)) +- return -EINVAL; +- +- return xt_check_entry_offsets(e, e->target_offset, e->next_offset); +-} +- +-static int + check_match(struct xt_entry_match *m, struct xt_mtchk_param *par) + { + const struct ipt_ip *ip = par->entryinfo; +@@ -760,7 +751,10 @@ check_entry_size_and_hooks(struct ipt_en + return -EINVAL; + } + +- err = check_entry(e); ++ if (!ip_checkentry(&e->ip)) ++ return -EINVAL; ++ ++ err = xt_check_entry_offsets(e, e->target_offset, e->next_offset); + if (err) + return err; + +@@ -1515,8 +1509,10 @@ check_compat_entry_size_and_hooks(struct + return -EINVAL; + } + +- /* For purposes of check_entry casting the compat entry is fine */ +- ret = check_entry((struct ipt_entry *)e); ++ if (!ip_checkentry(&e->ip)) ++ return -EINVAL; ++ ++ ret = xt_check_entry_offsets(e, e->target_offset, e->next_offset); + if (ret) + return ret; + +--- a/net/ipv6/netfilter/ip6_tables.c ++++ b/net/ipv6/netfilter/ip6_tables.c +@@ -599,15 +599,6 @@ static void cleanup_match(struct xt_entr + module_put(par.match->me); + } + +-static int +-check_entry(const struct ip6t_entry *e) +-{ +- if (!ip6_checkentry(&e->ipv6)) +- return -EINVAL; +- +- return xt_check_entry_offsets(e, e->target_offset, e->next_offset); +-} +- + static int check_match(struct xt_entry_match *m, struct xt_mtchk_param *par) + { + const struct ip6t_ip6 *ipv6 = par->entryinfo; +@@ -772,7 +763,10 @@ check_entry_size_and_hooks(struct ip6t_e + return -EINVAL; + } + +- err = check_entry(e); ++ if (!ip6_checkentry(&e->ipv6)) ++ return -EINVAL; ++ ++ err = xt_check_entry_offsets(e, e->target_offset, e->next_offset); + if (err) + return err; + +@@ -1527,8 +1521,10 @@ check_compat_entry_size_and_hooks(struct + return -EINVAL; + } + +- /* For purposes of check_entry casting the compat entry is fine */ +- ret = check_entry((struct ip6t_entry *)e); ++ if (!ip6_checkentry(&e->ipv6)) ++ return -EINVAL; ++ ++ ret = xt_check_entry_offsets(e, e->target_offset, e->next_offset); + if (ret) + return ret; + +From a08e4e190b866579896c09af59b3bdca821da2cd Mon Sep 17 00:00:00 2001 +From: Florian Westphal <fw@strlen.de> +Date: Fri, 1 Apr 2016 14:17:25 +0200 +Subject: netfilter: x_tables: assert minimum target size + +From: Florian Westphal <fw@strlen.de> + +commit a08e4e190b866579896c09af59b3bdca821da2cd upstream. + +The target size includes the size of the xt_entry_target struct. + +Signed-off-by: Florian Westphal <fw@strlen.de> +Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> +Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> + +--- + net/netfilter/x_tables.c | 3 +++ + 1 file changed, 3 insertions(+) + +--- a/net/netfilter/x_tables.c ++++ b/net/netfilter/x_tables.c +@@ -567,6 +567,9 @@ int xt_check_entry_offsets(const void *b + return -EINVAL; + + t = (void *)(e + target_offset); ++ if (t->u.target_size < sizeof(*t)) ++ return -EINVAL; ++ + if (target_offset + t->u.target_size > next_offset) + return -EINVAL; + +From fc1221b3a163d1386d1052184202d5dc50d302d1 Mon Sep 17 00:00:00 2001 +From: Florian Westphal <fw@strlen.de> +Date: Fri, 1 Apr 2016 14:17:26 +0200 +Subject: netfilter: x_tables: add compat version of xt_check_entry_offsets + +From: Florian Westphal <fw@strlen.de> + +commit fc1221b3a163d1386d1052184202d5dc50d302d1 upstream. + +32bit rulesets have different layout and alignment requirements, so once +more integrity checks get added to xt_check_entry_offsets it will reject +well-formed 32bit rulesets. + +Signed-off-by: Florian Westphal <fw@strlen.de> +Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> +Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> + +--- + include/linux/netfilter/x_tables.h | 3 +++ + net/ipv4/netfilter/arp_tables.c | 3 ++- + net/ipv4/netfilter/ip_tables.c | 3 ++- + net/ipv6/netfilter/ip6_tables.c | 3 ++- + net/netfilter/x_tables.c | 22 ++++++++++++++++++++++ + 5 files changed, 31 insertions(+), 3 deletions(-) + +--- a/include/linux/netfilter/x_tables.h ++++ b/include/linux/netfilter/x_tables.h +@@ -492,6 +492,9 @@ void xt_compat_target_from_user(struct x + unsigned int *size); + int xt_compat_target_to_user(const struct xt_entry_target *t, + void __user **dstptr, unsigned int *size); ++int xt_compat_check_entry_offsets(const void *base, ++ unsigned int target_offset, ++ unsigned int next_offset); + + #endif /* CONFIG_COMPAT */ + #endif /* _X_TABLES_H */ +--- a/net/ipv4/netfilter/arp_tables.c ++++ b/net/ipv4/netfilter/arp_tables.c +@@ -1253,7 +1253,8 @@ check_compat_entry_size_and_hooks(struct + if (!arp_checkentry(&e->arp)) + return -EINVAL; + +- ret = xt_check_entry_offsets(e, e->target_offset, e->next_offset); ++ ret = xt_compat_check_entry_offsets(e, e->target_offset, ++ e->next_offset); + if (ret) + return ret; + +--- a/net/ipv4/netfilter/ip_tables.c ++++ b/net/ipv4/netfilter/ip_tables.c +@@ -1512,7 +1512,8 @@ check_compat_entry_size_and_hooks(struct + if (!ip_checkentry(&e->ip)) + return -EINVAL; + +- ret = xt_check_entry_offsets(e, e->target_offset, e->next_offset); ++ ret = xt_compat_check_entry_offsets(e, ++ e->target_offset, e->next_offset); + if (ret) + return ret; + +--- a/net/ipv6/netfilter/ip6_tables.c ++++ b/net/ipv6/netfilter/ip6_tables.c +@@ -1524,7 +1524,8 @@ check_compat_entry_size_and_hooks(struct + if (!ip6_checkentry(&e->ipv6)) + return -EINVAL; + +- ret = xt_check_entry_offsets(e, e->target_offset, e->next_offset); ++ ret = xt_compat_check_entry_offsets(e, ++ e->target_offset, e->next_offset); + if (ret) + return ret; + +--- a/net/netfilter/x_tables.c ++++ b/net/netfilter/x_tables.c +@@ -538,6 +538,27 @@ int xt_compat_match_to_user(const struct + return 0; + } + EXPORT_SYMBOL_GPL(xt_compat_match_to_user); ++ ++int xt_compat_check_entry_offsets(const void *base, ++ unsigned int target_offset, ++ unsigned int next_offset) ++{ ++ const struct compat_xt_entry_target *t; ++ const char *e = base; ++ ++ if (target_offset + sizeof(*t) > next_offset) ++ return -EINVAL; ++ ++ t = (void *)(e + target_offset); ++ if (t->u.target_size < sizeof(*t)) ++ return -EINVAL; ++ ++ if (target_offset + t->u.target_size > next_offset) ++ return -EINVAL; ++ ++ return 0; ++} ++EXPORT_SYMBOL(xt_compat_check_entry_offsets); + #endif /* CONFIG_COMPAT */ + + /** +@@ -548,6 +569,7 @@ EXPORT_SYMBOL_GPL(xt_compat_match_to_use + * @next_offset: the arp/ip/ip6_t->next_offset + * + * validates that target_offset and next_offset are sane. ++ * Also see xt_compat_check_entry_offsets for CONFIG_COMPAT version. + * + * The arp/ip/ip6t_entry structure @base must have passed following tests: + * - it must point to a valid memory location +From 7ed2abddd20cf8f6bd27f65bd218f26fa5bf7f44 Mon Sep 17 00:00:00 2001 +From: Florian Westphal <fw@strlen.de> +Date: Fri, 1 Apr 2016 14:17:27 +0200 +Subject: netfilter: x_tables: check standard target size too + +From: Florian Westphal <fw@strlen.de> + +commit 7ed2abddd20cf8f6bd27f65bd218f26fa5bf7f44 upstream. + +We have targets and standard targets -- the latter carries a verdict. + +The ip/ip6tables validation functions will access t->verdict for the +standard targets to fetch the jump offset or verdict for chainloop +detection, but this happens before the targets get checked/validated. + +Thus we also need to check for verdict presence here, else t->verdict +can point right after a blob. + +Spotted with UBSAN while testing malformed blobs. + +Signed-off-by: Florian Westphal <fw@strlen.de> +Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> +Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> + +--- + net/netfilter/x_tables.c | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +--- a/net/netfilter/x_tables.c ++++ b/net/netfilter/x_tables.c +@@ -539,6 +539,13 @@ int xt_compat_match_to_user(const struct + } + EXPORT_SYMBOL_GPL(xt_compat_match_to_user); + ++/* non-compat version may have padding after verdict */ ++struct compat_xt_standard_target { ++ struct compat_xt_entry_target t; ++ compat_uint_t verdict; ++}; ++ ++/* see xt_check_entry_offsets */ + int xt_compat_check_entry_offsets(const void *base, + unsigned int target_offset, + unsigned int next_offset) +@@ -556,6 +563,10 @@ int xt_compat_check_entry_offsets(const + 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 compat_xt_standard_target) != next_offset) ++ return -EINVAL; ++ + return 0; + } + EXPORT_SYMBOL(xt_compat_check_entry_offsets); +@@ -595,6 +606,10 @@ int xt_check_entry_offsets(const void *b + 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_offsets); +From ce683e5f9d045e5d67d1312a42b359cb2ab2a13c Mon Sep 17 00:00:00 2001 +From: Florian Westphal <fw@strlen.de> +Date: Fri, 1 Apr 2016 14:17:28 +0200 +Subject: netfilter: x_tables: check for bogus target offset + +From: Florian Westphal <fw@strlen.de> + +commit ce683e5f9d045e5d67d1312a42b359cb2ab2a13c upstream. + +We're currently asserting that targetoff + targetsize <= nextoff. + +Extend it to also check that targetoff is >= sizeof(xt_entry). +Since this is generic code, add an argument pointing to the start of the +match/target, we can then derive the base structure size from the delta. + +We also need the e->elems pointer in a followup change to validate matches. + +Signed-off-by: Florian Westphal <fw@strlen.de> +Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> +Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> + +--- + include/linux/netfilter/x_tables.h | 4 ++-- + net/ipv4/netfilter/arp_tables.c | 5 +++-- + net/ipv4/netfilter/ip_tables.c | 5 +++-- + net/ipv6/netfilter/ip6_tables.c | 5 +++-- + net/netfilter/x_tables.c | 17 +++++++++++++++-- + 5 files changed, 26 insertions(+), 10 deletions(-) + +--- a/include/linux/netfilter/x_tables.h ++++ b/include/linux/netfilter/x_tables.h +@@ -239,7 +239,7 @@ void xt_unregister_match(struct xt_match + int xt_register_matches(struct xt_match *match, unsigned int n); + void xt_unregister_matches(struct xt_match *match, unsigned int n); + +-int xt_check_entry_offsets(const void *base, ++int xt_check_entry_offsets(const void *base, const char *elems, + unsigned int target_offset, + unsigned int next_offset); + +@@ -492,7 +492,7 @@ void xt_compat_target_from_user(struct x + unsigned int *size); + int xt_compat_target_to_user(const struct xt_entry_target *t, + void __user **dstptr, unsigned int *size); +-int xt_compat_check_entry_offsets(const void *base, ++int xt_compat_check_entry_offsets(const void *base, const char *elems, + unsigned int target_offset, + unsigned int next_offset); + +--- a/net/ipv4/netfilter/arp_tables.c ++++ b/net/ipv4/netfilter/arp_tables.c +@@ -592,7 +592,8 @@ static inline int check_entry_size_and_h + if (!arp_checkentry(&e->arp)) + return -EINVAL; + +- err = xt_check_entry_offsets(e, e->target_offset, e->next_offset); ++ err = xt_check_entry_offsets(e, e->elems, e->target_offset, ++ e->next_offset); + if (err) + return err; + +@@ -1253,7 +1254,7 @@ check_compat_entry_size_and_hooks(struct + if (!arp_checkentry(&e->arp)) + return -EINVAL; + +- ret = xt_compat_check_entry_offsets(e, e->target_offset, ++ ret = xt_compat_check_entry_offsets(e, e->elems, e->target_offset, + e->next_offset); + if (ret) + return ret; +--- a/net/ipv4/netfilter/ip_tables.c ++++ b/net/ipv4/netfilter/ip_tables.c +@@ -754,7 +754,8 @@ check_entry_size_and_hooks(struct ipt_en + if (!ip_checkentry(&e->ip)) + return -EINVAL; + +- err = xt_check_entry_offsets(e, e->target_offset, e->next_offset); ++ err = xt_check_entry_offsets(e, e->elems, e->target_offset, ++ e->next_offset); + if (err) + return err; + +@@ -1512,7 +1513,7 @@ check_compat_entry_size_and_hooks(struct + if (!ip_checkentry(&e->ip)) + return -EINVAL; + +- ret = xt_compat_check_entry_offsets(e, ++ ret = xt_compat_check_entry_offsets(e, e->elems, + e->target_offset, e->next_offset); + if (ret) + return ret; +--- a/net/ipv6/netfilter/ip6_tables.c ++++ b/net/ipv6/netfilter/ip6_tables.c +@@ -766,7 +766,8 @@ check_entry_size_and_hooks(struct ip6t_e + if (!ip6_checkentry(&e->ipv6)) + return -EINVAL; + +- err = xt_check_entry_offsets(e, e->target_offset, e->next_offset); ++ err = xt_check_entry_offsets(e, e->elems, e->target_offset, ++ e->next_offset); + if (err) + return err; + +@@ -1524,7 +1525,7 @@ check_compat_entry_size_and_hooks(struct + if (!ip6_checkentry(&e->ipv6)) + return -EINVAL; + +- ret = xt_compat_check_entry_offsets(e, ++ ret = xt_compat_check_entry_offsets(e, e->elems, + e->target_offset, e->next_offset); + if (ret) + return ret; +--- a/net/netfilter/x_tables.c ++++ b/net/netfilter/x_tables.c +@@ -545,14 +545,17 @@ struct compat_xt_standard_target { + compat_uint_t verdict; + }; + +-/* see xt_check_entry_offsets */ +-int xt_compat_check_entry_offsets(const void *base, ++int xt_compat_check_entry_offsets(const void *base, const char *elems, + unsigned int target_offset, + unsigned int next_offset) + { ++ long size_of_base_struct = elems - (const char *)base; + const struct compat_xt_entry_target *t; + const char *e = base; + ++ if (target_offset < size_of_base_struct) ++ return -EINVAL; ++ + if (target_offset + sizeof(*t) > next_offset) + return -EINVAL; + +@@ -576,12 +579,16 @@ EXPORT_SYMBOL(xt_compat_check_entry_offs + * xt_check_entry_offsets - validate arp/ip/ip6t_entry + * + * @base: pointer to arp/ip/ip6t_entry ++ * @elems: pointer to first xt_entry_match, i.e. ip(6)t_entry->elems + * @target_offset: the arp/ip/ip6_t->target_offset + * @next_offset: the arp/ip/ip6_t->next_offset + * + * validates that target_offset and next_offset are sane. + * Also see xt_compat_check_entry_offsets for CONFIG_COMPAT version. + * ++ * This function does not validate the targets or matches themselves, it ++ * only tests that all the offsets and sizes are correct. ++ * + * The arp/ip/ip6t_entry structure @base must have passed following tests: + * - it must point to a valid memory location + * - base to base + next_offset must be accessible, i.e. not exceed allocated +@@ -590,12 +597,18 @@ EXPORT_SYMBOL(xt_compat_check_entry_offs + * Return: 0 on success, negative errno on failure. + */ + int xt_check_entry_offsets(const void *base, ++ const char *elems, + unsigned int target_offset, + unsigned int next_offset) + { ++ long size_of_base_struct = elems - (const char *)base; + const struct xt_entry_target *t; + const char *e = base; + ++ /* target start is within the ip/ip6/arpt_entry struct */ ++ if (target_offset < size_of_base_struct) ++ return -EINVAL; ++ + if (target_offset + sizeof(*t) > next_offset) + return -EINVAL; + +From 13631bfc604161a9d69cd68991dff8603edd66f9 Mon Sep 17 00:00:00 2001 +From: Florian Westphal <fw@strlen.de> +Date: Fri, 1 Apr 2016 14:17:29 +0200 +Subject: netfilter: x_tables: validate all offsets and sizes in a rule + +From: Florian Westphal <fw@strlen.de> + +commit 13631bfc604161a9d69cd68991dff8603edd66f9 upstream. + +Validate that all matches (if any) add up to the beginning of +the target and that each match covers at least the base structure size. + +The compat path should be able to safely re-use the function +as the structures only differ in alignment; added a +BUILD_BUG_ON just in case we have an arch that adds padding as well. + +Signed-off-by: Florian Westphal <fw@strlen.de> +Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> +Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> + +--- + net/netfilter/x_tables.c | 81 ++++++++++++++++++++++++++++++++++++++++++++--- + 1 file changed, 76 insertions(+), 5 deletions(-) + +--- a/net/netfilter/x_tables.c ++++ b/net/netfilter/x_tables.c +@@ -415,6 +415,47 @@ int xt_check_match(struct xt_mtchk_param + } + EXPORT_SYMBOL_GPL(xt_check_match); + ++/** xt_check_entry_match - check that matches end before start of target ++ * ++ * @match: beginning of xt_entry_match ++ * @target: beginning of this rules target (alleged end of matches) ++ * @alignment: alignment requirement of match structures ++ * ++ * Validates that all matches add up to the beginning of the target, ++ * and that each match covers at least the base structure size. ++ * ++ * Return: 0 on success, negative errno on failure. ++ */ ++static int xt_check_entry_match(const char *match, const char *target, ++ const size_t alignment) ++{ ++ const struct xt_entry_match *pos; ++ int length = target - match; ++ ++ if (length == 0) /* no matches */ ++ return 0; ++ ++ pos = (struct xt_entry_match *)match; ++ do { ++ if ((unsigned long)pos % alignment) ++ return -EINVAL; ++ ++ if (length < (int)sizeof(struct xt_entry_match)) ++ return -EINVAL; ++ ++ if (pos->u.match_size < sizeof(struct xt_entry_match)) ++ return -EINVAL; ++ ++ if (pos->u.match_size > length) ++ return -EINVAL; ++ ++ length -= pos->u.match_size; ++ pos = ((void *)((char *)(pos) + (pos)->u.match_size)); ++ } while (length > 0); ++ ++ return 0; ++} ++ + #ifdef CONFIG_COMPAT + int xt_compat_add_offset(u_int8_t af, unsigned int offset, int delta) + { +@@ -570,7 +611,14 @@ int xt_compat_check_entry_offsets(const + target_offset + sizeof(struct compat_xt_standard_target) != next_offset) + return -EINVAL; + +- return 0; ++ /* compat_xt_entry match has less strict aligment requirements, ++ * otherwise they are identical. In case of padding differences ++ * we need to add compat version of xt_check_entry_match. ++ */ ++ BUILD_BUG_ON(sizeof(struct compat_xt_entry_match) != sizeof(struct xt_entry_match)); ++ ++ return xt_check_entry_match(elems, base + target_offset, ++ __alignof__(struct compat_xt_entry_match)); + } + EXPORT_SYMBOL(xt_compat_check_entry_offsets); + #endif /* CONFIG_COMPAT */ +@@ -583,17 +631,39 @@ EXPORT_SYMBOL(xt_compat_check_entry_offs + * @target_offset: the arp/ip/ip6_t->target_offset + * @next_offset: the arp/ip/ip6_t->next_offset + * +- * validates that target_offset and next_offset are sane. +- * Also see xt_compat_check_entry_offsets for CONFIG_COMPAT version. ++ * validates that target_offset and next_offset are sane and that all ++ * match sizes (if any) align with the target offset. + * + * This function does not validate the targets or matches themselves, it +- * only tests that all the offsets and sizes are correct. ++ * only tests that all the offsets and sizes are correct, that all ++ * match structures are aligned, and that the last structure ends where ++ * the target structure begins. ++ * ++ * Also see xt_compat_check_entry_offsets for CONFIG_COMPAT version. + * + * The arp/ip/ip6t_entry structure @base must have passed following tests: + * - it must point to a valid memory location + * - base to base + next_offset must be accessible, i.e. not exceed allocated + * length. + * ++ * A well-formed entry looks like this: ++ * ++ * ip(6)t_entry match [mtdata] match [mtdata] target [tgdata] ip(6)t_entry ++ * e->elems[]-----' | | ++ * matchsize | | ++ * matchsize | | ++ * | | ++ * target_offset---------------------------------' | ++ * next_offset---------------------------------------------------' ++ * ++ * elems[]: flexible array member at end of ip(6)/arpt_entry struct. ++ * This is where matches (if any) and the target reside. ++ * target_offset: beginning of target. ++ * next_offset: start of the next rule; also: size of this rule. ++ * Since targets have a minimum size, target_offset + minlen <= next_offset. ++ * ++ * Every match stores its size, sum of sizes must not exceed target_offset. ++ * + * Return: 0 on success, negative errno on failure. + */ + int xt_check_entry_offsets(const void *base, +@@ -623,7 +693,8 @@ int xt_check_entry_offsets(const void *b + target_offset + sizeof(struct xt_standard_target) != next_offset) + return -EINVAL; + +- return 0; ++ return xt_check_entry_match(elems, base + target_offset, ++ __alignof__(struct xt_entry_match)); + } + EXPORT_SYMBOL(xt_check_entry_offsets); + +From 7b7eba0f3515fca3296b8881d583f7c1042f5226 Mon Sep 17 00:00:00 2001 +From: Florian Westphal <fw@strlen.de> +Date: Wed, 1 Jun 2016 02:04:44 +0200 +Subject: netfilter: x_tables: don't reject valid target size on some architectures + +From: Florian Westphal <fw@strlen.de> + +commit 7b7eba0f3515fca3296b8881d583f7c1042f5226 upstream. + +Quoting John Stultz: + In updating a 32bit arm device from 4.6 to Linus' current HEAD, I + noticed I was having some trouble with networking, and realized that + /proc/net/ip_tables_names was suddenly empty. + Digging through the registration process, it seems we're catching on the: + + if (strcmp(t->u.user.name, XT_STANDARD_TARGET) == 0 && + target_offset + sizeof(struct xt_standard_target) != next_offset) + return -EINVAL; + + Where next_offset seems to be 4 bytes larger then the + offset + standard_target struct size. + +next_offset needs to be aligned via XT_ALIGN (so we can access all members +of ip(6)t_entry struct). + +This problem didn't show up on i686 as it only needs 4-byte alignment for +u64, but iptables userspace on other 32bit arches does insert extra padding. + +Reported-by: John Stultz <john.stultz@linaro.org> +Tested-by: John Stultz <john.stultz@linaro.org> +Fixes: 7ed2abddd20cf ("netfilter: x_tables: check standard target size too") +Signed-off-by: Florian Westphal <fw@strlen.de> +Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> +Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> + +--- + net/netfilter/x_tables.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +--- a/net/netfilter/x_tables.c ++++ b/net/netfilter/x_tables.c +@@ -608,7 +608,7 @@ int xt_compat_check_entry_offsets(const + return -EINVAL; + + if (strcmp(t->u.user.name, XT_STANDARD_TARGET) == 0 && +- target_offset + sizeof(struct compat_xt_standard_target) != next_offset) ++ COMPAT_XT_ALIGN(target_offset + sizeof(struct compat_xt_standard_target)) != next_offset) + return -EINVAL; + + /* compat_xt_entry match has less strict aligment requirements, +@@ -690,7 +690,7 @@ int xt_check_entry_offsets(const void *b + return -EINVAL; + + if (strcmp(t->u.user.name, XT_STANDARD_TARGET) == 0 && +- target_offset + sizeof(struct xt_standard_target) != next_offset) ++ XT_ALIGN(target_offset + sizeof(struct xt_standard_target)) != next_offset) + return -EINVAL; + + return xt_check_entry_match(elems, base + target_offset, +From 8dddd32756f6fe8e4e82a63361119b7e2384e02f Mon Sep 17 00:00:00 2001 +From: Florian Westphal <fw@strlen.de> +Date: Fri, 1 Apr 2016 14:17:32 +0200 +Subject: netfilter: arp_tables: simplify translate_compat_table args + +From: Florian Westphal <fw@strlen.de> + +commit 8dddd32756f6fe8e4e82a63361119b7e2384e02f upstream. + +Signed-off-by: Florian Westphal <fw@strlen.de> +Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> +Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> + +--- + net/ipv4/netfilter/arp_tables.c | 82 +++++++++++++++++----------------------- + 1 file changed, 36 insertions(+), 46 deletions(-) + +--- a/net/ipv4/netfilter/arp_tables.c ++++ b/net/ipv4/netfilter/arp_tables.c +@@ -1213,6 +1213,18 @@ static int do_add_counters(struct net *n + } + + #ifdef CONFIG_COMPAT ++struct compat_arpt_replace { ++ char name[XT_TABLE_MAXNAMELEN]; ++ u32 valid_hooks; ++ u32 num_entries; ++ u32 size; ++ u32 hook_entry[NF_ARP_NUMHOOKS]; ++ u32 underflow[NF_ARP_NUMHOOKS]; ++ u32 num_counters; ++ compat_uptr_t counters; ++ struct compat_arpt_entry entries[0]; ++}; ++ + static inline void compat_release_entry(struct compat_arpt_entry *e) + { + struct xt_entry_target *t; +@@ -1228,8 +1240,7 @@ check_compat_entry_size_and_hooks(struct + const unsigned char *base, + const unsigned char *limit, + const unsigned int *hook_entries, +- const unsigned int *underflows, +- const char *name) ++ const unsigned int *underflows) + { + struct xt_entry_target *t; + struct xt_target *target; +@@ -1300,7 +1311,7 @@ out: + + static int + compat_copy_entry_from_user(struct compat_arpt_entry *e, void **dstptr, +- unsigned int *size, const char *name, ++ unsigned int *size, + struct xt_table_info *newinfo, unsigned char *base) + { + struct xt_entry_target *t; +@@ -1333,14 +1344,9 @@ compat_copy_entry_from_user(struct compa + return ret; + } + +-static int translate_compat_table(const char *name, +- unsigned int valid_hooks, +- struct xt_table_info **pinfo, ++static int translate_compat_table(struct xt_table_info **pinfo, + void **pentry0, +- unsigned int total_size, +- unsigned int number, +- unsigned int *hook_entries, +- unsigned int *underflows) ++ const struct compat_arpt_replace *compatr) + { + unsigned int i, j; + struct xt_table_info *newinfo, *info; +@@ -1352,8 +1358,8 @@ static int translate_compat_table(const + + info = *pinfo; + entry0 = *pentry0; +- size = total_size; +- info->number = number; ++ size = compatr->size; ++ info->number = compatr->num_entries; + + /* Init all hooks to impossible value. */ + for (i = 0; i < NF_ARP_NUMHOOKS; i++) { +@@ -1364,40 +1370,39 @@ static int translate_compat_table(const + duprintf("translate_compat_table: size %u\n", info->size); + j = 0; + xt_compat_lock(NFPROTO_ARP); +- xt_compat_init_offsets(NFPROTO_ARP, number); ++ xt_compat_init_offsets(NFPROTO_ARP, compatr->num_entries); + /* Walk through entries, checking offsets. */ +- xt_entry_foreach(iter0, entry0, total_size) { ++ xt_entry_foreach(iter0, entry0, compatr->size) { + ret = check_compat_entry_size_and_hooks(iter0, info, &size, + entry0, +- entry0 + total_size, +- hook_entries, +- underflows, +- name); ++ entry0 + compatr->size, ++ compatr->hook_entry, ++ compatr->underflow); + if (ret != 0) + goto out_unlock; + ++j; + } + + ret = -EINVAL; +- if (j != number) { ++ if (j != compatr->num_entries) { + duprintf("translate_compat_table: %u not %u entries\n", +- j, number); ++ j, compatr->num_entries); + goto out_unlock; + } + + /* Check hooks all assigned */ + for (i = 0; i < NF_ARP_NUMHOOKS; i++) { + /* Only hooks which are valid */ +- if (!(valid_hooks & (1 << i))) ++ if (!(compatr->valid_hooks & (1 << i))) + continue; + if (info->hook_entry[i] == 0xFFFFFFFF) { + duprintf("Invalid hook entry %u %u\n", +- i, hook_entries[i]); ++ i, info->hook_entry[i]); + goto out_unlock; + } + if (info->underflow[i] == 0xFFFFFFFF) { + duprintf("Invalid underflow %u %u\n", +- i, underflows[i]); ++ i, info->underflow[i]); + goto out_unlock; + } + } +@@ -1407,17 +1412,17 @@ static int translate_compat_table(const + if (!newinfo) + goto out_unlock; + +- newinfo->number = number; ++ newinfo->number = compatr->num_entries; + for (i = 0; i < NF_ARP_NUMHOOKS; i++) { + newinfo->hook_entry[i] = info->hook_entry[i]; + newinfo->underflow[i] = info->underflow[i]; + } + entry1 = newinfo->entries; + pos = entry1; +- size = total_size; +- xt_entry_foreach(iter0, entry0, total_size) { ++ size = compatr->size; ++ xt_entry_foreach(iter0, entry0, compatr->size) { + ret = compat_copy_entry_from_user(iter0, &pos, &size, +- name, newinfo, entry1); ++ newinfo, entry1); + if (ret != 0) + break; + } +@@ -1427,7 +1432,7 @@ static int translate_compat_table(const + goto free_newinfo; + + ret = -ELOOP; +- if (!mark_source_chains(newinfo, valid_hooks, entry1)) ++ if (!mark_source_chains(newinfo, compatr->valid_hooks, entry1)) + goto free_newinfo; + + i = 0; +@@ -1438,7 +1443,7 @@ static int translate_compat_table(const + break; + } + +- ret = check_target(iter1, name); ++ ret = check_target(iter1, compatr->name); + if (ret != 0) { + xt_percpu_counter_free(iter1->counters.pcnt); + break; +@@ -1480,7 +1485,7 @@ static int translate_compat_table(const + free_newinfo: + xt_free_table_info(newinfo); + out: +- xt_entry_foreach(iter0, entry0, total_size) { ++ xt_entry_foreach(iter0, entry0, compatr->size) { + if (j-- == 0) + break; + compat_release_entry(iter0); +@@ -1492,18 +1497,6 @@ out_unlock: + goto out; + } + +-struct compat_arpt_replace { +- char name[XT_TABLE_MAXNAMELEN]; +- u32 valid_hooks; +- u32 num_entries; +- u32 size; +- u32 hook_entry[NF_ARP_NUMHOOKS]; +- u32 underflow[NF_ARP_NUMHOOKS]; +- u32 num_counters; +- compat_uptr_t counters; +- struct compat_arpt_entry entries[0]; +-}; +- + static int compat_do_replace(struct net *net, void __user *user, + unsigned int len) + { +@@ -1536,10 +1529,7 @@ static int compat_do_replace(struct net + goto free_newinfo; + } + +- ret = translate_compat_table(tmp.name, tmp.valid_hooks, +- &newinfo, &loc_cpu_entry, tmp.size, +- tmp.num_entries, tmp.hook_entry, +- tmp.underflow); ++ ret = translate_compat_table(&newinfo, &loc_cpu_entry, &tmp); + if (ret != 0) + goto free_newinfo; + +From 7d3f843eed29222254c9feab481f55175a1afcc9 Mon Sep 17 00:00:00 2001 +From: Florian Westphal <fw@strlen.de> +Date: Fri, 1 Apr 2016 14:17:30 +0200 +Subject: netfilter: ip_tables: simplify translate_compat_table args + +From: Florian Westphal <fw@strlen.de> + +commit 7d3f843eed29222254c9feab481f55175a1afcc9 upstream. + +Signed-off-by: Florian Westphal <fw@strlen.de> +Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> +Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> + +--- + net/ipv4/netfilter/ip_tables.c | 59 ++++++++++++++++------------------------- + 1 file changed, 24 insertions(+), 35 deletions(-) + +--- a/net/ipv4/netfilter/ip_tables.c ++++ b/net/ipv4/netfilter/ip_tables.c +@@ -1448,7 +1448,6 @@ compat_copy_entry_to_user(struct ipt_ent + + static int + compat_find_calc_match(struct xt_entry_match *m, +- const char *name, + const struct ipt_ip *ip, + int *size) + { +@@ -1485,8 +1484,7 @@ check_compat_entry_size_and_hooks(struct + const unsigned char *base, + const unsigned char *limit, + const unsigned int *hook_entries, +- const unsigned int *underflows, +- const char *name) ++ const unsigned int *underflows) + { + struct xt_entry_match *ematch; + struct xt_entry_target *t; +@@ -1522,7 +1520,7 @@ check_compat_entry_size_and_hooks(struct + entry_offset = (void *)e - (void *)base; + j = 0; + xt_ematch_foreach(ematch, e) { +- ret = compat_find_calc_match(ematch, name, &e->ip, &off); ++ ret = compat_find_calc_match(ematch, &e->ip, &off); + if (ret != 0) + goto release_matches; + ++j; +@@ -1571,7 +1569,7 @@ release_matches: + + static int + compat_copy_entry_from_user(struct compat_ipt_entry *e, void **dstptr, +- unsigned int *size, const char *name, ++ unsigned int *size, + struct xt_table_info *newinfo, unsigned char *base) + { + struct xt_entry_target *t; +@@ -1654,14 +1652,9 @@ compat_check_entry(struct ipt_entry *e, + + static int + translate_compat_table(struct net *net, +- const char *name, +- unsigned int valid_hooks, + struct xt_table_info **pinfo, + void **pentry0, +- unsigned int total_size, +- unsigned int number, +- unsigned int *hook_entries, +- unsigned int *underflows) ++ const struct compat_ipt_replace *compatr) + { + unsigned int i, j; + struct xt_table_info *newinfo, *info; +@@ -1673,8 +1666,8 @@ translate_compat_table(struct net *net, + + info = *pinfo; + entry0 = *pentry0; +- size = total_size; +- info->number = number; ++ size = compatr->size; ++ info->number = compatr->num_entries; + + /* Init all hooks to impossible value. */ + for (i = 0; i < NF_INET_NUMHOOKS; i++) { +@@ -1685,40 +1678,39 @@ translate_compat_table(struct net *net, + duprintf("translate_compat_table: size %u\n", info->size); + j = 0; + xt_compat_lock(AF_INET); +- xt_compat_init_offsets(AF_INET, number); ++ xt_compat_init_offsets(AF_INET, compatr->num_entries); + /* Walk through entries, checking offsets. */ +- xt_entry_foreach(iter0, entry0, total_size) { ++ xt_entry_foreach(iter0, entry0, compatr->size) { + ret = check_compat_entry_size_and_hooks(iter0, info, &size, + entry0, +- entry0 + total_size, +- hook_entries, +- underflows, +- name); ++ entry0 + compatr->size, ++ compatr->hook_entry, ++ compatr->underflow); + if (ret != 0) + goto out_unlock; + ++j; + } + + ret = -EINVAL; +- if (j != number) { ++ if (j != compatr->num_entries) { + duprintf("translate_compat_table: %u not %u entries\n", +- j, number); ++ j, compatr->num_entries); + goto out_unlock; + } + + /* Check hooks all assigned */ + for (i = 0; i < NF_INET_NUMHOOKS; i++) { + /* Only hooks which are valid */ +- if (!(valid_hooks & (1 << i))) ++ if (!(compatr->valid_hooks & (1 << i))) + continue; + if (info->hook_entry[i] == 0xFFFFFFFF) { + duprintf("Invalid hook entry %u %u\n", +- i, hook_entries[i]); ++ i, info->hook_entry[i]); + goto out_unlock; + } + if (info->underflow[i] == 0xFFFFFFFF) { + duprintf("Invalid underflow %u %u\n", +- i, underflows[i]); ++ i, info->underflow[i]); + goto out_unlock; + } + } +@@ -1728,17 +1720,17 @@ translate_compat_table(struct net *net, + if (!newinfo) + goto out_unlock; + +- newinfo->number = number; ++ newinfo->number = compatr->num_entries; + for (i = 0; i < NF_INET_NUMHOOKS; i++) { + newinfo->hook_entry[i] = info->hook_entry[i]; + newinfo->underflow[i] = info->underflow[i]; + } + entry1 = newinfo->entries; + pos = entry1; +- size = total_size; +- xt_entry_foreach(iter0, entry0, total_size) { ++ size = compatr->size; ++ xt_entry_foreach(iter0, entry0, compatr->size) { + ret = compat_copy_entry_from_user(iter0, &pos, &size, +- name, newinfo, entry1); ++ newinfo, entry1); + if (ret != 0) + break; + } +@@ -1748,12 +1740,12 @@ translate_compat_table(struct net *net, + goto free_newinfo; + + ret = -ELOOP; +- if (!mark_source_chains(newinfo, valid_hooks, entry1)) ++ if (!mark_source_chains(newinfo, compatr->valid_hooks, entry1)) + goto free_newinfo; + + i = 0; + xt_entry_foreach(iter1, entry1, newinfo->size) { +- ret = compat_check_entry(iter1, net, name); ++ ret = compat_check_entry(iter1, net, compatr->name); + if (ret != 0) + break; + ++i; +@@ -1793,7 +1785,7 @@ translate_compat_table(struct net *net, + free_newinfo: + xt_free_table_info(newinfo); + out: +- xt_entry_foreach(iter0, entry0, total_size) { ++ xt_entry_foreach(iter0, entry0, compatr->size) { + if (j-- == 0) + break; + compat_release_entry(iter0); +@@ -1838,10 +1830,7 @@ compat_do_replace(struct net *net, void + goto free_newinfo; + } + +- ret = translate_compat_table(net, tmp.name, tmp.valid_hooks, +- &newinfo, &loc_cpu_entry, tmp.size, +- tmp.num_entries, tmp.hook_entry, +- tmp.underflow); ++ ret = translate_compat_table(net, &newinfo, &loc_cpu_entry, &tmp); + if (ret != 0) + goto free_newinfo; + +From 329a0807124f12fe1c8032f95d8a8eb47047fb0e Mon Sep 17 00:00:00 2001 +From: Florian Westphal <fw@strlen.de> +Date: Fri, 1 Apr 2016 14:17:31 +0200 +Subject: netfilter: ip6_tables: simplify translate_compat_table args + +From: Florian Westphal <fw@strlen.de> + +commit 329a0807124f12fe1c8032f95d8a8eb47047fb0e upstream. + +Signed-off-by: Florian Westphal <fw@strlen.de> +Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> +Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> + +--- + net/ipv6/netfilter/ip6_tables.c | 59 ++++++++++++++++------------------------ + 1 file changed, 24 insertions(+), 35 deletions(-) + +--- a/net/ipv6/netfilter/ip6_tables.c ++++ b/net/ipv6/netfilter/ip6_tables.c +@@ -1460,7 +1460,6 @@ compat_copy_entry_to_user(struct ip6t_en + + static int + compat_find_calc_match(struct xt_entry_match *m, +- const char *name, + const struct ip6t_ip6 *ipv6, + int *size) + { +@@ -1497,8 +1496,7 @@ check_compat_entry_size_and_hooks(struct + const unsigned char *base, + const unsigned char *limit, + const unsigned int *hook_entries, +- const unsigned int *underflows, +- const char *name) ++ const unsigned int *underflows) + { + struct xt_entry_match *ematch; + struct xt_entry_target *t; +@@ -1534,7 +1532,7 @@ check_compat_entry_size_and_hooks(struct + entry_offset = (void *)e - (void *)base; + j = 0; + xt_ematch_foreach(ematch, e) { +- ret = compat_find_calc_match(ematch, name, &e->ipv6, &off); ++ ret = compat_find_calc_match(ematch, &e->ipv6, &off); + if (ret != 0) + goto release_matches; + ++j; +@@ -1583,7 +1581,7 @@ release_matches: + + static int + compat_copy_entry_from_user(struct compat_ip6t_entry *e, void **dstptr, +- unsigned int *size, const char *name, ++ unsigned int *size, + struct xt_table_info *newinfo, unsigned char *base) + { + struct xt_entry_target *t; +@@ -1663,14 +1661,9 @@ static int compat_check_entry(struct ip6 + + static int + translate_compat_table(struct net *net, +- const char *name, +- unsigned int valid_hooks, + struct xt_table_info **pinfo, + void **pentry0, +- unsigned int total_size, +- unsigned int number, +- unsigned int *hook_entries, +- unsigned int *underflows) ++ const struct compat_ip6t_replace *compatr) + { + unsigned int i, j; + struct xt_table_info *newinfo, *info; +@@ -1682,8 +1675,8 @@ translate_compat_table(struct net *net, + + info = *pinfo; + entry0 = *pentry0; +- size = total_size; +- info->number = number; ++ size = compatr->size; ++ info->number = compatr->num_entries; + + /* Init all hooks to impossible value. */ + for (i = 0; i < NF_INET_NUMHOOKS; i++) { +@@ -1694,40 +1687,39 @@ translate_compat_table(struct net *net, + duprintf("translate_compat_table: size %u\n", info->size); + j = 0; + xt_compat_lock(AF_INET6); +- xt_compat_init_offsets(AF_INET6, number); ++ xt_compat_init_offsets(AF_INET6, compatr->num_entries); + /* Walk through entries, checking offsets. */ +- xt_entry_foreach(iter0, entry0, total_size) { ++ xt_entry_foreach(iter0, entry0, compatr->size) { + ret = check_compat_entry_size_and_hooks(iter0, info, &size, + entry0, +- entry0 + total_size, +- hook_entries, +- underflows, +- name); ++ entry0 + compatr->size, ++ compatr->hook_entry, ++ compatr->underflow); + if (ret != 0) + goto out_unlock; + ++j; + } + + ret = -EINVAL; +- if (j != number) { ++ if (j != compatr->num_entries) { + duprintf("translate_compat_table: %u not %u entries\n", +- j, number); ++ j, compatr->num_entries); + goto out_unlock; + } + + /* Check hooks all assigned */ + for (i = 0; i < NF_INET_NUMHOOKS; i++) { + /* Only hooks which are valid */ +- if (!(valid_hooks & (1 << i))) ++ if (!(compatr->valid_hooks & (1 << i))) + continue; + if (info->hook_entry[i] == 0xFFFFFFFF) { + duprintf("Invalid hook entry %u %u\n", +- i, hook_entries[i]); ++ i, info->hook_entry[i]); + goto out_unlock; + } + if (info->underflow[i] == 0xFFFFFFFF) { + duprintf("Invalid underflow %u %u\n", +- i, underflows[i]); ++ i, info->underflow[i]); + goto out_unlock; + } + } +@@ -1737,17 +1729,17 @@ translate_compat_table(struct net *net, + if (!newinfo) + goto out_unlock; + +- newinfo->number = number; ++ newinfo->number = compatr->num_entries; + for (i = 0; i < NF_INET_NUMHOOKS; i++) { + newinfo->hook_entry[i] = info->hook_entry[i]; + newinfo->underflow[i] = info->underflow[i]; + } + entry1 = newinfo->entries; + pos = entry1; +- size = total_size; +- xt_entry_foreach(iter0, entry0, total_size) { ++ size = compatr->size; ++ xt_entry_foreach(iter0, entry0, compatr->size) { + ret = compat_copy_entry_from_user(iter0, &pos, &size, +- name, newinfo, entry1); ++ newinfo, entry1); + if (ret != 0) + break; + } +@@ -1757,12 +1749,12 @@ translate_compat_table(struct net *net, + goto free_newinfo; + + ret = -ELOOP; +- if (!mark_source_chains(newinfo, valid_hooks, entry1)) ++ if (!mark_source_chains(newinfo, compatr->valid_hooks, entry1)) + goto free_newinfo; + + i = 0; + xt_entry_foreach(iter1, entry1, newinfo->size) { +- ret = compat_check_entry(iter1, net, name); ++ ret = compat_check_entry(iter1, net, compatr->name); + if (ret != 0) + break; + ++i; +@@ -1802,7 +1794,7 @@ translate_compat_table(struct net *net, + free_newinfo: + xt_free_table_info(newinfo); + out: +- xt_entry_foreach(iter0, entry0, total_size) { ++ xt_entry_foreach(iter0, entry0, compatr->size) { + if (j-- == 0) + break; + compat_release_entry(iter0); +@@ -1847,10 +1839,7 @@ compat_do_replace(struct net *net, void + goto free_newinfo; + } + +- ret = translate_compat_table(net, tmp.name, tmp.valid_hooks, +- &newinfo, &loc_cpu_entry, tmp.size, +- tmp.num_entries, tmp.hook_entry, +- tmp.underflow); ++ ret = translate_compat_table(net, &newinfo, &loc_cpu_entry, &tmp); + if (ret != 0) + goto free_newinfo; + +From 0188346f21e6546498c2a0f84888797ad4063fc5 Mon Sep 17 00:00:00 2001 +From: Florian Westphal <fw@strlen.de> +Date: Fri, 1 Apr 2016 14:17:33 +0200 +Subject: netfilter: x_tables: xt_compat_match_from_user doesn't need a retval + +From: Florian Westphal <fw@strlen.de> + +commit 0188346f21e6546498c2a0f84888797ad4063fc5 upstream. + +Always returned 0. + +Signed-off-by: Florian Westphal <fw@strlen.de> +Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> +Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> + +--- + include/linux/netfilter/x_tables.h | 2 +- + net/ipv4/netfilter/arp_tables.c | 17 +++++------------ + net/ipv4/netfilter/ip_tables.c | 26 +++++++++----------------- + net/ipv6/netfilter/ip6_tables.c | 27 +++++++++------------------ + net/netfilter/x_tables.c | 5 ++--- + 5 files changed, 26 insertions(+), 51 deletions(-) + +--- a/include/linux/netfilter/x_tables.h ++++ b/include/linux/netfilter/x_tables.h +@@ -482,7 +482,7 @@ void xt_compat_init_offsets(u_int8_t af, + int xt_compat_calc_jump(u_int8_t af, unsigned int offset); + + int xt_compat_match_offset(const struct xt_match *match); +-int xt_compat_match_from_user(struct xt_entry_match *m, void **dstptr, ++void xt_compat_match_from_user(struct xt_entry_match *m, void **dstptr, + unsigned int *size); + int xt_compat_match_to_user(const struct xt_entry_match *m, + void __user **dstptr, unsigned int *size); +--- a/net/ipv4/netfilter/arp_tables.c ++++ b/net/ipv4/netfilter/arp_tables.c +@@ -1309,7 +1309,7 @@ out: + return ret; + } + +-static int ++static void + compat_copy_entry_from_user(struct compat_arpt_entry *e, void **dstptr, + unsigned int *size, + struct xt_table_info *newinfo, unsigned char *base) +@@ -1318,9 +1318,8 @@ compat_copy_entry_from_user(struct compa + struct xt_target *target; + struct arpt_entry *de; + unsigned int origsize; +- int ret, h; ++ int h; + +- ret = 0; + origsize = *size; + de = (struct arpt_entry *)*dstptr; + memcpy(de, e, sizeof(struct arpt_entry)); +@@ -1341,7 +1340,6 @@ compat_copy_entry_from_user(struct compa + if ((unsigned char *)de - base < newinfo->underflow[h]) + newinfo->underflow[h] -= origsize - *size; + } +- return ret; + } + + static int translate_compat_table(struct xt_table_info **pinfo, +@@ -1420,16 +1418,11 @@ static int translate_compat_table(struct + entry1 = newinfo->entries; + pos = entry1; + size = compatr->size; +- xt_entry_foreach(iter0, entry0, compatr->size) { +- ret = compat_copy_entry_from_user(iter0, &pos, &size, +- newinfo, entry1); +- if (ret != 0) +- break; +- } ++ xt_entry_foreach(iter0, entry0, compatr->size) ++ compat_copy_entry_from_user(iter0, &pos, &size, ++ newinfo, entry1); + xt_compat_flush_offsets(NFPROTO_ARP); + xt_compat_unlock(NFPROTO_ARP); +- if (ret) +- goto free_newinfo; + + ret = -ELOOP; + if (!mark_source_chains(newinfo, compatr->valid_hooks, entry1)) +--- a/net/ipv4/netfilter/ip_tables.c ++++ b/net/ipv4/netfilter/ip_tables.c +@@ -1567,7 +1567,7 @@ release_matches: + return ret; + } + +-static int ++static void + compat_copy_entry_from_user(struct compat_ipt_entry *e, void **dstptr, + unsigned int *size, + struct xt_table_info *newinfo, unsigned char *base) +@@ -1576,10 +1576,9 @@ compat_copy_entry_from_user(struct compa + struct xt_target *target; + struct ipt_entry *de; + unsigned int origsize; +- int ret, h; ++ int h; + struct xt_entry_match *ematch; + +- ret = 0; + origsize = *size; + de = (struct ipt_entry *)*dstptr; + memcpy(de, e, sizeof(struct ipt_entry)); +@@ -1588,11 +1587,9 @@ compat_copy_entry_from_user(struct compa + *dstptr += sizeof(struct ipt_entry); + *size += sizeof(struct ipt_entry) - sizeof(struct compat_ipt_entry); + +- xt_ematch_foreach(ematch, e) { +- ret = xt_compat_match_from_user(ematch, dstptr, size); +- if (ret != 0) +- return ret; +- } ++ xt_ematch_foreach(ematch, e) ++ xt_compat_match_from_user(ematch, dstptr, size); ++ + de->target_offset = e->target_offset - (origsize - *size); + t = compat_ipt_get_target(e); + target = t->u.kernel.target; +@@ -1605,7 +1602,6 @@ compat_copy_entry_from_user(struct compa + if ((unsigned char *)de - base < newinfo->underflow[h]) + newinfo->underflow[h] -= origsize - *size; + } +- return ret; + } + + static int +@@ -1728,16 +1724,12 @@ translate_compat_table(struct net *net, + entry1 = newinfo->entries; + pos = entry1; + size = compatr->size; +- xt_entry_foreach(iter0, entry0, compatr->size) { +- ret = compat_copy_entry_from_user(iter0, &pos, &size, +- newinfo, entry1); +- if (ret != 0) +- break; +- } ++ xt_entry_foreach(iter0, entry0, compatr->size) ++ compat_copy_entry_from_user(iter0, &pos, &size, ++ newinfo, entry1); ++ + xt_compat_flush_offsets(AF_INET); + xt_compat_unlock(AF_INET); +- if (ret) +- goto free_newinfo; + + ret = -ELOOP; + if (!mark_source_chains(newinfo, compatr->valid_hooks, entry1)) +--- a/net/ipv6/netfilter/ip6_tables.c ++++ b/net/ipv6/netfilter/ip6_tables.c +@@ -1579,7 +1579,7 @@ release_matches: + return ret; + } + +-static int ++static void + compat_copy_entry_from_user(struct compat_ip6t_entry *e, void **dstptr, + unsigned int *size, + struct xt_table_info *newinfo, unsigned char *base) +@@ -1587,10 +1587,9 @@ compat_copy_entry_from_user(struct compa + struct xt_entry_target *t; + struct ip6t_entry *de; + unsigned int origsize; +- int ret, h; ++ int h; + struct xt_entry_match *ematch; + +- ret = 0; + origsize = *size; + de = (struct ip6t_entry *)*dstptr; + memcpy(de, e, sizeof(struct ip6t_entry)); +@@ -1599,11 +1598,9 @@ compat_copy_entry_from_user(struct compa + *dstptr += sizeof(struct ip6t_entry); + *size += sizeof(struct ip6t_entry) - sizeof(struct compat_ip6t_entry); + +- xt_ematch_foreach(ematch, e) { +- ret = xt_compat_match_from_user(ematch, dstptr, size); +- if (ret != 0) +- return ret; +- } ++ xt_ematch_foreach(ematch, e) ++ xt_compat_match_from_user(ematch, dstptr, size); ++ + de->target_offset = e->target_offset - (origsize - *size); + t = compat_ip6t_get_target(e); + xt_compat_target_from_user(t, dstptr, size); +@@ -1615,7 +1612,6 @@ compat_copy_entry_from_user(struct compa + if ((unsigned char *)de - base < newinfo->underflow[h]) + newinfo->underflow[h] -= origsize - *size; + } +- return ret; + } + + static int compat_check_entry(struct ip6t_entry *e, struct net *net, +@@ -1736,17 +1732,12 @@ translate_compat_table(struct net *net, + } + entry1 = newinfo->entries; + pos = entry1; +- size = compatr->size; +- xt_entry_foreach(iter0, entry0, compatr->size) { +- ret = compat_copy_entry_from_user(iter0, &pos, &size, +- newinfo, entry1); +- if (ret != 0) +- break; +- } ++ xt_entry_foreach(iter0, entry0, compatr->size) ++ compat_copy_entry_from_user(iter0, &pos, &size, ++ newinfo, entry1); ++ + xt_compat_flush_offsets(AF_INET6); + xt_compat_unlock(AF_INET6); +- if (ret) +- goto free_newinfo; + + ret = -ELOOP; + if (!mark_source_chains(newinfo, compatr->valid_hooks, entry1)) +--- a/net/netfilter/x_tables.c ++++ b/net/netfilter/x_tables.c +@@ -525,8 +525,8 @@ int xt_compat_match_offset(const struct + } + EXPORT_SYMBOL_GPL(xt_compat_match_offset); + +-int xt_compat_match_from_user(struct xt_entry_match *m, void **dstptr, +- unsigned int *size) ++void xt_compat_match_from_user(struct xt_entry_match *m, void **dstptr, ++ unsigned int *size) + { + const struct xt_match *match = m->u.kernel.match; + struct compat_xt_entry_match *cm = (struct compat_xt_entry_match *)m; +@@ -548,7 +548,6 @@ int xt_compat_match_from_user(struct xt_ + + *size += off; + *dstptr += msize; +- return 0; + } + EXPORT_SYMBOL_GPL(xt_compat_match_from_user); + +From 09d9686047dbbe1cf4faa558d3ecc4aae2046054 Mon Sep 17 00:00:00 2001 +From: Florian Westphal <fw@strlen.de> +Date: Fri, 1 Apr 2016 14:17:34 +0200 +Subject: netfilter: x_tables: do compat validation via translate_table + +From: Florian Westphal <fw@strlen.de> + +commit 09d9686047dbbe1cf4faa558d3ecc4aae2046054 upstream. + +This looks like refactoring, but its also a bug fix. + +Problem is that the compat path (32bit iptables, 64bit kernel) lacks a few +sanity tests that are done in the normal path. + +For example, we do not check for underflows and the base chain policies. + +While its possible to also add such checks to the compat path, its more +copy&pastry, for instance we cannot reuse check_underflow() helper as +e->target_offset differs in the compat case. + +Other problem is that it makes auditing for validation errors harder; two +places need to be checked and kept in sync. + +At a high level 32 bit compat works like this: +1- initial pass over blob: + validate match/entry offsets, bounds checking + lookup all matches and targets + do bookkeeping wrt. size delta of 32/64bit structures + assign match/target.u.kernel pointer (points at kernel + implementation, needed to access ->compatsize etc.) + +2- allocate memory according to the total bookkeeping size to + contain the translated ruleset + +3- second pass over original blob: + for each entry, copy the 32bit representation to the newly allocated + memory. This also does any special match translations (e.g. + adjust 32bit to 64bit longs, etc). + +4- check if ruleset is free of loops (chase all jumps) + +5-first pass over translated blob: + call the checkentry function of all matches and targets. + +The alternative implemented by this patch is to drop steps 3&4 from the +compat process, the translation is changed into an intermediate step +rather than a full 1:1 translate_table replacement. + +In the 2nd pass (step #3), change the 64bit ruleset back to a kernel +representation, i.e. put() the kernel pointer and restore ->u.user.name . + +This gets us a 64bit ruleset that is in the format generated by a 64bit +iptables userspace -- we can then use translate_table() to get the +'native' sanity checks. + +This has two drawbacks: + +1. we re-validate all the match and target entry structure sizes even +though compat translation is supposed to never generate bogus offsets. +2. we put and then re-lookup each match and target. + +THe upside is that we get all sanity tests and ruleset validations +provided by the normal path and can remove some duplicated compat code. + +iptables-restore time of autogenerated ruleset with 300k chains of form +-A CHAIN0001 -m limit --limit 1/s -j CHAIN0002 +-A CHAIN0002 -m limit --limit 1/s -j CHAIN0003 + +shows no noticeable differences in restore times: +old: 0m30.796s +new: 0m31.521s +64bit: 0m25.674s + +Signed-off-by: Florian Westphal <fw@strlen.de> +Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> +Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> + +--- + net/ipv4/netfilter/arp_tables.c | 114 +++++------------------------ + net/ipv4/netfilter/ip_tables.c | 155 +++++++--------------------------------- + net/ipv6/netfilter/ip6_tables.c | 148 +++++--------------------------------- + net/netfilter/x_tables.c | 8 ++ + 4 files changed, 83 insertions(+), 342 deletions(-) + +--- a/net/ipv4/netfilter/arp_tables.c ++++ b/net/ipv4/netfilter/arp_tables.c +@@ -1233,19 +1233,17 @@ static inline void compat_release_entry( + module_put(t->u.kernel.target->me); + } + +-static inline int ++static int + check_compat_entry_size_and_hooks(struct compat_arpt_entry *e, + struct xt_table_info *newinfo, + unsigned int *size, + const unsigned char *base, +- const unsigned char *limit, +- const unsigned int *hook_entries, +- const unsigned int *underflows) ++ const unsigned char *limit) + { + struct xt_entry_target *t; + struct xt_target *target; + unsigned int entry_offset; +- int ret, off, h; ++ int ret, off; + + duprintf("check_compat_entry_size_and_hooks %p\n", e); + if ((unsigned long)e % __alignof__(struct compat_arpt_entry) != 0 || +@@ -1290,17 +1288,6 @@ check_compat_entry_size_and_hooks(struct + if (ret) + goto release_target; + +- /* Check hooks & underflows */ +- for (h = 0; h < NF_ARP_NUMHOOKS; h++) { +- if ((unsigned char *)e - base == hook_entries[h]) +- newinfo->hook_entry[h] = hook_entries[h]; +- if ((unsigned char *)e - base == underflows[h]) +- newinfo->underflow[h] = underflows[h]; +- } +- +- /* Clear counters and comefrom */ +- memset(&e->counters, 0, sizeof(e->counters)); +- e->comefrom = 0; + return 0; + + release_target: +@@ -1350,7 +1337,7 @@ static int translate_compat_table(struct + struct xt_table_info *newinfo, *info; + void *pos, *entry0, *entry1; + struct compat_arpt_entry *iter0; +- struct arpt_entry *iter1; ++ struct arpt_replace repl; + unsigned int size; + int ret = 0; + +@@ -1359,12 +1346,6 @@ static int translate_compat_table(struct + size = compatr->size; + info->number = compatr->num_entries; + +- /* Init all hooks to impossible value. */ +- for (i = 0; i < NF_ARP_NUMHOOKS; i++) { +- info->hook_entry[i] = 0xFFFFFFFF; +- info->underflow[i] = 0xFFFFFFFF; +- } +- + duprintf("translate_compat_table: size %u\n", info->size); + j = 0; + xt_compat_lock(NFPROTO_ARP); +@@ -1373,9 +1354,7 @@ static int translate_compat_table(struct + xt_entry_foreach(iter0, entry0, compatr->size) { + ret = check_compat_entry_size_and_hooks(iter0, info, &size, + entry0, +- entry0 + compatr->size, +- compatr->hook_entry, +- compatr->underflow); ++ entry0 + compatr->size); + if (ret != 0) + goto out_unlock; + ++j; +@@ -1388,23 +1367,6 @@ static int translate_compat_table(struct + goto out_unlock; + } + +- /* Check hooks all assigned */ +- for (i = 0; i < NF_ARP_NUMHOOKS; i++) { +- /* Only hooks which are valid */ +- if (!(compatr->valid_hooks & (1 << i))) +- continue; +- if (info->hook_entry[i] == 0xFFFFFFFF) { +- duprintf("Invalid hook entry %u %u\n", +- i, info->hook_entry[i]); +- goto out_unlock; +- } +- if (info->underflow[i] == 0xFFFFFFFF) { +- duprintf("Invalid underflow %u %u\n", +- i, info->underflow[i]); +- goto out_unlock; +- } +- } +- + ret = -ENOMEM; + newinfo = xt_alloc_table_info(size); + if (!newinfo) +@@ -1421,55 +1383,26 @@ static int translate_compat_table(struct + xt_entry_foreach(iter0, entry0, compatr->size) + compat_copy_entry_from_user(iter0, &pos, &size, + newinfo, entry1); ++ ++ /* all module references in entry0 are now gone */ ++ + xt_compat_flush_offsets(NFPROTO_ARP); + xt_compat_unlock(NFPROTO_ARP); + +- ret = -ELOOP; +- if (!mark_source_chains(newinfo, compatr->valid_hooks, entry1)) +- goto free_newinfo; +- +- i = 0; +- xt_entry_foreach(iter1, entry1, newinfo->size) { +- iter1->counters.pcnt = xt_percpu_counter_alloc(); +- if (IS_ERR_VALUE(iter1->counters.pcnt)) { +- ret = -ENOMEM; +- break; +- } ++ memcpy(&repl, compatr, sizeof(*compatr)); + +- ret = check_target(iter1, compatr->name); +- if (ret != 0) { +- xt_percpu_counter_free(iter1->counters.pcnt); +- break; +- } +- ++i; +- if (strcmp(arpt_get_target(iter1)->u.user.name, +- XT_ERROR_TARGET) == 0) +- ++newinfo->stacksize; +- } +- if (ret) { +- /* +- * The first i matches need cleanup_entry (calls ->destroy) +- * because they had called ->check already. The other j-i +- * entries need only release. +- */ +- int skip = i; +- j -= i; +- xt_entry_foreach(iter0, entry0, newinfo->size) { +- if (skip-- > 0) +- continue; +- if (j-- == 0) +- break; +- compat_release_entry(iter0); +- } +- xt_entry_foreach(iter1, entry1, newinfo->size) { +- if (i-- == 0) +- break; +- cleanup_entry(iter1); +- } +- xt_free_table_info(newinfo); +- return ret; ++ for (i = 0; i < NF_ARP_NUMHOOKS; i++) { ++ repl.hook_entry[i] = newinfo->hook_entry[i]; ++ repl.underflow[i] = newinfo->underflow[i]; + } + ++ repl.num_counters = 0; ++ repl.counters = NULL; ++ repl.size = newinfo->size; ++ ret = translate_table(newinfo, entry1, &repl); ++ if (ret) ++ goto free_newinfo; ++ + *pinfo = newinfo; + *pentry0 = entry1; + xt_free_table_info(info); +@@ -1477,17 +1410,16 @@ static int translate_compat_table(struct + + free_newinfo: + xt_free_table_info(newinfo); +-out: ++ return ret; ++out_unlock: ++ xt_compat_flush_offsets(NFPROTO_ARP); ++ xt_compat_unlock(NFPROTO_ARP); + xt_entry_foreach(iter0, entry0, compatr->size) { + if (j-- == 0) + break; + compat_release_entry(iter0); + } + return ret; +-out_unlock: +- xt_compat_flush_offsets(NFPROTO_ARP); +- xt_compat_unlock(NFPROTO_ARP); +- goto out; + } + + static int compat_do_replace(struct net *net, void __user *user, +--- a/net/ipv4/netfilter/ip_tables.c ++++ b/net/ipv4/netfilter/ip_tables.c +@@ -1482,16 +1482,14 @@ check_compat_entry_size_and_hooks(struct + struct xt_table_info *newinfo, + unsigned int *size, + const unsigned char *base, +- const unsigned char *limit, +- const unsigned int *hook_entries, +- const unsigned int *underflows) ++ const unsigned char *limit) + { + struct xt_entry_match *ematch; + struct xt_entry_target *t; + struct xt_target *target; + unsigned int entry_offset; + unsigned int j; +- int ret, off, h; ++ int ret, off; + + duprintf("check_compat_entry_size_and_hooks %p\n", e); + if ((unsigned long)e % __alignof__(struct compat_ipt_entry) != 0 || +@@ -1543,17 +1541,6 @@ check_compat_entry_size_and_hooks(struct + if (ret) + goto out; + +- /* Check hooks & underflows */ +- for (h = 0; h < NF_INET_NUMHOOKS; h++) { +- if ((unsigned char *)e - base == hook_entries[h]) +- newinfo->hook_entry[h] = hook_entries[h]; +- if ((unsigned char *)e - base == underflows[h]) +- newinfo->underflow[h] = underflows[h]; +- } +- +- /* Clear counters and comefrom */ +- memset(&e->counters, 0, sizeof(e->counters)); +- e->comefrom = 0; + return 0; + + out: +@@ -1596,6 +1583,7 @@ compat_copy_entry_from_user(struct compa + xt_compat_target_from_user(t, dstptr, size); + + de->next_offset = e->next_offset - (origsize - *size); ++ + for (h = 0; h < NF_INET_NUMHOOKS; h++) { + if ((unsigned char *)de - base < newinfo->hook_entry[h]) + newinfo->hook_entry[h] -= origsize - *size; +@@ -1605,48 +1593,6 @@ compat_copy_entry_from_user(struct compa + } + + static int +-compat_check_entry(struct ipt_entry *e, struct net *net, const char *name) +-{ +- struct xt_entry_match *ematch; +- struct xt_mtchk_param mtpar; +- unsigned int j; +- int ret = 0; +- +- e->counters.pcnt = xt_percpu_counter_alloc(); +- if (IS_ERR_VALUE(e->counters.pcnt)) +- return -ENOMEM; +- +- j = 0; +- mtpar.net = net; +- mtpar.table = name; +- mtpar.entryinfo = &e->ip; +- mtpar.hook_mask = e->comefrom; +- mtpar.family = NFPROTO_IPV4; +- xt_ematch_foreach(ematch, e) { +- ret = check_match(ematch, &mtpar); +- if (ret != 0) +- goto cleanup_matches; +- ++j; +- } +- +- ret = check_target(e, net, name); +- if (ret) +- goto cleanup_matches; +- return 0; +- +- cleanup_matches: +- xt_ematch_foreach(ematch, e) { +- if (j-- == 0) +- break; +- cleanup_match(ematch, net); +- } +- +- xt_percpu_counter_free(e->counters.pcnt); +- +- return ret; +-} +- +-static int + translate_compat_table(struct net *net, + struct xt_table_info **pinfo, + void **pentry0, +@@ -1656,7 +1602,7 @@ translate_compat_table(struct net *net, + struct xt_table_info *newinfo, *info; + void *pos, *entry0, *entry1; + struct compat_ipt_entry *iter0; +- struct ipt_entry *iter1; ++ struct ipt_replace repl; + unsigned int size; + int ret; + +@@ -1665,12 +1611,6 @@ translate_compat_table(struct net *net, + size = compatr->size; + info->number = compatr->num_entries; + +- /* Init all hooks to impossible value. */ +- for (i = 0; i < NF_INET_NUMHOOKS; i++) { +- info->hook_entry[i] = 0xFFFFFFFF; +- info->underflow[i] = 0xFFFFFFFF; +- } +- + duprintf("translate_compat_table: size %u\n", info->size); + j = 0; + xt_compat_lock(AF_INET); +@@ -1679,9 +1619,7 @@ translate_compat_table(struct net *net, + xt_entry_foreach(iter0, entry0, compatr->size) { + ret = check_compat_entry_size_and_hooks(iter0, info, &size, + entry0, +- entry0 + compatr->size, +- compatr->hook_entry, +- compatr->underflow); ++ entry0 + compatr->size); + if (ret != 0) + goto out_unlock; + ++j; +@@ -1694,23 +1632,6 @@ translate_compat_table(struct net *net, + goto out_unlock; + } + +- /* Check hooks all assigned */ +- for (i = 0; i < NF_INET_NUMHOOKS; i++) { +- /* Only hooks which are valid */ +- if (!(compatr->valid_hooks & (1 << i))) +- continue; +- if (info->hook_entry[i] == 0xFFFFFFFF) { +- duprintf("Invalid hook entry %u %u\n", +- i, info->hook_entry[i]); +- goto out_unlock; +- } +- if (info->underflow[i] == 0xFFFFFFFF) { +- duprintf("Invalid underflow %u %u\n", +- i, info->underflow[i]); +- goto out_unlock; +- } +- } +- + ret = -ENOMEM; + newinfo = xt_alloc_table_info(size); + if (!newinfo) +@@ -1718,8 +1639,8 @@ translate_compat_table(struct net *net, + + newinfo->number = compatr->num_entries; + for (i = 0; i < NF_INET_NUMHOOKS; i++) { +- newinfo->hook_entry[i] = info->hook_entry[i]; +- newinfo->underflow[i] = info->underflow[i]; ++ newinfo->hook_entry[i] = compatr->hook_entry[i]; ++ newinfo->underflow[i] = compatr->underflow[i]; + } + entry1 = newinfo->entries; + pos = entry1; +@@ -1728,47 +1649,30 @@ translate_compat_table(struct net *net, + compat_copy_entry_from_user(iter0, &pos, &size, + newinfo, entry1); + ++ /* all module references in entry0 are now gone. ++ * entry1/newinfo contains a 64bit ruleset that looks exactly as ++ * generated by 64bit userspace. ++ * ++ * Call standard translate_table() to validate all hook_entrys, ++ * underflows, check for loops, etc. ++ */ + xt_compat_flush_offsets(AF_INET); + xt_compat_unlock(AF_INET); + +- ret = -ELOOP; +- if (!mark_source_chains(newinfo, compatr->valid_hooks, entry1)) +- goto free_newinfo; ++ memcpy(&repl, compatr, sizeof(*compatr)); + +- i = 0; +- xt_entry_foreach(iter1, entry1, newinfo->size) { +- ret = compat_check_entry(iter1, net, compatr->name); +- if (ret != 0) +- break; +- ++i; +- if (strcmp(ipt_get_target(iter1)->u.user.name, +- XT_ERROR_TARGET) == 0) +- ++newinfo->stacksize; +- } +- if (ret) { +- /* +- * The first i matches need cleanup_entry (calls ->destroy) +- * because they had called ->check already. The other j-i +- * entries need only release. +- */ +- int skip = i; +- j -= i; +- xt_entry_foreach(iter0, entry0, newinfo->size) { +- if (skip-- > 0) +- continue; +- if (j-- == 0) +- break; +- compat_release_entry(iter0); +- } +- xt_entry_foreach(iter1, entry1, newinfo->size) { +- if (i-- == 0) +- break; +- cleanup_entry(iter1, net); +- } +- xt_free_table_info(newinfo); +- return ret; ++ for (i = 0; i < NF_INET_NUMHOOKS; i++) { ++ repl.hook_entry[i] = newinfo->hook_entry[i]; ++ repl.underflow[i] = newinfo->underflow[i]; + } + ++ repl.num_counters = 0; ++ repl.counters = NULL; ++ repl.size = newinfo->size; ++ ret = translate_table(net, newinfo, entry1, &repl); ++ if (ret) ++ goto free_newinfo; ++ + *pinfo = newinfo; + *pentry0 = entry1; + xt_free_table_info(info); +@@ -1776,17 +1680,16 @@ translate_compat_table(struct net *net, + + free_newinfo: + xt_free_table_info(newinfo); +-out: ++ return ret; ++out_unlock: ++ xt_compat_flush_offsets(AF_INET); ++ xt_compat_unlock(AF_INET); + xt_entry_foreach(iter0, entry0, compatr->size) { + if (j-- == 0) + break; + compat_release_entry(iter0); + } + return ret; +-out_unlock: +- xt_compat_flush_offsets(AF_INET); +- xt_compat_unlock(AF_INET); +- goto out; + } + + static int +--- a/net/ipv6/netfilter/ip6_tables.c ++++ b/net/ipv6/netfilter/ip6_tables.c +@@ -1494,16 +1494,14 @@ check_compat_entry_size_and_hooks(struct + struct xt_table_info *newinfo, + unsigned int *size, + const unsigned char *base, +- const unsigned char *limit, +- const unsigned int *hook_entries, +- const unsigned int *underflows) ++ const unsigned char *limit) + { + struct xt_entry_match *ematch; + struct xt_entry_target *t; + struct xt_target *target; + unsigned int entry_offset; + unsigned int j; +- int ret, off, h; ++ int ret, off; + + duprintf("check_compat_entry_size_and_hooks %p\n", e); + if ((unsigned long)e % __alignof__(struct compat_ip6t_entry) != 0 || +@@ -1555,17 +1553,6 @@ check_compat_entry_size_and_hooks(struct + if (ret) + goto out; + +- /* Check hooks & underflows */ +- for (h = 0; h < NF_INET_NUMHOOKS; h++) { +- if ((unsigned char *)e - base == hook_entries[h]) +- newinfo->hook_entry[h] = hook_entries[h]; +- if ((unsigned char *)e - base == underflows[h]) +- newinfo->underflow[h] = underflows[h]; +- } +- +- /* Clear counters and comefrom */ +- memset(&e->counters, 0, sizeof(e->counters)); +- e->comefrom = 0; + return 0; + + out: +@@ -1614,47 +1601,6 @@ compat_copy_entry_from_user(struct compa + } + } + +-static int compat_check_entry(struct ip6t_entry *e, struct net *net, +- const char *name) +-{ +- unsigned int j; +- int ret = 0; +- struct xt_mtchk_param mtpar; +- struct xt_entry_match *ematch; +- +- e->counters.pcnt = xt_percpu_counter_alloc(); +- if (IS_ERR_VALUE(e->counters.pcnt)) +- return -ENOMEM; +- j = 0; +- mtpar.net = net; +- mtpar.table = name; +- mtpar.entryinfo = &e->ipv6; +- mtpar.hook_mask = e->comefrom; +- mtpar.family = NFPROTO_IPV6; +- xt_ematch_foreach(ematch, e) { +- ret = check_match(ematch, &mtpar); +- if (ret != 0) +- goto cleanup_matches; +- ++j; +- } +- +- ret = check_target(e, net, name); +- if (ret) +- goto cleanup_matches; +- return 0; +- +- cleanup_matches: +- xt_ematch_foreach(ematch, e) { +- if (j-- == 0) +- break; +- cleanup_match(ematch, net); +- } +- +- xt_percpu_counter_free(e->counters.pcnt); +- +- return ret; +-} +- + static int + translate_compat_table(struct net *net, + struct xt_table_info **pinfo, +@@ -1665,7 +1611,7 @@ translate_compat_table(struct net *net, + struct xt_table_info *newinfo, *info; + void *pos, *entry0, *entry1; + struct compat_ip6t_entry *iter0; +- struct ip6t_entry *iter1; ++ struct ip6t_replace repl; + unsigned int size; + int ret = 0; + +@@ -1674,12 +1620,6 @@ translate_compat_table(struct net *net, + size = compatr->size; + info->number = compatr->num_entries; + +- /* Init all hooks to impossible value. */ +- for (i = 0; i < NF_INET_NUMHOOKS; i++) { +- info->hook_entry[i] = 0xFFFFFFFF; +- info->underflow[i] = 0xFFFFFFFF; +- } +- + duprintf("translate_compat_table: size %u\n", info->size); + j = 0; + xt_compat_lock(AF_INET6); +@@ -1688,9 +1628,7 @@ translate_compat_table(struct net *net, + xt_entry_foreach(iter0, entry0, compatr->size) { + ret = check_compat_entry_size_and_hooks(iter0, info, &size, + entry0, +- entry0 + compatr->size, +- compatr->hook_entry, +- compatr->underflow); ++ entry0 + compatr->size); + if (ret != 0) + goto out_unlock; + ++j; +@@ -1703,23 +1641,6 @@ translate_compat_table(struct net *net, + goto out_unlock; + } + +- /* Check hooks all assigned */ +- for (i = 0; i < NF_INET_NUMHOOKS; i++) { +- /* Only hooks which are valid */ +- if (!(compatr->valid_hooks & (1 << i))) +- continue; +- if (info->hook_entry[i] == 0xFFFFFFFF) { +- duprintf("Invalid hook entry %u %u\n", +- i, info->hook_entry[i]); +- goto out_unlock; +- } +- if (info->underflow[i] == 0xFFFFFFFF) { +- duprintf("Invalid underflow %u %u\n", +- i, info->underflow[i]); +- goto out_unlock; +- } +- } +- + ret = -ENOMEM; + newinfo = xt_alloc_table_info(size); + if (!newinfo) +@@ -1727,56 +1648,34 @@ translate_compat_table(struct net *net, + + newinfo->number = compatr->num_entries; + for (i = 0; i < NF_INET_NUMHOOKS; i++) { +- newinfo->hook_entry[i] = info->hook_entry[i]; +- newinfo->underflow[i] = info->underflow[i]; ++ newinfo->hook_entry[i] = compatr->hook_entry[i]; ++ newinfo->underflow[i] = compatr->underflow[i]; + } + entry1 = newinfo->entries; + pos = entry1; ++ size = compatr->size; + xt_entry_foreach(iter0, entry0, compatr->size) + compat_copy_entry_from_user(iter0, &pos, &size, + newinfo, entry1); + ++ /* all module references in entry0 are now gone. */ + xt_compat_flush_offsets(AF_INET6); + xt_compat_unlock(AF_INET6); + +- ret = -ELOOP; +- if (!mark_source_chains(newinfo, compatr->valid_hooks, entry1)) +- goto free_newinfo; ++ memcpy(&repl, compatr, sizeof(*compatr)); + +- i = 0; +- xt_entry_foreach(iter1, entry1, newinfo->size) { +- ret = compat_check_entry(iter1, net, compatr->name); +- if (ret != 0) +- break; +- ++i; +- if (strcmp(ip6t_get_target(iter1)->u.user.name, +- XT_ERROR_TARGET) == 0) +- ++newinfo->stacksize; +- } +- if (ret) { +- /* +- * The first i matches need cleanup_entry (calls ->destroy) +- * because they had called ->check already. The other j-i +- * entries need only release. +- */ +- int skip = i; +- j -= i; +- xt_entry_foreach(iter0, entry0, newinfo->size) { +- if (skip-- > 0) +- continue; +- if (j-- == 0) +- break; +- compat_release_entry(iter0); +- } +- xt_entry_foreach(iter1, entry1, newinfo->size) { +- if (i-- == 0) +- break; +- cleanup_entry(iter1, net); +- } +- xt_free_table_info(newinfo); +- return ret; ++ for (i = 0; i < NF_INET_NUMHOOKS; i++) { ++ repl.hook_entry[i] = newinfo->hook_entry[i]; ++ repl.underflow[i] = newinfo->underflow[i]; + } + ++ repl.num_counters = 0; ++ repl.counters = NULL; ++ repl.size = newinfo->size; ++ ret = translate_table(net, newinfo, entry1, &repl); ++ if (ret) ++ goto free_newinfo; ++ + *pinfo = newinfo; + *pentry0 = entry1; + xt_free_table_info(info); +@@ -1784,17 +1683,16 @@ translate_compat_table(struct net *net, + + free_newinfo: + xt_free_table_info(newinfo); +-out: ++ return ret; ++out_unlock: ++ xt_compat_flush_offsets(AF_INET6); ++ xt_compat_unlock(AF_INET6); + xt_entry_foreach(iter0, entry0, compatr->size) { + if (j-- == 0) + break; + compat_release_entry(iter0); + } + return ret; +-out_unlock: +- xt_compat_flush_offsets(AF_INET6); +- xt_compat_unlock(AF_INET6); +- goto out; + } + + static int +--- a/net/netfilter/x_tables.c ++++ b/net/netfilter/x_tables.c +@@ -532,6 +532,7 @@ void xt_compat_match_from_user(struct xt + struct compat_xt_entry_match *cm = (struct compat_xt_entry_match *)m; + int pad, off = xt_compat_match_offset(match); + u_int16_t msize = cm->u.user.match_size; ++ char name[sizeof(m->u.user.name)]; + + m = *dstptr; + memcpy(m, cm, sizeof(*cm)); +@@ -545,6 +546,9 @@ void xt_compat_match_from_user(struct xt + + msize += off; + m->u.user.match_size = msize; ++ strlcpy(name, match->name, sizeof(name)); ++ module_put(match->me); ++ strncpy(m->u.user.name, name, sizeof(m->u.user.name)); + + *size += off; + *dstptr += msize; +@@ -762,6 +766,7 @@ void xt_compat_target_from_user(struct x + struct compat_xt_entry_target *ct = (struct compat_xt_entry_target *)t; + int pad, off = xt_compat_target_offset(target); + u_int16_t tsize = ct->u.user.target_size; ++ char name[sizeof(t->u.user.name)]; + + t = *dstptr; + memcpy(t, ct, sizeof(*ct)); +@@ -775,6 +780,9 @@ void xt_compat_target_from_user(struct x + + tsize += off; + t->u.user.target_size = tsize; ++ strlcpy(name, target->name, sizeof(name)); ++ module_put(target->me); ++ strncpy(t->u.user.name, name, sizeof(t->u.user.name)); + + *size += off; + *dstptr += tsize; +From d7591f0c41ce3e67600a982bab6989ef0f07b3ce Mon Sep 17 00:00:00 2001 +From: Florian Westphal <fw@strlen.de> +Date: Fri, 1 Apr 2016 15:37:59 +0200 +Subject: netfilter: x_tables: introduce and use xt_copy_counters_from_user + +From: Florian Westphal <fw@strlen.de> + +commit d7591f0c41ce3e67600a982bab6989ef0f07b3ce upstream. + +The three variants use same copy&pasted code, condense this into a +helper and use that. + +Make sure info.name is 0-terminated. + +Signed-off-by: Florian Westphal <fw@strlen.de> +Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> +Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> + +--- + include/linux/netfilter/x_tables.h | 3 + + net/ipv4/netfilter/arp_tables.c | 48 ++---------------------- + net/ipv4/netfilter/ip_tables.c | 48 ++---------------------- + net/ipv6/netfilter/ip6_tables.c | 49 ++---------------------- + net/netfilter/x_tables.c | 74 +++++++++++++++++++++++++++++++++++++ + 5 files changed, 92 insertions(+), 130 deletions(-) + +--- a/include/linux/netfilter/x_tables.h ++++ b/include/linux/netfilter/x_tables.h +@@ -248,6 +248,9 @@ int xt_check_match(struct xt_mtchk_param + int xt_check_target(struct xt_tgchk_param *, unsigned int size, u_int8_t proto, + bool inv_proto); + ++void *xt_copy_counters_from_user(const void __user *user, unsigned int len, ++ struct xt_counters_info *info, bool compat); ++ + struct xt_table *xt_register_table(struct net *net, + const struct xt_table *table, + struct xt_table_info *bootstrap, +--- a/net/ipv4/netfilter/arp_tables.c ++++ b/net/ipv4/netfilter/arp_tables.c +@@ -1130,55 +1130,17 @@ static int do_add_counters(struct net *n + unsigned int i; + struct xt_counters_info tmp; + struct xt_counters *paddc; +- unsigned int num_counters; +- const char *name; +- int size; +- void *ptmp; + struct xt_table *t; + const struct xt_table_info *private; + int ret = 0; + struct arpt_entry *iter; + unsigned int addend; +-#ifdef CONFIG_COMPAT +- struct compat_xt_counters_info compat_tmp; + +- if (compat) { +- ptmp = &compat_tmp; +- size = sizeof(struct compat_xt_counters_info); +- } else +-#endif +- { +- ptmp = &tmp; +- size = sizeof(struct xt_counters_info); +- } +- +- if (copy_from_user(ptmp, user, size) != 0) +- return -EFAULT; +- +-#ifdef CONFIG_COMPAT +- if (compat) { +- num_counters = compat_tmp.num_counters; +- name = compat_tmp.name; +- } else +-#endif +- { +- num_counters = tmp.num_counters; +- name = tmp.name; +- } +- +- if (len != size + num_counters * sizeof(struct xt_counters)) +- return -EINVAL; +- +- paddc = vmalloc(len - size); +- if (!paddc) +- return -ENOMEM; +- +- if (copy_from_user(paddc, user + size, len - size) != 0) { +- ret = -EFAULT; +- goto free; +- } ++ paddc = xt_copy_counters_from_user(user, len, &tmp, compat); ++ if (IS_ERR(paddc)) ++ return PTR_ERR(paddc); + +- t = xt_find_table_lock(net, NFPROTO_ARP, name); ++ t = xt_find_table_lock(net, NFPROTO_ARP, tmp.name); + if (IS_ERR_OR_NULL(t)) { + ret = t ? PTR_ERR(t) : -ENOENT; + goto free; +@@ -1186,7 +1148,7 @@ static int do_add_counters(struct net *n + + local_bh_disable(); + private = t->private; +- if (private->number != num_counters) { ++ if (private->number != tmp.num_counters) { + ret = -EINVAL; + goto unlock_up_free; + } +--- a/net/ipv4/netfilter/ip_tables.c ++++ b/net/ipv4/netfilter/ip_tables.c +@@ -1313,55 +1313,17 @@ do_add_counters(struct net *net, const v + unsigned int i; + struct xt_counters_info tmp; + struct xt_counters *paddc; +- unsigned int num_counters; +- const char *name; +- int size; +- void *ptmp; + struct xt_table *t; + const struct xt_table_info *private; + int ret = 0; + struct ipt_entry *iter; + unsigned int addend; +-#ifdef CONFIG_COMPAT +- struct compat_xt_counters_info compat_tmp; + +- if (compat) { +- ptmp = &compat_tmp; +- size = sizeof(struct compat_xt_counters_info); +- } else +-#endif +- { +- ptmp = &tmp; +- size = sizeof(struct xt_counters_info); +- } +- +- if (copy_from_user(ptmp, user, size) != 0) +- return -EFAULT; +- +-#ifdef CONFIG_COMPAT +- if (compat) { +- num_counters = compat_tmp.num_counters; +- name = compat_tmp.name; +- } else +-#endif +- { +- num_counters = tmp.num_counters; +- name = tmp.name; +- } +- +- if (len != size + num_counters * sizeof(struct xt_counters)) +- return -EINVAL; +- +- paddc = vmalloc(len - size); +- if (!paddc) +- return -ENOMEM; +- +- if (copy_from_user(paddc, user + size, len - size) != 0) { +- ret = -EFAULT; +- goto free; +- } ++ paddc = xt_copy_counters_from_user(user, len, &tmp, compat); ++ if (IS_ERR(paddc)) ++ return PTR_ERR(paddc); + +- t = xt_find_table_lock(net, AF_INET, name); ++ t = xt_find_table_lock(net, AF_INET, tmp.name); + if (IS_ERR_OR_NULL(t)) { + ret = t ? PTR_ERR(t) : -ENOENT; + goto free; +@@ -1369,7 +1331,7 @@ do_add_counters(struct net *net, const v + + local_bh_disable(); + private = t->private; +- if (private->number != num_counters) { ++ if (private->number != tmp.num_counters) { + ret = -EINVAL; + goto unlock_up_free; + } +--- a/net/ipv6/netfilter/ip6_tables.c ++++ b/net/ipv6/netfilter/ip6_tables.c +@@ -1325,55 +1325,16 @@ do_add_counters(struct net *net, const v + unsigned int i; + struct xt_counters_info tmp; + struct xt_counters *paddc; +- unsigned int num_counters; +- char *name; +- int size; +- void *ptmp; + struct xt_table *t; + const struct xt_table_info *private; + int ret = 0; + struct ip6t_entry *iter; + unsigned int addend; +-#ifdef CONFIG_COMPAT +- struct compat_xt_counters_info compat_tmp; + +- if (compat) { +- ptmp = &compat_tmp; +- size = sizeof(struct compat_xt_counters_info); +- } else +-#endif +- { +- ptmp = &tmp; +- size = sizeof(struct xt_counters_info); +- } +- +- if (copy_from_user(ptmp, user, size) != 0) +- return -EFAULT; +- +-#ifdef CONFIG_COMPAT +- if (compat) { +- num_counters = compat_tmp.num_counters; +- name = compat_tmp.name; +- } else +-#endif +- { +- num_counters = tmp.num_counters; +- name = tmp.name; +- } +- +- if (len != size + num_counters * sizeof(struct xt_counters)) +- return -EINVAL; +- +- paddc = vmalloc(len - size); +- if (!paddc) +- return -ENOMEM; +- +- if (copy_from_user(paddc, user + size, len - size) != 0) { +- ret = -EFAULT; +- goto free; +- } +- +- t = xt_find_table_lock(net, AF_INET6, name); ++ paddc = xt_copy_counters_from_user(user, len, &tmp, compat); ++ if (IS_ERR(paddc)) ++ return PTR_ERR(paddc); ++ t = xt_find_table_lock(net, AF_INET6, tmp.name); + if (IS_ERR_OR_NULL(t)) { + ret = t ? PTR_ERR(t) : -ENOENT; + goto free; +@@ -1381,7 +1342,7 @@ do_add_counters(struct net *net, const v + + local_bh_disable(); + private = t->private; +- if (private->number != num_counters) { ++ if (private->number != tmp.num_counters) { + ret = -EINVAL; + goto unlock_up_free; + } +--- a/net/netfilter/x_tables.c ++++ b/net/netfilter/x_tables.c +@@ -751,6 +751,80 @@ int xt_check_target(struct xt_tgchk_para + } + EXPORT_SYMBOL_GPL(xt_check_target); + ++/** ++ * xt_copy_counters_from_user - copy counters and metadata from userspace ++ * ++ * @user: src pointer to userspace memory ++ * @len: alleged size of userspace memory ++ * @info: where to store the xt_counters_info metadata ++ * @compat: true if we setsockopt call is done by 32bit task on 64bit kernel ++ * ++ * Copies counter meta data from @user and stores it in @info. ++ * ++ * vmallocs memory to hold the counters, then copies the counter data ++ * from @user to the new memory and returns a pointer to it. ++ * ++ * If @compat is true, @info gets converted automatically to the 64bit ++ * representation. ++ * ++ * The metadata associated with the counters is stored in @info. ++ * ++ * Return: returns pointer that caller has to test via IS_ERR(). ++ * If IS_ERR is false, caller has to vfree the pointer. ++ */ ++void *xt_copy_counters_from_user(const void __user *user, unsigned int len, ++ struct xt_counters_info *info, bool compat) ++{ ++ void *mem; ++ u64 size; ++ ++#ifdef CONFIG_COMPAT ++ if (compat) { ++ /* structures only differ in size due to alignment */ ++ struct compat_xt_counters_info compat_tmp; ++ ++ if (len <= sizeof(compat_tmp)) ++ return ERR_PTR(-EINVAL); ++ ++ len -= sizeof(compat_tmp); ++ if (copy_from_user(&compat_tmp, user, sizeof(compat_tmp)) != 0) ++ return ERR_PTR(-EFAULT); ++ ++ strlcpy(info->name, compat_tmp.name, sizeof(info->name)); ++ info->num_counters = compat_tmp.num_counters; ++ user += sizeof(compat_tmp); ++ } else ++#endif ++ { ++ if (len <= sizeof(*info)) ++ return ERR_PTR(-EINVAL); ++ ++ len -= sizeof(*info); ++ if (copy_from_user(info, user, sizeof(*info)) != 0) ++ return ERR_PTR(-EFAULT); ++ ++ info->name[sizeof(info->name) - 1] = '\0'; ++ user += sizeof(*info); ++ } ++ ++ size = sizeof(struct xt_counters); ++ size *= info->num_counters; ++ ++ if (size != (u64)len) ++ return ERR_PTR(-EINVAL); ++ ++ mem = vmalloc(len); ++ if (!mem) ++ return ERR_PTR(-ENOMEM); ++ ++ if (copy_from_user(mem, user, len) == 0) ++ return mem; ++ ++ vfree(mem); ++ return ERR_PTR(-EFAULT); ++} ++EXPORT_SYMBOL_GPL(xt_copy_counters_from_user); ++ + #ifdef CONFIG_COMPAT + int xt_compat_target_offset(const struct xt_target *target) + { diff --git a/netfilter-x_tables-deal-with-bogus-nextoffset-values.patch b/netfilter-x_tables-deal-with-bogus-nextoffset-values.patch deleted file mode 100644 index ebfe1716f..000000000 --- a/netfilter-x_tables-deal-with-bogus-nextoffset-values.patch +++ /dev/null @@ -1,150 +0,0 @@ -Subject: [PATCH nf] netfilter: x_tables: deal with bogus nextoffset values -From: Florian Westphal <fw () strlen ! de> -Date: 2016-03-10 0:56:02 - -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. - -Signed-off-by: Florian Westphal <fw@strlen.de> ---- - net/ipv4/netfilter/arp_tables.c | 16 ++++++++-------- - net/ipv4/netfilter/ip_tables.c | 15 ++++++++------- - net/ipv6/netfilter/ip6_tables.c | 13 ++++++------- - 3 files changed, 22 insertions(+), 22 deletions(-) - -diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c -index b488cac..5a0b591 100644 ---- a/net/ipv4/netfilter/arp_tables.c -+++ b/net/ipv4/netfilter/arp_tables.c -@@ -437,6 +437,10 @@ static int mark_source_chains(const struct xt_table_info *newinfo, - - /* Move along one */ - size = e->next_offset; -+ -+ if (pos + size > newinfo->size - sizeof(*e)) -+ return 0; -+ - e = (struct arpt_entry *) - (entry0 + pos + size); - e->counters.pcnt = pos; -@@ -447,14 +451,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); -@@ -462,6 +458,10 @@ static int mark_source_chains(const struct xt_table_info *newinfo, - /* ... this is a fallthru */ - newpos = pos + e->next_offset; - } -+ -+ if (newpos > newinfo->size - sizeof(*e)) -+ 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 b99affa..ceb995f 100644 ---- a/net/ipv4/netfilter/ip_tables.c -+++ b/net/ipv4/netfilter/ip_tables.c -@@ -519,6 +519,10 @@ mark_source_chains(const struct xt_table_info *newinfo, - - /* Move along one */ - size = e->next_offset; -+ -+ if (pos + size > newinfo->size - sizeof(*e)) -+ return 0; -+ - e = (struct ipt_entry *) - (entry0 + pos + size); - e->counters.pcnt = pos; -@@ -529,13 +533,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); -@@ -543,6 +540,10 @@ mark_source_chains(const struct xt_table_info *newinfo, - /* ... this is a fallthru */ - newpos = pos + e->next_offset; - } -+ -+ if (newpos > newinfo->size - sizeof(*e)) -+ 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 99425cf..d88a794 100644 ---- a/net/ipv6/netfilter/ip6_tables.c -+++ b/net/ipv6/netfilter/ip6_tables.c -@@ -531,6 +531,8 @@ mark_source_chains(const struct xt_table_info *newinfo, - - /* Move along one */ - size = e->next_offset; -+ if (pos + size > newinfo->size - sizeof(*e)) -+ return 0; - e = (struct ip6t_entry *) - (entry0 + pos + size); - e->counters.pcnt = pos; -@@ -541,13 +543,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); -@@ -555,6 +550,10 @@ mark_source_chains(const struct xt_table_info *newinfo, - /* ... this is a fallthru */ - newpos = pos + e->next_offset; - } -+ -+ if (newpos > newinfo->size - sizeof(*e)) -+ return 0; -+ - e = (struct ip6t_entry *) - (entry0 + newpos); - e->counters.pcnt = pos; --- -2.4.10 |