Skip to content

Commit ee576c4

Browse files
zx2c4kuba-moo
authored andcommitted
net: icmp: pass zeroed opts from icmp{,v6}_ndo_send before sending
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: a2b78e9 ("sunvnet: generate ICMP PTMUD messages for smaller port MTUs") Reported-by: SinYu <[email protected]> Reviewed-by: Willem de Bruijn <[email protected]> Link: https://lore.kernel.org/netdev/CAF=yD-LOF116aHub6RMe8vB8ZpnrrnoTdqhobEx+bvoA8AsP0w@mail.gmail.com/T/ Signed-off-by: Jason A. Donenfeld <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Jakub Kicinski <[email protected]>
1 parent 42870a1 commit ee576c4

File tree

7 files changed

+44
-25
lines changed

7 files changed

+44
-25
lines changed

drivers/net/gtp.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,6 @@ static int gtp_build_skb_ip4(struct sk_buff *skb, struct net_device *dev,
543543
if (!skb_is_gso(skb) && (iph->frag_off & htons(IP_DF)) &&
544544
mtu < ntohs(iph->tot_len)) {
545545
netdev_dbg(dev, "packet too big, fragmentation needed\n");
546-
memset(IPCB(skb), 0, sizeof(*IPCB(skb)));
547546
icmp_ndo_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,
548547
htonl(mtu));
549548
goto err_rt;

include/linux/icmpv6.h

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#define _LINUX_ICMPV6_H
44

55
#include <linux/skbuff.h>
6+
#include <linux/ipv6.h>
67
#include <uapi/linux/icmpv6.h>
78

89
static inline struct icmp6hdr *icmp6_hdr(const struct sk_buff *skb)
@@ -15,13 +16,16 @@ static inline struct icmp6hdr *icmp6_hdr(const struct sk_buff *skb)
1516
#if IS_ENABLED(CONFIG_IPV6)
1617

1718
typedef void ip6_icmp_send_t(struct sk_buff *skb, u8 type, u8 code, __u32 info,
18-
const struct in6_addr *force_saddr);
19+
const struct in6_addr *force_saddr,
20+
const struct inet6_skb_parm *parm);
1921
void icmp6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info,
20-
const struct in6_addr *force_saddr);
22+
const struct in6_addr *force_saddr,
23+
const struct inet6_skb_parm *parm);
2124
#if IS_BUILTIN(CONFIG_IPV6)
22-
static inline void icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info)
25+
static inline void __icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info,
26+
const struct inet6_skb_parm *parm)
2327
{
24-
icmp6_send(skb, type, code, info, NULL);
28+
icmp6_send(skb, type, code, info, NULL, parm);
2529
}
2630
static inline int inet6_register_icmp_sender(ip6_icmp_send_t *fn)
2731
{
@@ -34,18 +38,28 @@ static inline int inet6_unregister_icmp_sender(ip6_icmp_send_t *fn)
3438
return 0;
3539
}
3640
#else
37-
extern void icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info);
41+
extern void __icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info,
42+
const struct inet6_skb_parm *parm);
3843
extern int inet6_register_icmp_sender(ip6_icmp_send_t *fn);
3944
extern int inet6_unregister_icmp_sender(ip6_icmp_send_t *fn);
4045
#endif
4146

47+
static inline void icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info)
48+
{
49+
__icmpv6_send(skb, type, code, info, IP6CB(skb));
50+
}
51+
4252
int ip6_err_gen_icmpv6_unreach(struct sk_buff *skb, int nhs, int type,
4353
unsigned int data_len);
4454

4555
#if IS_ENABLED(CONFIG_NF_NAT)
4656
void icmpv6_ndo_send(struct sk_buff *skb_in, u8 type, u8 code, __u32 info);
4757
#else
48-
#define icmpv6_ndo_send icmpv6_send
58+
static inline void icmpv6_ndo_send(struct sk_buff *skb_in, u8 type, u8 code, __u32 info)
59+
{
60+
struct inet6_skb_parm parm = { 0 };
61+
__icmpv6_send(skb_in, type, code, info, &parm);
62+
}
4963
#endif
5064

5165
#else

include/linux/ipv6.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ struct ipv6_params {
8585
__s32 autoconf;
8686
};
8787
extern struct ipv6_params ipv6_defaults;
88-
#include <linux/icmpv6.h>
8988
#include <linux/tcp.h>
9089
#include <linux/udp.h>
9190

include/net/icmp.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ static inline void icmp_send(struct sk_buff *skb_in, int type, int code, __be32
4646
#if IS_ENABLED(CONFIG_NF_NAT)
4747
void icmp_ndo_send(struct sk_buff *skb_in, int type, int code, __be32 info);
4848
#else
49-
#define icmp_ndo_send icmp_send
49+
static inline void icmp_ndo_send(struct sk_buff *skb_in, int type, int code, __be32 info)
50+
{
51+
struct ip_options opts = { 0 };
52+
__icmp_send(skb_in, type, code, info, &opts);
53+
}
5054
#endif
5155

5256
int icmp_rcv(struct sk_buff *skb);

net/ipv4/icmp.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -775,13 +775,14 @@ EXPORT_SYMBOL(__icmp_send);
775775
void icmp_ndo_send(struct sk_buff *skb_in, int type, int code, __be32 info)
776776
{
777777
struct sk_buff *cloned_skb = NULL;
778+
struct ip_options opts = { 0 };
778779
enum ip_conntrack_info ctinfo;
779780
struct nf_conn *ct;
780781
__be32 orig_ip;
781782

782783
ct = nf_ct_get(skb_in, &ctinfo);
783784
if (!ct || !(ct->status & IPS_SRC_NAT)) {
784-
icmp_send(skb_in, type, code, info);
785+
__icmp_send(skb_in, type, code, info, &opts);
785786
return;
786787
}
787788

@@ -796,7 +797,7 @@ void icmp_ndo_send(struct sk_buff *skb_in, int type, int code, __be32 info)
796797

797798
orig_ip = ip_hdr(skb_in)->saddr;
798799
ip_hdr(skb_in)->saddr = ct->tuplehash[0].tuple.src.u3.ip;
799-
icmp_send(skb_in, type, code, info);
800+
__icmp_send(skb_in, type, code, info, &opts);
800801
ip_hdr(skb_in)->saddr = orig_ip;
801802
out:
802803
consume_skb(cloned_skb);

net/ipv6/icmp.c

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -331,10 +331,9 @@ static int icmpv6_getfrag(void *from, char *to, int offset, int len, int odd, st
331331
}
332332

333333
#if IS_ENABLED(CONFIG_IPV6_MIP6)
334-
static void mip6_addr_swap(struct sk_buff *skb)
334+
static void mip6_addr_swap(struct sk_buff *skb, const struct inet6_skb_parm *opt)
335335
{
336336
struct ipv6hdr *iph = ipv6_hdr(skb);
337-
struct inet6_skb_parm *opt = IP6CB(skb);
338337
struct ipv6_destopt_hao *hao;
339338
struct in6_addr tmp;
340339
int off;
@@ -351,7 +350,7 @@ static void mip6_addr_swap(struct sk_buff *skb)
351350
}
352351
}
353352
#else
354-
static inline void mip6_addr_swap(struct sk_buff *skb) {}
353+
static inline void mip6_addr_swap(struct sk_buff *skb, const struct inet6_skb_parm *opt) {}
355354
#endif
356355

357356
static struct dst_entry *icmpv6_route_lookup(struct net *net,
@@ -446,7 +445,8 @@ static int icmp6_iif(const struct sk_buff *skb)
446445
* Send an ICMP message in response to a packet in error
447446
*/
448447
void icmp6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info,
449-
const struct in6_addr *force_saddr)
448+
const struct in6_addr *force_saddr,
449+
const struct inet6_skb_parm *parm)
450450
{
451451
struct inet6_dev *idev = NULL;
452452
struct ipv6hdr *hdr = ipv6_hdr(skb);
@@ -542,7 +542,7 @@ void icmp6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info,
542542
if (!(skb->dev->flags & IFF_LOOPBACK) && !icmpv6_global_allow(net, type))
543543
goto out_bh_enable;
544544

545-
mip6_addr_swap(skb);
545+
mip6_addr_swap(skb, parm);
546546

547547
sk = icmpv6_xmit_lock(net);
548548
if (!sk)
@@ -559,7 +559,7 @@ void icmp6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info,
559559
/* select a more meaningful saddr from input if */
560560
struct net_device *in_netdev;
561561

562-
in_netdev = dev_get_by_index(net, IP6CB(skb)->iif);
562+
in_netdev = dev_get_by_index(net, parm->iif);
563563
if (in_netdev) {
564564
ipv6_dev_get_saddr(net, in_netdev, &fl6.daddr,
565565
inet6_sk(sk)->srcprefs,
@@ -640,7 +640,7 @@ EXPORT_SYMBOL(icmp6_send);
640640
*/
641641
void icmpv6_param_prob(struct sk_buff *skb, u8 code, int pos)
642642
{
643-
icmp6_send(skb, ICMPV6_PARAMPROB, code, pos, NULL);
643+
icmp6_send(skb, ICMPV6_PARAMPROB, code, pos, NULL, IP6CB(skb));
644644
kfree_skb(skb);
645645
}
646646

@@ -697,10 +697,10 @@ int ip6_err_gen_icmpv6_unreach(struct sk_buff *skb, int nhs, int type,
697697
}
698698
if (type == ICMP_TIME_EXCEEDED)
699699
icmp6_send(skb2, ICMPV6_TIME_EXCEED, ICMPV6_EXC_HOPLIMIT,
700-
info, &temp_saddr);
700+
info, &temp_saddr, IP6CB(skb2));
701701
else
702702
icmp6_send(skb2, ICMPV6_DEST_UNREACH, ICMPV6_ADDR_UNREACH,
703-
info, &temp_saddr);
703+
info, &temp_saddr, IP6CB(skb2));
704704
if (rt)
705705
ip6_rt_put(rt);
706706

net/ipv6/ip6_icmp.c

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,31 +33,33 @@ int inet6_unregister_icmp_sender(ip6_icmp_send_t *fn)
3333
}
3434
EXPORT_SYMBOL(inet6_unregister_icmp_sender);
3535

36-
void icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info)
36+
void __icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info,
37+
const struct inet6_skb_parm *parm)
3738
{
3839
ip6_icmp_send_t *send;
3940

4041
rcu_read_lock();
4142
send = rcu_dereference(ip6_icmp_send);
4243
if (send)
43-
send(skb, type, code, info, NULL);
44+
send(skb, type, code, info, NULL, parm);
4445
rcu_read_unlock();
4546
}
46-
EXPORT_SYMBOL(icmpv6_send);
47+
EXPORT_SYMBOL(__icmpv6_send);
4748
#endif
4849

4950
#if IS_ENABLED(CONFIG_NF_NAT)
5051
#include <net/netfilter/nf_conntrack.h>
5152
void icmpv6_ndo_send(struct sk_buff *skb_in, u8 type, u8 code, __u32 info)
5253
{
54+
struct inet6_skb_parm parm = { 0 };
5355
struct sk_buff *cloned_skb = NULL;
5456
enum ip_conntrack_info ctinfo;
5557
struct in6_addr orig_ip;
5658
struct nf_conn *ct;
5759

5860
ct = nf_ct_get(skb_in, &ctinfo);
5961
if (!ct || !(ct->status & IPS_SRC_NAT)) {
60-
icmpv6_send(skb_in, type, code, info);
62+
__icmpv6_send(skb_in, type, code, info, &parm);
6163
return;
6264
}
6365

@@ -72,7 +74,7 @@ void icmpv6_ndo_send(struct sk_buff *skb_in, u8 type, u8 code, __u32 info)
7274

7375
orig_ip = ipv6_hdr(skb_in)->saddr;
7476
ipv6_hdr(skb_in)->saddr = ct->tuplehash[0].tuple.src.u3.in6;
75-
icmpv6_send(skb_in, type, code, info);
77+
__icmpv6_send(skb_in, type, code, info, &parm);
7678
ipv6_hdr(skb_in)->saddr = orig_ip;
7779
out:
7880
consume_skb(cloned_skb);

0 commit comments

Comments
 (0)