Skip to content

Commit 7d367e0

Browse files
Jozsef Kadlecsikummakynes
authored andcommitted
netfilter: ctnetlink: fix soft lockup when netlink adds new entries (v2)
Marcell Zambo and Janos Farago noticed and reported that when new conntrack entries are added via netlink and the conntrack table gets full, soft lockup happens. This is because the nf_conntrack_lock is held while nf_conntrack_alloc is called, which is in turn wants to lock nf_conntrack_lock while evicting entries from the full table. The patch fixes the soft lockup with limiting the holding of the nf_conntrack_lock to the minimum, where it's absolutely required. It required to extend (and thus change) nf_conntrack_hash_insert so that it makes sure conntrack and ctnetlink do not add the same entry twice to the conntrack table. Signed-off-by: Jozsef Kadlecsik <kadlec@blackhole.kfki.hu> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
1 parent 2790728 commit 7d367e0

File tree

3 files changed

+51
-35
lines changed

3 files changed

+51
-35
lines changed

include/net/netfilter/nf_conntrack.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ extern struct nf_conntrack_tuple_hash *
209209
__nf_conntrack_find(struct net *net, u16 zone,
210210
const struct nf_conntrack_tuple *tuple);
211211

212-
extern void nf_conntrack_hash_insert(struct nf_conn *ct);
212+
extern int nf_conntrack_hash_check_insert(struct nf_conn *ct);
213213
extern void nf_ct_delete_from_lists(struct nf_conn *ct);
214214
extern void nf_ct_insert_dying_list(struct nf_conn *ct);
215215

net/netfilter/nf_conntrack_core.c

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -404,19 +404,49 @@ static void __nf_conntrack_hash_insert(struct nf_conn *ct,
404404
&net->ct.hash[repl_hash]);
405405
}
406406

407-
void nf_conntrack_hash_insert(struct nf_conn *ct)
407+
int
408+
nf_conntrack_hash_check_insert(struct nf_conn *ct)
408409
{
409410
struct net *net = nf_ct_net(ct);
410411
unsigned int hash, repl_hash;
412+
struct nf_conntrack_tuple_hash *h;
413+
struct hlist_nulls_node *n;
411414
u16 zone;
412415

413416
zone = nf_ct_zone(ct);
414-
hash = hash_conntrack(net, zone, &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
415-
repl_hash = hash_conntrack(net, zone, &ct->tuplehash[IP_CT_DIR_REPLY].tuple);
417+
hash = hash_conntrack(net, zone,
418+
&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
419+
repl_hash = hash_conntrack(net, zone,
420+
&ct->tuplehash[IP_CT_DIR_REPLY].tuple);
421+
422+
spin_lock_bh(&nf_conntrack_lock);
416423

424+
/* See if there's one in the list already, including reverse */
425+
hlist_nulls_for_each_entry(h, n, &net->ct.hash[hash], hnnode)
426+
if (nf_ct_tuple_equal(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple,
427+
&h->tuple) &&
428+
zone == nf_ct_zone(nf_ct_tuplehash_to_ctrack(h)))
429+
goto out;
430+
hlist_nulls_for_each_entry(h, n, &net->ct.hash[repl_hash], hnnode)
431+
if (nf_ct_tuple_equal(&ct->tuplehash[IP_CT_DIR_REPLY].tuple,
432+
&h->tuple) &&
433+
zone == nf_ct_zone(nf_ct_tuplehash_to_ctrack(h)))
434+
goto out;
435+
436+
add_timer(&ct->timeout);
437+
nf_conntrack_get(&ct->ct_general);
417438
__nf_conntrack_hash_insert(ct, hash, repl_hash);
439+
NF_CT_STAT_INC(net, insert);
440+
spin_unlock_bh(&nf_conntrack_lock);
441+
442+
return 0;
443+
444+
out:
445+
NF_CT_STAT_INC(net, insert_failed);
446+
spin_unlock_bh(&nf_conntrack_lock);
447+
return -EEXIST;
418448
}
419-
EXPORT_SYMBOL_GPL(nf_conntrack_hash_insert);
449+
EXPORT_SYMBOL_GPL(nf_conntrack_hash_check_insert);
420450

421451
/* Confirm a connection given skb; places it in hash table */
422452
int

net/netfilter/nf_conntrack_netlink.c

Lines changed: 16 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1367,15 +1367,12 @@ ctnetlink_create_conntrack(struct net *net, u16 zone,
13671367
nf_ct_protonum(ct));
13681368
if (helper == NULL) {
13691369
rcu_read_unlock();
1370-
spin_unlock_bh(&nf_conntrack_lock);
13711370
#ifdef CONFIG_MODULES
13721371
if (request_module("nfct-helper-%s", helpname) < 0) {
1373-
spin_lock_bh(&nf_conntrack_lock);
13741372
err = -EOPNOTSUPP;
13751373
goto err1;
13761374
}
13771375

1378-
spin_lock_bh(&nf_conntrack_lock);
13791376
rcu_read_lock();
13801377
helper = __nf_conntrack_helper_find(helpname,
13811378
nf_ct_l3num(ct),
@@ -1468,8 +1465,10 @@ ctnetlink_create_conntrack(struct net *net, u16 zone,
14681465
if (tstamp)
14691466
tstamp->start = ktime_to_ns(ktime_get_real());
14701467

1471-
add_timer(&ct->timeout);
1472-
nf_conntrack_hash_insert(ct);
1468+
err = nf_conntrack_hash_check_insert(ct);
1469+
if (err < 0)
1470+
goto err2;
1471+
14731472
rcu_read_unlock();
14741473

14751474
return ct;
@@ -1490,6 +1489,7 @@ ctnetlink_new_conntrack(struct sock *ctnl, struct sk_buff *skb,
14901489
struct nf_conntrack_tuple otuple, rtuple;
14911490
struct nf_conntrack_tuple_hash *h = NULL;
14921491
struct nfgenmsg *nfmsg = nlmsg_data(nlh);
1492+
struct nf_conn *ct;
14931493
u_int8_t u3 = nfmsg->nfgen_family;
14941494
u16 zone;
14951495
int err;
@@ -1510,27 +1510,22 @@ ctnetlink_new_conntrack(struct sock *ctnl, struct sk_buff *skb,
15101510
return err;
15111511
}
15121512

1513-
spin_lock_bh(&nf_conntrack_lock);
15141513
if (cda[CTA_TUPLE_ORIG])
1515-
h = __nf_conntrack_find(net, zone, &otuple);
1514+
h = nf_conntrack_find_get(net, zone, &otuple);
15161515
else if (cda[CTA_TUPLE_REPLY])
1517-
h = __nf_conntrack_find(net, zone, &rtuple);
1516+
h = nf_conntrack_find_get(net, zone, &rtuple);
15181517

15191518
if (h == NULL) {
15201519
err = -ENOENT;
15211520
if (nlh->nlmsg_flags & NLM_F_CREATE) {
1522-
struct nf_conn *ct;
15231521
enum ip_conntrack_events events;
15241522

15251523
ct = ctnetlink_create_conntrack(net, zone, cda, &otuple,
15261524
&rtuple, u3);
1527-
if (IS_ERR(ct)) {
1528-
err = PTR_ERR(ct);
1529-
goto out_unlock;
1530-
}
1525+
if (IS_ERR(ct))
1526+
return PTR_ERR(ct);
1527+
15311528
err = 0;
1532-
nf_conntrack_get(&ct->ct_general);
1533-
spin_unlock_bh(&nf_conntrack_lock);
15341529
if (test_bit(IPS_EXPECTED_BIT, &ct->status))
15351530
events = IPCT_RELATED;
15361531
else
@@ -1545,23 +1540,19 @@ ctnetlink_new_conntrack(struct sock *ctnl, struct sk_buff *skb,
15451540
ct, NETLINK_CB(skb).pid,
15461541
nlmsg_report(nlh));
15471542
nf_ct_put(ct);
1548-
} else
1549-
spin_unlock_bh(&nf_conntrack_lock);
1543+
}
15501544

15511545
return err;
15521546
}
15531547
/* implicit 'else' */
15541548

1555-
/* We manipulate the conntrack inside the global conntrack table lock,
1556-
* so there's no need to increase the refcount */
15571549
err = -EEXIST;
1550+
ct = nf_ct_tuplehash_to_ctrack(h);
15581551
if (!(nlh->nlmsg_flags & NLM_F_EXCL)) {
1559-
struct nf_conn *ct = nf_ct_tuplehash_to_ctrack(h);
1560-
1552+
spin_lock_bh(&nf_conntrack_lock);
15611553
err = ctnetlink_change_conntrack(ct, cda);
1554+
spin_unlock_bh(&nf_conntrack_lock);
15621555
if (err == 0) {
1563-
nf_conntrack_get(&ct->ct_general);
1564-
spin_unlock_bh(&nf_conntrack_lock);
15651556
nf_conntrack_eventmask_report((1 << IPCT_REPLY) |
15661557
(1 << IPCT_ASSURED) |
15671558
(1 << IPCT_HELPER) |
@@ -1570,15 +1561,10 @@ ctnetlink_new_conntrack(struct sock *ctnl, struct sk_buff *skb,
15701561
(1 << IPCT_MARK),
15711562
ct, NETLINK_CB(skb).pid,
15721563
nlmsg_report(nlh));
1573-
nf_ct_put(ct);
1574-
} else
1575-
spin_unlock_bh(&nf_conntrack_lock);
1576-
1577-
return err;
1564+
}
15781565
}
15791566

1580-
out_unlock:
1581-
spin_unlock_bh(&nf_conntrack_lock);
1567+
nf_ct_put(ct);
15821568
return err;
15831569
}
15841570

0 commit comments

Comments
 (0)