Commit 5b423f6a authored by Pablo Neira Ayuso's avatar Pablo Neira Ayuso

netfilter: nf_conntrack: fix racy timer handling with reliable events

Existing code assumes that del_timer returns true for alive conntrack
entries. However, this is not true if reliable events are enabled.
In that case, del_timer may return true for entries that were
just inserted in the dying list. Note that packets / ctnetlink may
hold references to conntrack entries that were just inserted to such
list.

This patch fixes the issue by adding an independent timer for
event delivery. This increases the size of the ecache extension.
Still we can revisit this later and use variable size extensions
to allocate this area on demand.
Tested-by: default avatarOliver Smith <olipro@8.c.9.b.0.7.4.0.1.0.0.2.ip6.arpa>
Signed-off-by: default avatarPablo Neira Ayuso <pablo@netfilter.org>
parent 3f509c68
...@@ -18,6 +18,7 @@ struct nf_conntrack_ecache { ...@@ -18,6 +18,7 @@ struct nf_conntrack_ecache {
u16 ctmask; /* bitmask of ct events to be delivered */ u16 ctmask; /* bitmask of ct events to be delivered */
u16 expmask; /* bitmask of expect events to be delivered */ u16 expmask; /* bitmask of expect events to be delivered */
u32 pid; /* netlink pid of destroyer */ u32 pid; /* netlink pid of destroyer */
struct timer_list timeout;
}; };
static inline struct nf_conntrack_ecache * static inline struct nf_conntrack_ecache *
......
...@@ -249,12 +249,15 @@ static void death_by_event(unsigned long ul_conntrack) ...@@ -249,12 +249,15 @@ static void death_by_event(unsigned long ul_conntrack)
{ {
struct nf_conn *ct = (void *)ul_conntrack; struct nf_conn *ct = (void *)ul_conntrack;
struct net *net = nf_ct_net(ct); struct net *net = nf_ct_net(ct);
struct nf_conntrack_ecache *ecache = nf_ct_ecache_find(ct);
BUG_ON(ecache == NULL);
if (nf_conntrack_event(IPCT_DESTROY, ct) < 0) { if (nf_conntrack_event(IPCT_DESTROY, ct) < 0) {
/* bad luck, let's retry again */ /* bad luck, let's retry again */
ct->timeout.expires = jiffies + ecache->timeout.expires = jiffies +
(random32() % net->ct.sysctl_events_retry_timeout); (random32() % net->ct.sysctl_events_retry_timeout);
add_timer(&ct->timeout); add_timer(&ecache->timeout);
return; return;
} }
/* we've got the event delivered, now it's dying */ /* we've got the event delivered, now it's dying */
...@@ -268,6 +271,9 @@ static void death_by_event(unsigned long ul_conntrack) ...@@ -268,6 +271,9 @@ static void death_by_event(unsigned long ul_conntrack)
void nf_ct_insert_dying_list(struct nf_conn *ct) void nf_ct_insert_dying_list(struct nf_conn *ct)
{ {
struct net *net = nf_ct_net(ct); struct net *net = nf_ct_net(ct);
struct nf_conntrack_ecache *ecache = nf_ct_ecache_find(ct);
BUG_ON(ecache == NULL);
/* add this conntrack to the dying list */ /* add this conntrack to the dying list */
spin_lock_bh(&nf_conntrack_lock); spin_lock_bh(&nf_conntrack_lock);
...@@ -275,10 +281,10 @@ void nf_ct_insert_dying_list(struct nf_conn *ct) ...@@ -275,10 +281,10 @@ void nf_ct_insert_dying_list(struct nf_conn *ct)
&net->ct.dying); &net->ct.dying);
spin_unlock_bh(&nf_conntrack_lock); spin_unlock_bh(&nf_conntrack_lock);
/* set a new timer to retry event delivery */ /* set a new timer to retry event delivery */
setup_timer(&ct->timeout, death_by_event, (unsigned long)ct); setup_timer(&ecache->timeout, death_by_event, (unsigned long)ct);
ct->timeout.expires = jiffies + ecache->timeout.expires = jiffies +
(random32() % net->ct.sysctl_events_retry_timeout); (random32() % net->ct.sysctl_events_retry_timeout);
add_timer(&ct->timeout); add_timer(&ecache->timeout);
} }
EXPORT_SYMBOL_GPL(nf_ct_insert_dying_list); EXPORT_SYMBOL_GPL(nf_ct_insert_dying_list);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment