Skip to content

Commit e6f4206

Browse files
zx2c4ksacilotto
authored andcommitted
net: icmp: pass zeroed opts from icmp{,v6}_ndo_send before sending
BugLink: https://bugs.launchpad.net/bugs/1918974 commit ee576c4 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: 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]> Signed-off-by: Greg Kroah-Hartman <[email protected]> Signed-off-by: Kamal Mostafa <[email protected]> Signed-off-by: Kelsey Skunberg <[email protected]>
1 parent 07b626a commit e6f4206

File tree

7 files changed

+43
-24
lines changed

7 files changed

+43
-24
lines changed

drivers/net/gtp.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,6 @@ static int gtp_build_skb_ip4(struct sk_buff *skb, struct net_device *dev,
545545
if (!skb_is_gso(skb) && (iph->frag_off & htons(IP_DF)) &&
546546
mtu < ntohs(iph->tot_len)) {
547547
netdev_dbg(dev, "packet too big, fragmentation needed\n");
548-
memset(IPCB(skb), 0, sizeof(*IPCB(skb)));
549548
icmp_ndo_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,
550549
htonl(mtu));
551550
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
@@ -83,7 +83,6 @@ struct ipv6_params {
8383
__s32 autoconf;
8484
};
8585
extern struct ipv6_params ipv6_defaults;
86-
#include <linux/icmpv6.h>
8786
#include <linux/tcp.h>
8887
#include <linux/udp.h>
8988

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
@@ -755,13 +755,14 @@ EXPORT_SYMBOL(__icmp_send);
755755
void icmp_ndo_send(struct sk_buff *skb_in, int type, int code, __be32 info)
756756
{
757757
struct sk_buff *cloned_skb = NULL;
758+
struct ip_options opts = { 0 };
758759
enum ip_conntrack_info ctinfo;
759760
struct nf_conn *ct;
760761
__be32 orig_ip;
761762

762763
ct = nf_ct_get(skb_in, &ctinfo);
763764
if (!ct || !(ct->status & IPS_SRC_NAT)) {
764-
icmp_send(skb_in, type, code, info);
765+
__icmp_send(skb_in, type, code, info, &opts);
765766
return;
766767
}
767768

@@ -776,7 +777,7 @@ void icmp_ndo_send(struct sk_buff *skb_in, int type, int code, __be32 info)
776777

777778
orig_ip = ip_hdr(skb_in)->saddr;
778779
ip_hdr(skb_in)->saddr = ct->tuplehash[0].tuple.src.u3.ip;
779-
icmp_send(skb_in, type, code, info);
780+
__icmp_send(skb_in, type, code, info, &opts);
780781
ip_hdr(skb_in)->saddr = orig_ip;
781782
out:
782783
consume_skb(cloned_skb);

net/ipv6/icmp.c

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

314314
#if IS_ENABLED(CONFIG_IPV6_MIP6)
315-
static void mip6_addr_swap(struct sk_buff *skb)
315+
static void mip6_addr_swap(struct sk_buff *skb, const struct inet6_skb_parm *opt)
316316
{
317317
struct ipv6hdr *iph = ipv6_hdr(skb);
318-
struct inet6_skb_parm *opt = IP6CB(skb);
319318
struct ipv6_destopt_hao *hao;
320319
struct in6_addr tmp;
321320
int off;
@@ -332,7 +331,7 @@ static void mip6_addr_swap(struct sk_buff *skb)
332331
}
333332
}
334333
#else
335-
static inline void mip6_addr_swap(struct sk_buff *skb) {}
334+
static inline void mip6_addr_swap(struct sk_buff *skb, const struct inet6_skb_parm *opt) {}
336335
#endif
337336

338337
static struct dst_entry *icmpv6_route_lookup(struct net *net,
@@ -427,7 +426,8 @@ static int icmp6_iif(const struct sk_buff *skb)
427426
* Send an ICMP message in response to a packet in error
428427
*/
429428
void icmp6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info,
430-
const struct in6_addr *force_saddr)
429+
const struct in6_addr *force_saddr,
430+
const struct inet6_skb_parm *parm)
431431
{
432432
struct inet6_dev *idev = NULL;
433433
struct ipv6hdr *hdr = ipv6_hdr(skb);
@@ -520,7 +520,7 @@ void icmp6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info,
520520
if (!(skb->dev->flags & IFF_LOOPBACK) && !icmpv6_global_allow(net, type))
521521
goto out_bh_enable;
522522

523-
mip6_addr_swap(skb);
523+
mip6_addr_swap(skb, parm);
524524

525525
memset(&fl6, 0, sizeof(fl6));
526526
fl6.flowi6_proto = IPPROTO_ICMPV6;
@@ -606,7 +606,7 @@ EXPORT_SYMBOL(icmp6_send);
606606
*/
607607
void icmpv6_param_prob(struct sk_buff *skb, u8 code, int pos)
608608
{
609-
icmp6_send(skb, ICMPV6_PARAMPROB, code, pos, NULL);
609+
icmp6_send(skb, ICMPV6_PARAMPROB, code, pos, NULL, IP6CB(skb));
610610
kfree_skb(skb);
611611
}
612612

@@ -663,10 +663,10 @@ int ip6_err_gen_icmpv6_unreach(struct sk_buff *skb, int nhs, int type,
663663
}
664664
if (type == ICMP_TIME_EXCEEDED)
665665
icmp6_send(skb2, ICMPV6_TIME_EXCEED, ICMPV6_EXC_HOPLIMIT,
666-
info, &temp_saddr);
666+
info, &temp_saddr, IP6CB(skb2));
667667
else
668668
icmp6_send(skb2, ICMPV6_DEST_UNREACH, ICMPV6_ADDR_UNREACH,
669-
info, &temp_saddr);
669+
info, &temp_saddr, IP6CB(skb2));
670670
if (rt)
671671
ip6_rt_put(rt);
672672

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)