Commit message: netfilter: x_tables: Use correct memory barriers.

When a new table value was assigned, it was followed by a write memory
barrier. This ensured that all writes before this point would complete
before any writes after this point. However, to determine whether the
rules are unused, the sequence counter is read. To ensure that all
writes have been done before these reads, a full memory barrier is
needed, not just a write memory barrier. The same argument applies when
incrementing the counter, before the rules are read.

Changing to using smp_mb() instead of smp_wmb() fixes the kernel panic
reported in cc00bcaa5899 (which is still present), while still
maintaining the same speed of replacing tables.

The smb_mb() barriers potentially slow the packet path, however testing
has shown no measurable change in performance on a 4-core MIPS64
platform.

Fixes: 7f5c6d4f665b ("netfilter: get rid of atomic ops in fast path")
Signed-off-by: Mark Tomlinson <mark.tomlinson@alliedtelesis.co.nz>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
CVE: CVE-2021-29650
CWE: NVD-CWE-noinfo
Vulnerability description: An issue was discovered in the Linux kernel before 5.11.11. The netfilter subsystem allows attackers to cause a denial of service (panic) because net/netfilter/x_tables.c and include/linux/netfilter/x_tables.h lack a full memory barrier upon the assignment of a new table value, aka CID-175e476b8cdf.

Project: linux
File: x_tables.h

static inline struct net *xt_net(const struct xt_action_param *par)
{
	return par->state->net;
}

Project: linux
File: x_tables.h

static inline unsigned int xt_write_recseq_begin(void)
{
	unsigned int addend;

	/*
	 * Low order bit of sequence is set if we already
	 * called xt_write_recseq_begin().
	 */
	addend = (__this_cpu_read(xt_recseq.sequence) + 1) & 1;

	/*
	 * This is kind of a write_seqcount_begin(), but addend is 0 or 1
	 * We dont check addend value to avoid a test and conditional jump,
	 * since addend is most likely 1
	 */
	__this_cpu_add(xt_recseq.sequence, addend);
	smp_mb();

	return addend;
}

Project: linux
File: x_tables.h

static inline void xt_write_recseq_end(unsigned int addend)
{
	/* this is kind of a write_seqcount_end(), but addend is 0 or 1 */
	smp_wmb();
	__this_cpu_add(xt_recseq.sequence, addend);
}

Project: linux
File: x_tables.h

xt_get_this_cpu_counter(struct xt_counters *cnt)
{
	if (nr_cpu_ids > 1)
		return this_cpu_ptr((void __percpu *) (unsigned long) cnt->pcnt);

	return cnt;
}

Project: linux
File: x_tables.h

static inline const char *xt_inname(const struct xt_action_param *par)
{
	return par->state->in->name;
}

Project: linux
File: x_tables.h

static inline struct net_device *xt_out(const struct xt_action_param *par)
{
	return par->state->out;
}

Project: linux
File: x_tables.h

static inline u_int8_t xt_family(const struct xt_action_param *par)
{
	return par->state->pf;
}

Project: linux
File: x_tables.h

static inline const char *xt_outname(const struct xt_action_param *par)
{
	return par->state->out->name;
}

Project: linux
File: x_tables.h

static inline struct net_device *xt_in(const struct xt_action_param *par)
{
	return par->state->in;
}

Project: linux
File: x_tables.h

xt_get_per_cpu_counter(struct xt_counters *cnt, unsigned int cpu)
{
	if (nr_cpu_ids > 1)
		return per_cpu_ptr((void __percpu *) (unsigned long) cnt->pcnt, cpu);

	return cnt;
}

Project: linux
File: x_tables.h

static inline unsigned long ifname_compare_aligned(const char *_a,
						   const char *_b,
						   const char *_mask)
{
	const unsigned long *a = (const unsigned long *)_a;
	const unsigned long *b = (const unsigned long *)_b;
	const unsigned long *mask = (const unsigned long *)_mask;
	unsigned long ret;

	ret = (a[0] ^ b[0]) & mask[0];
	if (IFNAMSIZ > sizeof(unsigned long))
		ret |= (a[1] ^ b[1]) & mask[1];
	if (IFNAMSIZ > 2 * sizeof(unsigned long))
		ret |= (a[2] ^ b[2]) & mask[2];
	if (IFNAMSIZ > 3 * sizeof(unsigned long))
		ret |= (a[3] ^ b[3]) & mask[3];
	BUILD_BUG_ON(IFNAMSIZ > 4 * sizeof(unsigned long));
	return ret;
}

Project: linux
File: x_tables.h

static inline unsigned int xt_hooknum(const struct xt_action_param *par)
{
	return par->state->hook;
}