Skip to content

Commit a48c260

Browse files
committed
netfilter: extensions: introduce extension genid count
Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2111270 Upstream Status: commit c56716c commit c56716c Author: Florian Westphal <fw@strlen.de> Date: Mon Apr 11 13:01:21 2022 +0200 netfilter: extensions: introduce extension genid count Multiple netfilter extensions store pointers to external data in their extension area struct. Examples: 1. Timeout policies 2. Connection tracking helpers. No references are taken for these. When a helper or timeout policy is removed, the conntrack table gets traversed and affected extensions are cleared. Conntrack entries not yet in the hashtable are referenced via a special list, the unconfirmed list. On removal of a policy or connection tracking helper, the unconfirmed list gets traversed an all entries are marked as dying, this prevents them from getting committed to the table at insertion time: core checks for dying bit, if set, the conntrack entry gets destroyed at confirm time. The disadvantage is that each new conntrack has to be added to the percpu unconfirmed list, and each insertion needs to remove it from this list. The list is only ever needed when a policy or helper is removed -- a rare occurrence. Add a generation ID count: Instead of adding to the list and then traversing that list on policy/helper removal, increment a counter that is stored in the extension area. For unconfirmed conntracks, the extension has the genid valid at ct allocation time. Removal of a helper/policy etc. increments the counter. At confirmation time, validate that ext->genid == global_id. If the stored number is not the same, do not allow the conntrack insertion, just like as if a confirmed-list traversal would have flagged the entry as dying. After insertion, the genid is no longer relevant (conntrack entries are now reachable via the conntrack table iterators and is set to 0. This allows removal of the percpu unconfirmed list. Signed-off-by: Florian Westphal <fw@strlen.de> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> Signed-off-by: Florian Westphal <fwestpha@redhat.com>
1 parent a7df20e commit a48c260

File tree

4 files changed

+111
-17
lines changed

4 files changed

+111
-17
lines changed

include/net/netfilter/nf_conntrack_extend.h

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,11 @@ enum nf_ct_ext_id {
3434
NF_CT_EXT_NUM,
3535
};
3636

37-
#define NF_CT_EXT_HELPER_TYPE struct nf_conn_help
38-
#define NF_CT_EXT_NAT_TYPE struct nf_conn_nat
39-
#define NF_CT_EXT_SEQADJ_TYPE struct nf_conn_seqadj
40-
#define NF_CT_EXT_ACCT_TYPE struct nf_conn_acct
41-
#define NF_CT_EXT_ECACHE_TYPE struct nf_conntrack_ecache
42-
#define NF_CT_EXT_TSTAMP_TYPE struct nf_conn_tstamp
43-
#define NF_CT_EXT_TIMEOUT_TYPE struct nf_conn_timeout
44-
#define NF_CT_EXT_LABELS_TYPE struct nf_conn_labels
45-
#define NF_CT_EXT_SYNPROXY_TYPE struct nf_conn_synproxy
46-
#define NF_CT_EXT_ACT_CT_TYPE struct nf_conn_act_ct_ext
47-
4837
/* Extensions: optional stuff which isn't permanently in struct. */
4938
struct nf_ct_ext {
5039
u8 offset[NF_CT_EXT_NUM];
5140
u8 len;
41+
unsigned int gen_id;
5242
char data[] __aligned(8);
5343
};
5444

@@ -62,17 +52,28 @@ static inline bool nf_ct_ext_exist(const struct nf_conn *ct, u8 id)
6252
return (ct->ext && __nf_ct_ext_exist(ct->ext, id));
6353
}
6454

65-
static inline void *__nf_ct_ext_find(const struct nf_conn *ct, u8 id)
55+
void *__nf_ct_ext_find(const struct nf_ct_ext *ext, u8 id);
56+
57+
static inline void *nf_ct_ext_find(const struct nf_conn *ct, u8 id)
6658
{
67-
if (!nf_ct_ext_exist(ct, id))
59+
struct nf_ct_ext *ext = ct->ext;
60+
61+
if (!ext || !__nf_ct_ext_exist(ext, id))
6862
return NULL;
6963

64+
if (unlikely(ext->gen_id))
65+
return __nf_ct_ext_find(ext, id);
66+
7067
return (void *)ct->ext + ct->ext->offset[id];
7168
}
72-
#define nf_ct_ext_find(ext, id) \
73-
((id##_TYPE *)__nf_ct_ext_find((ext), (id)))
7469

7570
/* Add this type, returns pointer to data or NULL. */
7671
void *nf_ct_ext_add(struct nf_conn *ct, enum nf_ct_ext_id id, gfp_t gfp);
7772

73+
/* ext genid. if ext->id != ext_genid, extensions cannot be used
74+
* anymore unless conntrack has CONFIRMED bit set.
75+
*/
76+
extern atomic_t nf_conntrack_ext_genid;
77+
void nf_ct_ext_bump_genid(void);
78+
7879
#endif /* _NF_CONNTRACK_EXTEND_H */

include/net/netfilter/nf_conntrack_labels.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,18 @@ struct nf_conn_labels {
1717
unsigned long bits[NF_CT_LABELS_MAX_SIZE / sizeof(long)];
1818
};
1919

20+
/* Can't use nf_ct_ext_find(), flow dissector cannot use symbols
21+
* exported by nf_conntrack module.
22+
*/
2023
static inline struct nf_conn_labels *nf_ct_labels_find(const struct nf_conn *ct)
2124
{
2225
#ifdef CONFIG_NF_CONNTRACK_LABELS
23-
return nf_ct_ext_find(ct, NF_CT_EXT_LABELS);
26+
struct nf_ct_ext *ext = ct->ext;
27+
28+
if (!ext || !__nf_ct_ext_exist(ext, NF_CT_EXT_LABELS))
29+
return NULL;
30+
31+
return (void *)ct->ext + ct->ext->offset[NF_CT_EXT_LABELS];
2432
#else
2533
return NULL;
2634
#endif

net/netfilter/nf_conntrack_core.c

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -881,6 +881,33 @@ static void __nf_conntrack_hash_insert(struct nf_conn *ct,
881881
&nf_conntrack_hash[reply_hash]);
882882
}
883883

884+
static bool nf_ct_ext_valid_pre(const struct nf_ct_ext *ext)
885+
{
886+
/* if ext->gen_id is not equal to nf_conntrack_ext_genid, some extensions
887+
* may contain stale pointers to e.g. helper that has been removed.
888+
*
889+
* The helper can't clear this because the nf_conn object isn't in
890+
* any hash and synchronize_rcu() isn't enough because associated skb
891+
* might sit in a queue.
892+
*/
893+
return !ext || ext->gen_id == atomic_read(&nf_conntrack_ext_genid);
894+
}
895+
896+
static bool nf_ct_ext_valid_post(struct nf_ct_ext *ext)
897+
{
898+
if (!ext)
899+
return true;
900+
901+
if (ext->gen_id != atomic_read(&nf_conntrack_ext_genid))
902+
return false;
903+
904+
/* inserted into conntrack table, nf_ct_iterate_cleanup()
905+
* will find it. Disable nf_ct_ext_find() id check.
906+
*/
907+
WRITE_ONCE(ext->gen_id, 0);
908+
return true;
909+
}
910+
884911
int
885912
nf_conntrack_hash_check_insert(struct nf_conn *ct)
886913
{
@@ -896,6 +923,11 @@ nf_conntrack_hash_check_insert(struct nf_conn *ct)
896923

897924
zone = nf_ct_zone(ct);
898925

926+
if (!nf_ct_ext_valid_pre(ct->ext)) {
927+
NF_CT_STAT_INC(net, insert_failed);
928+
return -ETIMEDOUT;
929+
}
930+
899931
local_bh_disable();
900932
do {
901933
sequence = read_seqcount_begin(&nf_conntrack_generation);
@@ -936,6 +968,13 @@ nf_conntrack_hash_check_insert(struct nf_conn *ct)
936968
nf_conntrack_double_unlock(hash, reply_hash);
937969
NF_CT_STAT_INC(net, insert);
938970
local_bh_enable();
971+
972+
if (!nf_ct_ext_valid_post(ct->ext)) {
973+
nf_ct_kill(ct);
974+
NF_CT_STAT_INC(net, drop);
975+
return -ETIMEDOUT;
976+
}
977+
939978
return 0;
940979
chaintoolong:
941980
NF_CT_STAT_INC(net, chaintoolong);
@@ -1203,6 +1242,11 @@ __nf_conntrack_confirm(struct sk_buff *skb)
12031242
return NF_DROP;
12041243
}
12051244

1245+
if (!nf_ct_ext_valid_pre(ct->ext)) {
1246+
NF_CT_STAT_INC(net, insert_failed);
1247+
goto dying;
1248+
}
1249+
12061250
pr_debug("Confirming conntrack %p\n", ct);
12071251
/* We have to check the DYING flag after unlink to prevent
12081252
* a race against nf_ct_get_next_corpse() possibly called from
@@ -1259,6 +1303,16 @@ __nf_conntrack_confirm(struct sk_buff *skb)
12591303
nf_conntrack_double_unlock(hash, reply_hash);
12601304
local_bh_enable();
12611305

1306+
/* ext area is still valid (rcu read lock is held,
1307+
* but will go out of scope soon, we need to remove
1308+
* this conntrack again.
1309+
*/
1310+
if (!nf_ct_ext_valid_post(ct->ext)) {
1311+
nf_ct_kill(ct);
1312+
NF_CT_STAT_INC(net, drop);
1313+
return NF_DROP;
1314+
}
1315+
12621316
help = nfct_help(ct);
12631317
if (help && help->helper)
12641318
nf_conntrack_event_cache(IPCT_HELPER, ct);
@@ -2512,6 +2566,7 @@ nf_ct_iterate_destroy(int (*iter)(struct nf_conn *i, void *data), void *data)
25122566
*/
25132567
synchronize_net();
25142568

2569+
nf_ct_ext_bump_genid();
25152570
nf_ct_iterate_cleanup(iter, data, 0, 0);
25162571
}
25172572
EXPORT_SYMBOL_GPL(nf_ct_iterate_destroy);

net/netfilter/nf_conntrack_extend.c

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727

2828
#define NF_CT_EXT_PREALLOC 128u /* conntrack events are on by default */
2929

30+
atomic_t nf_conntrack_ext_genid __read_mostly = ATOMIC_INIT(1);
31+
3032
static const u8 nf_ct_ext_type_len[NF_CT_EXT_NUM] = {
3133
[NF_CT_EXT_HELPER] = sizeof(struct nf_conn_help),
3234
#if IS_ENABLED(CONFIG_NF_NAT)
@@ -116,8 +118,10 @@ void *nf_ct_ext_add(struct nf_conn *ct, enum nf_ct_ext_id id, gfp_t gfp)
116118
if (!new)
117119
return NULL;
118120

119-
if (!ct->ext)
121+
if (!ct->ext) {
120122
memset(new->offset, 0, sizeof(new->offset));
123+
new->gen_id = atomic_read(&nf_conntrack_ext_genid);
124+
}
121125

122126
new->offset[id] = newoff;
123127
new->len = newlen;
@@ -127,3 +131,29 @@ void *nf_ct_ext_add(struct nf_conn *ct, enum nf_ct_ext_id id, gfp_t gfp)
127131
return (void *)new + newoff;
128132
}
129133
EXPORT_SYMBOL(nf_ct_ext_add);
134+
135+
/* Use nf_ct_ext_find wrapper. This is only useful for unconfirmed entries. */
136+
void *__nf_ct_ext_find(const struct nf_ct_ext *ext, u8 id)
137+
{
138+
unsigned int gen_id = atomic_read(&nf_conntrack_ext_genid);
139+
unsigned int this_id = READ_ONCE(ext->gen_id);
140+
141+
if (!__nf_ct_ext_exist(ext, id))
142+
return NULL;
143+
144+
if (this_id == 0 || ext->gen_id == gen_id)
145+
return (void *)ext + ext->offset[id];
146+
147+
return NULL;
148+
}
149+
EXPORT_SYMBOL(__nf_ct_ext_find);
150+
151+
void nf_ct_ext_bump_genid(void)
152+
{
153+
unsigned int value = atomic_inc_return(&nf_conntrack_ext_genid);
154+
155+
if (value == UINT_MAX)
156+
atomic_set(&nf_conntrack_ext_genid, 1);
157+
158+
msleep(HZ);
159+
}

0 commit comments

Comments
 (0)