Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Patrick McHardy and committed by
David S. Miller
68b80f11 65c3e471

+10 -3
+1
include/net/netfilter/nf_conntrack_extend.h
··· 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];
+1 -2
net/ipv4/netfilter/nf_nat_core.c
··· 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 ··· 569 570 return; 570 571 571 572 spin_lock_bh(&nf_nat_lock); 572 - hlist_replace_rcu(&old_nat->bysource, &new_nat->bysource); 573 573 new_nat->ct = ct; 574 + hlist_replace_rcu(&old_nat->bysource, &new_nat->bysource); 574 575 spin_unlock_bh(&nf_nat_lock); 575 576 } 576 577
+8 -1
net/netfilter/nf_conntrack_extend.c
··· 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; 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); 66 73 } 67 74 68 75 void *__nf_ct_ext_add(struct nf_conn *ct, enum nf_ct_ext_id id, gfp_t gfp) ··· 113 106 (void *)ct->ext + ct->ext->offset[i]); 114 107 rcu_read_unlock(); 115 108 } 116 - kfree(ct->ext); 109 + call_rcu(&ct->ext->rcu, __nf_ct_ext_free_rcu); 117 110 ct->ext = new; 118 111 } 119 112