From: Thomas Graf <tgraf@redhat.com> Subject: [RHEL5 BZ212839] netfilter: service iptables stop fails because ip_conntrack cannot be unloaded. Date: Tue, 2 Jan 2007 16:15:16 +0100 Bugzilla: 212839 Message-Id: <20070102151516.GA4882@lsx.localdomain> Changelog: netfilter: iptables stop fails because ip_conntrack cannot unload. commit 5251e2d2125407bbff0c39394a4011be9ed8b5d0 Author: Pablo Neira Ayuso <pablo@netfilter.org> [NETFILTER]: conntrack: fix race condition in early_drop On SMP environments the maximum number of conntracks can be overpassed under heavy stress situations due to an existing race condition. CPU A CPU B atomic_read() ... early_drop() ... ... atomic_read() allocate conntrack allocate conntrack atomic_inc() atomic_inc() This patch moves the counter incrementation before the early drop stage. Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> Signed-off-by: Patrick McHardy <kaber@trash.net> Signed-off-by: David S. Miller <davem@davemloft.net> Index: linux-2.6.18.noarch/net/ipv4/netfilter/ip_conntrack_core.c =================================================================== --- linux-2.6.18.noarch.orig/net/ipv4/netfilter/ip_conntrack_core.c 2006-09-20 05:42:06.000000000 +0200 +++ linux-2.6.18.noarch/net/ipv4/netfilter/ip_conntrack_core.c 2007-01-02 16:11:42.000000000 +0100 @@ -640,11 +640,15 @@ struct ip_conntrack *ip_conntrack_alloc( ip_conntrack_hash_rnd_initted = 1; } + /* We don't want any race condition at early drop stage */ + atomic_inc(&ip_conntrack_count); + if (ip_conntrack_max - && atomic_read(&ip_conntrack_count) >= ip_conntrack_max) { + && atomic_read(&ip_conntrack_count) > ip_conntrack_max) { unsigned int hash = hash_conntrack(orig); /* Try dropping from this hash chain. */ if (!early_drop(&ip_conntrack_hash[hash])) { + atomic_dec(&ip_conntrack_count); if (net_ratelimit()) printk(KERN_WARNING "ip_conntrack: table full, dropping" @@ -656,6 +660,7 @@ struct ip_conntrack *ip_conntrack_alloc( conntrack = kmem_cache_alloc(ip_conntrack_cachep, GFP_ATOMIC); if (!conntrack) { DEBUGP("Can't allocate conntrack.\n"); + atomic_dec(&ip_conntrack_count); return ERR_PTR(-ENOMEM); } @@ -669,8 +674,6 @@ struct ip_conntrack *ip_conntrack_alloc( conntrack->timeout.data = (unsigned long)conntrack; conntrack->timeout.function = death_by_timeout; - atomic_inc(&ip_conntrack_count); - return conntrack; } Index: linux-2.6.18.noarch/net/netfilter/nf_conntrack_core.c =================================================================== --- linux-2.6.18.noarch.orig/net/netfilter/nf_conntrack_core.c 2006-09-20 05:42:06.000000000 +0200 +++ linux-2.6.18.noarch/net/netfilter/nf_conntrack_core.c 2007-01-02 16:11:42.000000000 +0100 @@ -866,11 +866,15 @@ __nf_conntrack_alloc(const struct nf_con nf_conntrack_hash_rnd_initted = 1; } + /* We don't want any race condition at early drop stage */ + atomic_inc(&nf_conntrack_count); + if (nf_conntrack_max - && atomic_read(&nf_conntrack_count) >= nf_conntrack_max) { + && atomic_read(&nf_conntrack_count) > nf_conntrack_max) { unsigned int hash = hash_conntrack(orig); /* Try dropping from this hash chain. */ if (!early_drop(&nf_conntrack_hash[hash])) { + atomic_dec(&nf_conntrack_count); if (net_ratelimit()) printk(KERN_WARNING "nf_conntrack: table full, dropping" @@ -921,10 +925,12 @@ __nf_conntrack_alloc(const struct nf_con init_timer(&conntrack->timeout); conntrack->timeout.data = (unsigned long)conntrack; conntrack->timeout.function = death_by_timeout; + read_unlock_bh(&nf_ct_cache_lock); - atomic_inc(&nf_conntrack_count); + return conntrack; out: read_unlock_bh(&nf_ct_cache_lock); + atomic_dec(&nf_conntrack_count); return conntrack; }