diff options
author | Herbert Xu <herbert@gondor.apana.org.au> | 2005-04-19 22:30:14 -0700 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2005-04-19 22:30:14 -0700 |
commit | 357b40a18b04c699da1d45608436e9b76b50e251 (patch) | |
tree | 51c4480c9508a911d52a3f69bbe84ec1191fd202 /net/ipv6 | |
parent | fd92833a52b972aafacced959f4a3f7541936a9b (diff) |
[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: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/ipv6')
-rw-r--r-- | net/ipv6/raw.c | 53 |
1 files changed, 40 insertions, 13 deletions
diff --git a/net/ipv6/raw.c b/net/ipv6/raw.c index 5488ad0de4f..3e2ad0a7041 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, |