Commit 821ea930 authored by David S. Miller's avatar David S. Miller

[IPV6]: Fix packet quoting in icmpv6_send().

- Mucking with the original skb pointers with push/pull around
  the packet quoting was wrong, muching with these pointers could
  cause problems with others using the SKB.

  It was also buggy, it only handled the case where skb->nh.raw was
  ahead of or equal to skb->data

- The fix is to record skb->nh.raw - skb->data and use this as
  a base offset in calls to skb_copy_and_csum_bits().  This is
  what the pre-IPSEC code did.

This fixes IPV6 oopses and packet corruption on 64-bit platforms
when sending UDP port unreachable ICMP messages.

Reported and analyzed by Jan Oravec (jan.oravec@6com.sk)
parent eab3e65d
...@@ -86,15 +86,6 @@ static struct inet6_protocol icmpv6_protocol = { ...@@ -86,15 +86,6 @@ static struct inet6_protocol icmpv6_protocol = {
.flags = INET6_PROTO_FINAL, .flags = INET6_PROTO_FINAL,
}; };
struct icmpv6_msg {
struct icmp6hdr icmph;
struct sk_buff *skb;
int offset;
struct in6_addr *daddr;
int len;
__u32 csum;
};
static __inline__ int icmpv6_xmit_lock(void) static __inline__ int icmpv6_xmit_lock(void)
{ {
local_bh_disable(); local_bh_disable();
...@@ -258,11 +249,19 @@ int icmpv6_push_pending_frames(struct sock *sk, struct flowi *fl, struct icmp6hd ...@@ -258,11 +249,19 @@ int icmpv6_push_pending_frames(struct sock *sk, struct flowi *fl, struct icmp6hd
return err; return err;
} }
struct icmpv6_msg {
struct sk_buff *skb;
int offset;
};
static int icmpv6_getfrag(void *from, char *to, int offset, int len, int odd, struct sk_buff *skb) static int icmpv6_getfrag(void *from, char *to, int offset, int len, int odd, struct sk_buff *skb)
{ {
struct sk_buff *org_skb = (struct sk_buff *)from; struct icmpv6_msg *msg = (struct icmpv6_msg *) from;
struct sk_buff *org_skb = msg->skb;
__u32 csum = 0; __u32 csum = 0;
csum = skb_copy_and_csum_bits(org_skb, offset, to, len, csum);
csum = skb_copy_and_csum_bits(org_skb, msg->offset + offset,
to, len, csum);
skb->csum = csum_block_add(skb->csum, csum, odd); skb->csum = csum_block_add(skb->csum, csum, odd);
return 0; return 0;
} }
...@@ -281,9 +280,10 @@ void icmpv6_send(struct sk_buff *skb, int type, int code, __u32 info, ...@@ -281,9 +280,10 @@ void icmpv6_send(struct sk_buff *skb, int type, int code, __u32 info,
struct dst_entry *dst; struct dst_entry *dst;
struct icmp6hdr tmp_hdr; struct icmp6hdr tmp_hdr;
struct flowi fl; struct flowi fl;
struct icmpv6_msg msg;
int iif = 0; int iif = 0;
int addr_type = 0; int addr_type = 0;
int len, plen; int len;
int hlimit = -1; int hlimit = -1;
int err = 0; int err = 0;
...@@ -379,27 +379,29 @@ void icmpv6_send(struct sk_buff *skb, int type, int code, __u32 info, ...@@ -379,27 +379,29 @@ void icmpv6_send(struct sk_buff *skb, int type, int code, __u32 info,
hlimit = dst_metric(dst, RTAX_HOPLIMIT); hlimit = dst_metric(dst, RTAX_HOPLIMIT);
} }
plen = skb->nh.raw - skb->data; msg.skb = skb;
__skb_pull(skb, plen); msg.offset = skb->nh.raw - skb->data;
len = skb->len; len = skb->len;
len = min_t(unsigned int, len, IPV6_MIN_MTU - sizeof(struct ipv6hdr) -sizeof(struct icmp6hdr)); len = min_t(unsigned int, len, IPV6_MIN_MTU - sizeof(struct ipv6hdr) -sizeof(struct icmp6hdr));
if (len < 0) { if (len < 0) {
if (net_ratelimit()) if (net_ratelimit())
printk(KERN_DEBUG "icmp: len problem\n"); printk(KERN_DEBUG "icmp: len problem\n");
__skb_push(skb, plen);
goto out_dst_release; goto out_dst_release;
} }
idev = in6_dev_get(skb->dev); idev = in6_dev_get(skb->dev);
err = ip6_append_data(sk, icmpv6_getfrag, skb, len + sizeof(struct icmp6hdr), sizeof(struct icmp6hdr), err = ip6_append_data(sk, icmpv6_getfrag, &msg,
hlimit, NULL, &fl, (struct rt6_info*)dst, MSG_DONTWAIT); len + sizeof(struct icmp6hdr),
sizeof(struct icmp6hdr),
hlimit, NULL, &fl, (struct rt6_info*)dst,
MSG_DONTWAIT);
if (err) { if (err) {
ip6_flush_pending_frames(sk); ip6_flush_pending_frames(sk);
goto out_put; goto out_put;
} }
err = icmpv6_push_pending_frames(sk, &fl, &tmp_hdr, len + sizeof(struct icmp6hdr)); err = icmpv6_push_pending_frames(sk, &fl, &tmp_hdr, len + sizeof(struct icmp6hdr));
__skb_push(skb, plen);
if (type >= ICMPV6_DEST_UNREACH && type <= ICMPV6_PARAMPROB) if (type >= ICMPV6_DEST_UNREACH && type <= ICMPV6_PARAMPROB)
ICMP6_INC_STATS_OFFSET_BH(idev, Icmp6OutDestUnreachs, type - ICMPV6_DEST_UNREACH); ICMP6_INC_STATS_OFFSET_BH(idev, Icmp6OutDestUnreachs, type - ICMPV6_DEST_UNREACH);
...@@ -423,6 +425,7 @@ static void icmpv6_echo_reply(struct sk_buff *skb) ...@@ -423,6 +425,7 @@ static void icmpv6_echo_reply(struct sk_buff *skb)
struct icmp6hdr *icmph = (struct icmp6hdr *) skb->h.raw; struct icmp6hdr *icmph = (struct icmp6hdr *) skb->h.raw;
struct icmp6hdr tmp_hdr; struct icmp6hdr tmp_hdr;
struct flowi fl; struct flowi fl;
struct icmpv6_msg msg;
struct dst_entry *dst; struct dst_entry *dst;
int err = 0; int err = 0;
int hlimit = -1; int hlimit = -1;
...@@ -464,7 +467,10 @@ static void icmpv6_echo_reply(struct sk_buff *skb) ...@@ -464,7 +467,10 @@ static void icmpv6_echo_reply(struct sk_buff *skb)
idev = in6_dev_get(skb->dev); idev = in6_dev_get(skb->dev);
err = ip6_append_data(sk, icmpv6_getfrag, skb, skb->len + sizeof(struct icmp6hdr), msg.skb = skb;
msg.offset = 0;
err = ip6_append_data(sk, icmpv6_getfrag, &msg, skb->len + sizeof(struct icmp6hdr),
sizeof(struct icmp6hdr), hlimit, NULL, &fl, sizeof(struct icmp6hdr), hlimit, NULL, &fl,
(struct rt6_info*)dst, MSG_DONTWAIT); (struct rt6_info*)dst, MSG_DONTWAIT);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment