Commit 68b80f11380889996aa7eadba29dbbb5c29a5864

Authored by Patrick McHardy
Committed by David S. Miller
1 parent 65c3e4715b

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=10875

Signed-off-by: Patrick McHardy <kaber@trash.net>
Signed-off-by: David S. Miller <davem@davemloft.net>

Showing 3 changed files with 10 additions and 3 deletions Side-by-side Diff

include/net/netfilter/nf_conntrack_extend.h
... ... @@ -15,6 +15,7 @@
15 15  
16 16 /* Extensions: optional stuff which isn't permanently in struct. */
17 17 struct nf_ct_ext {
  18 + struct rcu_head rcu;
18 19 u8 offset[NF_CT_EXT_NUM];
19 20 u8 len;
20 21 char data[0];
net/ipv4/netfilter/nf_nat_core.c
... ... @@ -556,7 +556,6 @@
556 556  
557 557 spin_lock_bh(&nf_nat_lock);
558 558 hlist_del_rcu(&nat->bysource);
559   - nat->ct = NULL;
560 559 spin_unlock_bh(&nf_nat_lock);
561 560 }
562 561  
563 562  
... ... @@ -570,8 +569,8 @@
570 569 return;
571 570  
572 571 spin_lock_bh(&nf_nat_lock);
573   - hlist_replace_rcu(&old_nat->bysource, &new_nat->bysource);
574 572 new_nat->ct = ct;
  573 + hlist_replace_rcu(&old_nat->bysource, &new_nat->bysource);
575 574 spin_unlock_bh(&nf_nat_lock);
576 575 }
577 576  
net/netfilter/nf_conntrack_extend.c
... ... @@ -59,12 +59,19 @@
59 59 if (!*ext)
60 60 return NULL;
61 61  
  62 + INIT_RCU_HEAD(&(*ext)->rcu);
62 63 (*ext)->offset[id] = off;
63 64 (*ext)->len = len;
64 65  
65 66 return (void *)(*ext) + off;
66 67 }
67 68  
  69 +static void __nf_ct_ext_free_rcu(struct rcu_head *head)
  70 +{
  71 + struct nf_ct_ext *ext = container_of(head, struct nf_ct_ext, rcu);
  72 + kfree(ext);
  73 +}
  74 +
68 75 void *__nf_ct_ext_add(struct nf_conn *ct, enum nf_ct_ext_id id, gfp_t gfp)
69 76 {
70 77 struct nf_ct_ext *new;
... ... @@ -106,7 +113,7 @@
106 113 (void *)ct->ext + ct->ext->offset[i]);
107 114 rcu_read_unlock();
108 115 }
109   - kfree(ct->ext);
  116 + call_rcu(&ct->ext->rcu, __nf_ct_ext_free_rcu);
110 117 ct->ext = new;
111 118 }
112 119