Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 357b40a1 authored by Herbert Xu's avatar Herbert Xu Committed by David S. Miller
Browse files

[IPV6]: IPV6_CHECKSUM socket option can corrupt kernel memory



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: default avatarHerbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent fd92833a
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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);
+88 −0
Original line number Diff line number Diff line
@@ -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,
+40 −13
Original line number Diff line number Diff line
@@ -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 @@ 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;
@@ -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);

	*csum = csum_ipv6_magic(&fl->fl6_src,
	tmp_csum = csum_ipv6_magic(&fl->fl6_src,
				   &fl->fl6_dst,
				len, fl->proto, tmp_csum);
				   inet->cork.length, fl->proto, tmp_csum);

	if (tmp_csum == 0)
		tmp_csum = -1;

	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 @@ 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,