Skip to content

Commit

Permalink
netfilter: nf_conncount: split gc in two phases
Browse files Browse the repository at this point in the history
The lockless workqueue garbage collector can race with packet path
garbage collector to delete list nodes, as it calls tree_nodes_free()
with the addresses of nodes that might have been free'd already from
another cpu.

To fix this, split gc into two phases.

One phase to perform gc on the connections: From a locking perspective,
this is the same as count_tree(): we hold rcu lock, but we do not
change the tree, we only change the nodes' contents.

The second phase acquires the tree lock and reaps empty nodes.
This avoids a race condition of the garbage collection vs.  packet path:
If a node has been free'd already, the second phase won't find it anymore.

This second phase is, from locking perspective, same as insert_tree().

The former only modifies nodes (list content, count), latter modifies
the tree itself (rb_erase or rb_insert).

Fixes: 5c789e1 ("netfilter: nf_conncount: Add list lock and gc worker, and RCU for init tree search")
Reviewed-by: Shawn Bohrer <[email protected]>
Signed-off-by: Florian Westphal <[email protected]>
Signed-off-by: Pablo Neira Ayuso <[email protected]>
  • Loading branch information
Florian Westphal authored and ummakynes committed Dec 29, 2018
1 parent 4cd273b commit f7fcc98
Showing 1 changed file with 19 additions and 3 deletions.
22 changes: 19 additions & 3 deletions net/netfilter/nf_conncount.c
Original file line number Diff line number Diff line change
Expand Up @@ -500,16 +500,32 @@ static void tree_gc_worker(struct work_struct *work)
for (node = rb_first(root); node != NULL; node = rb_next(node)) {
rbconn = rb_entry(node, struct nf_conncount_rb, node);
if (nf_conncount_gc_list(data->net, &rbconn->list))
gc_nodes[gc_count++] = rbconn;
gc_count++;
}
rcu_read_unlock();

spin_lock_bh(&nf_conncount_locks[tree]);
if (gc_count < ARRAY_SIZE(gc_nodes))
goto next; /* do not bother */

if (gc_count) {
tree_nodes_free(root, gc_nodes, gc_count);
gc_count = 0;
node = rb_first(root);
while (node != NULL) {
rbconn = rb_entry(node, struct nf_conncount_rb, node);
node = rb_next(node);

if (rbconn->list.count > 0)
continue;

gc_nodes[gc_count++] = rbconn;
if (gc_count >= ARRAY_SIZE(gc_nodes)) {
tree_nodes_free(root, gc_nodes, gc_count);
gc_count = 0;
}
}

tree_nodes_free(root, gc_nodes, gc_count);
next:
clear_bit(tree, data->pending_trees);

next_tree = (tree + 1) % CONNCOUNT_SLOTS;
Expand Down

0 comments on commit f7fcc98

Please sign in to comment.