netfilter: x_tables: introduce and use xt_copy_counters_from_user
authorFlorian Westphal <fw@strlen.de>
Fri, 1 Apr 2016 13:37:59 +0000 (15:37 +0200)
committerPablo Neira Ayuso <pablo@netfilter.org>
Wed, 13 Apr 2016 22:30:41 +0000 (00:30 +0200)
The three variants use same copy&pasted code, condense this into a
helper and use that.

Make sure info.name is 0-terminated.

Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
include/linux/netfilter/x_tables.h
net/ipv4/netfilter/arp_tables.c
net/ipv4/netfilter/ip_tables.c
net/ipv6/netfilter/ip6_tables.c
net/netfilter/x_tables.c

index e2da9b90f1b8ab22038ff10547d3b32786881211..4dd9306c9d5623b4f35c9c8a85297ef5ec7cfa8f 100644 (file)
@@ -251,6 +251,9 @@ int xt_check_match(struct xt_mtchk_param *, unsigned int size, u_int8_t proto,
 int xt_check_target(struct xt_tgchk_param *, unsigned int size, u_int8_t proto,
                    bool inv_proto);
 
+void *xt_copy_counters_from_user(const void __user *user, unsigned int len,
+                                struct xt_counters_info *info, bool compat);
+
 struct xt_table *xt_register_table(struct net *net,
                                   const struct xt_table *table,
                                   struct xt_table_info *bootstrap,
index 8cefb7a2606b6183229b65f86c32060d9dcc0f58..60f5161abcb43a7dbe5cad53069d400ebe8df36e 100644 (file)
@@ -1123,55 +1123,17 @@ static int do_add_counters(struct net *net, const void __user *user,
        unsigned int i;
        struct xt_counters_info tmp;
        struct xt_counters *paddc;
-       unsigned int num_counters;
-       const char *name;
-       int size;
-       void *ptmp;
        struct xt_table *t;
        const struct xt_table_info *private;
        int ret = 0;
        struct arpt_entry *iter;
        unsigned int addend;
-#ifdef CONFIG_COMPAT
-       struct compat_xt_counters_info compat_tmp;
-
-       if (compat) {
-               ptmp = &compat_tmp;
-               size = sizeof(struct compat_xt_counters_info);
-       } else
-#endif
-       {
-               ptmp = &tmp;
-               size = sizeof(struct xt_counters_info);
-       }
 
-       if (copy_from_user(ptmp, user, size) != 0)
-               return -EFAULT;
-
-#ifdef CONFIG_COMPAT
-       if (compat) {
-               num_counters = compat_tmp.num_counters;
-               name = compat_tmp.name;
-       } else
-#endif
-       {
-               num_counters = tmp.num_counters;
-               name = tmp.name;
-       }
-
-       if (len != size + num_counters * sizeof(struct xt_counters))
-               return -EINVAL;
-
-       paddc = vmalloc(len - size);
-       if (!paddc)
-               return -ENOMEM;
-
-       if (copy_from_user(paddc, user + size, len - size) != 0) {
-               ret = -EFAULT;
-               goto free;
-       }
+       paddc = xt_copy_counters_from_user(user, len, &tmp, compat);
+       if (IS_ERR(paddc))
+               return PTR_ERR(paddc);
 
-       t = xt_find_table_lock(net, NFPROTO_ARP, name);
+       t = xt_find_table_lock(net, NFPROTO_ARP, tmp.name);
        if (IS_ERR_OR_NULL(t)) {
                ret = t ? PTR_ERR(t) : -ENOENT;
                goto free;
@@ -1179,7 +1141,7 @@ static int do_add_counters(struct net *net, const void __user *user,
 
        local_bh_disable();
        private = t->private;
-       if (private->number != num_counters) {
+       if (private->number != tmp.num_counters) {
                ret = -EINVAL;
                goto unlock_up_free;
        }
index 9340ce0a7549fcfbf4f56dae44245d39bd0e0a8c..735d1ee8c1ab64cdcdb363ce7b47636b4388227d 100644 (file)
@@ -1307,55 +1307,17 @@ do_add_counters(struct net *net, const void __user *user,
        unsigned int i;
        struct xt_counters_info tmp;
        struct xt_counters *paddc;
-       unsigned int num_counters;
-       const char *name;
-       int size;
-       void *ptmp;
        struct xt_table *t;
        const struct xt_table_info *private;
        int ret = 0;
        struct ipt_entry *iter;
        unsigned int addend;
-#ifdef CONFIG_COMPAT
-       struct compat_xt_counters_info compat_tmp;
-
-       if (compat) {
-               ptmp = &compat_tmp;
-               size = sizeof(struct compat_xt_counters_info);
-       } else
-#endif
-       {
-               ptmp = &tmp;
-               size = sizeof(struct xt_counters_info);
-       }
 
-       if (copy_from_user(ptmp, user, size) != 0)
-               return -EFAULT;
-
-#ifdef CONFIG_COMPAT
-       if (compat) {
-               num_counters = compat_tmp.num_counters;
-               name = compat_tmp.name;
-       } else
-#endif
-       {
-               num_counters = tmp.num_counters;
-               name = tmp.name;
-       }
-
-       if (len != size + num_counters * sizeof(struct xt_counters))
-               return -EINVAL;
-
-       paddc = vmalloc(len - size);
-       if (!paddc)
-               return -ENOMEM;
-
-       if (copy_from_user(paddc, user + size, len - size) != 0) {
-               ret = -EFAULT;
-               goto free;
-       }
+       paddc = xt_copy_counters_from_user(user, len, &tmp, compat);
+       if (IS_ERR(paddc))
+               return PTR_ERR(paddc);
 
-       t = xt_find_table_lock(net, AF_INET, name);
+       t = xt_find_table_lock(net, AF_INET, tmp.name);
        if (IS_ERR_OR_NULL(t)) {
                ret = t ? PTR_ERR(t) : -ENOENT;
                goto free;
@@ -1363,7 +1325,7 @@ do_add_counters(struct net *net, const void __user *user,
 
        local_bh_disable();
        private = t->private;
-       if (private->number != num_counters) {
+       if (private->number != tmp.num_counters) {
                ret = -EINVAL;
                goto unlock_up_free;
        }
index aa010856a255c71cadf324dfdec03541abce4ce9..73e606c719ef43e418a8ffd215f87e7a4bfede79 100644 (file)
@@ -1319,55 +1319,16 @@ do_add_counters(struct net *net, const void __user *user, unsigned int len,
        unsigned int i;
        struct xt_counters_info tmp;
        struct xt_counters *paddc;
-       unsigned int num_counters;
-       char *name;
-       int size;
-       void *ptmp;
        struct xt_table *t;
        const struct xt_table_info *private;
        int ret = 0;
        struct ip6t_entry *iter;
        unsigned int addend;
-#ifdef CONFIG_COMPAT
-       struct compat_xt_counters_info compat_tmp;
-
-       if (compat) {
-               ptmp = &compat_tmp;
-               size = sizeof(struct compat_xt_counters_info);
-       } else
-#endif
-       {
-               ptmp = &tmp;
-               size = sizeof(struct xt_counters_info);
-       }
-
-       if (copy_from_user(ptmp, user, size) != 0)
-               return -EFAULT;
-
-#ifdef CONFIG_COMPAT
-       if (compat) {
-               num_counters = compat_tmp.num_counters;
-               name = compat_tmp.name;
-       } else
-#endif
-       {
-               num_counters = tmp.num_counters;
-               name = tmp.name;
-       }
-
-       if (len != size + num_counters * sizeof(struct xt_counters))
-               return -EINVAL;
-
-       paddc = vmalloc(len - size);
-       if (!paddc)
-               return -ENOMEM;
-
-       if (copy_from_user(paddc, user + size, len - size) != 0) {
-               ret = -EFAULT;
-               goto free;
-       }
 
-       t = xt_find_table_lock(net, AF_INET6, name);
+       paddc = xt_copy_counters_from_user(user, len, &tmp, compat);
+       if (IS_ERR(paddc))
+               return PTR_ERR(paddc);
+       t = xt_find_table_lock(net, AF_INET6, tmp.name);
        if (IS_ERR_OR_NULL(t)) {
                ret = t ? PTR_ERR(t) : -ENOENT;
                goto free;
@@ -1375,7 +1336,7 @@ do_add_counters(struct net *net, const void __user *user, unsigned int len,
 
        local_bh_disable();
        private = t->private;
-       if (private->number != num_counters) {
+       if (private->number != tmp.num_counters) {
                ret = -EINVAL;
                goto unlock_up_free;
        }
index 9ec23ffa43b407975bb2bc45f4f84d884846a5d9..c69c892231d7b6b8699f20913847f64f1a758a42 100644 (file)
@@ -752,6 +752,80 @@ int xt_check_target(struct xt_tgchk_param *par,
 }
 EXPORT_SYMBOL_GPL(xt_check_target);
 
+/**
+ * xt_copy_counters_from_user - copy counters and metadata from userspace
+ *
+ * @user: src pointer to userspace memory
+ * @len: alleged size of userspace memory
+ * @info: where to store the xt_counters_info metadata
+ * @compat: true if we setsockopt call is done by 32bit task on 64bit kernel
+ *
+ * Copies counter meta data from @user and stores it in @info.
+ *
+ * vmallocs memory to hold the counters, then copies the counter data
+ * from @user to the new memory and returns a pointer to it.
+ *
+ * If @compat is true, @info gets converted automatically to the 64bit
+ * representation.
+ *
+ * The metadata associated with the counters is stored in @info.
+ *
+ * Return: returns pointer that caller has to test via IS_ERR().
+ * If IS_ERR is false, caller has to vfree the pointer.
+ */
+void *xt_copy_counters_from_user(const void __user *user, unsigned int len,
+                                struct xt_counters_info *info, bool compat)
+{
+       void *mem;
+       u64 size;
+
+#ifdef CONFIG_COMPAT
+       if (compat) {
+               /* structures only differ in size due to alignment */
+               struct compat_xt_counters_info compat_tmp;
+
+               if (len <= sizeof(compat_tmp))
+                       return ERR_PTR(-EINVAL);
+
+               len -= sizeof(compat_tmp);
+               if (copy_from_user(&compat_tmp, user, sizeof(compat_tmp)) != 0)
+                       return ERR_PTR(-EFAULT);
+
+               strlcpy(info->name, compat_tmp.name, sizeof(info->name));
+               info->num_counters = compat_tmp.num_counters;
+               user += sizeof(compat_tmp);
+       } else
+#endif
+       {
+               if (len <= sizeof(*info))
+                       return ERR_PTR(-EINVAL);
+
+               len -= sizeof(*info);
+               if (copy_from_user(info, user, sizeof(*info)) != 0)
+                       return ERR_PTR(-EFAULT);
+
+               info->name[sizeof(info->name) - 1] = '\0';
+               user += sizeof(*info);
+       }
+
+       size = sizeof(struct xt_counters);
+       size *= info->num_counters;
+
+       if (size != (u64)len)
+               return ERR_PTR(-EINVAL);
+
+       mem = vmalloc(len);
+       if (!mem)
+               return ERR_PTR(-ENOMEM);
+
+       if (copy_from_user(mem, user, len) == 0)
+               return mem;
+
+       vfree(mem);
+       return ERR_PTR(-EFAULT);
+}
+EXPORT_SYMBOL_GPL(xt_copy_counters_from_user);
+
 #ifdef CONFIG_COMPAT
 int xt_compat_target_offset(const struct xt_target *target)
 {