mirror of
https://github.com/openwrt/openwrt.git
synced 2025-01-20 11:39:02 +00:00
7d4143234c
These will eventually make their way to 5.10, but it could be a while. https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net.git/commit/?id=ee576c47db60432c37e54b1e2b43a8ca6d3a8dca https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net.git/commit/?id=5a0598695634a6bb4126818902dd9140cd9df8b6 https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net.git/commit/?id=99fff5264e7ab06f45b0ad60243475be0a8d0559 https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net.git/commit/?id=8b5553ace83cced775eefd0f3f18b5c6214ccf7a https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net.git/commit/?id=bce2473927af8de12ad131a743f55d69d358c0b9 Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com> [Rename 082-wireguard-kconfig... to 083-wireguard-kconfig...] Signed-off-by: Ilya Lipnitskiy <ilya.lipnitskiy@gmail.com>
338 lines
12 KiB
Diff
338 lines
12 KiB
Diff
From 4a25324891a32d080589a6e3a4dec2be2d9e3d60 Mon Sep 17 00:00:00 2001
|
|
From: "Jason A. Donenfeld" <Jason@zx2c4.com>
|
|
Date: Tue, 23 Feb 2021 14:18:58 +0100
|
|
Subject: [PATCH] net: icmp: pass zeroed opts from icmp{,v6}_ndo_send before
|
|
sending
|
|
|
|
commit ee576c47db60432c37e54b1e2b43a8ca6d3a8dca upstream.
|
|
|
|
The icmp{,v6}_send functions make all sorts of use of skb->cb, casting
|
|
it with IPCB or IP6CB, assuming the skb to have come directly from the
|
|
inet layer. But when the packet comes from the ndo layer, especially
|
|
when forwarded, there's no telling what might be in skb->cb at that
|
|
point. As a result, the icmp sending code risks reading bogus memory
|
|
contents, which can result in nasty stack overflows such as this one
|
|
reported by a user:
|
|
|
|
panic+0x108/0x2ea
|
|
__stack_chk_fail+0x14/0x20
|
|
__icmp_send+0x5bd/0x5c0
|
|
icmp_ndo_send+0x148/0x160
|
|
|
|
In icmp_send, skb->cb is cast with IPCB and an ip_options struct is read
|
|
from it. The optlen parameter there is of particular note, as it can
|
|
induce writes beyond bounds. There are quite a few ways that can happen
|
|
in __ip_options_echo. For example:
|
|
|
|
// sptr/skb are attacker-controlled skb bytes
|
|
sptr = skb_network_header(skb);
|
|
// dptr/dopt points to stack memory allocated by __icmp_send
|
|
dptr = dopt->__data;
|
|
// sopt is the corrupt skb->cb in question
|
|
if (sopt->rr) {
|
|
optlen = sptr[sopt->rr+1]; // corrupt skb->cb + skb->data
|
|
soffset = sptr[sopt->rr+2]; // corrupt skb->cb + skb->data
|
|
// this now writes potentially attacker-controlled data, over
|
|
// flowing the stack:
|
|
memcpy(dptr, sptr+sopt->rr, optlen);
|
|
}
|
|
|
|
In the icmpv6_send case, the story is similar, but not as dire, as only
|
|
IP6CB(skb)->iif and IP6CB(skb)->dsthao are used. The dsthao case is
|
|
worse than the iif case, but it is passed to ipv6_find_tlv, which does
|
|
a bit of bounds checking on the value.
|
|
|
|
This is easy to simulate by doing a `memset(skb->cb, 0x41,
|
|
sizeof(skb->cb));` before calling icmp{,v6}_ndo_send, and it's only by
|
|
good fortune and the rarity of icmp sending from that context that we've
|
|
avoided reports like this until now. For example, in KASAN:
|
|
|
|
BUG: KASAN: stack-out-of-bounds in __ip_options_echo+0xa0e/0x12b0
|
|
Write of size 38 at addr ffff888006f1f80e by task ping/89
|
|
CPU: 2 PID: 89 Comm: ping Not tainted 5.10.0-rc7-debug+ #5
|
|
Call Trace:
|
|
dump_stack+0x9a/0xcc
|
|
print_address_description.constprop.0+0x1a/0x160
|
|
__kasan_report.cold+0x20/0x38
|
|
kasan_report+0x32/0x40
|
|
check_memory_region+0x145/0x1a0
|
|
memcpy+0x39/0x60
|
|
__ip_options_echo+0xa0e/0x12b0
|
|
__icmp_send+0x744/0x1700
|
|
|
|
Actually, out of the 4 drivers that do this, only gtp zeroed the cb for
|
|
the v4 case, while the rest did not. So this commit actually removes the
|
|
gtp-specific zeroing, while putting the code where it belongs in the
|
|
shared infrastructure of icmp{,v6}_ndo_send.
|
|
|
|
This commit fixes the issue by passing an empty IPCB or IP6CB along to
|
|
the functions that actually do the work. For the icmp_send, this was
|
|
already trivial, thanks to __icmp_send providing the plumbing function.
|
|
For icmpv6_send, this required a tiny bit of refactoring to make it
|
|
behave like the v4 case, after which it was straight forward.
|
|
|
|
Fixes: a2b78e9b2cac ("sunvnet: generate ICMP PTMUD messages for smaller port MTUs")
|
|
Reported-by: SinYu <liuxyon@gmail.com>
|
|
Reviewed-by: Willem de Bruijn <willemb@google.com>
|
|
Link: https://lore.kernel.org/netdev/CAF=yD-LOF116aHub6RMe8vB8ZpnrrnoTdqhobEx+bvoA8AsP0w@mail.gmail.com/T/
|
|
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
|
|
Link: https://lore.kernel.org/r/20210223131858.72082-1-Jason@zx2c4.com
|
|
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
|
[Jason: backported to 5.10]
|
|
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
|
|
---
|
|
include/linux/icmpv6.h | 17 ++++++++++++++---
|
|
include/linux/ipv6.h | 1 -
|
|
include/net/icmp.h | 6 +++++-
|
|
net/ipv4/icmp.c | 5 +++--
|
|
net/ipv6/icmp.c | 16 ++++++++--------
|
|
net/ipv6/ip6_icmp.c | 12 +++++++-----
|
|
6 files changed, 37 insertions(+), 20 deletions(-)
|
|
|
|
--- a/drivers/net/gtp.c
|
|
+++ b/drivers/net/gtp.c
|
|
@@ -539,7 +539,6 @@ static int gtp_build_skb_ip4(struct sk_b
|
|
if (!skb_is_gso(skb) && (iph->frag_off & htons(IP_DF)) &&
|
|
mtu < ntohs(iph->tot_len)) {
|
|
netdev_dbg(dev, "packet too big, fragmentation needed\n");
|
|
- memset(IPCB(skb), 0, sizeof(*IPCB(skb)));
|
|
icmp_ndo_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,
|
|
htonl(mtu));
|
|
goto err_rt;
|
|
--- a/include/linux/icmpv6.h
|
|
+++ b/include/linux/icmpv6.h
|
|
@@ -3,6 +3,7 @@
|
|
#define _LINUX_ICMPV6_H
|
|
|
|
#include <linux/skbuff.h>
|
|
+#include <linux/ipv6.h>
|
|
#include <uapi/linux/icmpv6.h>
|
|
|
|
static inline struct icmp6hdr *icmp6_hdr(const struct sk_buff *skb)
|
|
@@ -15,13 +16,16 @@ static inline struct icmp6hdr *icmp6_hdr
|
|
#if IS_ENABLED(CONFIG_IPV6)
|
|
|
|
typedef void ip6_icmp_send_t(struct sk_buff *skb, u8 type, u8 code, __u32 info,
|
|
- const struct in6_addr *force_saddr);
|
|
+ const struct in6_addr *force_saddr,
|
|
+ const struct inet6_skb_parm *parm);
|
|
#if IS_BUILTIN(CONFIG_IPV6)
|
|
void icmp6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info,
|
|
- const struct in6_addr *force_saddr);
|
|
-static inline void icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info)
|
|
+ const struct in6_addr *force_saddr,
|
|
+ const struct inet6_skb_parm *parm);
|
|
+static inline void __icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info,
|
|
+ const struct inet6_skb_parm *parm)
|
|
{
|
|
- icmp6_send(skb, type, code, info, NULL);
|
|
+ icmp6_send(skb, type, code, info, NULL, parm);
|
|
}
|
|
static inline int inet6_register_icmp_sender(ip6_icmp_send_t *fn)
|
|
{
|
|
@@ -34,18 +38,28 @@ static inline int inet6_unregister_icmp_
|
|
return 0;
|
|
}
|
|
#else
|
|
-extern void icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info);
|
|
+extern void __icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info,
|
|
+ const struct inet6_skb_parm *parm);
|
|
extern int inet6_register_icmp_sender(ip6_icmp_send_t *fn);
|
|
extern int inet6_unregister_icmp_sender(ip6_icmp_send_t *fn);
|
|
#endif
|
|
|
|
+static inline void icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info)
|
|
+{
|
|
+ __icmpv6_send(skb, type, code, info, IP6CB(skb));
|
|
+}
|
|
+
|
|
int ip6_err_gen_icmpv6_unreach(struct sk_buff *skb, int nhs, int type,
|
|
unsigned int data_len);
|
|
|
|
#if IS_ENABLED(CONFIG_NF_NAT)
|
|
void icmpv6_ndo_send(struct sk_buff *skb_in, u8 type, u8 code, __u32 info);
|
|
#else
|
|
-#define icmpv6_ndo_send icmpv6_send
|
|
+static inline void icmpv6_ndo_send(struct sk_buff *skb_in, u8 type, u8 code, __u32 info)
|
|
+{
|
|
+ struct inet6_skb_parm parm = { 0 };
|
|
+ __icmpv6_send(skb_in, type, code, info, &parm);
|
|
+}
|
|
#endif
|
|
|
|
#else
|
|
--- a/include/linux/ipv6.h
|
|
+++ b/include/linux/ipv6.h
|
|
@@ -84,7 +84,6 @@ struct ipv6_params {
|
|
__s32 autoconf;
|
|
};
|
|
extern struct ipv6_params ipv6_defaults;
|
|
-#include <linux/icmpv6.h>
|
|
#include <linux/tcp.h>
|
|
#include <linux/udp.h>
|
|
|
|
--- a/include/net/icmp.h
|
|
+++ b/include/net/icmp.h
|
|
@@ -46,7 +46,11 @@ static inline void icmp_send(struct sk_b
|
|
#if IS_ENABLED(CONFIG_NF_NAT)
|
|
void icmp_ndo_send(struct sk_buff *skb_in, int type, int code, __be32 info);
|
|
#else
|
|
-#define icmp_ndo_send icmp_send
|
|
+static inline void icmp_ndo_send(struct sk_buff *skb_in, int type, int code, __be32 info)
|
|
+{
|
|
+ struct ip_options opts = { 0 };
|
|
+ __icmp_send(skb_in, type, code, info, &opts);
|
|
+}
|
|
#endif
|
|
|
|
int icmp_rcv(struct sk_buff *skb);
|
|
--- a/net/ipv4/icmp.c
|
|
+++ b/net/ipv4/icmp.c
|
|
@@ -775,13 +775,14 @@ EXPORT_SYMBOL(__icmp_send);
|
|
void icmp_ndo_send(struct sk_buff *skb_in, int type, int code, __be32 info)
|
|
{
|
|
struct sk_buff *cloned_skb = NULL;
|
|
+ struct ip_options opts = { 0 };
|
|
enum ip_conntrack_info ctinfo;
|
|
struct nf_conn *ct;
|
|
__be32 orig_ip;
|
|
|
|
ct = nf_ct_get(skb_in, &ctinfo);
|
|
if (!ct || !(ct->status & IPS_SRC_NAT)) {
|
|
- icmp_send(skb_in, type, code, info);
|
|
+ __icmp_send(skb_in, type, code, info, &opts);
|
|
return;
|
|
}
|
|
|
|
@@ -796,7 +797,7 @@ void icmp_ndo_send(struct sk_buff *skb_i
|
|
|
|
orig_ip = ip_hdr(skb_in)->saddr;
|
|
ip_hdr(skb_in)->saddr = ct->tuplehash[0].tuple.src.u3.ip;
|
|
- icmp_send(skb_in, type, code, info);
|
|
+ __icmp_send(skb_in, type, code, info, &opts);
|
|
ip_hdr(skb_in)->saddr = orig_ip;
|
|
out:
|
|
consume_skb(cloned_skb);
|
|
--- a/net/ipv6/icmp.c
|
|
+++ b/net/ipv6/icmp.c
|
|
@@ -331,10 +331,9 @@ static int icmpv6_getfrag(void *from, ch
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_IPV6_MIP6)
|
|
-static void mip6_addr_swap(struct sk_buff *skb)
|
|
+static void mip6_addr_swap(struct sk_buff *skb, const struct inet6_skb_parm *opt)
|
|
{
|
|
struct ipv6hdr *iph = ipv6_hdr(skb);
|
|
- struct inet6_skb_parm *opt = IP6CB(skb);
|
|
struct ipv6_destopt_hao *hao;
|
|
struct in6_addr tmp;
|
|
int off;
|
|
@@ -351,7 +350,7 @@ static void mip6_addr_swap(struct sk_buf
|
|
}
|
|
}
|
|
#else
|
|
-static inline void mip6_addr_swap(struct sk_buff *skb) {}
|
|
+static inline void mip6_addr_swap(struct sk_buff *skb, const struct inet6_skb_parm *opt) {}
|
|
#endif
|
|
|
|
static struct dst_entry *icmpv6_route_lookup(struct net *net,
|
|
@@ -446,7 +445,8 @@ static int icmp6_iif(const struct sk_buf
|
|
* Send an ICMP message in response to a packet in error
|
|
*/
|
|
void icmp6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info,
|
|
- const struct in6_addr *force_saddr)
|
|
+ const struct in6_addr *force_saddr,
|
|
+ const struct inet6_skb_parm *parm)
|
|
{
|
|
struct inet6_dev *idev = NULL;
|
|
struct ipv6hdr *hdr = ipv6_hdr(skb);
|
|
@@ -542,7 +542,7 @@ void icmp6_send(struct sk_buff *skb, u8
|
|
if (!(skb->dev->flags & IFF_LOOPBACK) && !icmpv6_global_allow(net, type))
|
|
goto out_bh_enable;
|
|
|
|
- mip6_addr_swap(skb);
|
|
+ mip6_addr_swap(skb, parm);
|
|
|
|
sk = icmpv6_xmit_lock(net);
|
|
if (!sk)
|
|
@@ -559,7 +559,7 @@ void icmp6_send(struct sk_buff *skb, u8
|
|
/* select a more meaningful saddr from input if */
|
|
struct net_device *in_netdev;
|
|
|
|
- in_netdev = dev_get_by_index(net, IP6CB(skb)->iif);
|
|
+ in_netdev = dev_get_by_index(net, parm->iif);
|
|
if (in_netdev) {
|
|
ipv6_dev_get_saddr(net, in_netdev, &fl6.daddr,
|
|
inet6_sk(sk)->srcprefs,
|
|
@@ -640,7 +640,7 @@ EXPORT_SYMBOL(icmp6_send);
|
|
*/
|
|
void icmpv6_param_prob(struct sk_buff *skb, u8 code, int pos)
|
|
{
|
|
- icmp6_send(skb, ICMPV6_PARAMPROB, code, pos, NULL);
|
|
+ icmp6_send(skb, ICMPV6_PARAMPROB, code, pos, NULL, IP6CB(skb));
|
|
kfree_skb(skb);
|
|
}
|
|
|
|
@@ -697,10 +697,10 @@ int ip6_err_gen_icmpv6_unreach(struct sk
|
|
}
|
|
if (type == ICMP_TIME_EXCEEDED)
|
|
icmp6_send(skb2, ICMPV6_TIME_EXCEED, ICMPV6_EXC_HOPLIMIT,
|
|
- info, &temp_saddr);
|
|
+ info, &temp_saddr, IP6CB(skb2));
|
|
else
|
|
icmp6_send(skb2, ICMPV6_DEST_UNREACH, ICMPV6_ADDR_UNREACH,
|
|
- info, &temp_saddr);
|
|
+ info, &temp_saddr, IP6CB(skb2));
|
|
if (rt)
|
|
ip6_rt_put(rt);
|
|
|
|
--- a/net/ipv6/ip6_icmp.c
|
|
+++ b/net/ipv6/ip6_icmp.c
|
|
@@ -33,23 +33,25 @@ int inet6_unregister_icmp_sender(ip6_icm
|
|
}
|
|
EXPORT_SYMBOL(inet6_unregister_icmp_sender);
|
|
|
|
-void icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info)
|
|
+void __icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info,
|
|
+ const struct inet6_skb_parm *parm)
|
|
{
|
|
ip6_icmp_send_t *send;
|
|
|
|
rcu_read_lock();
|
|
send = rcu_dereference(ip6_icmp_send);
|
|
if (send)
|
|
- send(skb, type, code, info, NULL);
|
|
+ send(skb, type, code, info, NULL, parm);
|
|
rcu_read_unlock();
|
|
}
|
|
-EXPORT_SYMBOL(icmpv6_send);
|
|
+EXPORT_SYMBOL(__icmpv6_send);
|
|
#endif
|
|
|
|
#if IS_ENABLED(CONFIG_NF_NAT)
|
|
#include <net/netfilter/nf_conntrack.h>
|
|
void icmpv6_ndo_send(struct sk_buff *skb_in, u8 type, u8 code, __u32 info)
|
|
{
|
|
+ struct inet6_skb_parm parm = { 0 };
|
|
struct sk_buff *cloned_skb = NULL;
|
|
enum ip_conntrack_info ctinfo;
|
|
struct in6_addr orig_ip;
|
|
@@ -57,7 +59,7 @@ void icmpv6_ndo_send(struct sk_buff *skb
|
|
|
|
ct = nf_ct_get(skb_in, &ctinfo);
|
|
if (!ct || !(ct->status & IPS_SRC_NAT)) {
|
|
- icmpv6_send(skb_in, type, code, info);
|
|
+ __icmpv6_send(skb_in, type, code, info, &parm);
|
|
return;
|
|
}
|
|
|
|
@@ -72,7 +74,7 @@ void icmpv6_ndo_send(struct sk_buff *skb
|
|
|
|
orig_ip = ipv6_hdr(skb_in)->saddr;
|
|
ipv6_hdr(skb_in)->saddr = ct->tuplehash[0].tuple.src.u3.in6;
|
|
- icmpv6_send(skb_in, type, code, info);
|
|
+ __icmpv6_send(skb_in, type, code, info, &parm);
|
|
ipv6_hdr(skb_in)->saddr = orig_ip;
|
|
out:
|
|
consume_skb(cloned_skb);
|