netfilter: conntrack: don't call iter for non-confirmed conntracks
nf_ct_iterate_cleanup_net currently calls iter() callback also for conntracks on the unconfirmed list, but this is unsafe. Acesses to nf_conn are fine, but some users access the extension area in the iter() callback, but that does only work reliably for confirmed conntracks (ct->ext can be reallocated at any time for unconfirmed conntrack). The seond issue is that there is a short window where a conntrack entry is neither on the list nor in the table: To confirm an entry, it is first removed from the unconfirmed list, then insert into the table. Fix this by iterating the unconfirmed list first and marking all entries as dying, then wait for rcu grace period. This makes sure all entries that were about to be confirmed either are in the main table, or will be dropped soon. Signed-off-by: Florian Westphal <fw@strlen.de> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
This commit is contained in:
parent
9fd6452d67
commit
b0feacaad1
1 changed files with 32 additions and 13 deletions
|
@ -1592,7 +1592,6 @@ get_next_corpse(struct net *net, int (*iter)(struct nf_conn *i, void *data),
|
|||
struct nf_conntrack_tuple_hash *h;
|
||||
struct nf_conn *ct;
|
||||
struct hlist_nulls_node *n;
|
||||
int cpu;
|
||||
spinlock_t *lockp;
|
||||
|
||||
for (; *bucket < nf_conntrack_htable_size; (*bucket)++) {
|
||||
|
@ -1614,18 +1613,6 @@ get_next_corpse(struct net *net, int (*iter)(struct nf_conn *i, void *data),
|
|||
cond_resched();
|
||||
}
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
struct ct_pcpu *pcpu = per_cpu_ptr(net->ct.pcpu_lists, cpu);
|
||||
|
||||
spin_lock_bh(&pcpu->lock);
|
||||
hlist_nulls_for_each_entry(h, n, &pcpu->unconfirmed, hnnode) {
|
||||
ct = nf_ct_tuplehash_to_ctrack(h);
|
||||
if (iter(ct, data))
|
||||
set_bit(IPS_DYING_BIT, &ct->status);
|
||||
}
|
||||
spin_unlock_bh(&pcpu->lock);
|
||||
cond_resched();
|
||||
}
|
||||
return NULL;
|
||||
found:
|
||||
atomic_inc(&ct->ct_general.use);
|
||||
|
@ -1634,6 +1621,34 @@ found:
|
|||
return ct;
|
||||
}
|
||||
|
||||
static void
|
||||
__nf_ct_unconfirmed_destroy(struct net *net)
|
||||
{
|
||||
int cpu;
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
struct nf_conntrack_tuple_hash *h;
|
||||
struct hlist_nulls_node *n;
|
||||
struct ct_pcpu *pcpu;
|
||||
|
||||
pcpu = per_cpu_ptr(net->ct.pcpu_lists, cpu);
|
||||
|
||||
spin_lock_bh(&pcpu->lock);
|
||||
hlist_nulls_for_each_entry(h, n, &pcpu->unconfirmed, hnnode) {
|
||||
struct nf_conn *ct;
|
||||
|
||||
ct = nf_ct_tuplehash_to_ctrack(h);
|
||||
|
||||
/* we cannot call iter() on unconfirmed list, the
|
||||
* owning cpu can reallocate ct->ext at any time.
|
||||
*/
|
||||
set_bit(IPS_DYING_BIT, &ct->status);
|
||||
}
|
||||
spin_unlock_bh(&pcpu->lock);
|
||||
cond_resched();
|
||||
}
|
||||
}
|
||||
|
||||
void nf_ct_iterate_cleanup_net(struct net *net,
|
||||
int (*iter)(struct nf_conn *i, void *data),
|
||||
void *data, u32 portid, int report)
|
||||
|
@ -1646,6 +1661,10 @@ void nf_ct_iterate_cleanup_net(struct net *net,
|
|||
if (atomic_read(&net->ct.count) == 0)
|
||||
return;
|
||||
|
||||
__nf_ct_unconfirmed_destroy(net);
|
||||
|
||||
synchronize_net();
|
||||
|
||||
while ((ct = get_next_corpse(net, iter, data, &bucket)) != NULL) {
|
||||
/* Time to push up daises... */
|
||||
|
||||
|
|
Loading…
Reference in a new issue