NF_CT_EXT_NUM,
 };
 
-#define NF_CT_EXT_HELPER_TYPE struct nf_conn_help
-#define NF_CT_EXT_NAT_TYPE struct nf_conn_nat
-#define NF_CT_EXT_SEQADJ_TYPE struct nf_conn_seqadj
-#define NF_CT_EXT_ACCT_TYPE struct nf_conn_acct
-#define NF_CT_EXT_ECACHE_TYPE struct nf_conntrack_ecache
-#define NF_CT_EXT_TSTAMP_TYPE struct nf_conn_tstamp
-#define NF_CT_EXT_TIMEOUT_TYPE struct nf_conn_timeout
-#define NF_CT_EXT_LABELS_TYPE struct nf_conn_labels
-#define NF_CT_EXT_SYNPROXY_TYPE struct nf_conn_synproxy
-#define NF_CT_EXT_ACT_CT_TYPE struct nf_conn_act_ct_ext
-
 /* Extensions: optional stuff which isn't permanently in struct. */
 struct nf_ct_ext {
        u8 offset[NF_CT_EXT_NUM];
        u8 len;
+       unsigned int gen_id;
        char data[] __aligned(8);
 };
 
        return (ct->ext && __nf_ct_ext_exist(ct->ext, id));
 }
 
-static inline void *__nf_ct_ext_find(const struct nf_conn *ct, u8 id)
+void *__nf_ct_ext_find(const struct nf_ct_ext *ext, u8 id);
+
+static inline void *nf_ct_ext_find(const struct nf_conn *ct, u8 id)
 {
-       if (!nf_ct_ext_exist(ct, id))
+       struct nf_ct_ext *ext = ct->ext;
+
+       if (!ext || !__nf_ct_ext_exist(ext, id))
                return NULL;
 
+       if (unlikely(ext->gen_id))
+               return __nf_ct_ext_find(ext, id);
+
        return (void *)ct->ext + ct->ext->offset[id];
 }
-#define nf_ct_ext_find(ext, id)        \
-       ((id##_TYPE *)__nf_ct_ext_find((ext), (id)))
 
 /* Add this type, returns pointer to data or NULL. */
 void *nf_ct_ext_add(struct nf_conn *ct, enum nf_ct_ext_id id, gfp_t gfp);
 
+/* ext genid.  if ext->id != ext_genid, extensions cannot be used
+ * anymore unless conntrack has CONFIRMED bit set.
+ */
+extern atomic_t nf_conntrack_ext_genid;
+void nf_ct_ext_bump_genid(void);
+
 #endif /* _NF_CONNTRACK_EXTEND_H */
 
        unsigned long bits[NF_CT_LABELS_MAX_SIZE / sizeof(long)];
 };
 
+/* Can't use nf_ct_ext_find(), flow dissector cannot use symbols
+ * exported by nf_conntrack module.
+ */
 static inline struct nf_conn_labels *nf_ct_labels_find(const struct nf_conn *ct)
 {
 #ifdef CONFIG_NF_CONNTRACK_LABELS
-       return nf_ct_ext_find(ct, NF_CT_EXT_LABELS);
+       struct nf_ct_ext *ext = ct->ext;
+
+       if (!ext || !__nf_ct_ext_exist(ext, NF_CT_EXT_LABELS))
+               return NULL;
+
+       return (void *)ct->ext + ct->ext->offset[NF_CT_EXT_LABELS];
 #else
        return NULL;
 #endif
 
                           &nf_conntrack_hash[reply_hash]);
 }
 
+static bool nf_ct_ext_valid_pre(const struct nf_ct_ext *ext)
+{
+       /* if ext->gen_id is not equal to nf_conntrack_ext_genid, some extensions
+        * may contain stale pointers to e.g. helper that has been removed.
+        *
+        * The helper can't clear this because the nf_conn object isn't in
+        * any hash and synchronize_rcu() isn't enough because associated skb
+        * might sit in a queue.
+        */
+       return !ext || ext->gen_id == atomic_read(&nf_conntrack_ext_genid);
+}
+
+static bool nf_ct_ext_valid_post(struct nf_ct_ext *ext)
+{
+       if (!ext)
+               return true;
+
+       if (ext->gen_id != atomic_read(&nf_conntrack_ext_genid))
+               return false;
+
+       /* inserted into conntrack table, nf_ct_iterate_cleanup()
+        * will find it.  Disable nf_ct_ext_find() id check.
+        */
+       WRITE_ONCE(ext->gen_id, 0);
+       return true;
+}
+
 int
 nf_conntrack_hash_check_insert(struct nf_conn *ct)
 {
 
        zone = nf_ct_zone(ct);
 
+       if (!nf_ct_ext_valid_pre(ct->ext)) {
+               NF_CT_STAT_INC(net, insert_failed);
+               return -ETIMEDOUT;
+       }
+
        local_bh_disable();
        do {
                sequence = read_seqcount_begin(&nf_conntrack_generation);
        nf_conntrack_double_unlock(hash, reply_hash);
        NF_CT_STAT_INC(net, insert);
        local_bh_enable();
+
+       if (!nf_ct_ext_valid_post(ct->ext)) {
+               nf_ct_kill(ct);
+               NF_CT_STAT_INC(net, drop);
+               return -ETIMEDOUT;
+       }
+
        return 0;
 chaintoolong:
        NF_CT_STAT_INC(net, chaintoolong);
                return NF_DROP;
        }
 
+       if (!nf_ct_ext_valid_pre(ct->ext)) {
+               NF_CT_STAT_INC(net, insert_failed);
+               goto dying;
+       }
+
        pr_debug("Confirming conntrack %p\n", ct);
        /* We have to check the DYING flag after unlink to prevent
         * a race against nf_ct_get_next_corpse() possibly called from
        nf_conntrack_double_unlock(hash, reply_hash);
        local_bh_enable();
 
+       /* ext area is still valid (rcu read lock is held,
+        * but will go out of scope soon, we need to remove
+        * this conntrack again.
+        */
+       if (!nf_ct_ext_valid_post(ct->ext)) {
+               nf_ct_kill(ct);
+               NF_CT_STAT_INC(net, drop);
+               return NF_DROP;
+       }
+
        help = nfct_help(ct);
        if (help && help->helper)
                nf_conntrack_event_cache(IPCT_HELPER, ct);
         */
        synchronize_net();
 
+       nf_ct_ext_bump_genid();
        nf_ct_iterate_cleanup(iter, data, 0, 0);
 }
 EXPORT_SYMBOL_GPL(nf_ct_iterate_destroy);
 
 
 #define NF_CT_EXT_PREALLOC     128u /* conntrack events are on by default */
 
+atomic_t nf_conntrack_ext_genid __read_mostly = ATOMIC_INIT(1);
+
 static const u8 nf_ct_ext_type_len[NF_CT_EXT_NUM] = {
        [NF_CT_EXT_HELPER] = sizeof(struct nf_conn_help),
 #if IS_ENABLED(CONFIG_NF_NAT)
        if (!new)
                return NULL;
 
-       if (!ct->ext)
+       if (!ct->ext) {
                memset(new->offset, 0, sizeof(new->offset));
+               new->gen_id = atomic_read(&nf_conntrack_ext_genid);
+       }
 
        new->offset[id] = newoff;
        new->len = newlen;
        return (void *)new + newoff;
 }
 EXPORT_SYMBOL(nf_ct_ext_add);
+
+/* Use nf_ct_ext_find wrapper. This is only useful for unconfirmed entries. */
+void *__nf_ct_ext_find(const struct nf_ct_ext *ext, u8 id)
+{
+       unsigned int gen_id = atomic_read(&nf_conntrack_ext_genid);
+       unsigned int this_id = READ_ONCE(ext->gen_id);
+
+       if (!__nf_ct_ext_exist(ext, id))
+               return NULL;
+
+       if (this_id == 0 || ext->gen_id == gen_id)
+               return (void *)ext + ext->offset[id];
+
+       return NULL;
+}
+EXPORT_SYMBOL(__nf_ct_ext_find);
+
+void nf_ct_ext_bump_genid(void)
+{
+       unsigned int value = atomic_inc_return(&nf_conntrack_ext_genid);
+
+       if (value == UINT_MAX)
+               atomic_set(&nf_conntrack_ext_genid, 1);
+
+       msleep(HZ);
+}