Home Home > GIT Browse
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichal Marek <mmarek@suse.com>2016-07-15 16:20:56 +0200
committerMichal Marek <mmarek@suse.com>2016-07-15 16:20:56 +0200
commit52252ccd2f617eae8d28c94c5625474cf9addad2 (patch)
treecd120f8ae88b59dccb5349c439aee23133bd9b77
parent8c86751f7335cb9df1e09467e510a7fe69557874 (diff)
parent1d7e8ed116a3ad1a5087175701802d2e9fdd2127 (diff)
Merge branch 'users/mkubecek/cve/linux-3.0/for-next' into cve/linux-3.0
Pull netfilter security fix from Michal Kubeček (CVE-2016-4997 bsc#986362).
-rw-r--r--patches.fixes/netfilter-arp_tables-simplify-translate_compat_table.patch216
-rw-r--r--patches.fixes/netfilter-ip6_tables-simplify-translate_compat_table.patch194
-rw-r--r--patches.fixes/netfilter-ip_tables-simplify-translate_compat_table-.patch195
-rw-r--r--patches.fixes/netfilter-x_tables-add-and-use-xt_check_entry_offset.patch168
-rw-r--r--patches.fixes/netfilter-x_tables-add-compat-version-of-xt_check_en.patch122
-rw-r--r--patches.fixes/netfilter-x_tables-assert-minimum-target-size.patch34
-rw-r--r--patches.fixes/netfilter-x_tables-check-for-bogus-target-offset.patch181
-rw-r--r--patches.fixes/netfilter-x_tables-check-standard-target-size-too.patch69
-rw-r--r--patches.fixes/netfilter-x_tables-do-compat-validation-via-translat.patch792
-rw-r--r--patches.fixes/netfilter-x_tables-don-t-move-to-non-existent-next-r.patch113
-rw-r--r--patches.fixes/netfilter-x_tables-don-t-reject-valid-target-size-on.patch62
-rw-r--r--patches.fixes/netfilter-x_tables-kill-check_entry-helper.patch162
-rw-r--r--patches.fixes/netfilter-x_tables-validate-all-offsets-and-sizes-in.patch146
-rw-r--r--patches.fixes/netfilter-x_tables-validate-targets-of-jumps.patch143
-rw-r--r--patches.fixes/netfilter-x_tables-xt_compat_match_from_user-doesn-t.patch251
-rw-r--r--patches.kabi/kabi-revert-xt_compat_match_from_user-signature-chan.patch58
-rw-r--r--series.conf18
17 files changed, 2924 insertions, 0 deletions
diff --git a/patches.fixes/netfilter-arp_tables-simplify-translate_compat_table.patch b/patches.fixes/netfilter-arp_tables-simplify-translate_compat_table.patch
new file mode 100644
index 0000000000..9a7606bbc4
--- /dev/null
+++ b/patches.fixes/netfilter-arp_tables-simplify-translate_compat_table.patch
@@ -0,0 +1,216 @@
+From: Florian Westphal <fw@strlen.de>
+Date: Fri, 1 Apr 2016 14:17:32 +0200
+Subject: netfilter: arp_tables: simplify translate_compat_table args
+Patch-mainline: v4.7-rc1
+Git-commit: 8dddd32756f6fe8e4e82a63361119b7e2384e02f
+References: CVE-2016-4997 bsc#986362
+
+Signed-off-by: Florian Westphal <fw@strlen.de>
+Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
+Acked-by: Michal Kubecek <mkubecek@suse.cz>
+
+---
+ net/ipv4/netfilter/arp_tables.c | 82 ++++++++++++++++++-----------------------
+ 1 file changed, 36 insertions(+), 46 deletions(-)
+
+diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c
+index db5f28dc214f..e1fdffa0f9ce 100644
+--- a/net/ipv4/netfilter/arp_tables.c
++++ b/net/ipv4/netfilter/arp_tables.c
+@@ -1198,6 +1198,18 @@ static int do_add_counters(struct net *net, const void __user *user,
+ }
+
+ #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;
+@@ -1213,8 +1225,7 @@ check_compat_entry_size_and_hooks(struct compat_arpt_entry *e,
+ 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;
+@@ -1285,7 +1296,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;
+@@ -1318,14 +1329,9 @@ compat_copy_entry_from_user(struct compat_arpt_entry *e, void **dstptr,
+ 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;
+@@ -1337,8 +1343,8 @@ static int translate_compat_table(const char *name,
+
+ 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++) {
+@@ -1349,40 +1355,39 @@ static int translate_compat_table(const char *name,
+ 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;
+ }
+ }
+@@ -1392,17 +1397,17 @@ static int translate_compat_table(const char *name,
+ 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[raw_smp_processor_id()];
+ 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;
+ }
+@@ -1412,12 +1417,12 @@ static int translate_compat_table(const char *name,
+ 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 = check_target(iter1, name);
++ ret = check_target(iter1, compatr->name);
+ if (ret != 0)
+ break;
+ ++i;
+@@ -1462,7 +1467,7 @@ static int translate_compat_table(const char *name,
+ 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);
+@@ -1474,18 +1479,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)
+ {
+@@ -1516,10 +1509,7 @@ static int compat_do_replace(struct net *net, void __user *user,
+ 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;
+
+--
+2.9.0
+
diff --git a/patches.fixes/netfilter-ip6_tables-simplify-translate_compat_table.patch b/patches.fixes/netfilter-ip6_tables-simplify-translate_compat_table.patch
new file mode 100644
index 0000000000..4a8dd3e6eb
--- /dev/null
+++ b/patches.fixes/netfilter-ip6_tables-simplify-translate_compat_table.patch
@@ -0,0 +1,194 @@
+From: Florian Westphal <fw@strlen.de>
+Date: Fri, 1 Apr 2016 14:17:31 +0200
+Subject: netfilter: ip6_tables: simplify translate_compat_table args
+Patch-mainline: v4.7-rc1
+Git-commit: 329a0807124f12fe1c8032f95d8a8eb47047fb0e
+References: CVE-2016-4997 bsc#986362
+
+Signed-off-by: Florian Westphal <fw@strlen.de>
+Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
+Acked-by: Michal Kubecek <mkubecek@suse.cz>
+
+---
+ net/ipv6/netfilter/ip6_tables.c | 59 +++++++++++++++++------------------------
+ 1 file changed, 24 insertions(+), 35 deletions(-)
+
+diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_tables.c
+index cb11f27fa15d..a1ce9cb9b0a5 100644
+--- a/net/ipv6/netfilter/ip6_tables.c
++++ b/net/ipv6/netfilter/ip6_tables.c
+@@ -1463,7 +1463,6 @@ compat_copy_entry_to_user(struct ip6t_entry *e, void __user **dstptr,
+
+ static int
+ compat_find_calc_match(struct xt_entry_match *m,
+- const char *name,
+ const struct ip6t_ip6 *ipv6,
+ unsigned int hookmask,
+ int *size)
+@@ -1501,8 +1500,7 @@ check_compat_entry_size_and_hooks(struct compat_ip6t_entry *e,
+ 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;
+@@ -1538,7 +1536,7 @@ check_compat_entry_size_and_hooks(struct compat_ip6t_entry *e,
+ entry_offset = (void *)e - (void *)base;
+ j = 0;
+ xt_ematch_foreach(ematch, e) {
+- ret = compat_find_calc_match(ematch, name,
++ ret = compat_find_calc_match(ematch,
+ &e->ipv6, e->comefrom, &off);
+ if (ret != 0)
+ goto release_matches;
+@@ -1588,7 +1586,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;
+@@ -1662,14 +1660,9 @@ static int compat_check_entry(struct ip6t_entry *e, struct net *net,
+
+ 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;
+@@ -1681,8 +1674,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++) {
+@@ -1693,40 +1686,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;
+ }
+ }
+@@ -1736,17 +1728,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[raw_smp_processor_id()];
+ 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;
+ }
+@@ -1756,12 +1748,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;
+@@ -1806,7 +1798,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);
+@@ -1849,10 +1841,7 @@ compat_do_replace(struct net *net, void __user *user, unsigned int len)
+ 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;
+
+--
+2.9.0
+
diff --git a/patches.fixes/netfilter-ip_tables-simplify-translate_compat_table-.patch b/patches.fixes/netfilter-ip_tables-simplify-translate_compat_table-.patch
new file mode 100644
index 0000000000..3d5436fa8b
--- /dev/null
+++ b/patches.fixes/netfilter-ip_tables-simplify-translate_compat_table-.patch
@@ -0,0 +1,195 @@
+From: Florian Westphal <fw@strlen.de>
+Date: Fri, 1 Apr 2016 14:17:30 +0200
+Subject: netfilter: ip_tables: simplify translate_compat_table args
+Patch-mainline: v4.7-rc1
+Git-commit: 7d3f843eed29222254c9feab481f55175a1afcc9
+References: CVE-2016-4997 bsc#986362
+
+Signed-off-by: Florian Westphal <fw@strlen.de>
+Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
+Acked-by: Michal Kubecek <mkubecek@suse.cz>
+
+---
+ net/ipv4/netfilter/ip_tables.c | 60 +++++++++++++++++-------------------------
+ 1 file changed, 24 insertions(+), 36 deletions(-)
+
+diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c
+index fcd77b511730..70a347ae8978 100644
+--- a/net/ipv4/netfilter/ip_tables.c
++++ b/net/ipv4/netfilter/ip_tables.c
+@@ -1439,7 +1439,6 @@ compat_copy_entry_to_user(struct ipt_entry *e, void __user **dstptr,
+
+ static int
+ compat_find_calc_match(struct xt_entry_match *m,
+- const char *name,
+ const struct ipt_ip *ip,
+ unsigned int hookmask,
+ int *size)
+@@ -1477,8 +1476,7 @@ check_compat_entry_size_and_hooks(struct compat_ipt_entry *e,
+ 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;
+@@ -1514,8 +1512,7 @@ check_compat_entry_size_and_hooks(struct compat_ipt_entry *e,
+ entry_offset = (void *)e - (void *)base;
+ j = 0;
+ xt_ematch_foreach(ematch, e) {
+- ret = compat_find_calc_match(ematch, name,
+- &e->ip, e->comefrom, &off);
++ ret = compat_find_calc_match(ematch, &e->ip, e->comefrom, &off);
+ if (ret != 0)
+ goto release_matches;
+ ++j;
+@@ -1564,7 +1561,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;
+@@ -1640,14 +1637,9 @@ compat_check_entry(struct ipt_entry *e, struct net *net, const char *name)
+
+ 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;
+@@ -1659,8 +1651,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++) {
+@@ -1671,40 +1663,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;
+ }
+ }
+@@ -1714,17 +1705,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[raw_smp_processor_id()];
+ 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;
+ }
+@@ -1734,12 +1725,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;
+@@ -1784,7 +1775,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);
+@@ -1827,10 +1818,7 @@ compat_do_replace(struct net *net, void __user *user, unsigned int len)
+ 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;
+
+--
+2.9.0
+
diff --git a/patches.fixes/netfilter-x_tables-add-and-use-xt_check_entry_offset.patch b/patches.fixes/netfilter-x_tables-add-and-use-xt_check_entry_offset.patch
new file mode 100644
index 0000000000..dedb9a71d1
--- /dev/null
+++ b/patches.fixes/netfilter-x_tables-add-and-use-xt_check_entry_offset.patch
@@ -0,0 +1,168 @@
+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
+Patch-mainline: v4.7-rc1
+Git-commit: 7d35812c3214afa5b37a675113555259cfd67b98
+References: CVE-2016-4997 bsc#986362
+
+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>
+Acked-by: Michal Kubecek <mkubecek@suse.cz>
+
+---
+ 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(-)
+
+diff --git a/include/linux/netfilter/x_tables.h b/include/linux/netfilter/x_tables.h
+index 32cddf78b13e..7305f8405ced 100644
+--- a/include/linux/netfilter/x_tables.h
++++ b/include/linux/netfilter/x_tables.h
+@@ -421,6 +421,10 @@ extern void xt_unregister_match(struct xt_match *target);
+ extern int xt_register_matches(struct xt_match *match, unsigned int n);
+ extern 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);
++
+ extern int xt_check_match(struct xt_mtchk_param *,
+ unsigned int size, u_int8_t proto, bool inv_proto);
+ extern int xt_check_target(struct xt_tgchk_param *,
+diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c
+index 866e8ccd890f..c6882461be18 100644
+--- a/net/ipv4/netfilter/arp_tables.c
++++ b/net/ipv4/netfilter/arp_tables.c
+@@ -488,19 +488,10 @@ static int mark_source_chains(const struct xt_table_info *newinfo,
+
+ 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)
+diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c
+index 8a76bad1de0f..1bc79912d274 100644
+--- a/net/ipv4/netfilter/ip_tables.c
++++ b/net/ipv4/netfilter/ip_tables.c
+@@ -583,20 +583,10 @@ static void cleanup_match(struct xt_entry_match *m, struct net *net)
+ static int
+ check_entry(const struct ipt_entry *e)
+ {
+- const struct xt_entry_target *t;
+-
+ if (!ip_checkentry(&e->ip))
+ return -EINVAL;
+
+- if (e->target_offset + sizeof(struct xt_entry_target) >
+- e->next_offset)
+- return -EINVAL;
+-
+- t = ipt_get_target_c(e);
+- if (e->target_offset + t->u.target_size > e->next_offset)
+- return -EINVAL;
+-
+- return 0;
++ return xt_check_entry_offsets(e, e->target_offset, e->next_offset);
+ }
+
+ static int
+diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_tables.c
+index 5800c1e914df..ec1868d999dc 100644
+--- a/net/ipv6/netfilter/ip6_tables.c
++++ b/net/ipv6/netfilter/ip6_tables.c
+@@ -605,20 +605,10 @@ static void cleanup_match(struct xt_entry_match *m, struct net *net)
+ static int
+ check_entry(const struct ip6t_entry *e)
+ {
+- const struct xt_entry_target *t;
+-
+ if (!ip6_checkentry(&e->ipv6))
+ return -EINVAL;
+
+- if (e->target_offset + sizeof(struct xt_entry_target) >
+- e->next_offset)
+- return -EINVAL;
+-
+- t = ip6t_get_target_c(e);
+- if (e->target_offset + t->u.target_size > e->next_offset)
+- return -EINVAL;
+-
+- return 0;
++ return xt_check_entry_offsets(e, e->target_offset, e->next_offset);
+ }
+
+ static int check_match(struct xt_entry_match *m, struct xt_mtchk_param *par)
+diff --git a/net/netfilter/x_tables.c b/net/netfilter/x_tables.c
+index b0869fe3633b..7c4f962e6a82 100644
+--- a/net/netfilter/x_tables.c
++++ b/net/netfilter/x_tables.c
+@@ -548,6 +548,40 @@ int xt_compat_match_to_user(const struct xt_entry_match *m,
+ 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)
+ {
+--
+2.9.0
+
diff --git a/patches.fixes/netfilter-x_tables-add-compat-version-of-xt_check_en.patch b/patches.fixes/netfilter-x_tables-add-compat-version-of-xt_check_en.patch
new file mode 100644
index 0000000000..5a8b243060
--- /dev/null
+++ b/patches.fixes/netfilter-x_tables-add-compat-version-of-xt_check_en.patch
@@ -0,0 +1,122 @@
+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
+Patch-mainline: v4.7-rc1
+Git-commit: fc1221b3a163d1386d1052184202d5dc50d302d1
+References: CVE-2016-4997 bsc#986362
+
+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>
+Acked-by: Michal Kubecek <mkubecek@suse.cz>
+
+---
+ 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(-)
+
+diff --git a/include/linux/netfilter/x_tables.h b/include/linux/netfilter/x_tables.h
+index 7305f8405ced..07405fd119c9 100644
+--- a/include/linux/netfilter/x_tables.h
++++ b/include/linux/netfilter/x_tables.h
+@@ -619,6 +619,9 @@ extern void xt_compat_target_from_user(struct xt_entry_target *t,
+ void **dstptr, unsigned int *size);
+ extern 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 /* __KERNEL__ */
+diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c
+index 0bd16273edfe..99c50cef8f9b 100644
+--- a/net/ipv4/netfilter/arp_tables.c
++++ b/net/ipv4/netfilter/arp_tables.c
+@@ -1238,7 +1238,8 @@ check_compat_entry_size_and_hooks(struct compat_arpt_entry *e,
+ 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;
+
+diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c
+index 31f3c4e9c49e..31d47183e9f7 100644
+--- a/net/ipv4/netfilter/ip_tables.c
++++ b/net/ipv4/netfilter/ip_tables.c
+@@ -1504,7 +1504,8 @@ check_compat_entry_size_and_hooks(struct compat_ipt_entry *e,
+ 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;
+
+diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_tables.c
+index ae2ddc47d82a..2c7a381c62be 100644
+--- a/net/ipv6/netfilter/ip6_tables.c
++++ b/net/ipv6/netfilter/ip6_tables.c
+@@ -1528,7 +1528,8 @@ check_compat_entry_size_and_hooks(struct compat_ip6t_entry *e,
+ 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;
+
+diff --git a/net/netfilter/x_tables.c b/net/netfilter/x_tables.c
+index a1317fbb9036..2980288cd9b0 100644
+--- a/net/netfilter/x_tables.c
++++ b/net/netfilter/x_tables.c
+@@ -546,6 +546,27 @@ int xt_compat_match_to_user(const struct xt_entry_match *m,
+ 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 */
+
+ /**
+@@ -556,6 +577,7 @@ EXPORT_SYMBOL_GPL(xt_compat_match_to_user);
+ * @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
+--
+2.9.0
+
diff --git a/patches.fixes/netfilter-x_tables-assert-minimum-target-size.patch b/patches.fixes/netfilter-x_tables-assert-minimum-target-size.patch
new file mode 100644
index 0000000000..a3ffbc18f4
--- /dev/null
+++ b/patches.fixes/netfilter-x_tables-assert-minimum-target-size.patch
@@ -0,0 +1,34 @@
+From: Florian Westphal <fw@strlen.de>
+Date: Fri, 1 Apr 2016 14:17:25 +0200
+Subject: netfilter: x_tables: assert minimum target size
+Patch-mainline: v4.7-rc1
+Git-commit: a08e4e190b866579896c09af59b3bdca821da2cd
+References: CVE-2016-4997 bsc#986362
+
+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>
+Acked-by: Michal Kubecek <mkubecek@suse.cz>
+
+---
+ net/netfilter/x_tables.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/net/netfilter/x_tables.c b/net/netfilter/x_tables.c
+index 7c4f962e6a82..a1317fbb9036 100644
+--- a/net/netfilter/x_tables.c
++++ b/net/netfilter/x_tables.c
+@@ -575,6 +575,9 @@ int xt_check_entry_offsets(const void *base,
+ 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;
+
+--
+2.9.0
+
diff --git a/patches.fixes/netfilter-x_tables-check-for-bogus-target-offset.patch b/patches.fixes/netfilter-x_tables-check-for-bogus-target-offset.patch
new file mode 100644
index 0000000000..cedee67e8f
--- /dev/null
+++ b/patches.fixes/netfilter-x_tables-check-for-bogus-target-offset.patch
@@ -0,0 +1,181 @@
+From: Florian Westphal <fw@strlen.de>
+Date: Fri, 1 Apr 2016 14:17:28 +0200
+Subject: netfilter: x_tables: check for bogus target offset
+Patch-mainline: v4.7-rc1
+Git-commit: ce683e5f9d045e5d67d1312a42b359cb2ab2a13c
+References: CVE-2016-4997 bsc#986362
+
+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>
+Acked-by: Michal Kubecek <mkubecek@suse.cz>
+
+---
+ 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(-)
+
+diff --git a/include/linux/netfilter/x_tables.h b/include/linux/netfilter/x_tables.h
+index 07405fd119c9..63ebc2f1f5f5 100644
+--- a/include/linux/netfilter/x_tables.h
++++ b/include/linux/netfilter/x_tables.h
+@@ -421,7 +421,7 @@ extern void xt_unregister_match(struct xt_match *target);
+ extern int xt_register_matches(struct xt_match *match, unsigned int n);
+ extern 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);
+
+@@ -619,7 +619,7 @@ extern void xt_compat_target_from_user(struct xt_entry_target *t,
+ void **dstptr, unsigned int *size);
+ extern 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);
+
+diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c
+index 99c50cef8f9b..db5f28dc214f 100644
+--- a/net/ipv4/netfilter/arp_tables.c
++++ b/net/ipv4/netfilter/arp_tables.c
+@@ -578,7 +578,8 @@ static inline int check_entry_size_and_hooks(struct arpt_entry *e,
+ 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;
+
+@@ -1238,7 +1239,7 @@ check_compat_entry_size_and_hooks(struct compat_arpt_entry *e,
+ 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;
+diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c
+index 31d47183e9f7..fcd77b511730 100644
+--- a/net/ipv4/netfilter/ip_tables.c
++++ b/net/ipv4/netfilter/ip_tables.c
+@@ -739,7 +739,8 @@ check_entry_size_and_hooks(struct ipt_entry *e,
+ 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;
+
+@@ -1504,7 +1505,7 @@ check_compat_entry_size_and_hooks(struct compat_ipt_entry *e,
+ 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;
+diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_tables.c
+index 2c7a381c62be..cb11f27fa15d 100644
+--- a/net/ipv6/netfilter/ip6_tables.c
++++ b/net/ipv6/netfilter/ip6_tables.c
+@@ -762,7 +762,8 @@ check_entry_size_and_hooks(struct ip6t_entry *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;
+
+@@ -1528,7 +1529,7 @@ check_compat_entry_size_and_hooks(struct compat_ip6t_entry *e,
+ 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;
+diff --git a/net/netfilter/x_tables.c b/net/netfilter/x_tables.c
+index 4a59465d678d..f9d56635d17b 100644
+--- a/net/netfilter/x_tables.c
++++ b/net/netfilter/x_tables.c
+@@ -553,14 +553,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;
+
+@@ -584,12 +587,16 @@ EXPORT_SYMBOL(xt_compat_check_entry_offsets);
+ * 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
+@@ -598,12 +605,18 @@ EXPORT_SYMBOL(xt_compat_check_entry_offsets);
+ * 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;
+
+--
+2.9.0
+
diff --git a/patches.fixes/netfilter-x_tables-check-standard-target-size-too.patch b/patches.fixes/netfilter-x_tables-check-standard-target-size-too.patch
new file mode 100644
index 0000000000..a3f117240a
--- /dev/null
+++ b/patches.fixes/netfilter-x_tables-check-standard-target-size-too.patch
@@ -0,0 +1,69 @@
+From: Florian Westphal <fw@strlen.de>
+Date: Fri, 1 Apr 2016 14:17:27 +0200
+Subject: netfilter: x_tables: check standard target size too
+Patch-mainline: v4.7-rc1
+Git-commit: 7ed2abddd20cf8f6bd27f65bd218f26fa5bf7f44
+References: CVE-2016-4997 bsc#986362
+
+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>
+Acked-by: Michal Kubecek <mkubecek@suse.cz>
+
+---
+ net/netfilter/x_tables.c | 15 +++++++++++++++
+ 1 file changed, 15 insertions(+)
+
+diff --git a/net/netfilter/x_tables.c b/net/netfilter/x_tables.c
+index 2980288cd9b0..4a59465d678d 100644
+--- a/net/netfilter/x_tables.c
++++ b/net/netfilter/x_tables.c
+@@ -547,6 +547,13 @@ int xt_compat_match_to_user(const struct xt_entry_match *m,
+ }
+ 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)
+@@ -564,6 +571,10 @@ int xt_compat_check_entry_offsets(const void *base,
+ 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);
+@@ -603,6 +614,10 @@ int xt_check_entry_offsets(const void *base,
+ 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);
+--
+2.9.0
+
diff --git a/patches.fixes/netfilter-x_tables-do-compat-validation-via-translat.patch b/patches.fixes/netfilter-x_tables-do-compat-validation-via-translat.patch
new file mode 100644
index 0000000000..03b91489e0
--- /dev/null
+++ b/patches.fixes/netfilter-x_tables-do-compat-validation-via-translat.patch
@@ -0,0 +1,792 @@
+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
+Patch-mainline: v4.7-rc1
+Git-commit: 09d9686047dbbe1cf4faa558d3ecc4aae2046054
+References: CVE-2016-4997 bsc#986362
+
+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: Michal Kubecek <mkubecek@suse.cz>
+
+---
+ net/ipv4/netfilter/arp_tables.c | 106 +++++++---------------------
+ net/ipv4/netfilter/ip_tables.c | 148 ++++++++--------------------------------
+ net/ipv6/netfilter/ip6_tables.c | 142 +++++++-------------------------------
+ net/netfilter/x_tables.c | 8 +++
+ 4 files changed, 83 insertions(+), 321 deletions(-)
+
+diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c
+index 86c7cd8c04a8..038215b6eb05 100644
+--- a/net/ipv4/netfilter/arp_tables.c
++++ b/net/ipv4/netfilter/arp_tables.c
+@@ -1218,19 +1218,17 @@ static inline void compat_release_entry(struct compat_arpt_entry *e)
+ 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 ||
+@@ -1275,17 +1273,6 @@ check_compat_entry_size_and_hooks(struct compat_arpt_entry *e,
+ 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:
+@@ -1335,7 +1322,7 @@ static int translate_compat_table(struct xt_table_info **pinfo,
+ 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;
+
+@@ -1344,12 +1331,6 @@ static int translate_compat_table(struct xt_table_info **pinfo,
+ 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);
+@@ -1358,9 +1339,7 @@ static int translate_compat_table(struct xt_table_info **pinfo,
+ 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;
+@@ -1373,23 +1352,6 @@ static int translate_compat_table(struct xt_table_info **pinfo,
+ 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)
+@@ -1406,47 +1368,26 @@ static int translate_compat_table(struct xt_table_info **pinfo,
+ 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;
++ memcpy(&repl, compatr, sizeof(*compatr));
+
+- i = 0;
+- xt_entry_foreach(iter1, entry1, newinfo->size) {
+- ret = check_target(iter1, compatr->name);
+- if (ret != 0)
+- 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;
++
+ /* And one copy for every other CPU */
+ for_each_possible_cpu(i)
+ if (newinfo->entries[i] && newinfo->entries[i] != entry1)
+@@ -1459,17 +1400,16 @@ static int translate_compat_table(struct xt_table_info **pinfo,
+
+ 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,
+diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c
+index 7075de08ef2f..e9658ebb36fd 100644
+--- a/net/ipv4/netfilter/ip_tables.c
++++ b/net/ipv4/netfilter/ip_tables.c
+@@ -1474,16 +1474,14 @@ check_compat_entry_size_and_hooks(struct compat_ipt_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_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 ||
+@@ -1535,17 +1533,6 @@ check_compat_entry_size_and_hooks(struct compat_ipt_entry *e,
+ 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:
+@@ -1588,6 +1575,7 @@ compat_copy_entry_from_user(struct compat_ipt_entry *e, void **dstptr,
+ 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;
+@@ -1597,41 +1585,6 @@ compat_copy_entry_from_user(struct compat_ipt_entry *e, void **dstptr,
+ }
+
+ 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;
+-
+- 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);
+- }
+- return ret;
+-}
+-
+-static int
+ translate_compat_table(struct net *net,
+ struct xt_table_info **pinfo,
+ void **pentry0,
+@@ -1641,7 +1594,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;
+
+@@ -1650,12 +1603,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);
+@@ -1664,9 +1611,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;
+@@ -1679,23 +1624,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)
+@@ -1703,8 +1631,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[raw_smp_processor_id()];
+ pos = entry1;
+@@ -1713,47 +1641,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;
++
+ /* And one copy for every other CPU */
+ for_each_possible_cpu(i)
+ if (newinfo->entries[i] && newinfo->entries[i] != entry1)
+@@ -1766,17 +1677,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
+diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_tables.c
+index 43191b3d800f..d0f78a60d18f 100644
+--- a/net/ipv6/netfilter/ip6_tables.c
++++ b/net/ipv6/netfilter/ip6_tables.c
+@@ -1498,16 +1498,14 @@ check_compat_entry_size_and_hooks(struct compat_ip6t_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_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 ||
+@@ -1560,17 +1558,6 @@ check_compat_entry_size_and_hooks(struct compat_ip6t_entry *e,
+ 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:
+@@ -1619,41 +1606,6 @@ compat_copy_entry_from_user(struct compat_ip6t_entry *e, void **dstptr,
+ }
+ }
+
+-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;
+-
+- 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);
+- }
+- return ret;
+-}
+-
+ static int
+ translate_compat_table(struct net *net,
+ struct xt_table_info **pinfo,
+@@ -1664,7 +1616,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;
+
+@@ -1673,12 +1625,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);
+@@ -1687,9 +1633,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;
+@@ -1702,23 +1646,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)
+@@ -1726,56 +1653,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[raw_smp_processor_id()];
+ 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;
++
+ /* And one copy for every other CPU */
+ for_each_possible_cpu(i)
+ if (newinfo->entries[i] && newinfo->entries[i] != entry1)
+@@ -1788,17 +1693,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
+diff --git a/net/netfilter/x_tables.c b/net/netfilter/x_tables.c
+index cdb41c569f3d..c2432909f930 100644
+--- a/net/netfilter/x_tables.c
++++ b/net/netfilter/x_tables.c
+@@ -540,6 +540,7 @@ int xt_compat_match_from_user(struct xt_entry_match *m, void **dstptr,
+ 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));
+@@ -553,6 +554,9 @@ int xt_compat_match_from_user(struct xt_entry_match *m, void **dstptr,
+
+ 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;
+@@ -769,6 +773,7 @@ void xt_compat_target_from_user(struct xt_entry_target *t, void **dstptr,
+ 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));
+@@ -782,6 +787,9 @@ void xt_compat_target_from_user(struct xt_entry_target *t, void **dstptr,
+
+ 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;
+--
+2.9.0
+
diff --git a/patches.fixes/netfilter-x_tables-don-t-move-to-non-existent-next-r.patch b/patches.fixes/netfilter-x_tables-don-t-move-to-non-existent-next-r.patch
new file mode 100644
index 0000000000..1e04b82036
--- /dev/null
+++ b/patches.fixes/netfilter-x_tables-don-t-move-to-non-existent-next-r.patch
@@ -0,0 +1,113 @@
+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
+Patch-mainline: v4.7-rc1
+Git-commit: f24e230d257af1ad7476c6e81a8dc3127a74204e
+References: CVE-2016-4997 bsc#986362
+
+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>
+Acked-by: Michal Kubecek <mkubecek@suse.cz>
+
+---
+ net/ipv4/netfilter/arp_tables.c | 8 +++++---
+ net/ipv4/netfilter/ip_tables.c | 4 ++++
+ net/ipv6/netfilter/ip6_tables.c | 4 ++++
+ 3 files changed, 13 insertions(+), 3 deletions(-)
+
+diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c
+index f4865d2958f4..9cec90c4afbd 100644
+--- a/net/ipv4/netfilter/arp_tables.c
++++ b/net/ipv4/netfilter/arp_tables.c
+@@ -430,6 +430,8 @@ static int mark_source_chains(const struct xt_table_info *newinfo,
+ size = e->next_offset;
+ e = (struct arpt_entry *)
+ (entry0 + pos + size);
++ if (pos + size >= newinfo->size)
++ return 0;
+ e->counters.pcnt = pos;
+ pos += size;
+ } else {
+@@ -452,6 +454,8 @@ static int mark_source_chains(const struct xt_table_info *newinfo,
+ } else {
+ /* ... this is a fallthru */
+ newpos = pos + e->next_offset;
++ if (newpos >= newinfo->size)
++ return 0;
+ }
+ e = (struct arpt_entry *)
+ (entry0 + newpos);
+@@ -675,10 +679,8 @@ static int translate_table(struct xt_table_info *newinfo, void *entry0,
+ }
+ }
+
+- if (!mark_source_chains(newinfo, repl->valid_hooks, entry0)) {
+- duprintf("Looping hook\n");
++ if (!mark_source_chains(newinfo, repl->valid_hooks, entry0))
+ return -ELOOP;
+- }
+
+ /* Finally, each sanity check must pass */
+ i = 0;
+diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c
+index d0f907567e1f..4cf120dca38c 100644
+--- a/net/ipv4/netfilter/ip_tables.c
++++ b/net/ipv4/netfilter/ip_tables.c
+@@ -512,6 +512,8 @@ mark_source_chains(const struct xt_table_info *newinfo,
+ 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 {
+@@ -533,6 +535,8 @@ mark_source_chains(const struct xt_table_info *newinfo,
+ } else {
+ /* ... this is a fallthru */
+ newpos = pos + e->next_offset;
++ if (newpos >= newinfo->size)
++ return 0;
+ }
+ e = (struct ipt_entry *)
+ (entry0 + newpos);
+diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_tables.c
+index 796f6fc568ec..6bbb66fe5d35 100644
+--- a/net/ipv6/netfilter/ip6_tables.c
++++ b/net/ipv6/netfilter/ip6_tables.c
+@@ -534,6 +534,8 @@ mark_source_chains(const struct xt_table_info *newinfo,
+ size = e->next_offset;
+ e = (struct ip6t_entry *)
+ (entry0 + pos + size);
++ if (pos + size >= newinfo->size)
++ return 0;
+ e->counters.pcnt = pos;
+ pos += size;
+ } else {
+@@ -555,6 +557,8 @@ mark_source_chains(const struct xt_table_info *newinfo,
+ } else {
+ /* ... this is a fallthru */
+ newpos = pos + e->next_offset;
++ if (newpos >= newinfo->size)
++ return 0;
+ }
+ e = (struct ip6t_entry *)
+ (entry0 + newpos);
+--
+2.9.0
+
diff --git a/patches.fixes/netfilter-x_tables-don-t-reject-valid-target-size-on.patch b/patches.fixes/netfilter-x_tables-don-t-reject-valid-target-size-on.patch
new file mode 100644
index 0000000000..ca08e9ca30
--- /dev/null
+++ b/patches.fixes/netfilter-x_tables-don-t-reject-valid-target-size-on.patch
@@ -0,0 +1,62 @@
+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
+Patch-mainline: v4.7-rc3
+Git-commit: 7b7eba0f3515fca3296b8881d583f7c1042f5226
+References: CVE-2016-4997 bsc#986362
+
+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>
+Acked-by: Michal Kubecek <mkubecek@suse.cz>
+
+---
+ net/netfilter/x_tables.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/net/netfilter/x_tables.c b/net/netfilter/x_tables.c
+index c2432909f930..93cbf1ba0ccd 100644
+--- a/net/netfilter/x_tables.c
++++ b/net/netfilter/x_tables.c
+@@ -620,7 +620,7 @@ int xt_compat_check_entry_offsets(const void *base, const char *elems,
+ 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,
+@@ -702,7 +702,7 @@ int xt_check_entry_offsets(const void *base,
+ 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,
+--
+2.9.0
+
diff --git a/patches.fixes/netfilter-x_tables-kill-check_entry-helper.patch b/patches.fixes/netfilter-x_tables-kill-check_entry-helper.patch
new file mode 100644
index 0000000000..c8eda4fcbe
--- /dev/null
+++ b/patches.fixes/netfilter-x_tables-kill-check_entry-helper.patch
@@ -0,0 +1,162 @@
+From: Florian Westphal <fw@strlen.de>
+Date: Fri, 1 Apr 2016 14:17:24 +0200
+Subject: netfilter: x_tables: kill check_entry helper
+Patch-mainline: v4.7-rc1
+Git-commit: aa412ba225dd3bc36d404c28cdc3d674850d80d0
+References: CVE-2016-4997 bsc#986362
+
+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>
+Acked-by: Michal Kubecek <mkubecek@suse.cz>
+
+---
+ 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(-)
+
+diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c
+index c6882461be18..0bd16273edfe 100644
+--- a/net/ipv4/netfilter/arp_tables.c
++++ b/net/ipv4/netfilter/arp_tables.c
+@@ -486,14 +486,6 @@ static int mark_source_chains(const struct xt_table_info *newinfo,
+ 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);
+@@ -583,7 +575,10 @@ static inline int check_entry_size_and_hooks(struct arpt_entry *e,
+ 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;
+
+@@ -1240,8 +1235,10 @@ check_compat_entry_size_and_hooks(struct compat_arpt_entry *e,
+ 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;
+
+diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c
+index 1bc79912d274..31f3c4e9c49e 100644
+--- a/net/ipv4/netfilter/ip_tables.c
++++ b/net/ipv4/netfilter/ip_tables.c
+@@ -581,15 +581,6 @@ static void cleanup_match(struct xt_entry_match *m, struct net *net)
+ }
+
+ 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;
+@@ -745,7 +736,10 @@ check_entry_size_and_hooks(struct ipt_entry *e,
+ 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;
+
+@@ -1507,8 +1501,10 @@ check_compat_entry_size_and_hooks(struct compat_ipt_entry *e,
+ 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;
+
+diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_tables.c
+index ec1868d999dc..ae2ddc47d82a 100644
+--- a/net/ipv6/netfilter/ip6_tables.c
++++ b/net/ipv6/netfilter/ip6_tables.c
+@@ -602,15 +602,6 @@ static void cleanup_match(struct xt_entry_match *m, struct net *net)
+ 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;
+@@ -768,7 +759,10 @@ check_entry_size_and_hooks(struct ip6t_entry *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;
+
+@@ -1531,8 +1525,10 @@ check_compat_entry_size_and_hooks(struct compat_ip6t_entry *e,
+ 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;
+
+--
+2.9.0
+
diff --git a/patches.fixes/netfilter-x_tables-validate-all-offsets-and-sizes-in.patch b/patches.fixes/netfilter-x_tables-validate-all-offsets-and-sizes-in.patch
new file mode 100644
index 0000000000..69456b05bc
--- /dev/null
+++ b/patches.fixes/netfilter-x_tables-validate-all-offsets-and-sizes-in.patch
@@ -0,0 +1,146 @@
+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
+Patch-mainline: v4.7-rc1
+Git-commit: 13631bfc604161a9d69cd68991dff8603edd66f9
+References: CVE-2016-4997 bsc#986362
+
+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>
+Acked-by: Michal Kubecek <mkubecek@suse.cz>
+
+---
+ net/netfilter/x_tables.c | 81 +++++++++++++++++++++++++++++++++++++++++++++---
+ 1 file changed, 76 insertions(+), 5 deletions(-)
+
+diff --git a/net/netfilter/x_tables.c b/net/netfilter/x_tables.c
+index f9d56635d17b..cdb41c569f3d 100644
+--- a/net/netfilter/x_tables.c
++++ b/net/netfilter/x_tables.c
+@@ -423,6 +423,47 @@ int xt_check_match(struct xt_mtchk_param *par,
+ }
+ 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)
+ {
+@@ -578,7 +619,14 @@ int xt_compat_check_entry_offsets(const void *base, const char *elems,
+ 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 */
+@@ -591,17 +639,39 @@ EXPORT_SYMBOL(xt_compat_check_entry_offsets);
+ * @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,
+@@ -631,7 +701,8 @@ int xt_check_entry_offsets(const void *base,
+ 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);
+
+--
+2.9.0
+
diff --git a/patches.fixes/netfilter-x_tables-validate-targets-of-jumps.patch b/patches.fixes/netfilter-x_tables-validate-targets-of-jumps.patch
new file mode 100644
index 0000000000..b5b02958d7
--- /dev/null
+++ b/patches.fixes/netfilter-x_tables-validate-targets-of-jumps.patch
@@ -0,0 +1,143 @@
+From: Florian Westphal <fw@strlen.de>
+Date: Fri, 1 Apr 2016 14:17:22 +0200
+Subject: netfilter: x_tables: validate targets of jumps
+Patch-mainline: v4.7-rc1
+Git-commit: 36472341017529e2b12573093cc0f68719300997
+References: CVE-2016-4997 bsc#986362
+
+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>
+Acked-by: Michal Kubecek <mkubecek@suse.cz>
+
+---
+ net/ipv4/netfilter/arp_tables.c | 17 +++++++++++++++++
+ net/ipv4/netfilter/ip_tables.c | 17 +++++++++++++++++
+ net/ipv6/netfilter/ip6_tables.c | 17 +++++++++++++++++
+ 3 files changed, 51 insertions(+)
+
+diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c
+index 9cec90c4afbd..866e8ccd890f 100644
+--- a/net/ipv4/netfilter/arp_tables.c
++++ b/net/ipv4/netfilter/arp_tables.c
+@@ -358,6 +358,19 @@ static inline bool unconditional(const struct arpt_entry *e)
+ 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;
++ void *loc_cpu_entry = t->entries[raw_smp_processor_id()];
++
++ xt_entry_foreach(iter, loc_cpu_entry, 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.
+ */
+@@ -451,6 +464,10 @@ static int mark_source_chains(const struct xt_table_info *newinfo,
+ /* 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;
+diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c
+index 4cf120dca38c..8a76bad1de0f 100644
+--- a/net/ipv4/netfilter/ip_tables.c
++++ b/net/ipv4/netfilter/ip_tables.c
+@@ -435,6 +435,19 @@ 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;
++ void *loc_cpu_entry = t->entries[raw_smp_processor_id()];
++
++ xt_entry_foreach(iter, loc_cpu_entry, 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
+@@ -532,6 +545,10 @@ mark_source_chains(const struct xt_table_info *newinfo,
+ /* 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;
+diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_tables.c
+index 6bbb66fe5d35..5800c1e914df 100644
+--- a/net/ipv6/netfilter/ip6_tables.c
++++ b/net/ipv6/netfilter/ip6_tables.c
+@@ -457,6 +457,19 @@ 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;
++ void *loc_cpu_entry = t->entries[raw_smp_processor_id()];
++
++ xt_entry_foreach(iter, loc_cpu_entry, 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
+@@ -554,6 +567,10 @@ mark_source_chains(const struct xt_table_info *newinfo,
+ /* 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;
+--
+2.9.0
+
diff --git a/patches.fixes/netfilter-x_tables-xt_compat_match_from_user-doesn-t.patch b/patches.fixes/netfilter-x_tables-xt_compat_match_from_user-doesn-t.patch
new file mode 100644
index 0000000000..6fb65244dd
--- /dev/null
+++ b/patches.fixes/netfilter-x_tables-xt_compat_match_from_user-doesn-t.patch
@@ -0,0 +1,251 @@
+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
+Patch-mainline: v4.7-rc1
+Git-commit: 0188346f21e6546498c2a0f84888797ad4063fc5
+References: CVE-2016-4997 bsc#986362
+
+Always returned 0.
+
+Signed-off-by: Florian Westphal <fw@strlen.de>
+Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
+Acked-by: Michal Kubecek <mkubecek@suse.cz>
+
+---
+ 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(-)
+
+diff --git a/include/linux/netfilter/x_tables.h b/include/linux/netfilter/x_tables.h
+index 63ebc2f1f5f5..6da5f79bf286 100644
+--- a/include/linux/netfilter/x_tables.h
++++ b/include/linux/netfilter/x_tables.h
+@@ -609,7 +609,7 @@ extern void xt_compat_init_offsets(u_int8_t af, unsigned int number);
+ extern int xt_compat_calc_jump(u_int8_t af, unsigned int offset);
+
+ extern int xt_compat_match_offset(const struct xt_match *match);
+-extern int xt_compat_match_from_user(struct xt_entry_match *m,
++extern void xt_compat_match_from_user(struct xt_entry_match *m,
+ void **dstptr, unsigned int *size);
+ extern int xt_compat_match_to_user(const struct xt_entry_match *m,
+ void __user **dstptr, unsigned int *size);
+diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c
+index e1fdffa0f9ce..86c7cd8c04a8 100644
+--- a/net/ipv4/netfilter/arp_tables.c
++++ b/net/ipv4/netfilter/arp_tables.c
+@@ -1294,7 +1294,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)
+@@ -1303,9 +1303,8 @@ compat_copy_entry_from_user(struct compat_arpt_entry *e, void **dstptr,
+ 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));
+@@ -1326,7 +1325,6 @@ compat_copy_entry_from_user(struct compat_arpt_entry *e, void **dstptr,
+ 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,
+@@ -1405,16 +1403,11 @@ static int translate_compat_table(struct xt_table_info **pinfo,
+ entry1 = newinfo->entries[raw_smp_processor_id()];
+ 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))
+diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c
+index 70a347ae8978..7075de08ef2f 100644
+--- a/net/ipv4/netfilter/ip_tables.c
++++ b/net/ipv4/netfilter/ip_tables.c
+@@ -1559,7 +1559,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)
+@@ -1568,10 +1568,9 @@ compat_copy_entry_from_user(struct compat_ipt_entry *e, void **dstptr,
+ 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));
+@@ -1580,11 +1579,9 @@ compat_copy_entry_from_user(struct compat_ipt_entry *e, void **dstptr,
+ *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;
+@@ -1597,7 +1594,6 @@ compat_copy_entry_from_user(struct compat_ipt_entry *e, void **dstptr,
+ if ((unsigned char *)de - base < newinfo->underflow[h])
+ newinfo->underflow[h] -= origsize - *size;
+ }
+- return ret;
+ }
+
+ static int
+@@ -1713,16 +1709,12 @@ translate_compat_table(struct net *net,
+ entry1 = newinfo->entries[raw_smp_processor_id()];
+ 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))
+diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_tables.c
+index a1ce9cb9b0a5..43191b3d800f 100644
+--- a/net/ipv6/netfilter/ip6_tables.c
++++ b/net/ipv6/netfilter/ip6_tables.c
+@@ -1584,7 +1584,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)
+@@ -1592,10 +1592,9 @@ compat_copy_entry_from_user(struct compat_ip6t_entry *e, void **dstptr,
+ 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));
+@@ -1604,11 +1603,9 @@ compat_copy_entry_from_user(struct compat_ip6t_entry *e, void **dstptr,
+ *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);
+@@ -1620,7 +1617,6 @@ compat_copy_entry_from_user(struct compat_ip6t_entry *e, void **dstptr,
+ 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,
+@@ -1735,17 +1731,12 @@ translate_compat_table(struct net *net,
+ }
+ entry1 = newinfo->entries[raw_smp_processor_id()];
+ 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))
+diff --git a/net/netfilter/x_tables.c b/net/netfilter/x_tables.c
+index cdb41c569f3d..0db2b357df6f 100644
+--- a/net/netfilter/x_tables.c
++++ b/net/netfilter/x_tables.c
+@@ -533,8 +533,8 @@ int xt_compat_match_offset(const struct xt_match *match)
+ }
+ 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;
+@@ -556,7 +556,6 @@ int xt_compat_match_from_user(struct xt_entry_match *m, void **dstptr,
+
+ *size += off;
+ *dstptr += msize;
+- return 0;
+ }
+ EXPORT_SYMBOL_GPL(xt_compat_match_from_user);
+
+--
+2.9.0
+
diff --git a/patches.kabi/kabi-revert-xt_compat_match_from_user-signature-chan.patch b/patches.kabi/kabi-revert-xt_compat_match_from_user-signature-chan.patch
new file mode 100644
index 0000000000..fa5e708367
--- /dev/null
+++ b/patches.kabi/kabi-revert-xt_compat_match_from_user-signature-chan.patch
@@ -0,0 +1,58 @@
+From: Michal Kubecek <mkubecek@suse.cz>
+Date: Fri, 24 Jun 2016 19:47:00 +0200
+Subject: kabi: revert xt_compat_match_from_user signature change
+Patch-mainline: Never, kabi workaround
+References: CVE-2016-4997 bsc#986362
+
+Backport of mainline commit 0188346f21e6 ("netfilter: x_tables:
+xt_compat_match_from_user doesn't need a retval") changes return type of
+protected function xt_compat_match_from_user(). Dropping that commit
+would do no harm but it would require unnecessary adjustments of the
+following patch; therefore we just revert the change of return type and
+put back "return 0" at the end.
+
+Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
+---
+ include/linux/netfilter/x_tables.h | 2 +-
+ net/netfilter/x_tables.c | 5 +++--
+ 2 files changed, 4 insertions(+), 3 deletions(-)
+
+diff --git a/include/linux/netfilter/x_tables.h b/include/linux/netfilter/x_tables.h
+index 6da5f79bf286..63ebc2f1f5f5 100644
+--- a/include/linux/netfilter/x_tables.h
++++ b/include/linux/netfilter/x_tables.h
+@@ -609,7 +609,7 @@ extern void xt_compat_init_offsets(u_int8_t af, unsigned int number);
+ extern int xt_compat_calc_jump(u_int8_t af, unsigned int offset);
+
+ extern int xt_compat_match_offset(const struct xt_match *match);
+-extern void xt_compat_match_from_user(struct xt_entry_match *m,
++extern int xt_compat_match_from_user(struct xt_entry_match *m,
+ void **dstptr, unsigned int *size);
+ extern int xt_compat_match_to_user(const struct xt_entry_match *m,
+ void __user **dstptr, unsigned int *size);
+diff --git a/net/netfilter/x_tables.c b/net/netfilter/x_tables.c
+index 0db2b357df6f..cdb41c569f3d 100644
+--- a/net/netfilter/x_tables.c
++++ b/net/netfilter/x_tables.c
+@@ -533,8 +533,8 @@ int xt_compat_match_offset(const struct xt_match *match)
+ }
+ EXPORT_SYMBOL_GPL(xt_compat_match_offset);
+
+-void xt_compat_match_from_user(struct xt_entry_match *m, void **dstptr,
+- unsigned int *size)
++int 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;
+@@ -556,6 +556,7 @@ void xt_compat_match_from_user(struct xt_entry_match *m, void **dstptr,
+
+ *size += off;
+ *dstptr += msize;
++ return 0;
+ }
+ EXPORT_SYMBOL_GPL(xt_compat_match_from_user);
+
+--
+2.9.0
+
diff --git a/series.conf b/series.conf
index f9ad70baee..1d63a3c5c8 100644
--- a/series.conf
+++ b/series.conf
@@ -2334,6 +2334,24 @@
patches.fixes/netfilter-x_tables-make-sure-e-next_offset-covers-re.patch
patches.fixes/netfilter-x_tables-fix-unconditional-helper.patch
+ # bsc#986362 / CVE-2016-4997
+ patches.fixes/netfilter-x_tables-don-t-move-to-non-existent-next-r.patch
+ patches.fixes/netfilter-x_tables-validate-targets-of-jumps.patch
+ patches.fixes/netfilter-x_tables-add-and-use-xt_check_entry_offset.patch
+ patches.fixes/netfilter-x_tables-kill-check_entry-helper.patch
+ patches.fixes/netfilter-x_tables-assert-minimum-target-size.patch
+ patches.fixes/netfilter-x_tables-add-compat-version-of-xt_check_en.patch
+ patches.fixes/netfilter-x_tables-check-standard-target-size-too.patch
+ patches.fixes/netfilter-x_tables-check-for-bogus-target-offset.patch
+ patches.fixes/netfilter-x_tables-validate-all-offsets-and-sizes-in.patch
+ patches.fixes/netfilter-ip_tables-simplify-translate_compat_table-.patch
+ patches.fixes/netfilter-ip6_tables-simplify-translate_compat_table.patch
+ patches.fixes/netfilter-arp_tables-simplify-translate_compat_table.patch
+ patches.fixes/netfilter-x_tables-xt_compat_match_from_user-doesn-t.patch
+ patches.kabi/kabi-revert-xt_compat_match_from_user-signature-chan.patch
+ patches.fixes/netfilter-x_tables-do-compat-validation-via-translat.patch
+ patches.fixes/netfilter-x_tables-don-t-reject-valid-target-size-on.patch
+
########################################################
#
# Device drivers