diff options
| author | Florian Westphal <fw@strlen.de> | 2018-12-27 19:24:44 -0500 |
|---|---|---|
| committer | Pablo Neira Ayuso <pablo@netfilter.org> | 2018-12-28 20:45:19 -0500 |
| commit | f7fcc98dfc2d136722007fec0debbed761679b94 (patch) | |
| tree | abac7ea27b0b8414e427da2ed4995d1c14a85b80 /net | |
| parent | 4cd273bb91b3001f623f516ec726c49754571b1a (diff) | |
netfilter: nf_conncount: split gc in two phases
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: 5c789e131cbb9 ("netfilter: nf_conncount: Add list lock and gc worker, and RCU for init tree search")
Reviewed-by: Shawn Bohrer <sbohrer@cloudflare.com>
Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Diffstat (limited to 'net')
| -rw-r--r-- | net/netfilter/nf_conncount.c | 22 |
1 files changed, 19 insertions, 3 deletions
diff --git a/net/netfilter/nf_conncount.c b/net/netfilter/nf_conncount.c index 8bb4ed85c262..753132e4afa8 100644 --- a/net/netfilter/nf_conncount.c +++ b/net/netfilter/nf_conncount.c | |||
| @@ -500,16 +500,32 @@ static void tree_gc_worker(struct work_struct *work) | |||
| 500 | for (node = rb_first(root); node != NULL; node = rb_next(node)) { | 500 | for (node = rb_first(root); node != NULL; node = rb_next(node)) { |
| 501 | rbconn = rb_entry(node, struct nf_conncount_rb, node); | 501 | rbconn = rb_entry(node, struct nf_conncount_rb, node); |
| 502 | if (nf_conncount_gc_list(data->net, &rbconn->list)) | 502 | if (nf_conncount_gc_list(data->net, &rbconn->list)) |
| 503 | gc_nodes[gc_count++] = rbconn; | 503 | gc_count++; |
| 504 | } | 504 | } |
| 505 | rcu_read_unlock(); | 505 | rcu_read_unlock(); |
| 506 | 506 | ||
| 507 | spin_lock_bh(&nf_conncount_locks[tree]); | 507 | spin_lock_bh(&nf_conncount_locks[tree]); |
| 508 | if (gc_count < ARRAY_SIZE(gc_nodes)) | ||
| 509 | goto next; /* do not bother */ | ||
| 508 | 510 | ||
| 509 | if (gc_count) { | 511 | gc_count = 0; |
| 510 | tree_nodes_free(root, gc_nodes, gc_count); | 512 | node = rb_first(root); |
| 513 | while (node != NULL) { | ||
| 514 | rbconn = rb_entry(node, struct nf_conncount_rb, node); | ||
| 515 | node = rb_next(node); | ||
| 516 | |||
| 517 | if (rbconn->list.count > 0) | ||
| 518 | continue; | ||
| 519 | |||
| 520 | gc_nodes[gc_count++] = rbconn; | ||
| 521 | if (gc_count >= ARRAY_SIZE(gc_nodes)) { | ||
| 522 | tree_nodes_free(root, gc_nodes, gc_count); | ||
| 523 | gc_count = 0; | ||
| 524 | } | ||
| 511 | } | 525 | } |
| 512 | 526 | ||
| 527 | tree_nodes_free(root, gc_nodes, gc_count); | ||
| 528 | next: | ||
| 513 | clear_bit(tree, data->pending_trees); | 529 | clear_bit(tree, data->pending_trees); |
| 514 | 530 | ||
| 515 | next_tree = (tree + 1) % CONNCOUNT_SLOTS; | 531 | next_tree = (tree + 1) % CONNCOUNT_SLOTS; |
