Skip to content

Commit

Permalink
[NETFILTER]: nf_conntrack: fix helper module unload races
Browse files Browse the repository at this point in the history
When a helper module is unloaded all conntracks refering to it have their
helper pointer NULLed out, leading to lots of races. In most places this
can be fixed by proper use of RCU (they do already check for != NULL,
but in a racy way), additionally nf_conntrack_expect_related needs to
bail out when no helper is present.

Also remove two paranoid BUG_ONs in nf_conntrack_proto_gre that are racy
and not worth fixing.

Signed-off-by: Patrick McHarrdy <kaber@trash.net>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Patrick McHarrdy authored and David S. Miller committed Jun 7, 2007
1 parent 51055be commit 3c158f7
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 29 deletions.
13 changes: 8 additions & 5 deletions net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c
Original file line number Diff line number Diff line change
Expand Up @@ -133,19 +133,22 @@ static unsigned int ipv4_conntrack_help(unsigned int hooknum,
struct nf_conn *ct;
enum ip_conntrack_info ctinfo;
struct nf_conn_help *help;
struct nf_conntrack_helper *helper;

/* This is where we call the helper: as the packet goes out. */
ct = nf_ct_get(*pskb, &ctinfo);
if (!ct || ctinfo == IP_CT_RELATED + IP_CT_IS_REPLY)
return NF_ACCEPT;

help = nfct_help(ct);
if (!help || !help->helper)
if (!help)
return NF_ACCEPT;

return help->helper->help(pskb,
skb_network_offset(*pskb) + ip_hdrlen(*pskb),
ct, ctinfo);
/* rcu_read_lock()ed by nf_hook_slow */
helper = rcu_dereference(help->helper);
if (!helper)
return NF_ACCEPT;
return helper->help(pskb, skb_network_offset(*pskb) + ip_hdrlen(*pskb),
ct, ctinfo);
}

static unsigned int ipv4_conntrack_defrag(unsigned int hooknum,
Expand Down
9 changes: 7 additions & 2 deletions net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ static unsigned int ipv6_confirm(unsigned int hooknum,
{
struct nf_conn *ct;
struct nf_conn_help *help;
struct nf_conntrack_helper *helper;
enum ip_conntrack_info ctinfo;
unsigned int ret, protoff;
unsigned int extoff = (u8 *)(ipv6_hdr(*pskb) + 1) - (*pskb)->data;
Expand All @@ -172,7 +173,11 @@ static unsigned int ipv6_confirm(unsigned int hooknum,
goto out;

help = nfct_help(ct);
if (!help || !help->helper)
if (!help)
goto out;
/* rcu_read_lock()ed by nf_hook_slow */
helper = rcu_dereference(help->helper);
if (!helper)
goto out;

protoff = nf_ct_ipv6_skip_exthdr(*pskb, extoff, &pnum,
Expand All @@ -182,7 +187,7 @@ static unsigned int ipv6_confirm(unsigned int hooknum,
return NF_ACCEPT;
}

ret = help->helper->help(pskb, protoff, ct, ctinfo);
ret = helper->help(pskb, protoff, ct, ctinfo);
if (ret != NF_ACCEPT)
return ret;
out:
Expand Down
26 changes: 18 additions & 8 deletions net/netfilter/nf_conntrack_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -350,9 +350,15 @@ static void death_by_timeout(unsigned long ul_conntrack)
{
struct nf_conn *ct = (void *)ul_conntrack;
struct nf_conn_help *help = nfct_help(ct);
struct nf_conntrack_helper *helper;

if (help && help->helper && help->helper->destroy)
help->helper->destroy(ct);
if (help) {
rcu_read_lock();
helper = rcu_dereference(help->helper);
if (helper && helper->destroy)
helper->destroy(ct);
rcu_read_unlock();
}

write_lock_bh(&nf_conntrack_lock);
/* Inside lock so preempt is disabled on module removal path.
Expand Down Expand Up @@ -661,6 +667,7 @@ init_conntrack(const struct nf_conntrack_tuple *tuple,
unsigned int dataoff)
{
struct nf_conn *conntrack;
struct nf_conn_help *help;
struct nf_conntrack_tuple repl_tuple;
struct nf_conntrack_expect *exp;
u_int32_t features = 0;
Expand Down Expand Up @@ -691,14 +698,15 @@ init_conntrack(const struct nf_conntrack_tuple *tuple,
write_lock_bh(&nf_conntrack_lock);
exp = find_expectation(tuple);

help = nfct_help(conntrack);
if (exp) {
DEBUGP("conntrack: expectation arrives ct=%p exp=%p\n",
conntrack, exp);
/* Welcome, Mr. Bond. We've been expecting you... */
__set_bit(IPS_EXPECTED_BIT, &conntrack->status);
conntrack->master = exp->master;
if (exp->helper)
nfct_help(conntrack)->helper = exp->helper;
rcu_assign_pointer(help->helper, exp->helper);
#ifdef CONFIG_NF_CONNTRACK_MARK
conntrack->mark = exp->master->mark;
#endif
Expand All @@ -708,10 +716,11 @@ init_conntrack(const struct nf_conntrack_tuple *tuple,
nf_conntrack_get(&conntrack->master->ct_general);
NF_CT_STAT_INC(expect_new);
} else {
struct nf_conn_help *help = nfct_help(conntrack);

if (help)
help->helper = __nf_ct_helper_find(&repl_tuple);
if (help) {
/* not in hash table yet, so not strictly necessary */
rcu_assign_pointer(help->helper,
__nf_ct_helper_find(&repl_tuple));
}
NF_CT_STAT_INC(new);
}

Expand Down Expand Up @@ -893,7 +902,8 @@ void nf_conntrack_alter_reply(struct nf_conn *ct,
helper = __nf_ct_helper_find(newreply);
if (helper)
memset(&help->help, 0, sizeof(help->help));
help->helper = helper;
/* not in hash table yet, so not strictly necessary */
rcu_assign_pointer(help->helper, helper);
}
write_unlock_bh(&nf_conntrack_lock);
}
Expand Down
4 changes: 4 additions & 0 deletions net/netfilter/nf_conntrack_expect.c
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,10 @@ int nf_conntrack_expect_related(struct nf_conntrack_expect *expect)
NF_CT_ASSERT(master_help);

write_lock_bh(&nf_conntrack_lock);
if (!master_help->helper) {
ret = -ESHUTDOWN;
goto out;
}
list_for_each_entry(i, &nf_conntrack_expect_list, list) {
if (expect_matches(i, expect)) {
/* Refresh timer: if it's dying, ignore.. */
Expand Down
2 changes: 1 addition & 1 deletion net/netfilter/nf_conntrack_helper.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ static inline int unhelp(struct nf_conntrack_tuple_hash *i,

if (help && help->helper == me) {
nf_conntrack_event(IPCT_HELPER, ct);
help->helper = NULL;
rcu_assign_pointer(help->helper, NULL);
}
return 0;
}
Expand Down
34 changes: 23 additions & 11 deletions net/netfilter/nf_conntrack_netlink.c
Original file line number Diff line number Diff line change
Expand Up @@ -171,21 +171,29 @@ ctnetlink_dump_helpinfo(struct sk_buff *skb, const struct nf_conn *ct)
{
struct nfattr *nest_helper;
const struct nf_conn_help *help = nfct_help(ct);
struct nf_conntrack_helper *helper;

if (!help || !help->helper)
if (!help)
return 0;

rcu_read_lock();
helper = rcu_dereference(help->helper);
if (!helper)
goto out;

nest_helper = NFA_NEST(skb, CTA_HELP);
NFA_PUT(skb, CTA_HELP_NAME, strlen(help->helper->name), help->helper->name);
NFA_PUT(skb, CTA_HELP_NAME, strlen(helper->name), helper->name);

if (help->helper->to_nfattr)
help->helper->to_nfattr(skb, ct);
if (helper->to_nfattr)
helper->to_nfattr(skb, ct);

NFA_NEST_END(skb, nest_helper);

out:
rcu_read_unlock();
return 0;

nfattr_failure:
rcu_read_unlock();
return -1;
}

Expand Down Expand Up @@ -842,7 +850,7 @@ ctnetlink_change_helper(struct nf_conn *ct, struct nfattr *cda[])
if (help && help->helper) {
/* we had a helper before ... */
nf_ct_remove_expectations(ct);
help->helper = NULL;
rcu_assign_pointer(help->helper, NULL);
}

return 0;
Expand All @@ -866,7 +874,7 @@ ctnetlink_change_helper(struct nf_conn *ct, struct nfattr *cda[])

/* need to zero data of old helper */
memset(&help->help, 0, sizeof(help->help));
help->helper = helper;
rcu_assign_pointer(help->helper, helper);

return 0;
}
Expand Down Expand Up @@ -950,6 +958,7 @@ ctnetlink_create_conntrack(struct nfattr *cda[],
struct nf_conn *ct;
int err = -EINVAL;
struct nf_conn_help *help;
struct nf_conntrack_helper *helper = NULL;

ct = nf_conntrack_alloc(otuple, rtuple);
if (ct == NULL || IS_ERR(ct))
Expand Down Expand Up @@ -980,14 +989,17 @@ ctnetlink_create_conntrack(struct nfattr *cda[],
#endif

help = nfct_help(ct);
if (help)
help->helper = nf_ct_helper_find_get(rtuple);
if (help) {
helper = nf_ct_helper_find_get(rtuple);
/* not in hash table yet so not strictly necessary */
rcu_assign_pointer(help->helper, helper);
}

add_timer(&ct->timeout);
nf_conntrack_hash_insert(ct);

if (help && help->helper)
nf_ct_helper_put(help->helper);
if (helper)
nf_ct_helper_put(helper);

return 0;

Expand Down
2 changes: 0 additions & 2 deletions net/netfilter/nf_conntrack_proto_gre.c
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ int nf_ct_gre_keymap_add(struct nf_conn *ct, enum ip_conntrack_dir dir,
struct nf_conn_help *help = nfct_help(ct);
struct nf_ct_gre_keymap **kmp, *km;

BUG_ON(strcmp(help->helper->name, "pptp"));
kmp = &help->help.ct_pptp_info.keymap[dir];
if (*kmp) {
/* check whether it's a retransmission */
Expand Down Expand Up @@ -137,7 +136,6 @@ void nf_ct_gre_keymap_destroy(struct nf_conn *ct)
enum ip_conntrack_dir dir;

DEBUGP("entering for ct %p\n", ct);
BUG_ON(strcmp(help->helper->name, "pptp"));

write_lock_bh(&nf_ct_gre_lock);
for (dir = IP_CT_DIR_ORIGINAL; dir < IP_CT_DIR_MAX; dir++) {
Expand Down

0 comments on commit 3c158f7

Please sign in to comment.