summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorHerbert Xu <herbert@gondor.apana.org.au>2009-02-05 15:15:50 -0800
committerDavid S. Miller <davem@davemloft.net>2009-02-05 15:15:50 -0800
commit0178b695fd6b40a62a215cbeb03dd51ada3bb5e0 (patch)
tree122ac633fa8ad71ac58e7228efc51a639f44466a /net
parent12402b5b7a2aa7ebed246db4570a5eb905fe79ea (diff)
downloadlinux-0178b695fd6b40a62a215cbeb03dd51ada3bb5e0.tar.bz2
ipv6: Copy cork options in ip6_append_data
As the options passed to ip6_append_data may be ephemeral, we need to duplicate it for corking. This patch applies the simplest fix which is to memdup all the relevant bits. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net')
-rw-r--r--net/ipv6/ip6_output.c67
1 files changed, 52 insertions, 15 deletions
diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c
index 4b15938bef4d..9fb49c3b518a 100644
--- a/net/ipv6/ip6_output.c
+++ b/net/ipv6/ip6_output.c
@@ -1105,6 +1105,18 @@ static inline int ip6_ufo_append_data(struct sock *sk,
return err;
}
+static inline struct ipv6_opt_hdr *ip6_opt_dup(struct ipv6_opt_hdr *src,
+ gfp_t gfp)
+{
+ return src ? kmemdup(src, (src->hdrlen + 1) * 8, gfp) : NULL;
+}
+
+static inline struct ipv6_rt_hdr *ip6_rthdr_dup(struct ipv6_rt_hdr *src,
+ gfp_t gfp)
+{
+ return src ? kmemdup(src, (src->hdrlen + 1) * 8, gfp) : NULL;
+}
+
int ip6_append_data(struct sock *sk, int getfrag(void *from, char *to,
int offset, int len, int odd, struct sk_buff *skb),
void *from, int length, int transhdrlen,
@@ -1130,17 +1142,37 @@ int ip6_append_data(struct sock *sk, int getfrag(void *from, char *to,
* setup for corking
*/
if (opt) {
- if (np->cork.opt == NULL) {
- np->cork.opt = kmalloc(opt->tot_len,
- sk->sk_allocation);
- if (unlikely(np->cork.opt == NULL))
- return -ENOBUFS;
- } else if (np->cork.opt->tot_len < opt->tot_len) {
- printk(KERN_DEBUG "ip6_append_data: invalid option length\n");
+ if (WARN_ON(np->cork.opt))
return -EINVAL;
- }
- memcpy(np->cork.opt, opt, opt->tot_len);
- inet->cork.flags |= IPCORK_OPT;
+
+ np->cork.opt = kmalloc(opt->tot_len, sk->sk_allocation);
+ if (unlikely(np->cork.opt == NULL))
+ return -ENOBUFS;
+
+ np->cork.opt->tot_len = opt->tot_len;
+ np->cork.opt->opt_flen = opt->opt_flen;
+ np->cork.opt->opt_nflen = opt->opt_nflen;
+
+ np->cork.opt->dst0opt = ip6_opt_dup(opt->dst0opt,
+ sk->sk_allocation);
+ if (opt->dst0opt && !np->cork.opt->dst0opt)
+ return -ENOBUFS;
+
+ np->cork.opt->dst1opt = ip6_opt_dup(opt->dst1opt,
+ sk->sk_allocation);
+ if (opt->dst1opt && !np->cork.opt->dst1opt)
+ return -ENOBUFS;
+
+ np->cork.opt->hopopt = ip6_opt_dup(opt->hopopt,
+ sk->sk_allocation);
+ if (opt->hopopt && !np->cork.opt->hopopt)
+ return -ENOBUFS;
+
+ np->cork.opt->srcrt = ip6_rthdr_dup(opt->srcrt,
+ sk->sk_allocation);
+ if (opt->srcrt && !np->cork.opt->srcrt)
+ return -ENOBUFS;
+
/* need source address above miyazawa*/
}
dst_hold(&rt->u.dst);
@@ -1167,8 +1199,7 @@ int ip6_append_data(struct sock *sk, int getfrag(void *from, char *to,
} else {
rt = (struct rt6_info *)inet->cork.dst;
fl = &inet->cork.fl;
- if (inet->cork.flags & IPCORK_OPT)
- opt = np->cork.opt;
+ opt = np->cork.opt;
transhdrlen = 0;
exthdrlen = 0;
mtu = inet->cork.fragsize;
@@ -1407,9 +1438,15 @@ error:
static void ip6_cork_release(struct inet_sock *inet, struct ipv6_pinfo *np)
{
- inet->cork.flags &= ~IPCORK_OPT;
- kfree(np->cork.opt);
- np->cork.opt = NULL;
+ if (np->cork.opt) {
+ kfree(np->cork.opt->dst0opt);
+ kfree(np->cork.opt->dst1opt);
+ kfree(np->cork.opt->hopopt);
+ kfree(np->cork.opt->srcrt);
+ kfree(np->cork.opt);
+ np->cork.opt = NULL;
+ }
+
if (inet->cork.dst) {
dst_release(inet->cork.dst);
inet->cork.dst = NULL;