Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

netfilter: nf_tables: don't skip inactive chains during update

There is no synchronization between packet path and the configuration plane.

The packet path uses two arrays with rules, one contains the current (active)
generation. The other either contains the last (obsolete) generation or
the future one.

Consider:
cpu1 cpu2
nft_do_chain(c);
delete c
net->gen++;
genbit = !!net->gen;
rules = c->rg[genbit];

cpu1 ignores c when updating if c is not active anymore in the new
generation.

On cpu2, we now use rules from wrong generation, as c->rg[old]
contains the rules matching 'c' whereas c->rg[new] was not updated and
can even point to rules that have been free'd already, causing a crash.

To fix this, make sure that 'current' to the 'next' generation are
identical for chains that are going away so that c->rg[new] will just
use the matching rules even if genbit was incremented already.

Fixes: 0cbc06b3faba7 ("netfilter: nf_tables: remove synchronize_rcu in commit phase")
Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>

authored by

Florian Westphal and committed by
Pablo Neira Ayuso
0fb39bbe 3c5cdb17

+3 -6
+3 -6
net/netfilter/nf_tables_api.c
··· 6324 6324 call_rcu(&old->h, __nf_tables_commit_chain_free_rules_old); 6325 6325 } 6326 6326 6327 - static void nf_tables_commit_chain_active(struct net *net, struct nft_chain *chain) 6327 + static void nf_tables_commit_chain(struct net *net, struct nft_chain *chain) 6328 6328 { 6329 6329 struct nft_rule **g0, **g1; 6330 6330 bool next_genbit; ··· 6441 6441 6442 6442 /* step 2. Make rules_gen_X visible to packet path */ 6443 6443 list_for_each_entry(table, &net->nft.tables, list) { 6444 - list_for_each_entry(chain, &table->chains, list) { 6445 - if (!nft_is_active_next(net, chain)) 6446 - continue; 6447 - nf_tables_commit_chain_active(net, chain); 6448 - } 6444 + list_for_each_entry(chain, &table->chains, list) 6445 + nf_tables_commit_chain(net, chain); 6449 6446 } 6450 6447 6451 6448 /*