]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/blame - releases/4.9.45/netfilter-nf_ct_ext-fix-possible-panic-after-nf_ct_extend_unregister.patch
4.9-stable patches
[thirdparty/kernel/stable-queue.git] / releases / 4.9.45 / netfilter-nf_ct_ext-fix-possible-panic-after-nf_ct_extend_unregister.patch
CommitLineData
91e32d44
GKH
1From 9c3f3794926a997b1cab6c42480ff300efa2d162 Mon Sep 17 00:00:00 2001
2From: Liping Zhang <zlpnobody@gmail.com>
3Date: Sat, 25 Mar 2017 16:35:29 +0800
4Subject: netfilter: nf_ct_ext: fix possible panic after nf_ct_extend_unregister
5
6From: Liping Zhang <zlpnobody@gmail.com>
7
8commit 9c3f3794926a997b1cab6c42480ff300efa2d162 upstream.
9
10If one cpu is doing nf_ct_extend_unregister while another cpu is doing
11__nf_ct_ext_add_length, then we may hit BUG_ON(t == NULL). Moreover,
12there's no synchronize_rcu invocation after set nf_ct_ext_types[id] to
13NULL, so it's possible that we may access invalid pointer.
14
15But actually, most of the ct extends are built-in, so the problem listed
16above will not happen. However, there are two exceptions: NF_CT_EXT_NAT
17and NF_CT_EXT_SYNPROXY.
18
19For _EXT_NAT, the panic will not happen, since adding the nat extend and
20unregistering the nat extend are located in the same file(nf_nat_core.c),
21this means that after the nat module is removed, we cannot add the nat
22extend too.
23
24For _EXT_SYNPROXY, synproxy extend may be added by init_conntrack, while
25synproxy extend unregister will be done by synproxy_core_exit. So after
26nf_synproxy_core.ko is removed, we may still try to add the synproxy
27extend, then kernel panic may happen.
28
29I know it's very hard to reproduce this issue, but I can play a tricky
30game to make it happen very easily :)
31
32Step 1. Enable SYNPROXY for tcp dport 1234 at FORWARD hook:
33 # iptables -I FORWARD -p tcp --dport 1234 -j SYNPROXY
34Step 2. Queue the syn packet to the userspace at raw table OUTPUT hook.
35 Also note, in the userspace we only add a 20s' delay, then
36 reinject the syn packet to the kernel:
37 # iptables -t raw -I OUTPUT -p tcp --syn -j NFQUEUE --queue-num 1
38Step 3. Using "nc 2.2.2.2 1234" to connect the server.
39Step 4. Now remove the nf_synproxy_core.ko quickly:
40 # iptables -F FORWARD
41 # rmmod ipt_SYNPROXY
42 # rmmod nf_synproxy_core
43Step 5. After 20s' delay, the syn packet is reinjected to the kernel.
44
45Now you will see the panic like this:
46 kernel BUG at net/netfilter/nf_conntrack_extend.c:91!
47 Call Trace:
48 ? __nf_ct_ext_add_length+0x53/0x3c0 [nf_conntrack]
49 init_conntrack+0x12b/0x600 [nf_conntrack]
50 nf_conntrack_in+0x4cc/0x580 [nf_conntrack]
51 ipv4_conntrack_local+0x48/0x50 [nf_conntrack_ipv4]
52 nf_reinject+0x104/0x270
53 nfqnl_recv_verdict+0x3e1/0x5f9 [nfnetlink_queue]
54 ? nfqnl_recv_verdict+0x5/0x5f9 [nfnetlink_queue]
55 ? nla_parse+0xa0/0x100
56 nfnetlink_rcv_msg+0x175/0x6a9 [nfnetlink]
57 [...]
58
59One possible solution is to make NF_CT_EXT_SYNPROXY extend built-in, i.e.
60introduce nf_conntrack_synproxy.c and only do ct extend register and
61unregister in it, similar to nf_conntrack_timeout.c.
62
63But having such a obscure restriction of nf_ct_extend_unregister is not a
64good idea, so we should invoke synchronize_rcu after set nf_ct_ext_types
65to NULL, and check the NULL pointer when do __nf_ct_ext_add_length. Then
66it will be easier if we add new ct extend in the future.
67
68Last, we use kfree_rcu to free nf_ct_ext, so rcu_barrier() is unnecessary
69anymore, remove it too.
70
71Signed-off-by: Liping Zhang <zlpnobody@gmail.com>
72Acked-by: Florian Westphal <fw@strlen.de>
73Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
74Cc: Stefan Bader <stefan.bader@canonical.com>
75Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
76
77---
78 net/netfilter/nf_conntrack_extend.c | 13 ++++++++++---
79 1 file changed, 10 insertions(+), 3 deletions(-)
80
81--- a/net/netfilter/nf_conntrack_extend.c
82+++ b/net/netfilter/nf_conntrack_extend.c
83@@ -53,7 +53,11 @@ nf_ct_ext_create(struct nf_ct_ext **ext,
84
85 rcu_read_lock();
86 t = rcu_dereference(nf_ct_ext_types[id]);
87- BUG_ON(t == NULL);
88+ if (!t) {
89+ rcu_read_unlock();
90+ return NULL;
91+ }
92+
93 off = ALIGN(sizeof(struct nf_ct_ext), t->align);
94 len = off + t->len + var_alloc_len;
95 alloc_size = t->alloc_size + var_alloc_len;
96@@ -88,7 +92,10 @@ void *__nf_ct_ext_add_length(struct nf_c
97
98 rcu_read_lock();
99 t = rcu_dereference(nf_ct_ext_types[id]);
100- BUG_ON(t == NULL);
101+ if (!t) {
102+ rcu_read_unlock();
103+ return NULL;
104+ }
105
106 newoff = ALIGN(old->len, t->align);
107 newlen = newoff + t->len + var_alloc_len;
108@@ -175,6 +182,6 @@ void nf_ct_extend_unregister(struct nf_c
109 RCU_INIT_POINTER(nf_ct_ext_types[type->id], NULL);
110 update_alloc_size(type);
111 mutex_unlock(&nf_ct_ext_type_mutex);
112- rcu_barrier(); /* Wait for completion of call_rcu()'s */
113+ synchronize_rcu();
114 }
115 EXPORT_SYMBOL_GPL(nf_ct_extend_unregister);