netfilter: ecache: document extension area access rules
authorFlorian Westphal <fw@strlen.de>
Sun, 13 Oct 2019 18:19:45 +0000 (20:19 +0200)
committerPablo Neira Ayuso <pablo@netfilter.org>
Thu, 17 Oct 2019 09:26:20 +0000 (11:26 +0200)
Once ct->ext gets free'd via kfree() rather than kfree_rcu we can't
access the extension area anymore without owning the conntrack.

This is a special case:

The worker is walking the pcpu dying list while holding dying list lock:
Neither ct nor ct->ext can be free'd until after the walk has completed.

Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
net/netfilter/nf_conntrack_ecache.c

index 6fba74b5aaf799fc8c413ca48b7a8a97abb5ffa2..0d83c159671c2ec7e8bb46a9a9274e8d54c5617e 100644 (file)
@@ -30,6 +30,7 @@
 static DEFINE_MUTEX(nf_ct_ecache_mutex);
 
 #define ECACHE_RETRY_WAIT (HZ/10)
+#define ECACHE_STACK_ALLOC (256 / sizeof(void *))
 
 enum retry_state {
        STATE_CONGESTED,
@@ -39,11 +40,11 @@ enum retry_state {
 
 static enum retry_state ecache_work_evict_list(struct ct_pcpu *pcpu)
 {
-       struct nf_conn *refs[16];
+       struct nf_conn *refs[ECACHE_STACK_ALLOC];
+       enum retry_state ret = STATE_DONE;
        struct nf_conntrack_tuple_hash *h;
        struct hlist_nulls_node *n;
        unsigned int evicted = 0;
-       enum retry_state ret = STATE_DONE;
 
        spin_lock(&pcpu->lock);
 
@@ -54,10 +55,22 @@ static enum retry_state ecache_work_evict_list(struct ct_pcpu *pcpu)
                if (!nf_ct_is_confirmed(ct))
                        continue;
 
+               /* This ecache access is safe because the ct is on the
+                * pcpu dying list and we hold the spinlock -- the entry
+                * cannot be free'd until after the lock is released.
+                *
+                * This is true even if ct has a refcount of 0: the
+                * cpu that is about to free the entry must remove it
+                * from the dying list and needs the lock to do so.
+                */
                e = nf_ct_ecache_find(ct);
                if (!e || e->state != NFCT_ECACHE_DESTROY_FAIL)
                        continue;
 
+               /* ct is in NFCT_ECACHE_DESTROY_FAIL state, this means
+                * the worker owns this entry: the ct will remain valid
+                * until the worker puts its ct reference.
+                */
                if (nf_conntrack_event(IPCT_DESTROY, ct)) {
                        ret = STATE_CONGESTED;
                        break;