CVE-2016-3134 (C, published 2016-04-27T17:59Z)
Description: value: The netfilter subsystem in the Linux kernel through 4.5.2 does not validate certain offset fields, which allows local users to gain privileges or cause a denial of service (heap memory corruption) via an IPT_SO_SET_REPLACE setsockopt call.
Severity: HIGH (CVSS v3 base score: 8.4)
Weakness: CWE-119 \u2014 Improper Restriction of Operations within the Bounds of a Memory Buffer
The product performs operations on a memory buffer, but it reads from or writes to a memory location outside the buffer's intended boundary. This may result in read or write operations on unexpected memory locations that could be linked to other variables, data structures, or internal program data.
Repository: https://github.com/torvalds/linux
Files changed:
net/ipv4/netfilter/arp_tables.c
net/ipv4/netfilter/ip_tables.c
net/ipv6/netfilter/ip6_tables.c
Commit message: 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 <hawkes@google.com>
Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>


Vulnerable code:
static inline bool unconditional(const struct arpt_arp *arp)
	return memcmp(arp, &uncond, sizeof(uncond)) == 0;
			if ((e->target_offset == sizeof(struct arpt_entry) &&
			     t->verdict < 0 && unconditional(&e->arp)) ||
			    visited) {
	if (!unconditional(&e->arp))
				pr_err("Underflows must be unconditional and "
				       "use the STANDARD target with "
				       "ACCEPT/DROP\n");
static inline bool unconditional(const struct ipt_ip *ip)
	return memcmp(ip, &uncond, sizeof(uncond)) == 0;
		if (s->target_offset == sizeof(struct ipt_entry) &&
		   t->verdict < 0 &&
		   unconditional(&s->ip)) {
			if ((e->target_offset == sizeof(struct ipt_entry) &&
			     t->verdict < 0 && unconditional(&e->ip)) ||
			    visited) {
	if (!unconditional(&e->ip))
				pr_err("Underflows must be unconditional and "
				       "use the STANDARD target with "
				       "ACCEPT/DROP\n");
static inline bool unconditional(const struct ip6t_ip6 *ipv6)
	return memcmp(ipv6, &uncond, sizeof(uncond)) == 0;
		if (s->target_offset == sizeof(struct ip6t_entry) &&
		    t->verdict < 0 &&
		    unconditional(&s->ipv6)) {
			if ((e->target_offset == sizeof(struct ip6t_entry) &&
			     t->verdict < 0 &&
			     unconditional(&e->ipv6)) || visited) {
	if (!unconditional(&e->ipv6))
				pr_err("Underflows must be unconditional and "
				       "use the STANDARD target with "
				       "ACCEPT/DROP\n");

Fixed code:
static inline bool unconditional(const struct arpt_entry *e)
	return e->target_offset == sizeof(struct arpt_entry) &&
	       memcmp(&e->arp, &uncond, sizeof(uncond)) == 0;
			if ((unconditional(e) &&
			     t->verdict < 0) || visited) {
	if (!unconditional(e))
				pr_debug("Underflows must be unconditional and "
					 "use the STANDARD target with "
					 "ACCEPT/DROP\n");
static inline bool unconditional(const struct ipt_entry *e)
	return e->target_offset == sizeof(struct ipt_entry) &&
	       memcmp(&e->ip, &uncond, sizeof(uncond)) == 0;
		if (unconditional(s) &&
		   t->verdict < 0) {
			if ((unconditional(e) &&
			     t->verdict < 0) || visited) {
	if (!unconditional(e))
				pr_debug("Underflows must be unconditional and "
					 "use the STANDARD target with "
					 "ACCEPT/DROP\n");
static inline bool unconditional(const struct ip6t_entry *e)
	return e->target_offset == sizeof(struct ip6t_entry) &&
	       memcmp(&e->ipv6, &uncond, sizeof(uncond)) == 0;
		if (unconditional(s) &&
		    t->verdict < 0) {
			if ((unconditional(e) &&
			     t->verdict < 0) || visited) {
	if (!unconditional(e))
				pr_debug("Underflows must be unconditional and "
					 "use the STANDARD target with "
					 "ACCEPT/DROP\n");