Skip to content

Commit

Permalink
[IPV6]: IPV6_CHECKSUM socket option can corrupt kernel memory
Browse files Browse the repository at this point in the history
So here is a patch that introduces skb_store_bits -- the opposite of
skb_copy_bits, and uses them to read/write the csum field in rawv6.

Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
herbertx authored and davem330 committed Apr 20, 2005
1 parent fd92833 commit 357b40a
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 13 deletions.
2 changes: 2 additions & 0 deletions include/linux/skbuff.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
88 changes: 88 additions & 0 deletions net/core/skbuff.c
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,94 @@ int skb_copy_bits(const struct sk_buff *skb, int offset, void *to, int len)
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,
Expand Down
53 changes: 40 additions & 13 deletions net/ipv6/raw.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -452,23 +453,26 @@ static int rawv6_recvmsg(struct kiocb *iocb, struct sock *sk,
}

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;

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;
}

Expand All @@ -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:
Expand Down Expand Up @@ -774,7 +801,7 @@ static int rawv6_sendmsg(struct kiocb *iocb, struct sock *sk,
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,
Expand Down

0 comments on commit 357b40a

Please sign in to comment.