summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/linux/skbuff.h2
-rw-r--r--net/core/skbuff.c88
-rw-r--r--net/ipv6/raw.c53
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,