diff options
author | Florian Westphal | 2013-04-10 23:22:39 -0500 |
---|---|---|
committer | Greg Kroah-Hartman | 2013-05-11 15:53:54 -0500 |
commit | e0b95c5fdefb0e9eca0c1754ef4e2fa84323ff96 (patch) | |
tree | 5710ab14d949652c1c3b0d0de2632b7cec2be015 /net | |
parent | 023477dfd365edcbca4e157b72f7c4bd099fed81 (diff) | |
download | kernel-omap-e0b95c5fdefb0e9eca0c1754ef4e2fa84323ff96.tar.gz kernel-omap-e0b95c5fdefb0e9eca0c1754ef4e2fa84323ff96.tar.xz kernel-omap-e0b95c5fdefb0e9eca0c1754ef4e2fa84323ff96.zip |
netfilter: nf_nat: fix race when unloading protocol modules
commit c2d421e171868586939c328dfb91bab840fe4c49 upstream.
following oops was reported:
RIP: 0010:[<ffffffffa03227f2>] [<ffffffffa03227f2>] nf_nat_cleanup_conntrack+0x42/0x70 [nf_nat]
RSP: 0018:ffff880202c63d40 EFLAGS: 00010246
RAX: 0000000000000000 RBX: ffff8801ac7bec28 RCX: ffff8801d0eedbe0
RDX: dead000000200200 RSI: 0000000000000011 RDI: ffffffffa03265b8
[..]
Call Trace:
[..]
[<ffffffffa02febed>] destroy_conntrack+0xbd/0x110 [nf_conntrack]
Happens when a conntrack timeout expires right after first part
of the nat cleanup has completed (bysrc hash removal), but before
part 2 has completed (re-initialization of nat area).
[ destroy callback tries to delete bysrc again ]
Patrick suggested to just remove the affected conntracks -- the
connections won't work properly anyway without nat transformation.
So, lets do that.
Reported-by: CAI Qian <caiqian@redhat.com>
Cc: Patrick McHardy <kaber@trash.net>
Signed-off-by: Florian Westphal <fw@strlen.de>
Acked-by: Patrick McHardy <kaber@trash.net>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'net')
-rw-r--r-- | net/netfilter/nf_nat_core.c | 40 |
1 files changed, 7 insertions, 33 deletions
diff --git a/net/netfilter/nf_nat_core.c b/net/netfilter/nf_nat_core.c index 5f2f9109f461..4bc2aafcd417 100644 --- a/net/netfilter/nf_nat_core.c +++ b/net/netfilter/nf_nat_core.c | |||
@@ -468,33 +468,22 @@ EXPORT_SYMBOL_GPL(nf_nat_packet); | |||
468 | struct nf_nat_proto_clean { | 468 | struct nf_nat_proto_clean { |
469 | u8 l3proto; | 469 | u8 l3proto; |
470 | u8 l4proto; | 470 | u8 l4proto; |
471 | bool hash; | ||
472 | }; | 471 | }; |
473 | 472 | ||
474 | /* Clear NAT section of all conntracks, in case we're loaded again. */ | 473 | /* kill conntracks with affected NAT section */ |
475 | static int nf_nat_proto_clean(struct nf_conn *i, void *data) | 474 | static int nf_nat_proto_remove(struct nf_conn *i, void *data) |
476 | { | 475 | { |
477 | const struct nf_nat_proto_clean *clean = data; | 476 | const struct nf_nat_proto_clean *clean = data; |
478 | struct nf_conn_nat *nat = nfct_nat(i); | 477 | struct nf_conn_nat *nat = nfct_nat(i); |
479 | 478 | ||
480 | if (!nat) | 479 | if (!nat) |
481 | return 0; | 480 | return 0; |
482 | if (!(i->status & IPS_SRC_NAT_DONE)) | 481 | |
483 | return 0; | ||
484 | if ((clean->l3proto && nf_ct_l3num(i) != clean->l3proto) || | 482 | if ((clean->l3proto && nf_ct_l3num(i) != clean->l3proto) || |
485 | (clean->l4proto && nf_ct_protonum(i) != clean->l4proto)) | 483 | (clean->l4proto && nf_ct_protonum(i) != clean->l4proto)) |
486 | return 0; | 484 | return 0; |
487 | 485 | ||
488 | if (clean->hash) { | 486 | return i->status & IPS_NAT_MASK ? 1 : 0; |
489 | spin_lock_bh(&nf_nat_lock); | ||
490 | hlist_del_rcu(&nat->bysource); | ||
491 | spin_unlock_bh(&nf_nat_lock); | ||
492 | } else { | ||
493 | memset(nat, 0, sizeof(*nat)); | ||
494 | i->status &= ~(IPS_NAT_MASK | IPS_NAT_DONE_MASK | | ||
495 | IPS_SEQ_ADJUST); | ||
496 | } | ||
497 | return 0; | ||
498 | } | 487 | } |
499 | 488 | ||
500 | static void nf_nat_l4proto_clean(u8 l3proto, u8 l4proto) | 489 | static void nf_nat_l4proto_clean(u8 l3proto, u8 l4proto) |
@@ -506,16 +495,8 @@ static void nf_nat_l4proto_clean(u8 l3proto, u8 l4proto) | |||
506 | struct net *net; | 495 | struct net *net; |
507 | 496 | ||
508 | rtnl_lock(); | 497 | rtnl_lock(); |
509 | /* Step 1 - remove from bysource hash */ | ||
510 | clean.hash = true; | ||
511 | for_each_net(net) | 498 | for_each_net(net) |
512 | nf_ct_iterate_cleanup(net, nf_nat_proto_clean, &clean); | 499 | nf_ct_iterate_cleanup(net, nf_nat_proto_remove, &clean); |
513 | synchronize_rcu(); | ||
514 | |||
515 | /* Step 2 - clean NAT section */ | ||
516 | clean.hash = false; | ||
517 | for_each_net(net) | ||
518 | nf_ct_iterate_cleanup(net, nf_nat_proto_clean, &clean); | ||
519 | rtnl_unlock(); | 500 | rtnl_unlock(); |
520 | } | 501 | } |
521 | 502 | ||
@@ -527,16 +508,9 @@ static void nf_nat_l3proto_clean(u8 l3proto) | |||
527 | struct net *net; | 508 | struct net *net; |
528 | 509 | ||
529 | rtnl_lock(); | 510 | rtnl_lock(); |
530 | /* Step 1 - remove from bysource hash */ | ||
531 | clean.hash = true; | ||
532 | for_each_net(net) | ||
533 | nf_ct_iterate_cleanup(net, nf_nat_proto_clean, &clean); | ||
534 | synchronize_rcu(); | ||
535 | 511 | ||
536 | /* Step 2 - clean NAT section */ | ||
537 | clean.hash = false; | ||
538 | for_each_net(net) | 512 | for_each_net(net) |
539 | nf_ct_iterate_cleanup(net, nf_nat_proto_clean, &clean); | 513 | nf_ct_iterate_cleanup(net, nf_nat_proto_remove, &clean); |
540 | rtnl_unlock(); | 514 | rtnl_unlock(); |
541 | } | 515 | } |
542 | 516 | ||
@@ -774,7 +748,7 @@ static void __net_exit nf_nat_net_exit(struct net *net) | |||
774 | { | 748 | { |
775 | struct nf_nat_proto_clean clean = {}; | 749 | struct nf_nat_proto_clean clean = {}; |
776 | 750 | ||
777 | nf_ct_iterate_cleanup(net, &nf_nat_proto_clean, &clean); | 751 | nf_ct_iterate_cleanup(net, &nf_nat_proto_remove, &clean); |
778 | synchronize_rcu(); | 752 | synchronize_rcu(); |
779 | nf_ct_free_hashtable(net->ct.nat_bysource, net->ct.nat_htable_size); | 753 | nf_ct_free_hashtable(net->ct.nat_bysource, net->ct.nat_htable_size); |
780 | } | 754 | } |