net: filter: keep original BPF program around
[firefly-linux-kernel-4.4.55.git] / net / core / filter.c
index bb3c76458ca9cc3c2f6aefc8022a224055c5fa65..9730e7fe477002d76b576d9950818d988a868a3f 100644 (file)
@@ -629,6 +629,37 @@ int sk_chk_filter(struct sock_filter *filter, unsigned int flen)
 }
 EXPORT_SYMBOL(sk_chk_filter);
 
+static int sk_store_orig_filter(struct sk_filter *fp,
+                               const struct sock_fprog *fprog)
+{
+       unsigned int fsize = sk_filter_proglen(fprog);
+       struct sock_fprog_kern *fkprog;
+
+       fp->orig_prog = kmalloc(sizeof(*fkprog), GFP_KERNEL);
+       if (!fp->orig_prog)
+               return -ENOMEM;
+
+       fkprog = fp->orig_prog;
+       fkprog->len = fprog->len;
+       fkprog->filter = kmemdup(fp->insns, fsize, GFP_KERNEL);
+       if (!fkprog->filter) {
+               kfree(fp->orig_prog);
+               return -ENOMEM;
+       }
+
+       return 0;
+}
+
+static void sk_release_orig_filter(struct sk_filter *fp)
+{
+       struct sock_fprog_kern *fprog = fp->orig_prog;
+
+       if (fprog) {
+               kfree(fprog->filter);
+               kfree(fprog);
+       }
+}
+
 /**
  *     sk_filter_release_rcu - Release a socket filter by rcu_head
  *     @rcu: rcu_head that contains the sk_filter to free
@@ -637,6 +668,7 @@ void sk_filter_release_rcu(struct rcu_head *rcu)
 {
        struct sk_filter *fp = container_of(rcu, struct sk_filter, rcu);
 
+       sk_release_orig_filter(fp);
        bpf_jit_free(fp);
 }
 EXPORT_SYMBOL(sk_filter_release_rcu);
@@ -669,8 +701,8 @@ static int __sk_prepare_filter(struct sk_filter *fp)
 int sk_unattached_filter_create(struct sk_filter **pfp,
                                struct sock_fprog *fprog)
 {
+       unsigned int fsize = sk_filter_proglen(fprog);
        struct sk_filter *fp;
-       unsigned int fsize = sizeof(struct sock_filter) * fprog->len;
        int err;
 
        /* Make sure new filter is there and in the right amounts. */
@@ -680,10 +712,16 @@ int sk_unattached_filter_create(struct sk_filter **pfp,
        fp = kmalloc(sk_filter_size(fprog->len), GFP_KERNEL);
        if (!fp)
                return -ENOMEM;
+
        memcpy(fp->insns, fprog->filter, fsize);
 
        atomic_set(&fp->refcnt, 1);
        fp->len = fprog->len;
+       /* Since unattached filters are not copied back to user
+        * space through sk_get_filter(), we do not need to hold
+        * a copy here, and can spare us the work.
+        */
+       fp->orig_prog = NULL;
 
        err = __sk_prepare_filter(fp);
        if (err)
@@ -716,7 +754,7 @@ EXPORT_SYMBOL_GPL(sk_unattached_filter_destroy);
 int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk)
 {
        struct sk_filter *fp, *old_fp;
-       unsigned int fsize = sizeof(struct sock_filter) * fprog->len;
+       unsigned int fsize = sk_filter_proglen(fprog);
        unsigned int sk_fsize = sk_filter_size(fprog->len);
        int err;
 
@@ -730,6 +768,7 @@ int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk)
        fp = sock_kmalloc(sk, sk_fsize, GFP_KERNEL);
        if (!fp)
                return -ENOMEM;
+
        if (copy_from_user(fp->insns, fprog->filter, fsize)) {
                sock_kfree_s(sk, fp, sk_fsize);
                return -EFAULT;
@@ -738,6 +777,12 @@ int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk)
        atomic_set(&fp->refcnt, 1);
        fp->len = fprog->len;
 
+       err = sk_store_orig_filter(fp, fprog);
+       if (err) {
+               sk_filter_uncharge(sk, fp);
+               return -ENOMEM;
+       }
+
        err = __sk_prepare_filter(fp);
        if (err) {
                sk_filter_uncharge(sk, fp);
@@ -750,6 +795,7 @@ int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk)
 
        if (old_fp)
                sk_filter_uncharge(sk, old_fp);
+
        return 0;
 }
 EXPORT_SYMBOL_GPL(sk_attach_filter);
@@ -769,6 +815,7 @@ int sk_detach_filter(struct sock *sk)
                sk_filter_uncharge(sk, filter);
                ret = 0;
        }
+
        return ret;
 }
 EXPORT_SYMBOL_GPL(sk_detach_filter);
@@ -851,34 +898,41 @@ void sk_decode_filter(struct sock_filter *filt, struct sock_filter *to)
        to->k = filt->k;
 }
 
-int sk_get_filter(struct sock *sk, struct sock_filter __user *ubuf, unsigned int len)
+int sk_get_filter(struct sock *sk, struct sock_filter __user *ubuf,
+                 unsigned int len)
 {
+       struct sock_fprog_kern *fprog;
        struct sk_filter *filter;
-       int i, ret;
+       int ret = 0;
 
        lock_sock(sk);
        filter = rcu_dereference_protected(sk->sk_filter,
-                       sock_owned_by_user(sk));
-       ret = 0;
+                                          sock_owned_by_user(sk));
        if (!filter)
                goto out;
-       ret = filter->len;
+
+       /* We're copying the filter that has been originally attached,
+        * so no conversion/decode needed anymore.
+        */
+       fprog = filter->orig_prog;
+
+       ret = fprog->len;
        if (!len)
+               /* User space only enquires number of filter blocks. */
                goto out;
+
        ret = -EINVAL;
-       if (len < filter->len)
+       if (len < fprog->len)
                goto out;
 
        ret = -EFAULT;
-       for (i = 0; i < filter->len; i++) {
-               struct sock_filter fb;
-
-               sk_decode_filter(&filter->insns[i], &fb);
-               if (copy_to_user(&ubuf[i], &fb, sizeof(fb)))
-                       goto out;
-       }
+       if (copy_to_user(ubuf, fprog->filter, sk_filter_proglen(fprog)))
+               goto out;
 
-       ret = filter->len;
+       /* Instead of bytes, the API requests to return the number
+        * of filter blocks.
+        */
+       ret = fprog->len;
 out:
        release_sock(sk);
        return ret;