diff --git a/CVE-2016-4998.patch b/CVE-2016-4998.patch new file mode 100644 index 0000000..758cc64 --- /dev/null +++ b/CVE-2016-4998.patch @@ -0,0 +1,525 @@ +From bdf533de6968e9686df777dc178486f600c6e617 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Tue, 22 Mar 2016 18:02:49 +0100 +Subject: [PATCH] netfilter: x_tables: validate e->target_offset early + +We should check that e->target_offset is sane before +mark_source_chains gets called since it will fetch the target entry +for loop detection. + +Signed-off-by: Florian Westphal +Signed-off-by: Pablo Neira Ayuso +--- + net/ipv4/netfilter/arp_tables.c | 17 ++++++++--------- + net/ipv4/netfilter/ip_tables.c | 17 ++++++++--------- + net/ipv6/netfilter/ip6_tables.c | 17 ++++++++--------- + 3 files changed, 24 insertions(+), 27 deletions(-) + +diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c +index bf08192..830bbe8 100644 +--- a/net/ipv4/netfilter/arp_tables.c ++++ b/net/ipv4/netfilter/arp_tables.c +@@ -474,14 +474,12 @@ next: + return 1; + } + +-static inline int check_entry(const struct arpt_entry *e, const char *name) ++static inline int check_entry(const struct arpt_entry *e) + { + const struct xt_entry_target *t; + +- if (!arp_checkentry(&e->arp)) { +- duprintf("arp_tables: arp check failed %p %s.\n", e, name); ++ if (!arp_checkentry(&e->arp)) + return -EINVAL; +- } + + if (e->target_offset + sizeof(struct xt_entry_target) > e->next_offset) + return -EINVAL; +@@ -522,10 +520,6 @@ find_check_entry(struct arpt_entry *e, const char *name, unsigned int size) + struct xt_target *target; + int ret; + +- ret = check_entry(e, name); +- if (ret) +- return ret; +- + e->counters.pcnt = xt_percpu_counter_alloc(); + if (IS_ERR_VALUE(e->counters.pcnt)) + return -ENOMEM; +@@ -576,6 +570,7 @@ static inline int check_entry_size_and_hooks(struct arpt_entry *e, + unsigned int valid_hooks) + { + unsigned int h; ++ int err; + + if ((unsigned long)e % __alignof__(struct arpt_entry) != 0 || + (unsigned char *)e + sizeof(struct arpt_entry) >= limit) { +@@ -590,6 +585,10 @@ static inline int check_entry_size_and_hooks(struct arpt_entry *e, + return -EINVAL; + } + ++ err = check_entry(e); ++ if (err) ++ return err; ++ + /* Check hooks & underflows */ + for (h = 0; h < NF_ARP_NUMHOOKS; h++) { + if (!(valid_hooks & (1 << h))) +@@ -1246,7 +1245,7 @@ check_compat_entry_size_and_hooks(struct compat_arpt_entry *e, + } + + /* For purposes of check_entry casting the compat entry is fine */ +- ret = check_entry((struct arpt_entry *)e, name); ++ ret = check_entry((struct arpt_entry *)e); + if (ret) + return ret; + +diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c +index e53f8d6..1d72a3c 100644 +--- a/net/ipv4/netfilter/ip_tables.c ++++ b/net/ipv4/netfilter/ip_tables.c +@@ -569,14 +569,12 @@ static void cleanup_match(struct xt_entry_match *m, struct net *net) + } + + static int +-check_entry(const struct ipt_entry *e, const char *name) ++check_entry(const struct ipt_entry *e) + { + const struct xt_entry_target *t; + +- if (!ip_checkentry(&e->ip)) { +- duprintf("ip check failed %p %s.\n", e, name); ++ if (!ip_checkentry(&e->ip)) + return -EINVAL; +- } + + if (e->target_offset + sizeof(struct xt_entry_target) > + e->next_offset) +@@ -666,10 +664,6 @@ find_check_entry(struct ipt_entry *e, struct net *net, const char *name, + struct xt_mtchk_param mtpar; + struct xt_entry_match *ematch; + +- ret = check_entry(e, name); +- if (ret) +- return ret; +- + e->counters.pcnt = xt_percpu_counter_alloc(); + if (IS_ERR_VALUE(e->counters.pcnt)) + return -ENOMEM; +@@ -741,6 +735,7 @@ check_entry_size_and_hooks(struct ipt_entry *e, + unsigned int valid_hooks) + { + unsigned int h; ++ int err; + + if ((unsigned long)e % __alignof__(struct ipt_entry) != 0 || + (unsigned char *)e + sizeof(struct ipt_entry) >= limit) { +@@ -755,6 +750,10 @@ check_entry_size_and_hooks(struct ipt_entry *e, + return -EINVAL; + } + ++ err = check_entry(e); ++ if (err) ++ return err; ++ + /* Check hooks & underflows */ + for (h = 0; h < NF_INET_NUMHOOKS; h++) { + if (!(valid_hooks & (1 << h))) +@@ -1506,7 +1505,7 @@ check_compat_entry_size_and_hooks(struct compat_ipt_entry *e, + } + + /* For purposes of check_entry casting the compat entry is fine */ +- ret = check_entry((struct ipt_entry *)e, name); ++ ret = check_entry((struct ipt_entry *)e); + if (ret) + return ret; + +diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_tables.c +index 84f9baf..26a5ad1 100644 +--- a/net/ipv6/netfilter/ip6_tables.c ++++ b/net/ipv6/netfilter/ip6_tables.c +@@ -581,14 +581,12 @@ static void cleanup_match(struct xt_entry_match *m, struct net *net) + } + + static int +-check_entry(const struct ip6t_entry *e, const char *name) ++check_entry(const struct ip6t_entry *e) + { + const struct xt_entry_target *t; + +- if (!ip6_checkentry(&e->ipv6)) { +- duprintf("ip_tables: ip check failed %p %s.\n", e, name); ++ if (!ip6_checkentry(&e->ipv6)) + return -EINVAL; +- } + + if (e->target_offset + sizeof(struct xt_entry_target) > + e->next_offset) +@@ -679,10 +677,6 @@ find_check_entry(struct ip6t_entry *e, struct net *net, const char *name, + struct xt_mtchk_param mtpar; + struct xt_entry_match *ematch; + +- ret = check_entry(e, name); +- if (ret) +- return ret; +- + e->counters.pcnt = xt_percpu_counter_alloc(); + if (IS_ERR_VALUE(e->counters.pcnt)) + return -ENOMEM; +@@ -753,6 +747,7 @@ check_entry_size_and_hooks(struct ip6t_entry *e, + unsigned int valid_hooks) + { + unsigned int h; ++ int err; + + if ((unsigned long)e % __alignof__(struct ip6t_entry) != 0 || + (unsigned char *)e + sizeof(struct ip6t_entry) >= limit) { +@@ -767,6 +762,10 @@ check_entry_size_and_hooks(struct ip6t_entry *e, + return -EINVAL; + } + ++ err = check_entry(e); ++ if (err) ++ return err; ++ + /* Check hooks & underflows */ + for (h = 0; h < NF_INET_NUMHOOKS; h++) { + if (!(valid_hooks & (1 << h))) +@@ -1518,7 +1517,7 @@ check_compat_entry_size_and_hooks(struct compat_ip6t_entry *e, + } + + /* For purposes of check_entry casting the compat entry is fine */ +- ret = check_entry((struct ip6t_entry *)e, name); ++ ret = check_entry((struct ip6t_entry *)e); + if (ret) + return ret; + +-- +2.7.4 + +From 6e94e0cfb0887e4013b3b930fa6ab1fe6bb6ba91 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Tue, 22 Mar 2016 18:02:50 +0100 +Subject: [PATCH] netfilter: x_tables: make sure e->next_offset covers + remaining blob size + +Otherwise this function may read data beyond the ruleset blob. + +Signed-off-by: Florian Westphal +Signed-off-by: Pablo Neira Ayuso +--- + net/ipv4/netfilter/arp_tables.c | 6 ++++-- + net/ipv4/netfilter/ip_tables.c | 6 ++++-- + net/ipv6/netfilter/ip6_tables.c | 6 ++++-- + 3 files changed, 12 insertions(+), 6 deletions(-) + +diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c +index 830bbe8..51d4fe5 100644 +--- a/net/ipv4/netfilter/arp_tables.c ++++ b/net/ipv4/netfilter/arp_tables.c +@@ -573,7 +573,8 @@ static inline int check_entry_size_and_hooks(struct arpt_entry *e, + int err; + + if ((unsigned long)e % __alignof__(struct arpt_entry) != 0 || +- (unsigned char *)e + sizeof(struct arpt_entry) >= limit) { ++ (unsigned char *)e + sizeof(struct arpt_entry) >= limit || ++ (unsigned char *)e + e->next_offset > limit) { + duprintf("Bad offset %p\n", e); + return -EINVAL; + } +@@ -1232,7 +1233,8 @@ check_compat_entry_size_and_hooks(struct compat_arpt_entry *e, + + duprintf("check_compat_entry_size_and_hooks %p\n", e); + if ((unsigned long)e % __alignof__(struct compat_arpt_entry) != 0 || +- (unsigned char *)e + sizeof(struct compat_arpt_entry) >= limit) { ++ (unsigned char *)e + sizeof(struct compat_arpt_entry) >= limit || ++ (unsigned char *)e + e->next_offset > limit) { + duprintf("Bad offset %p, limit = %p\n", e, limit); + return -EINVAL; + } +diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c +index 1d72a3c..fb7694e 100644 +--- a/net/ipv4/netfilter/ip_tables.c ++++ b/net/ipv4/netfilter/ip_tables.c +@@ -738,7 +738,8 @@ check_entry_size_and_hooks(struct ipt_entry *e, + int err; + + if ((unsigned long)e % __alignof__(struct ipt_entry) != 0 || +- (unsigned char *)e + sizeof(struct ipt_entry) >= limit) { ++ (unsigned char *)e + sizeof(struct ipt_entry) >= limit || ++ (unsigned char *)e + e->next_offset > limit) { + duprintf("Bad offset %p\n", e); + return -EINVAL; + } +@@ -1492,7 +1493,8 @@ check_compat_entry_size_and_hooks(struct compat_ipt_entry *e, + + duprintf("check_compat_entry_size_and_hooks %p\n", e); + if ((unsigned long)e % __alignof__(struct compat_ipt_entry) != 0 || +- (unsigned char *)e + sizeof(struct compat_ipt_entry) >= limit) { ++ (unsigned char *)e + sizeof(struct compat_ipt_entry) >= limit || ++ (unsigned char *)e + e->next_offset > limit) { + duprintf("Bad offset %p, limit = %p\n", e, limit); + return -EINVAL; + } +diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_tables.c +index 26a5ad1..b248528f 100644 +--- a/net/ipv6/netfilter/ip6_tables.c ++++ b/net/ipv6/netfilter/ip6_tables.c +@@ -750,7 +750,8 @@ check_entry_size_and_hooks(struct ip6t_entry *e, + int err; + + if ((unsigned long)e % __alignof__(struct ip6t_entry) != 0 || +- (unsigned char *)e + sizeof(struct ip6t_entry) >= limit) { ++ (unsigned char *)e + sizeof(struct ip6t_entry) >= limit || ++ (unsigned char *)e + e->next_offset > limit) { + duprintf("Bad offset %p\n", e); + return -EINVAL; + } +@@ -1504,7 +1505,8 @@ check_compat_entry_size_and_hooks(struct compat_ip6t_entry *e, + + duprintf("check_compat_entry_size_and_hooks %p\n", e); + if ((unsigned long)e % __alignof__(struct compat_ip6t_entry) != 0 || +- (unsigned char *)e + sizeof(struct compat_ip6t_entry) >= limit) { ++ (unsigned char *)e + sizeof(struct compat_ip6t_entry) >= limit || ++ (unsigned char *)e + e->next_offset > limit) { + duprintf("Bad offset %p, limit = %p\n", e, limit); + return -EINVAL; + } +-- +2.7.4 + +From 54d83fc74aa9ec72794373cb47432c5f7fb1a309 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Tue, 22 Mar 2016 18:02:52 +0100 +Subject: [PATCH] netfilter: x_tables: fix unconditional helper + +Ben Hawkes says: + + In the mark_source_chains function (net/ipv4/netfilter/ip_tables.c) it + is possible for a user-supplied ipt_entry structure to have a large + next_offset field. This field is not bounds checked prior to writing a + counter value at the supplied offset. + +Problem is that mark_source_chains should not have been called -- +the rule doesn't have a next entry, so its supposed to return +an absolute verdict of either ACCEPT or DROP. + +However, the function conditional() doesn't work as the name implies. +It only checks that the rule is using wildcard address matching. + +However, an unconditional rule must also not be using any matches +(no -m args). + +The underflow validator only checked the addresses, therefore +passing the 'unconditional absolute verdict' test, while +mark_source_chains also tested for presence of matches, and thus +proceeeded to the next (not-existent) rule. + +Unify this so that all the callers have same idea of 'unconditional rule'. + +Reported-by: Ben Hawkes +Signed-off-by: Florian Westphal +Signed-off-by: Pablo Neira Ayuso +--- + net/ipv4/netfilter/arp_tables.c | 18 +++++++++--------- + net/ipv4/netfilter/ip_tables.c | 23 +++++++++++------------ + net/ipv6/netfilter/ip6_tables.c | 23 +++++++++++------------ + 3 files changed, 31 insertions(+), 33 deletions(-) + +diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c +index 51d4fe5..a1bb5e7 100644 +--- a/net/ipv4/netfilter/arp_tables.c ++++ b/net/ipv4/netfilter/arp_tables.c +@@ -359,11 +359,12 @@ unsigned int arpt_do_table(struct sk_buff *skb, + } + + /* All zeroes == unconditional rule. */ +-static inline bool unconditional(const struct arpt_arp *arp) ++static inline bool unconditional(const struct arpt_entry *e) + { + static const struct arpt_arp uncond; + +- return memcmp(arp, &uncond, sizeof(uncond)) == 0; ++ return e->target_offset == sizeof(struct arpt_entry) && ++ memcmp(&e->arp, &uncond, sizeof(uncond)) == 0; + } + + /* Figures out from what hook each rule can be called: returns 0 if +@@ -402,11 +403,10 @@ static int mark_source_chains(const struct xt_table_info *newinfo, + |= ((1 << hook) | (1 << NF_ARP_NUMHOOKS)); + + /* Unconditional return/END. */ +- if ((e->target_offset == sizeof(struct arpt_entry) && ++ if ((unconditional(e) && + (strcmp(t->target.u.user.name, + XT_STANDARD_TARGET) == 0) && +- t->verdict < 0 && unconditional(&e->arp)) || +- visited) { ++ t->verdict < 0) || visited) { + unsigned int oldpos, size; + + if ((strcmp(t->target.u.user.name, +@@ -551,7 +551,7 @@ static bool check_underflow(const struct arpt_entry *e) + const struct xt_entry_target *t; + unsigned int verdict; + +- if (!unconditional(&e->arp)) ++ if (!unconditional(e)) + return false; + t = arpt_get_target_c(e); + if (strcmp(t->u.user.name, XT_STANDARD_TARGET) != 0) +@@ -598,9 +598,9 @@ static inline int check_entry_size_and_hooks(struct arpt_entry *e, + newinfo->hook_entry[h] = hook_entries[h]; + if ((unsigned char *)e - base == underflows[h]) { + if (!check_underflow(e)) { +- pr_err("Underflows must be unconditional and " +- "use the STANDARD target with " +- "ACCEPT/DROP\n"); ++ pr_debug("Underflows must be unconditional and " ++ "use the STANDARD target with " ++ "ACCEPT/DROP\n"); + return -EINVAL; + } + newinfo->underflow[h] = underflows[h]; +diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c +index fb7694e..89b5d95 100644 +--- a/net/ipv4/netfilter/ip_tables.c ++++ b/net/ipv4/netfilter/ip_tables.c +@@ -168,11 +168,12 @@ get_entry(const void *base, unsigned int offset) + + /* All zeroes == unconditional rule. */ + /* Mildly perf critical (only if packet tracing is on) */ +-static inline bool unconditional(const struct ipt_ip *ip) ++static inline bool unconditional(const struct ipt_entry *e) + { + static const struct ipt_ip uncond; + +- return memcmp(ip, &uncond, sizeof(uncond)) == 0; ++ return e->target_offset == sizeof(struct ipt_entry) && ++ memcmp(&e->ip, &uncond, sizeof(uncond)) == 0; + #undef FWINV + } + +@@ -229,11 +230,10 @@ get_chainname_rulenum(const struct ipt_entry *s, const struct ipt_entry *e, + } else if (s == e) { + (*rulenum)++; + +- if (s->target_offset == sizeof(struct ipt_entry) && ++ if (unconditional(s) && + strcmp(t->target.u.kernel.target->name, + XT_STANDARD_TARGET) == 0 && +- t->verdict < 0 && +- unconditional(&s->ip)) { ++ t->verdict < 0) { + /* Tail of chains: STANDARD target (return/policy) */ + *comment = *chainname == hookname + ? comments[NF_IP_TRACE_COMMENT_POLICY] +@@ -476,11 +476,10 @@ mark_source_chains(const struct xt_table_info *newinfo, + e->comefrom |= ((1 << hook) | (1 << NF_INET_NUMHOOKS)); + + /* Unconditional return/END. */ +- if ((e->target_offset == sizeof(struct ipt_entry) && ++ if ((unconditional(e) && + (strcmp(t->target.u.user.name, + XT_STANDARD_TARGET) == 0) && +- t->verdict < 0 && unconditional(&e->ip)) || +- visited) { ++ t->verdict < 0) || visited) { + unsigned int oldpos, size; + + if ((strcmp(t->target.u.user.name, +@@ -715,7 +714,7 @@ static bool check_underflow(const struct ipt_entry *e) + const struct xt_entry_target *t; + unsigned int verdict; + +- if (!unconditional(&e->ip)) ++ if (!unconditional(e)) + return false; + t = ipt_get_target_c(e); + if (strcmp(t->u.user.name, XT_STANDARD_TARGET) != 0) +@@ -763,9 +762,9 @@ check_entry_size_and_hooks(struct ipt_entry *e, + newinfo->hook_entry[h] = hook_entries[h]; + if ((unsigned char *)e - base == underflows[h]) { + if (!check_underflow(e)) { +- pr_err("Underflows must be unconditional and " +- "use the STANDARD target with " +- "ACCEPT/DROP\n"); ++ pr_debug("Underflows must be unconditional and " ++ "use the STANDARD target with " ++ "ACCEPT/DROP\n"); + return -EINVAL; + } + newinfo->underflow[h] = underflows[h]; +diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_tables.c +index b248528f..541b59f 100644 +--- a/net/ipv6/netfilter/ip6_tables.c ++++ b/net/ipv6/netfilter/ip6_tables.c +@@ -198,11 +198,12 @@ get_entry(const void *base, unsigned int offset) + + /* All zeroes == unconditional rule. */ + /* Mildly perf critical (only if packet tracing is on) */ +-static inline bool unconditional(const struct ip6t_ip6 *ipv6) ++static inline bool unconditional(const struct ip6t_entry *e) + { + static const struct ip6t_ip6 uncond; + +- return memcmp(ipv6, &uncond, sizeof(uncond)) == 0; ++ return e->target_offset == sizeof(struct ip6t_entry) && ++ memcmp(&e->ipv6, &uncond, sizeof(uncond)) == 0; + } + + static inline const struct xt_entry_target * +@@ -258,11 +259,10 @@ get_chainname_rulenum(const struct ip6t_entry *s, const struct ip6t_entry *e, + } else if (s == e) { + (*rulenum)++; + +- if (s->target_offset == sizeof(struct ip6t_entry) && ++ if (unconditional(s) && + strcmp(t->target.u.kernel.target->name, + XT_STANDARD_TARGET) == 0 && +- t->verdict < 0 && +- unconditional(&s->ipv6)) { ++ t->verdict < 0) { + /* Tail of chains: STANDARD target (return/policy) */ + *comment = *chainname == hookname + ? comments[NF_IP6_TRACE_COMMENT_POLICY] +@@ -488,11 +488,10 @@ mark_source_chains(const struct xt_table_info *newinfo, + e->comefrom |= ((1 << hook) | (1 << NF_INET_NUMHOOKS)); + + /* Unconditional return/END. */ +- if ((e->target_offset == sizeof(struct ip6t_entry) && ++ if ((unconditional(e) && + (strcmp(t->target.u.user.name, + XT_STANDARD_TARGET) == 0) && +- t->verdict < 0 && +- unconditional(&e->ipv6)) || visited) { ++ t->verdict < 0) || visited) { + unsigned int oldpos, size; + + if ((strcmp(t->target.u.user.name, +@@ -727,7 +726,7 @@ static bool check_underflow(const struct ip6t_entry *e) + const struct xt_entry_target *t; + unsigned int verdict; + +- if (!unconditional(&e->ipv6)) ++ if (!unconditional(e)) + return false; + t = ip6t_get_target_c(e); + if (strcmp(t->u.user.name, XT_STANDARD_TARGET) != 0) +@@ -775,9 +774,9 @@ check_entry_size_and_hooks(struct ip6t_entry *e, + newinfo->hook_entry[h] = hook_entries[h]; + if ((unsigned char *)e - base == underflows[h]) { + if (!check_underflow(e)) { +- pr_err("Underflows must be unconditional and " +- "use the STANDARD target with " +- "ACCEPT/DROP\n"); ++ pr_debug("Underflows must be unconditional and " ++ "use the STANDARD target with " ++ "ACCEPT/DROP\n"); + return -EINVAL; + } + newinfo->underflow[h] = underflows[h]; +-- +2.7.4 + diff --git a/kernel.spec b/kernel.spec index 9709814..99343d5 100644 --- a/kernel.spec +++ b/kernel.spec @@ -42,7 +42,7 @@ Summary: The Linux kernel # For non-released -rc kernels, this will be appended after the rcX and # gitX tags, so a 3 here would become part of release "0.rcX.gitX.3" # -%global baserelease 200 +%global baserelease 201 %global fedora_build %{baserelease} # base_sublevel is the kernel version we're starting with and patching @@ -670,6 +670,9 @@ Patch727: KEYS-potential-uninitialized-variable.patch #rhbz 1338025 Patch728: hp-wmi-fix-wifi-cannot-be-hard-unblock.patch +#CVE-2016-4998 rhbz 1349886 1350316 +Patch729: CVE-2016-4998.patch + # END OF PATCH DEFINITIONS %endif @@ -2187,6 +2190,9 @@ fi # # %changelog +* Mon Jun 27 2016 Justin M. Forbes 4.5.7-201 +- CVE-2016-4998 oob reads when processing IPT_SO_SET_REPLACE setsockopt (rhbz 1349886 1350316) + * Wed Jun 15 2016 Laura Abbott - hp-wmi: fix wifi cannot be hard-unblock (rhbz 1338025)