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 = {
.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)
{
local_bh_disable();
......@@ -258,11 +249,19 @@ int icmpv6_push_pending_frames(struct sock *sk, struct flowi *fl, struct icmp6hd
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)
{
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;
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);
return 0;
}
......@@ -281,9 +280,10 @@ void icmpv6_send(struct sk_buff *skb, int type, int code, __u32 info,
struct dst_entry *dst;
struct icmp6hdr tmp_hdr;
struct flowi fl;
struct icmpv6_msg msg;
int iif = 0;
int addr_type = 0;
int len, plen;
int len;
int hlimit = -1;
int err = 0;
......@@ -379,27 +379,29 @@ void icmpv6_send(struct sk_buff *skb, int type, int code, __u32 info,
hlimit = dst_metric(dst, RTAX_HOPLIMIT);
}
plen = skb->nh.raw - skb->data;
__skb_pull(skb, plen);
msg.skb = skb;
msg.offset = skb->nh.raw - skb->data;
len = skb->len;
len = min_t(unsigned int, len, IPV6_MIN_MTU - sizeof(struct ipv6hdr) -sizeof(struct icmp6hdr));
if (len < 0) {
if (net_ratelimit())
printk(KERN_DEBUG "icmp: len problem\n");
__skb_push(skb, plen);
goto out_dst_release;
}
idev = in6_dev_get(skb->dev);
err = ip6_append_data(sk, icmpv6_getfrag, skb, len + sizeof(struct icmp6hdr), sizeof(struct icmp6hdr),
hlimit, NULL, &fl, (struct rt6_info*)dst, MSG_DONTWAIT);
err = ip6_append_data(sk, icmpv6_getfrag, &msg,
len + sizeof(struct icmp6hdr),
sizeof(struct icmp6hdr),
hlimit, NULL, &fl, (struct rt6_info*)dst,
MSG_DONTWAIT);
if (err) {
ip6_flush_pending_frames(sk);
goto out_put;
}
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)
ICMP6_INC_STATS_OFFSET_BH(idev, Icmp6OutDestUnreachs, type - ICMPV6_DEST_UNREACH);
......@@ -423,6 +425,7 @@ static void icmpv6_echo_reply(struct sk_buff *skb)
struct icmp6hdr *icmph = (struct icmp6hdr *) skb->h.raw;
struct icmp6hdr tmp_hdr;
struct flowi fl;
struct icmpv6_msg msg;
struct dst_entry *dst;
int err = 0;
int hlimit = -1;
......@@ -464,7 +467,10 @@ static void icmpv6_echo_reply(struct sk_buff *skb)
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,
(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