diff options
-rw-r--r-- | include/linux/skbuff.h | 2 | ||||
-rw-r--r-- | net/core/skbuff.c | 88 | ||||
-rw-r--r-- | net/ipv6/raw.c | 53 |
3 files changed, 130 insertions, 13 deletions
diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h index aa35797ebfbf..9f2d75e4f087 100644 --- a/include/linux/skbuff.h +++ b/include/linux/skbuff.h @@ -1183,6 +1183,8 @@ extern unsigned int skb_checksum(const struct sk_buff *skb, int offset, int len, unsigned int csum); extern int skb_copy_bits(const struct sk_buff *skb, int offset, void *to, int len); +extern int skb_store_bits(const struct sk_buff *skb, int offset, + void *from, int len); extern unsigned int skb_copy_and_csum_bits(const struct sk_buff *skb, int offset, u8 *to, int len, unsigned int csum); diff --git a/net/core/skbuff.c b/net/core/skbuff.c index bf02ca9f80ac..c96559574a3f 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -985,6 +985,94 @@ fault: return -EFAULT; } +/** + * skb_store_bits - store bits from kernel buffer to skb + * @skb: destination buffer + * @offset: offset in destination + * @from: source buffer + * @len: number of bytes to copy + * + * Copy the specified number of bytes from the source buffer to the + * destination skb. This function handles all the messy bits of + * traversing fragment lists and such. + */ + +int skb_store_bits(const struct sk_buff *skb, int offset, void *from, int len) +{ + int i, copy; + int start = skb_headlen(skb); + + if (offset > (int)skb->len - len) + goto fault; + + if ((copy = start - offset) > 0) { + if (copy > len) + copy = len; + memcpy(skb->data + offset, from, copy); + if ((len -= copy) == 0) + return 0; + offset += copy; + from += copy; + } + + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; + int end; + + BUG_TRAP(start <= offset + len); + + end = start + frag->size; + if ((copy = end - offset) > 0) { + u8 *vaddr; + + if (copy > len) + copy = len; + + vaddr = kmap_skb_frag(frag); + memcpy(vaddr + frag->page_offset + offset - start, + from, copy); + kunmap_skb_frag(vaddr); + + if ((len -= copy) == 0) + return 0; + offset += copy; + from += copy; + } + start = end; + } + + if (skb_shinfo(skb)->frag_list) { + struct sk_buff *list = skb_shinfo(skb)->frag_list; + + for (; list; list = list->next) { + int end; + + BUG_TRAP(start <= offset + len); + + end = start + list->len; + if ((copy = end - offset) > 0) { + if (copy > len) + copy = len; + if (skb_store_bits(list, offset - start, + from, copy)) + goto fault; + if ((len -= copy) == 0) + return 0; + offset += copy; + from += copy; + } + start = end; + } + } + if (!len) + return 0; + +fault: + return -EFAULT; +} + +EXPORT_SYMBOL(skb_store_bits); + /* Checksum skb data. */ unsigned int skb_checksum(const struct sk_buff *skb, int offset, diff --git a/net/ipv6/raw.c b/net/ipv6/raw.c index 5488ad0de4f6..3e2ad0a70412 100644 --- a/net/ipv6/raw.c +++ b/net/ipv6/raw.c @@ -34,6 +34,7 @@ #include <linux/netfilter_ipv6.h> #include <asm/uaccess.h> #include <asm/ioctls.h> +#include <asm/bug.h> #include <net/ip.h> #include <net/sock.h> @@ -452,12 +453,15 @@ csum_copy_err: } static int rawv6_push_pending_frames(struct sock *sk, struct flowi *fl, - struct raw6_sock *rp, int len) + struct raw6_sock *rp) { + struct inet_sock *inet = inet_sk(sk); struct sk_buff *skb; int err = 0; - u16 *csum; + int offset; + int len; u32 tmp_csum; + u16 csum; if (!rp->checksum) goto send; @@ -465,10 +469,10 @@ static int rawv6_push_pending_frames(struct sock *sk, struct flowi *fl, if ((skb = skb_peek(&sk->sk_write_queue)) == NULL) goto out; - if (rp->offset + 1 < len) - csum = (u16 *)(skb->h.raw + rp->offset); - else { + offset = rp->offset; + if (offset >= inet->cork.length - 1) { err = -EINVAL; + ip6_flush_pending_frames(sk); goto out; } @@ -479,23 +483,46 @@ static int rawv6_push_pending_frames(struct sock *sk, struct flowi *fl, */ tmp_csum = skb->csum; } else { + struct sk_buff *csum_skb = NULL; tmp_csum = 0; skb_queue_walk(&sk->sk_write_queue, skb) { tmp_csum = csum_add(tmp_csum, skb->csum); + + if (csum_skb) + continue; + + len = skb->len - (skb->h.raw - skb->data); + if (offset >= len) { + offset -= len; + continue; + } + + csum_skb = skb; } + + skb = csum_skb; } + offset += skb->h.raw - skb->data; + if (skb_copy_bits(skb, offset, &csum, 2)) + BUG(); + /* in case cksum was not initialized */ - if (unlikely(*csum)) - tmp_csum = csum_sub(tmp_csum, *csum); + if (unlikely(csum)) + tmp_csum = csum_sub(tmp_csum, csum); + + tmp_csum = csum_ipv6_magic(&fl->fl6_src, + &fl->fl6_dst, + inet->cork.length, fl->proto, tmp_csum); + + if (tmp_csum == 0) + tmp_csum = -1; - *csum = csum_ipv6_magic(&fl->fl6_src, - &fl->fl6_dst, - len, fl->proto, tmp_csum); + csum = tmp_csum; + if (skb_store_bits(skb, offset, &csum, 2)) + BUG(); - if (*csum == 0) - *csum = -1; send: err = ip6_push_pending_frames(sk); out: @@ -774,7 +801,7 @@ back_from_confirm: if (err) ip6_flush_pending_frames(sk); else if (!(msg->msg_flags & MSG_MORE)) - err = rawv6_push_pending_frames(sk, &fl, rp, len); + err = rawv6_push_pending_frames(sk, &fl, rp); } done: ip6_dst_store(sk, dst, |