Commit 68b80f11 authored by Patrick McHardy's avatar Patrick McHardy Committed by David S. Miller

netfilter: nf_nat: fix RCU races

Fix three ct_extend/NAT extension related races:

- When cleaning up the extension area and removing it from the bysource hash,
  the nat->ct pointer must not be set to NULL since it may still be used in
  a RCU read side

- When replacing a NAT extension area in the bysource hash, the nat->ct
  pointer must be assigned before performing the replacement

- When reallocating extension storage in ct_extend, the old memory must
  not be freed immediately since it may still be used by a RCU read side

Possibly fixes https://bugzilla.redhat.com/show_bug.cgi?id=449315
and/or http://bugzilla.kernel.org/show_bug.cgi?id=10875Signed-off-by: default avatarPatrick McHardy <kaber@trash.net>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 65c3e471
...@@ -15,6 +15,7 @@ enum nf_ct_ext_id ...@@ -15,6 +15,7 @@ enum nf_ct_ext_id
/* Extensions: optional stuff which isn't permanently in struct. */ /* Extensions: optional stuff which isn't permanently in struct. */
struct nf_ct_ext { struct nf_ct_ext {
struct rcu_head rcu;
u8 offset[NF_CT_EXT_NUM]; u8 offset[NF_CT_EXT_NUM];
u8 len; u8 len;
char data[0]; char data[0];
......
...@@ -556,7 +556,6 @@ static void nf_nat_cleanup_conntrack(struct nf_conn *ct) ...@@ -556,7 +556,6 @@ static void nf_nat_cleanup_conntrack(struct nf_conn *ct)
spin_lock_bh(&nf_nat_lock); spin_lock_bh(&nf_nat_lock);
hlist_del_rcu(&nat->bysource); hlist_del_rcu(&nat->bysource);
nat->ct = NULL;
spin_unlock_bh(&nf_nat_lock); spin_unlock_bh(&nf_nat_lock);
} }
...@@ -570,8 +569,8 @@ static void nf_nat_move_storage(void *new, void *old) ...@@ -570,8 +569,8 @@ static void nf_nat_move_storage(void *new, void *old)
return; return;
spin_lock_bh(&nf_nat_lock); spin_lock_bh(&nf_nat_lock);
hlist_replace_rcu(&old_nat->bysource, &new_nat->bysource);
new_nat->ct = ct; new_nat->ct = ct;
hlist_replace_rcu(&old_nat->bysource, &new_nat->bysource);
spin_unlock_bh(&nf_nat_lock); spin_unlock_bh(&nf_nat_lock);
} }
......
...@@ -59,12 +59,19 @@ nf_ct_ext_create(struct nf_ct_ext **ext, enum nf_ct_ext_id id, gfp_t gfp) ...@@ -59,12 +59,19 @@ nf_ct_ext_create(struct nf_ct_ext **ext, enum nf_ct_ext_id id, gfp_t gfp)
if (!*ext) if (!*ext)
return NULL; return NULL;
INIT_RCU_HEAD(&(*ext)->rcu);
(*ext)->offset[id] = off; (*ext)->offset[id] = off;
(*ext)->len = len; (*ext)->len = len;
return (void *)(*ext) + off; return (void *)(*ext) + off;
} }
static void __nf_ct_ext_free_rcu(struct rcu_head *head)
{
struct nf_ct_ext *ext = container_of(head, struct nf_ct_ext, rcu);
kfree(ext);
}
void *__nf_ct_ext_add(struct nf_conn *ct, enum nf_ct_ext_id id, gfp_t gfp) void *__nf_ct_ext_add(struct nf_conn *ct, enum nf_ct_ext_id id, gfp_t gfp)
{ {
struct nf_ct_ext *new; struct nf_ct_ext *new;
...@@ -106,7 +113,7 @@ void *__nf_ct_ext_add(struct nf_conn *ct, enum nf_ct_ext_id id, gfp_t gfp) ...@@ -106,7 +113,7 @@ void *__nf_ct_ext_add(struct nf_conn *ct, enum nf_ct_ext_id id, gfp_t gfp)
(void *)ct->ext + ct->ext->offset[i]); (void *)ct->ext + ct->ext->offset[i]);
rcu_read_unlock(); rcu_read_unlock();
} }
kfree(ct->ext); call_rcu(&ct->ext->rcu, __nf_ct_ext_free_rcu);
ct->ext = new; ct->ext = new;
} }
......
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