[NETFILTER]: ip_conntrack_expect_related must not free expectation

If a connection tracking helper tells us to expect a connection, and
we're already expecting that connection, we simply free the one they
gave us and return success.

The problem is that NAT helpers (eg. FTP) have to allocate the
expectation first (to see what port is available) then rewrite the
packet.  If that rewrite fails, they try to remove the expectation,
but it was freed in ip_conntrack_expect_related.

This is one example of a larger problem: having registered the
expectation, the pointer is no longer ours to use.  Reference counting
is needed for ctnetlink anyway, so introduce it now.

To have a single "put" path, we need to grab the reference to the
connection on creation, rather than open-coding it in the caller.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Rusty Russell 2005-07-21 13:14:46 -07:00 committed by David S. Miller
parent 4aa49d130d
commit 4acdbdbe50
12 changed files with 46 additions and 60 deletions

View file

@ -197,6 +197,9 @@ struct ip_conntrack_expect
/* Timer function; deletes the expectation. */ /* Timer function; deletes the expectation. */
struct timer_list timeout; struct timer_list timeout;
/* Usage count. */
atomic_t use;
#ifdef CONFIG_IP_NF_NAT_NEEDED #ifdef CONFIG_IP_NF_NAT_NEEDED
/* This is the original per-proto part, used to map the /* This is the original per-proto part, used to map the
* expected connection the way the recipient expects. */ * expected connection the way the recipient expects. */

View file

@ -30,9 +30,10 @@ extern int ip_conntrack_helper_register(struct ip_conntrack_helper *);
extern void ip_conntrack_helper_unregister(struct ip_conntrack_helper *); extern void ip_conntrack_helper_unregister(struct ip_conntrack_helper *);
/* Allocate space for an expectation: this is mandatory before calling /* Allocate space for an expectation: this is mandatory before calling
ip_conntrack_expect_related. */ ip_conntrack_expect_related. You will have to call put afterwards. */
extern struct ip_conntrack_expect *ip_conntrack_expect_alloc(void); extern struct ip_conntrack_expect *
extern void ip_conntrack_expect_free(struct ip_conntrack_expect *exp); ip_conntrack_expect_alloc(struct ip_conntrack *master);
extern void ip_conntrack_expect_put(struct ip_conntrack_expect *exp);
/* Add an expected connection: can have more than one per connection */ /* Add an expected connection: can have more than one per connection */
extern int ip_conntrack_expect_related(struct ip_conntrack_expect *exp); extern int ip_conntrack_expect_related(struct ip_conntrack_expect *exp);

View file

@ -101,14 +101,13 @@ static int help(struct sk_buff **pskb,
if (port == 0 || len > 5) if (port == 0 || len > 5)
break; break;
exp = ip_conntrack_expect_alloc(); exp = ip_conntrack_expect_alloc(ct);
if (exp == NULL) { if (exp == NULL) {
ret = NF_DROP; ret = NF_DROP;
goto out; goto out;
} }
exp->expectfn = NULL; exp->expectfn = NULL;
exp->master = ct;
exp->tuple.src.ip = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip; exp->tuple.src.ip = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip;
exp->tuple.src.u.tcp.port = 0; exp->tuple.src.u.tcp.port = 0;
@ -126,10 +125,9 @@ static int help(struct sk_buff **pskb,
ret = ip_nat_amanda_hook(pskb, ctinfo, ret = ip_nat_amanda_hook(pskb, ctinfo,
tmp - amanda_buffer, tmp - amanda_buffer,
len, exp); len, exp);
else if (ip_conntrack_expect_related(exp) != 0) { else if (ip_conntrack_expect_related(exp) != 0)
ip_conntrack_expect_free(exp);
ret = NF_DROP; ret = NF_DROP;
} ip_conntrack_expect_put(exp);
} }
out: out:

View file

@ -137,19 +137,12 @@ ip_ct_invert_tuple(struct ip_conntrack_tuple *inverse,
/* ip_conntrack_expect helper functions */ /* ip_conntrack_expect helper functions */
static void destroy_expect(struct ip_conntrack_expect *exp)
{
ip_conntrack_put(exp->master);
IP_NF_ASSERT(!timer_pending(&exp->timeout));
kmem_cache_free(ip_conntrack_expect_cachep, exp);
CONNTRACK_STAT_INC(expect_delete);
}
static void unlink_expect(struct ip_conntrack_expect *exp) static void unlink_expect(struct ip_conntrack_expect *exp)
{ {
ASSERT_WRITE_LOCK(&ip_conntrack_lock); ASSERT_WRITE_LOCK(&ip_conntrack_lock);
IP_NF_ASSERT(!timer_pending(&exp->timeout));
list_del(&exp->list); list_del(&exp->list);
/* Logically in destroy_expect, but we hold the lock here. */ CONNTRACK_STAT_INC(expect_delete);
exp->master->expecting--; exp->master->expecting--;
} }
@ -160,7 +153,7 @@ static void expectation_timed_out(unsigned long ul_expect)
write_lock_bh(&ip_conntrack_lock); write_lock_bh(&ip_conntrack_lock);
unlink_expect(exp); unlink_expect(exp);
write_unlock_bh(&ip_conntrack_lock); write_unlock_bh(&ip_conntrack_lock);
destroy_expect(exp); ip_conntrack_expect_put(exp);
} }
/* If an expectation for this connection is found, it gets delete from /* If an expectation for this connection is found, it gets delete from
@ -198,7 +191,7 @@ static void remove_expectations(struct ip_conntrack *ct)
list_for_each_entry_safe(i, tmp, &ip_conntrack_expect_list, list) { list_for_each_entry_safe(i, tmp, &ip_conntrack_expect_list, list) {
if (i->master == ct && del_timer(&i->timeout)) { if (i->master == ct && del_timer(&i->timeout)) {
unlink_expect(i); unlink_expect(i);
destroy_expect(i); ip_conntrack_expect_put(i);
} }
} }
} }
@ -537,7 +530,7 @@ init_conntrack(const struct ip_conntrack_tuple *tuple,
if (exp) { if (exp) {
if (exp->expectfn) if (exp->expectfn)
exp->expectfn(conntrack, exp); exp->expectfn(conntrack, exp);
destroy_expect(exp); ip_conntrack_expect_put(exp);
} }
return &conntrack->tuplehash[IP_CT_DIR_ORIGINAL]; return &conntrack->tuplehash[IP_CT_DIR_ORIGINAL];
@ -729,14 +722,14 @@ void ip_conntrack_unexpect_related(struct ip_conntrack_expect *exp)
if (expect_matches(i, exp) && del_timer(&i->timeout)) { if (expect_matches(i, exp) && del_timer(&i->timeout)) {
unlink_expect(i); unlink_expect(i);
write_unlock_bh(&ip_conntrack_lock); write_unlock_bh(&ip_conntrack_lock);
destroy_expect(i); ip_conntrack_expect_put(i);
return; return;
} }
} }
write_unlock_bh(&ip_conntrack_lock); write_unlock_bh(&ip_conntrack_lock);
} }
struct ip_conntrack_expect *ip_conntrack_expect_alloc(void) struct ip_conntrack_expect *ip_conntrack_expect_alloc(struct ip_conntrack *me)
{ {
struct ip_conntrack_expect *new; struct ip_conntrack_expect *new;
@ -745,18 +738,23 @@ struct ip_conntrack_expect *ip_conntrack_expect_alloc(void)
DEBUGP("expect_related: OOM allocating expect\n"); DEBUGP("expect_related: OOM allocating expect\n");
return NULL; return NULL;
} }
new->master = NULL; new->master = me;
atomic_inc(&new->master->ct_general.use);
atomic_set(&new->use, 1);
return new; return new;
} }
void ip_conntrack_expect_free(struct ip_conntrack_expect *expect) void ip_conntrack_expect_put(struct ip_conntrack_expect *exp)
{ {
kmem_cache_free(ip_conntrack_expect_cachep, expect); if (atomic_dec_and_test(&exp->use)) {
ip_conntrack_put(exp->master);
kmem_cache_free(ip_conntrack_expect_cachep, exp);
}
} }
static void ip_conntrack_expect_insert(struct ip_conntrack_expect *exp) static void ip_conntrack_expect_insert(struct ip_conntrack_expect *exp)
{ {
atomic_inc(&exp->master->ct_general.use); atomic_inc(&exp->use);
exp->master->expecting++; exp->master->expecting++;
list_add(&exp->list, &ip_conntrack_expect_list); list_add(&exp->list, &ip_conntrack_expect_list);
@ -778,7 +776,7 @@ static void evict_oldest_expect(struct ip_conntrack *master)
if (i->master == master) { if (i->master == master) {
if (del_timer(&i->timeout)) { if (del_timer(&i->timeout)) {
unlink_expect(i); unlink_expect(i);
destroy_expect(i); ip_conntrack_expect_put(i);
} }
break; break;
} }
@ -810,8 +808,6 @@ int ip_conntrack_expect_related(struct ip_conntrack_expect *expect)
/* Refresh timer: if it's dying, ignore.. */ /* Refresh timer: if it's dying, ignore.. */
if (refresh_timer(i)) { if (refresh_timer(i)) {
ret = 0; ret = 0;
/* We don't need the one they've given us. */
ip_conntrack_expect_free(expect);
goto out; goto out;
} }
} else if (expect_clash(i, expect)) { } else if (expect_clash(i, expect)) {
@ -881,7 +877,7 @@ void ip_conntrack_helper_unregister(struct ip_conntrack_helper *me)
list_for_each_entry_safe(exp, tmp, &ip_conntrack_expect_list, list) { list_for_each_entry_safe(exp, tmp, &ip_conntrack_expect_list, list) {
if (exp->master->helper == me && del_timer(&exp->timeout)) { if (exp->master->helper == me && del_timer(&exp->timeout)) {
unlink_expect(exp); unlink_expect(exp);
destroy_expect(exp); ip_conntrack_expect_put(exp);
} }
} }
/* Get rid of expecteds, set helpers to NULL. */ /* Get rid of expecteds, set helpers to NULL. */

View file

@ -376,7 +376,7 @@ static int help(struct sk_buff **pskb,
fb_ptr + matchoff, matchlen, ntohl(th->seq) + matchoff); fb_ptr + matchoff, matchlen, ntohl(th->seq) + matchoff);
/* Allocate expectation which will be inserted */ /* Allocate expectation which will be inserted */
exp = ip_conntrack_expect_alloc(); exp = ip_conntrack_expect_alloc(ct);
if (exp == NULL) { if (exp == NULL) {
ret = NF_DROP; ret = NF_DROP;
goto out; goto out;
@ -403,8 +403,7 @@ static int help(struct sk_buff **pskb,
networks, or the packet filter itself). */ networks, or the packet filter itself). */
if (!loose) { if (!loose) {
ret = NF_ACCEPT; ret = NF_ACCEPT;
ip_conntrack_expect_free(exp); goto out_put_expect;
goto out_update_nl;
} }
exp->tuple.dst.ip = htonl((array[0] << 24) | (array[1] << 16) exp->tuple.dst.ip = htonl((array[0] << 24) | (array[1] << 16)
| (array[2] << 8) | array[3]); | (array[2] << 8) | array[3]);
@ -419,7 +418,6 @@ static int help(struct sk_buff **pskb,
{ 0xFFFFFFFF, { .tcp = { 0xFFFF } }, 0xFF }}); { 0xFFFFFFFF, { .tcp = { 0xFFFF } }, 0xFF }});
exp->expectfn = NULL; exp->expectfn = NULL;
exp->master = ct;
/* Now, NAT might want to mangle the packet, and register the /* Now, NAT might want to mangle the packet, and register the
* (possibly changed) expectation itself. */ * (possibly changed) expectation itself. */
@ -428,13 +426,15 @@ static int help(struct sk_buff **pskb,
matchoff, matchlen, exp, &seq); matchoff, matchlen, exp, &seq);
else { else {
/* Can't expect this? Best to drop packet now. */ /* Can't expect this? Best to drop packet now. */
if (ip_conntrack_expect_related(exp) != 0) { if (ip_conntrack_expect_related(exp) != 0)
ip_conntrack_expect_free(exp);
ret = NF_DROP; ret = NF_DROP;
} else else
ret = NF_ACCEPT; ret = NF_ACCEPT;
} }
out_put_expect:
ip_conntrack_expect_put(exp);
out_update_nl: out_update_nl:
/* Now if this ends in \n, update ftp info. Seq may have been /* Now if this ends in \n, update ftp info. Seq may have been
* adjusted by NAT code. */ * adjusted by NAT code. */

View file

@ -197,7 +197,7 @@ static int help(struct sk_buff **pskb,
continue; continue;
} }
exp = ip_conntrack_expect_alloc(); exp = ip_conntrack_expect_alloc(ct);
if (exp == NULL) { if (exp == NULL) {
ret = NF_DROP; ret = NF_DROP;
goto out; goto out;
@ -221,16 +221,14 @@ static int help(struct sk_buff **pskb,
{ { 0, { 0 } }, { { 0, { 0 } },
{ 0xFFFFFFFF, { .tcp = { 0xFFFF } }, 0xFF }}); { 0xFFFFFFFF, { .tcp = { 0xFFFF } }, 0xFF }});
exp->expectfn = NULL; exp->expectfn = NULL;
exp->master = ct;
if (ip_nat_irc_hook) if (ip_nat_irc_hook)
ret = ip_nat_irc_hook(pskb, ctinfo, ret = ip_nat_irc_hook(pskb, ctinfo,
addr_beg_p - ib_ptr, addr_beg_p - ib_ptr,
addr_end_p - addr_beg_p, addr_end_p - addr_beg_p,
exp); exp);
else if (ip_conntrack_expect_related(exp) != 0) { else if (ip_conntrack_expect_related(exp) != 0)
ip_conntrack_expect_free(exp);
ret = NF_DROP; ret = NF_DROP;
} ip_conntrack_expect_put(exp);
goto out; goto out;
} /* for .. NUM_DCCPROTO */ } /* for .. NUM_DCCPROTO */
} /* while data < ... */ } /* while data < ... */

View file

@ -985,7 +985,7 @@ EXPORT_SYMBOL(ip_ct_refresh_acct);
EXPORT_SYMBOL(ip_ct_protos); EXPORT_SYMBOL(ip_ct_protos);
EXPORT_SYMBOL(ip_ct_find_proto); EXPORT_SYMBOL(ip_ct_find_proto);
EXPORT_SYMBOL(ip_conntrack_expect_alloc); EXPORT_SYMBOL(ip_conntrack_expect_alloc);
EXPORT_SYMBOL(ip_conntrack_expect_free); EXPORT_SYMBOL(ip_conntrack_expect_put);
EXPORT_SYMBOL(ip_conntrack_expect_related); EXPORT_SYMBOL(ip_conntrack_expect_related);
EXPORT_SYMBOL(ip_conntrack_unexpect_related); EXPORT_SYMBOL(ip_conntrack_unexpect_related);
EXPORT_SYMBOL(ip_conntrack_tuple_taken); EXPORT_SYMBOL(ip_conntrack_tuple_taken);

View file

@ -65,7 +65,7 @@ static int tftp_help(struct sk_buff **pskb,
DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple); DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_REPLY].tuple); DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_REPLY].tuple);
exp = ip_conntrack_expect_alloc(); exp = ip_conntrack_expect_alloc(ct);
if (exp == NULL) if (exp == NULL)
return NF_DROP; return NF_DROP;
@ -75,17 +75,15 @@ static int tftp_help(struct sk_buff **pskb,
exp->mask.dst.u.udp.port = 0xffff; exp->mask.dst.u.udp.port = 0xffff;
exp->mask.dst.protonum = 0xff; exp->mask.dst.protonum = 0xff;
exp->expectfn = NULL; exp->expectfn = NULL;
exp->master = ct;
DEBUGP("expect: "); DEBUGP("expect: ");
DUMP_TUPLE(&exp->tuple); DUMP_TUPLE(&exp->tuple);
DUMP_TUPLE(&exp->mask); DUMP_TUPLE(&exp->mask);
if (ip_nat_tftp_hook) if (ip_nat_tftp_hook)
ret = ip_nat_tftp_hook(pskb, ctinfo, exp); ret = ip_nat_tftp_hook(pskb, ctinfo, exp);
else if (ip_conntrack_expect_related(exp) != 0) { else if (ip_conntrack_expect_related(exp) != 0)
ip_conntrack_expect_free(exp);
ret = NF_DROP; ret = NF_DROP;
} ip_conntrack_expect_put(exp);
break; break;
case TFTP_OPCODE_DATA: case TFTP_OPCODE_DATA:
case TFTP_OPCODE_ACK: case TFTP_OPCODE_ACK:

View file

@ -56,10 +56,8 @@ static unsigned int help(struct sk_buff **pskb,
break; break;
} }
if (port == 0) { if (port == 0)
ip_conntrack_expect_free(exp);
return NF_DROP; return NF_DROP;
}
sprintf(buffer, "%u", port); sprintf(buffer, "%u", port);
ret = ip_nat_mangle_udp_packet(pskb, exp->master, ctinfo, ret = ip_nat_mangle_udp_packet(pskb, exp->master, ctinfo,

View file

@ -143,10 +143,8 @@ static unsigned int ip_nat_ftp(struct sk_buff **pskb,
break; break;
} }
if (port == 0) { if (port == 0)
ip_conntrack_expect_free(exp);
return NF_DROP; return NF_DROP;
}
if (!mangle[type](pskb, newip, port, matchoff, matchlen, ct, ctinfo, if (!mangle[type](pskb, newip, port, matchoff, matchlen, ct, ctinfo,
seq)) { seq)) {

View file

@ -65,10 +65,8 @@ static unsigned int help(struct sk_buff **pskb,
break; break;
} }
if (port == 0) { if (port == 0)
ip_conntrack_expect_free(exp);
return NF_DROP; return NF_DROP;
}
/* strlen("\1DCC CHAT chat AAAAAAAA P\1\n")=27 /* strlen("\1DCC CHAT chat AAAAAAAA P\1\n")=27
* strlen("\1DCC SCHAT chat AAAAAAAA P\1\n")=28 * strlen("\1DCC SCHAT chat AAAAAAAA P\1\n")=28

View file

@ -45,10 +45,8 @@ static unsigned int help(struct sk_buff **pskb,
exp->saved_proto.udp.port = exp->tuple.dst.u.tcp.port; exp->saved_proto.udp.port = exp->tuple.dst.u.tcp.port;
exp->dir = IP_CT_DIR_REPLY; exp->dir = IP_CT_DIR_REPLY;
exp->expectfn = ip_nat_follow_master; exp->expectfn = ip_nat_follow_master;
if (ip_conntrack_expect_related(exp) != 0) { if (ip_conntrack_expect_related(exp) != 0)
ip_conntrack_expect_free(exp);
return NF_DROP; return NF_DROP;
}
return NF_ACCEPT; return NF_ACCEPT;
} }