2019-05-27 00:55:01 -06:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2007-07-07 23:23:21 -06:00
|
|
|
/* Structure dynamic extension infrastructure
|
|
|
|
* Copyright (C) 2004 Rusty Russell IBM Corporation
|
|
|
|
* Copyright (C) 2007 Netfilter Core Team <coreteam@netfilter.org>
|
|
|
|
* Copyright (C) 2007 USAGI/WIDE Project <http://www.linux-ipv6.org>
|
|
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
2018-04-16 17:28:59 -06:00
|
|
|
#include <linux/kmemleak.h>
|
2007-07-07 23:23:21 -06:00
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/mutex.h>
|
|
|
|
#include <linux/rcupdate.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/skbuff.h>
|
|
|
|
#include <net/netfilter/nf_conntrack_extend.h>
|
|
|
|
|
2010-03-09 12:59:15 -07:00
|
|
|
static struct nf_ct_ext_type __rcu *nf_ct_ext_types[NF_CT_EXT_NUM];
|
2007-07-07 23:23:21 -06:00
|
|
|
static DEFINE_MUTEX(nf_ct_ext_type_mutex);
|
2017-04-20 01:54:22 -06:00
|
|
|
#define NF_CT_EXT_PREALLOC 128u /* conntrack events are on by default */
|
2007-07-07 23:23:21 -06:00
|
|
|
|
2017-04-29 07:59:49 -06:00
|
|
|
void nf_ct_ext_destroy(struct nf_conn *ct)
|
2007-07-07 23:23:21 -06:00
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
struct nf_ct_ext_type *t;
|
|
|
|
|
|
|
|
for (i = 0; i < NF_CT_EXT_NUM; i++) {
|
|
|
|
rcu_read_lock();
|
|
|
|
t = rcu_dereference(nf_ct_ext_types[i]);
|
|
|
|
|
|
|
|
/* Here the nf_ct_ext_type might have been unregisterd.
|
|
|
|
* I.e., it has responsible to cleanup private
|
|
|
|
* area in all conntracks when it is unregisterd.
|
|
|
|
*/
|
|
|
|
if (t && t->destroy)
|
|
|
|
t->destroy(ct);
|
|
|
|
rcu_read_unlock();
|
|
|
|
}
|
|
|
|
}
|
2017-04-29 07:59:49 -06:00
|
|
|
EXPORT_SYMBOL(nf_ct_ext_destroy);
|
2007-07-07 23:23:21 -06:00
|
|
|
|
2017-04-15 17:29:18 -06:00
|
|
|
void *nf_ct_ext_add(struct nf_conn *ct, enum nf_ct_ext_id id, gfp_t gfp)
|
2007-07-07 23:23:21 -06:00
|
|
|
{
|
2017-04-20 01:54:24 -06:00
|
|
|
unsigned int newlen, newoff, oldlen, alloc;
|
2010-08-02 09:06:19 -06:00
|
|
|
struct nf_ct_ext *old, *new;
|
2007-07-07 23:23:21 -06:00
|
|
|
struct nf_ct_ext_type *t;
|
|
|
|
|
2008-04-14 03:15:51 -06:00
|
|
|
/* Conntrack must not be confirmed to avoid races on reallocation. */
|
2017-08-30 02:07:11 -06:00
|
|
|
WARN_ON(nf_ct_is_confirmed(ct));
|
2008-04-14 03:15:51 -06:00
|
|
|
|
2010-08-02 09:06:19 -06:00
|
|
|
old = ct->ext;
|
2007-07-07 23:23:21 -06:00
|
|
|
|
2017-04-20 01:54:24 -06:00
|
|
|
if (old) {
|
|
|
|
if (__nf_ct_ext_exist(old, id))
|
|
|
|
return NULL;
|
|
|
|
oldlen = old->len;
|
|
|
|
} else {
|
|
|
|
oldlen = sizeof(*new);
|
|
|
|
}
|
2007-07-07 23:23:21 -06:00
|
|
|
|
|
|
|
rcu_read_lock();
|
|
|
|
t = rcu_dereference(nf_ct_ext_types[id]);
|
netfilter: nf_ct_ext: fix possible panic after nf_ct_extend_unregister
If one cpu is doing nf_ct_extend_unregister while another cpu is doing
__nf_ct_ext_add_length, then we may hit BUG_ON(t == NULL). Moreover,
there's no synchronize_rcu invocation after set nf_ct_ext_types[id] to
NULL, so it's possible that we may access invalid pointer.
But actually, most of the ct extends are built-in, so the problem listed
above will not happen. However, there are two exceptions: NF_CT_EXT_NAT
and NF_CT_EXT_SYNPROXY.
For _EXT_NAT, the panic will not happen, since adding the nat extend and
unregistering the nat extend are located in the same file(nf_nat_core.c),
this means that after the nat module is removed, we cannot add the nat
extend too.
For _EXT_SYNPROXY, synproxy extend may be added by init_conntrack, while
synproxy extend unregister will be done by synproxy_core_exit. So after
nf_synproxy_core.ko is removed, we may still try to add the synproxy
extend, then kernel panic may happen.
I know it's very hard to reproduce this issue, but I can play a tricky
game to make it happen very easily :)
Step 1. Enable SYNPROXY for tcp dport 1234 at FORWARD hook:
# iptables -I FORWARD -p tcp --dport 1234 -j SYNPROXY
Step 2. Queue the syn packet to the userspace at raw table OUTPUT hook.
Also note, in the userspace we only add a 20s' delay, then
reinject the syn packet to the kernel:
# iptables -t raw -I OUTPUT -p tcp --syn -j NFQUEUE --queue-num 1
Step 3. Using "nc 2.2.2.2 1234" to connect the server.
Step 4. Now remove the nf_synproxy_core.ko quickly:
# iptables -F FORWARD
# rmmod ipt_SYNPROXY
# rmmod nf_synproxy_core
Step 5. After 20s' delay, the syn packet is reinjected to the kernel.
Now you will see the panic like this:
kernel BUG at net/netfilter/nf_conntrack_extend.c:91!
Call Trace:
? __nf_ct_ext_add_length+0x53/0x3c0 [nf_conntrack]
init_conntrack+0x12b/0x600 [nf_conntrack]
nf_conntrack_in+0x4cc/0x580 [nf_conntrack]
ipv4_conntrack_local+0x48/0x50 [nf_conntrack_ipv4]
nf_reinject+0x104/0x270
nfqnl_recv_verdict+0x3e1/0x5f9 [nfnetlink_queue]
? nfqnl_recv_verdict+0x5/0x5f9 [nfnetlink_queue]
? nla_parse+0xa0/0x100
nfnetlink_rcv_msg+0x175/0x6a9 [nfnetlink]
[...]
One possible solution is to make NF_CT_EXT_SYNPROXY extend built-in, i.e.
introduce nf_conntrack_synproxy.c and only do ct extend register and
unregister in it, similar to nf_conntrack_timeout.c.
But having such a obscure restriction of nf_ct_extend_unregister is not a
good idea, so we should invoke synchronize_rcu after set nf_ct_ext_types
to NULL, and check the NULL pointer when do __nf_ct_ext_add_length. Then
it will be easier if we add new ct extend in the future.
Last, we use kfree_rcu to free nf_ct_ext, so rcu_barrier() is unnecessary
anymore, remove it too.
Signed-off-by: Liping Zhang <zlpnobody@gmail.com>
Acked-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
2017-03-25 02:35:29 -06:00
|
|
|
if (!t) {
|
|
|
|
rcu_read_unlock();
|
|
|
|
return NULL;
|
|
|
|
}
|
2007-07-07 23:23:21 -06:00
|
|
|
|
2017-04-20 01:54:24 -06:00
|
|
|
newoff = ALIGN(oldlen, t->align);
|
2017-04-15 17:29:18 -06:00
|
|
|
newlen = newoff + t->len;
|
2007-07-07 23:23:21 -06:00
|
|
|
rcu_read_unlock();
|
|
|
|
|
2017-04-20 01:54:24 -06:00
|
|
|
alloc = max(newlen, NF_CT_EXT_PREALLOC);
|
2018-03-30 14:22:06 -06:00
|
|
|
kmemleak_not_leak(old);
|
2017-04-20 01:54:24 -06:00
|
|
|
new = __krealloc(old, alloc, gfp);
|
2008-06-09 16:58:39 -06:00
|
|
|
if (!new)
|
|
|
|
return NULL;
|
2007-07-07 23:23:21 -06:00
|
|
|
|
2017-04-20 01:54:24 -06:00
|
|
|
if (!old) {
|
|
|
|
memset(new->offset, 0, sizeof(new->offset));
|
|
|
|
ct->ext = new;
|
|
|
|
} else if (new != old) {
|
2011-03-17 22:07:09 -06:00
|
|
|
kfree_rcu(old, rcu);
|
2016-07-05 04:07:23 -06:00
|
|
|
rcu_assign_pointer(ct->ext, new);
|
2007-07-07 23:23:21 -06:00
|
|
|
}
|
|
|
|
|
2008-07-26 18:50:05 -06:00
|
|
|
new->offset[id] = newoff;
|
|
|
|
new->len = newlen;
|
|
|
|
memset((void *)new + newoff, 0, newlen - newoff);
|
|
|
|
return (void *)new + newoff;
|
2007-07-07 23:23:21 -06:00
|
|
|
}
|
2017-04-15 17:29:18 -06:00
|
|
|
EXPORT_SYMBOL(nf_ct_ext_add);
|
2007-07-07 23:23:21 -06:00
|
|
|
|
|
|
|
/* This MUST be called in process context. */
|
2017-04-20 01:54:23 -06:00
|
|
|
int nf_ct_extend_register(const struct nf_ct_ext_type *type)
|
2007-07-07 23:23:21 -06:00
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
mutex_lock(&nf_ct_ext_type_mutex);
|
|
|
|
if (nf_ct_ext_types[type->id]) {
|
|
|
|
ret = -EBUSY;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2012-01-11 21:41:32 -07:00
|
|
|
rcu_assign_pointer(nf_ct_ext_types[type->id], type);
|
2007-07-07 23:23:21 -06:00
|
|
|
out:
|
|
|
|
mutex_unlock(&nf_ct_ext_type_mutex);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nf_ct_extend_register);
|
|
|
|
|
|
|
|
/* This MUST be called in process context. */
|
2017-04-20 01:54:23 -06:00
|
|
|
void nf_ct_extend_unregister(const struct nf_ct_ext_type *type)
|
2007-07-07 23:23:21 -06:00
|
|
|
{
|
|
|
|
mutex_lock(&nf_ct_ext_type_mutex);
|
2011-08-01 10:19:00 -06:00
|
|
|
RCU_INIT_POINTER(nf_ct_ext_types[type->id], NULL);
|
2007-07-07 23:23:21 -06:00
|
|
|
mutex_unlock(&nf_ct_ext_type_mutex);
|
netfilter: nf_ct_ext: fix possible panic after nf_ct_extend_unregister
If one cpu is doing nf_ct_extend_unregister while another cpu is doing
__nf_ct_ext_add_length, then we may hit BUG_ON(t == NULL). Moreover,
there's no synchronize_rcu invocation after set nf_ct_ext_types[id] to
NULL, so it's possible that we may access invalid pointer.
But actually, most of the ct extends are built-in, so the problem listed
above will not happen. However, there are two exceptions: NF_CT_EXT_NAT
and NF_CT_EXT_SYNPROXY.
For _EXT_NAT, the panic will not happen, since adding the nat extend and
unregistering the nat extend are located in the same file(nf_nat_core.c),
this means that after the nat module is removed, we cannot add the nat
extend too.
For _EXT_SYNPROXY, synproxy extend may be added by init_conntrack, while
synproxy extend unregister will be done by synproxy_core_exit. So after
nf_synproxy_core.ko is removed, we may still try to add the synproxy
extend, then kernel panic may happen.
I know it's very hard to reproduce this issue, but I can play a tricky
game to make it happen very easily :)
Step 1. Enable SYNPROXY for tcp dport 1234 at FORWARD hook:
# iptables -I FORWARD -p tcp --dport 1234 -j SYNPROXY
Step 2. Queue the syn packet to the userspace at raw table OUTPUT hook.
Also note, in the userspace we only add a 20s' delay, then
reinject the syn packet to the kernel:
# iptables -t raw -I OUTPUT -p tcp --syn -j NFQUEUE --queue-num 1
Step 3. Using "nc 2.2.2.2 1234" to connect the server.
Step 4. Now remove the nf_synproxy_core.ko quickly:
# iptables -F FORWARD
# rmmod ipt_SYNPROXY
# rmmod nf_synproxy_core
Step 5. After 20s' delay, the syn packet is reinjected to the kernel.
Now you will see the panic like this:
kernel BUG at net/netfilter/nf_conntrack_extend.c:91!
Call Trace:
? __nf_ct_ext_add_length+0x53/0x3c0 [nf_conntrack]
init_conntrack+0x12b/0x600 [nf_conntrack]
nf_conntrack_in+0x4cc/0x580 [nf_conntrack]
ipv4_conntrack_local+0x48/0x50 [nf_conntrack_ipv4]
nf_reinject+0x104/0x270
nfqnl_recv_verdict+0x3e1/0x5f9 [nfnetlink_queue]
? nfqnl_recv_verdict+0x5/0x5f9 [nfnetlink_queue]
? nla_parse+0xa0/0x100
nfnetlink_rcv_msg+0x175/0x6a9 [nfnetlink]
[...]
One possible solution is to make NF_CT_EXT_SYNPROXY extend built-in, i.e.
introduce nf_conntrack_synproxy.c and only do ct extend register and
unregister in it, similar to nf_conntrack_timeout.c.
But having such a obscure restriction of nf_ct_extend_unregister is not a
good idea, so we should invoke synchronize_rcu after set nf_ct_ext_types
to NULL, and check the NULL pointer when do __nf_ct_ext_add_length. Then
it will be easier if we add new ct extend in the future.
Last, we use kfree_rcu to free nf_ct_ext, so rcu_barrier() is unnecessary
anymore, remove it too.
Signed-off-by: Liping Zhang <zlpnobody@gmail.com>
Acked-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
2017-03-25 02:35:29 -06:00
|
|
|
synchronize_rcu();
|
2007-07-07 23:23:21 -06:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(nf_ct_extend_unregister);
|