netfilter: x_tables: move hook entry checks into core
[linux-2.6-microblaze.git] / net / netfilter / x_tables.c
index 8fa4d37..5d8ba89 100644 (file)
@@ -434,36 +434,35 @@ int xt_check_match(struct xt_mtchk_param *par,
                 * ebt_among is exempt from centralized matchsize checking
                 * because it uses a dynamic-size data set.
                 */
-               pr_err("%s_tables: %s.%u match: invalid size "
-                      "%u (kernel) != (user) %u\n",
-                      xt_prefix[par->family], par->match->name,
-                      par->match->revision,
-                      XT_ALIGN(par->match->matchsize), size);
+               pr_err_ratelimited("%s_tables: %s.%u match: invalid size %u (kernel) != (user) %u\n",
+                                  xt_prefix[par->family], par->match->name,
+                                  par->match->revision,
+                                  XT_ALIGN(par->match->matchsize), size);
                return -EINVAL;
        }
        if (par->match->table != NULL &&
            strcmp(par->match->table, par->table) != 0) {
-               pr_err("%s_tables: %s match: only valid in %s table, not %s\n",
-                      xt_prefix[par->family], par->match->name,
-                      par->match->table, par->table);
+               pr_info_ratelimited("%s_tables: %s match: only valid in %s table, not %s\n",
+                                   xt_prefix[par->family], par->match->name,
+                                   par->match->table, par->table);
                return -EINVAL;
        }
        if (par->match->hooks && (par->hook_mask & ~par->match->hooks) != 0) {
                char used[64], allow[64];
 
-               pr_err("%s_tables: %s match: used from hooks %s, but only "
-                      "valid from %s\n",
-                      xt_prefix[par->family], par->match->name,
-                      textify_hooks(used, sizeof(used), par->hook_mask,
-                                    par->family),
-                      textify_hooks(allow, sizeof(allow), par->match->hooks,
-                                    par->family));
+               pr_info_ratelimited("%s_tables: %s match: used from hooks %s, but only valid from %s\n",
+                                   xt_prefix[par->family], par->match->name,
+                                   textify_hooks(used, sizeof(used),
+                                                 par->hook_mask, par->family),
+                                   textify_hooks(allow, sizeof(allow),
+                                                 par->match->hooks,
+                                                 par->family));
                return -EINVAL;
        }
        if (par->match->proto && (par->match->proto != proto || inv_proto)) {
-               pr_err("%s_tables: %s match: only valid for protocol %u\n",
-                      xt_prefix[par->family], par->match->name,
-                      par->match->proto);
+               pr_info_ratelimited("%s_tables: %s match: only valid for protocol %u\n",
+                                   xt_prefix[par->family], par->match->name,
+                                   par->match->proto);
                return -EINVAL;
        }
        if (par->match->checkentry != NULL) {
@@ -519,6 +518,35 @@ static int xt_check_entry_match(const char *match, const char *target,
        return 0;
 }
 
+/** xt_check_table_hooks - check hook entry points are sane
+ *
+ * @info xt_table_info to check
+ * @valid_hooks - hook entry points that we can enter from
+ *
+ * Validates that the hook entry and underflows points are set up.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int xt_check_table_hooks(const struct xt_table_info *info, unsigned int valid_hooks)
+{
+       unsigned int i;
+
+       BUILD_BUG_ON(ARRAY_SIZE(info->hook_entry) != ARRAY_SIZE(info->underflow));
+
+       for (i = 0; i < ARRAY_SIZE(info->hook_entry); i++) {
+               if (!(valid_hooks & (1 << i)))
+                       continue;
+
+               if (info->hook_entry[i] == 0xFFFFFFFF)
+                       return -EINVAL;
+               if (info->underflow[i] == 0xFFFFFFFF)
+                       return -EINVAL;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL(xt_check_table_hooks);
+
 #ifdef CONFIG_COMPAT
 int xt_compat_add_offset(u_int8_t af, unsigned int offset, int delta)
 {
@@ -655,6 +683,42 @@ struct compat_xt_standard_target {
        compat_uint_t verdict;
 };
 
+struct compat_xt_error_target {
+       struct compat_xt_entry_target t;
+       char errorname[XT_FUNCTION_MAXNAMELEN];
+};
+
+static bool verdict_ok(int verdict)
+{
+       if (verdict > 0)
+               return true;
+
+       if (verdict < 0) {
+               int v = -verdict - 1;
+
+               if (verdict == XT_RETURN)
+                       return true;
+
+               switch (v) {
+               case NF_ACCEPT: return true;
+               case NF_DROP: return true;
+               case NF_QUEUE: return true;
+               default:
+                       break;
+               }
+
+               return false;
+       }
+
+       return false;
+}
+
+static bool error_tg_ok(unsigned int usersize, unsigned int kernsize,
+                       const char *msg, unsigned int msglen)
+{
+       return usersize == kernsize && strnlen(msg, msglen) < msglen;
+}
+
 int xt_compat_check_entry_offsets(const void *base, const char *elems,
                                  unsigned int target_offset,
                                  unsigned int next_offset)
@@ -676,9 +740,21 @@ int xt_compat_check_entry_offsets(const void *base, const char *elems,
        if (target_offset + t->u.target_size > next_offset)
                return -EINVAL;
 
-       if (strcmp(t->u.user.name, XT_STANDARD_TARGET) == 0 &&
-           COMPAT_XT_ALIGN(target_offset + sizeof(struct compat_xt_standard_target)) != next_offset)
-               return -EINVAL;
+       if (strcmp(t->u.user.name, XT_STANDARD_TARGET) == 0) {
+               const struct compat_xt_standard_target *st = (const void *)t;
+
+               if (COMPAT_XT_ALIGN(target_offset + sizeof(*st)) != next_offset)
+                       return -EINVAL;
+
+               if (!verdict_ok(st->verdict))
+                       return -EINVAL;
+       } else if (strcmp(t->u.user.name, XT_ERROR_TARGET) == 0) {
+               const struct compat_xt_error_target *et = (const void *)t;
+
+               if (!error_tg_ok(t->u.target_size, sizeof(*et),
+                                et->errorname, sizeof(et->errorname)))
+                       return -EINVAL;
+       }
 
        /* compat_xt_entry match has less strict alignment requirements,
         * otherwise they are identical.  In case of padding differences
@@ -758,9 +834,21 @@ int xt_check_entry_offsets(const void *base,
        if (target_offset + t->u.target_size > next_offset)
                return -EINVAL;
 
-       if (strcmp(t->u.user.name, XT_STANDARD_TARGET) == 0 &&
-           XT_ALIGN(target_offset + sizeof(struct xt_standard_target)) != next_offset)
-               return -EINVAL;
+       if (strcmp(t->u.user.name, XT_STANDARD_TARGET) == 0) {
+               const struct xt_standard_target *st = (const void *)t;
+
+               if (XT_ALIGN(target_offset + sizeof(*st)) != next_offset)
+                       return -EINVAL;
+
+               if (!verdict_ok(st->verdict))
+                       return -EINVAL;
+       } else if (strcmp(t->u.user.name, XT_ERROR_TARGET) == 0) {
+               const struct xt_error_target *et = (const void *)t;
+
+               if (!error_tg_ok(t->u.target_size, sizeof(*et),
+                                et->errorname, sizeof(et->errorname)))
+                       return -EINVAL;
+       }
 
        return xt_check_entry_match(elems, base + target_offset,
                                    __alignof__(struct xt_entry_match));
@@ -814,36 +902,35 @@ int xt_check_target(struct xt_tgchk_param *par,
        int ret;
 
        if (XT_ALIGN(par->target->targetsize) != size) {
-               pr_err("%s_tables: %s.%u target: invalid size "
-                      "%u (kernel) != (user) %u\n",
-                      xt_prefix[par->family], par->target->name,
-                      par->target->revision,
-                      XT_ALIGN(par->target->targetsize), size);
+               pr_err_ratelimited("%s_tables: %s.%u target: invalid size %u (kernel) != (user) %u\n",
+                                  xt_prefix[par->family], par->target->name,
+                                  par->target->revision,
+                                  XT_ALIGN(par->target->targetsize), size);
                return -EINVAL;
        }
        if (par->target->table != NULL &&
            strcmp(par->target->table, par->table) != 0) {
-               pr_err("%s_tables: %s target: only valid in %s table, not %s\n",
-                      xt_prefix[par->family], par->target->name,
-                      par->target->table, par->table);
+               pr_info_ratelimited("%s_tables: %s target: only valid in %s table, not %s\n",
+                                   xt_prefix[par->family], par->target->name,
+                                   par->target->table, par->table);
                return -EINVAL;
        }
        if (par->target->hooks && (par->hook_mask & ~par->target->hooks) != 0) {
                char used[64], allow[64];
 
-               pr_err("%s_tables: %s target: used from hooks %s, but only "
-                      "usable from %s\n",
-                      xt_prefix[par->family], par->target->name,
-                      textify_hooks(used, sizeof(used), par->hook_mask,
-                                    par->family),
-                      textify_hooks(allow, sizeof(allow), par->target->hooks,
-                                    par->family));
+               pr_info_ratelimited("%s_tables: %s target: used from hooks %s, but only usable from %s\n",
+                                   xt_prefix[par->family], par->target->name,
+                                   textify_hooks(used, sizeof(used),
+                                                 par->hook_mask, par->family),
+                                   textify_hooks(allow, sizeof(allow),
+                                                 par->target->hooks,
+                                                 par->family));
                return -EINVAL;
        }
        if (par->target->proto && (par->target->proto != proto || inv_proto)) {
-               pr_err("%s_tables: %s target: only valid for protocol %u\n",
-                      xt_prefix[par->family], par->target->name,
-                      par->target->proto);
+               pr_info_ratelimited("%s_tables: %s target: only valid for protocol %u\n",
+                                   xt_prefix[par->family], par->target->name,
+                                   par->target->proto);
                return -EINVAL;
        }
        if (par->target->checkentry != NULL) {
@@ -1004,11 +1091,12 @@ struct xt_table_info *xt_alloc_table_info(unsigned int size)
        if (sz < sizeof(*info))
                return NULL;
 
-       /* Pedantry: prevent them from hitting BUG() in vmalloc.c --RR */
-       if ((size >> PAGE_SHIFT) + 2 > totalram_pages)
-               return NULL;
-
-       info = kvmalloc(sz, GFP_KERNEL);
+       /* __GFP_NORETRY is not fully supported by kvmalloc but it should
+        * work reasonably well if sz is too large and bail out rather
+        * than shoot all processes down before realizing there is nothing
+        * more to reclaim.
+        */
+       info = kvmalloc(sz, GFP_KERNEL | __GFP_NORETRY);
        if (!info)
                return NULL;
 
@@ -1760,6 +1848,7 @@ static void __net_exit xt_net_exit(struct net *net)
 static struct pernet_operations xt_net_ops = {
        .init = xt_net_init,
        .exit = xt_net_exit,
+       .async = true,
 };
 
 static int __init xt_init(void)