2a5c356f147262e8a21a42284980b89809b16c32
[openwrt/staging/981213.git] /
1 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
2 From: "Jason A. Donenfeld" <Jason@zx2c4.com>
3 Date: Tue, 23 Feb 2021 14:18:58 +0100
4 Subject: [PATCH] net: icmp: pass zeroed opts from icmp{,v6}_ndo_send before
5 sending
6
7 commit ee576c47db60432c37e54b1e2b43a8ca6d3a8dca upstream.
8
9 The icmp{,v6}_send functions make all sorts of use of skb->cb, casting
10 it with IPCB or IP6CB, assuming the skb to have come directly from the
11 inet layer. But when the packet comes from the ndo layer, especially
12 when forwarded, there's no telling what might be in skb->cb at that
13 point. As a result, the icmp sending code risks reading bogus memory
14 contents, which can result in nasty stack overflows such as this one
15 reported by a user:
16
17 panic+0x108/0x2ea
18 __stack_chk_fail+0x14/0x20
19 __icmp_send+0x5bd/0x5c0
20 icmp_ndo_send+0x148/0x160
21
22 In icmp_send, skb->cb is cast with IPCB and an ip_options struct is read
23 from it. The optlen parameter there is of particular note, as it can
24 induce writes beyond bounds. There are quite a few ways that can happen
25 in __ip_options_echo. For example:
26
27 // sptr/skb are attacker-controlled skb bytes
28 sptr = skb_network_header(skb);
29 // dptr/dopt points to stack memory allocated by __icmp_send
30 dptr = dopt->__data;
31 // sopt is the corrupt skb->cb in question
32 if (sopt->rr) {
33 optlen = sptr[sopt->rr+1]; // corrupt skb->cb + skb->data
34 soffset = sptr[sopt->rr+2]; // corrupt skb->cb + skb->data
35 // this now writes potentially attacker-controlled data, over
36 // flowing the stack:
37 memcpy(dptr, sptr+sopt->rr, optlen);
38 }
39
40 In the icmpv6_send case, the story is similar, but not as dire, as only
41 IP6CB(skb)->iif and IP6CB(skb)->dsthao are used. The dsthao case is
42 worse than the iif case, but it is passed to ipv6_find_tlv, which does
43 a bit of bounds checking on the value.
44
45 This is easy to simulate by doing a `memset(skb->cb, 0x41,
46 sizeof(skb->cb));` before calling icmp{,v6}_ndo_send, and it's only by
47 good fortune and the rarity of icmp sending from that context that we've
48 avoided reports like this until now. For example, in KASAN:
49
50 BUG: KASAN: stack-out-of-bounds in __ip_options_echo+0xa0e/0x12b0
51 Write of size 38 at addr ffff888006f1f80e by task ping/89
52 CPU: 2 PID: 89 Comm: ping Not tainted 5.10.0-rc7-debug+ #5
53 Call Trace:
54 dump_stack+0x9a/0xcc
55 print_address_description.constprop.0+0x1a/0x160
56 __kasan_report.cold+0x20/0x38
57 kasan_report+0x32/0x40
58 check_memory_region+0x145/0x1a0
59 memcpy+0x39/0x60
60 __ip_options_echo+0xa0e/0x12b0
61 __icmp_send+0x744/0x1700
62
63 Actually, out of the 4 drivers that do this, only gtp zeroed the cb for
64 the v4 case, while the rest did not. So this commit actually removes the
65 gtp-specific zeroing, while putting the code where it belongs in the
66 shared infrastructure of icmp{,v6}_ndo_send.
67
68 This commit fixes the issue by passing an empty IPCB or IP6CB along to
69 the functions that actually do the work. For the icmp_send, this was
70 already trivial, thanks to __icmp_send providing the plumbing function.
71 For icmpv6_send, this required a tiny bit of refactoring to make it
72 behave like the v4 case, after which it was straight forward.
73
74 Fixes: a2b78e9b2cac ("sunvnet: generate ICMP PTMUD messages for smaller port MTUs")
75 Reported-by: SinYu <liuxyon@gmail.com>
76 Reviewed-by: Willem de Bruijn <willemb@google.com>
77 Link: https://lore.kernel.org/netdev/CAF=yD-LOF116aHub6RMe8vB8ZpnrrnoTdqhobEx+bvoA8AsP0w@mail.gmail.com/T/
78 Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
79 Link: https://lore.kernel.org/r/20210223131858.72082-1-Jason@zx2c4.com
80 Signed-off-by: Jakub Kicinski <kuba@kernel.org>
81 [Jason: the gtp part didn't apply because it doesn't use icmp_ndo_send on 5.4]
82 Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
83 ---
84 include/linux/icmpv6.h | 17 ++++++++++++++---
85 include/linux/ipv6.h | 1 -
86 include/net/icmp.h | 6 +++++-
87 net/ipv4/icmp.c | 5 +++--
88 net/ipv6/icmp.c | 16 ++++++++--------
89 net/ipv6/ip6_icmp.c | 12 +++++++-----
90 6 files changed, 37 insertions(+), 20 deletions(-)
91
92 --- a/include/linux/icmpv6.h
93 +++ b/include/linux/icmpv6.h
94 @@ -3,6 +3,7 @@
95 #define _LINUX_ICMPV6_H
96
97 #include <linux/skbuff.h>
98 +#include <linux/ipv6.h>
99 #include <uapi/linux/icmpv6.h>
100
101 static inline struct icmp6hdr *icmp6_hdr(const struct sk_buff *skb)
102 @@ -13,10 +14,16 @@ static inline struct icmp6hdr *icmp6_hdr
103 #include <linux/netdevice.h>
104
105 #if IS_ENABLED(CONFIG_IPV6)
106 -extern void icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info);
107 +extern void __icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info,
108 + const struct inet6_skb_parm *parm);
109
110 +static inline void icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info)
111 +{
112 + __icmpv6_send(skb, type, code, info, IP6CB(skb));
113 +}
114 typedef void ip6_icmp_send_t(struct sk_buff *skb, u8 type, u8 code, __u32 info,
115 - const struct in6_addr *force_saddr);
116 + const struct in6_addr *force_saddr,
117 + const struct inet6_skb_parm *parm);
118 extern int inet6_register_icmp_sender(ip6_icmp_send_t *fn);
119 extern int inet6_unregister_icmp_sender(ip6_icmp_send_t *fn);
120 int ip6_err_gen_icmpv6_unreach(struct sk_buff *skb, int nhs, int type,
121 @@ -25,7 +32,11 @@ int ip6_err_gen_icmpv6_unreach(struct sk
122 #if IS_ENABLED(CONFIG_NF_NAT)
123 void icmpv6_ndo_send(struct sk_buff *skb_in, u8 type, u8 code, __u32 info);
124 #else
125 -#define icmpv6_ndo_send icmpv6_send
126 +static inline void icmpv6_ndo_send(struct sk_buff *skb_in, u8 type, u8 code, __u32 info)
127 +{
128 + struct inet6_skb_parm parm = { 0 };
129 + __icmpv6_send(skb_in, type, code, info, &parm);
130 +}
131 #endif
132
133 #else
134 --- a/include/linux/ipv6.h
135 +++ b/include/linux/ipv6.h
136 @@ -83,7 +83,6 @@ struct ipv6_params {
137 __s32 autoconf;
138 };
139 extern struct ipv6_params ipv6_defaults;
140 -#include <linux/icmpv6.h>
141 #include <linux/tcp.h>
142 #include <linux/udp.h>
143
144 --- a/include/net/icmp.h
145 +++ b/include/net/icmp.h
146 @@ -46,7 +46,11 @@ static inline void icmp_send(struct sk_b
147 #if IS_ENABLED(CONFIG_NF_NAT)
148 void icmp_ndo_send(struct sk_buff *skb_in, int type, int code, __be32 info);
149 #else
150 -#define icmp_ndo_send icmp_send
151 +static inline void icmp_ndo_send(struct sk_buff *skb_in, int type, int code, __be32 info)
152 +{
153 + struct ip_options opts = { 0 };
154 + __icmp_send(skb_in, type, code, info, &opts);
155 +}
156 #endif
157
158 int icmp_rcv(struct sk_buff *skb);
159 --- a/net/ipv4/icmp.c
160 +++ b/net/ipv4/icmp.c
161 @@ -755,13 +755,14 @@ EXPORT_SYMBOL(__icmp_send);
162 void icmp_ndo_send(struct sk_buff *skb_in, int type, int code, __be32 info)
163 {
164 struct sk_buff *cloned_skb = NULL;
165 + struct ip_options opts = { 0 };
166 enum ip_conntrack_info ctinfo;
167 struct nf_conn *ct;
168 __be32 orig_ip;
169
170 ct = nf_ct_get(skb_in, &ctinfo);
171 if (!ct || !(ct->status & IPS_SRC_NAT)) {
172 - icmp_send(skb_in, type, code, info);
173 + __icmp_send(skb_in, type, code, info, &opts);
174 return;
175 }
176
177 @@ -776,7 +777,7 @@ void icmp_ndo_send(struct sk_buff *skb_i
178
179 orig_ip = ip_hdr(skb_in)->saddr;
180 ip_hdr(skb_in)->saddr = ct->tuplehash[0].tuple.src.u3.ip;
181 - icmp_send(skb_in, type, code, info);
182 + __icmp_send(skb_in, type, code, info, &opts);
183 ip_hdr(skb_in)->saddr = orig_ip;
184 out:
185 consume_skb(cloned_skb);
186 --- a/net/ipv6/icmp.c
187 +++ b/net/ipv6/icmp.c
188 @@ -312,10 +312,9 @@ static int icmpv6_getfrag(void *from, ch
189 }
190
191 #if IS_ENABLED(CONFIG_IPV6_MIP6)
192 -static void mip6_addr_swap(struct sk_buff *skb)
193 +static void mip6_addr_swap(struct sk_buff *skb, const struct inet6_skb_parm *opt)
194 {
195 struct ipv6hdr *iph = ipv6_hdr(skb);
196 - struct inet6_skb_parm *opt = IP6CB(skb);
197 struct ipv6_destopt_hao *hao;
198 struct in6_addr tmp;
199 int off;
200 @@ -332,7 +331,7 @@ static void mip6_addr_swap(struct sk_buf
201 }
202 }
203 #else
204 -static inline void mip6_addr_swap(struct sk_buff *skb) {}
205 +static inline void mip6_addr_swap(struct sk_buff *skb, const struct inet6_skb_parm *opt) {}
206 #endif
207
208 static struct dst_entry *icmpv6_route_lookup(struct net *net,
209 @@ -427,7 +426,8 @@ static int icmp6_iif(const struct sk_buf
210 * Send an ICMP message in response to a packet in error
211 */
212 static void icmp6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info,
213 - const struct in6_addr *force_saddr)
214 + const struct in6_addr *force_saddr,
215 + const struct inet6_skb_parm *parm)
216 {
217 struct inet6_dev *idev = NULL;
218 struct ipv6hdr *hdr = ipv6_hdr(skb);
219 @@ -520,7 +520,7 @@ static void icmp6_send(struct sk_buff *s
220 if (!(skb->dev->flags & IFF_LOOPBACK) && !icmpv6_global_allow(net, type))
221 goto out_bh_enable;
222
223 - mip6_addr_swap(skb);
224 + mip6_addr_swap(skb, parm);
225
226 memset(&fl6, 0, sizeof(fl6));
227 fl6.flowi6_proto = IPPROTO_ICMPV6;
228 @@ -605,7 +605,7 @@ out_bh_enable:
229 */
230 void icmpv6_param_prob(struct sk_buff *skb, u8 code, int pos)
231 {
232 - icmp6_send(skb, ICMPV6_PARAMPROB, code, pos, NULL);
233 + icmp6_send(skb, ICMPV6_PARAMPROB, code, pos, NULL, IP6CB(skb));
234 kfree_skb(skb);
235 }
236
237 @@ -662,10 +662,10 @@ int ip6_err_gen_icmpv6_unreach(struct sk
238 }
239 if (type == ICMP_TIME_EXCEEDED)
240 icmp6_send(skb2, ICMPV6_TIME_EXCEED, ICMPV6_EXC_HOPLIMIT,
241 - info, &temp_saddr);
242 + info, &temp_saddr, IP6CB(skb2));
243 else
244 icmp6_send(skb2, ICMPV6_DEST_UNREACH, ICMPV6_ADDR_UNREACH,
245 - info, &temp_saddr);
246 + info, &temp_saddr, IP6CB(skb2));
247 if (rt)
248 ip6_rt_put(rt);
249
250 --- a/net/ipv6/ip6_icmp.c
251 +++ b/net/ipv6/ip6_icmp.c
252 @@ -31,7 +31,8 @@ int inet6_unregister_icmp_sender(ip6_icm
253 }
254 EXPORT_SYMBOL(inet6_unregister_icmp_sender);
255
256 -void icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info)
257 +void __icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info,
258 + const struct inet6_skb_parm *parm)
259 {
260 ip6_icmp_send_t *send;
261
262 @@ -40,16 +41,17 @@ void icmpv6_send(struct sk_buff *skb, u8
263
264 if (!send)
265 goto out;
266 - send(skb, type, code, info, NULL);
267 + send(skb, type, code, info, NULL, parm);
268 out:
269 rcu_read_unlock();
270 }
271 -EXPORT_SYMBOL(icmpv6_send);
272 +EXPORT_SYMBOL(__icmpv6_send);
273
274 #if IS_ENABLED(CONFIG_NF_NAT)
275 #include <net/netfilter/nf_conntrack.h>
276 void icmpv6_ndo_send(struct sk_buff *skb_in, u8 type, u8 code, __u32 info)
277 {
278 + struct inet6_skb_parm parm = { 0 };
279 struct sk_buff *cloned_skb = NULL;
280 enum ip_conntrack_info ctinfo;
281 struct in6_addr orig_ip;
282 @@ -57,7 +59,7 @@ void icmpv6_ndo_send(struct sk_buff *skb
283
284 ct = nf_ct_get(skb_in, &ctinfo);
285 if (!ct || !(ct->status & IPS_SRC_NAT)) {
286 - icmpv6_send(skb_in, type, code, info);
287 + __icmpv6_send(skb_in, type, code, info, &parm);
288 return;
289 }
290
291 @@ -72,7 +74,7 @@ void icmpv6_ndo_send(struct sk_buff *skb
292
293 orig_ip = ipv6_hdr(skb_in)->saddr;
294 ipv6_hdr(skb_in)->saddr = ct->tuplehash[0].tuple.src.u3.in6;
295 - icmpv6_send(skb_in, type, code, info);
296 + __icmpv6_send(skb_in, type, code, info, &parm);
297 ipv6_hdr(skb_in)->saddr = orig_ip;
298 out:
299 consume_skb(cloned_skb);