netfilter: x_tables: validate all offsets and sizes in a rule
authorFlorian Westphal <fw@strlen.de>
Fri, 1 Apr 2016 12:17:29 +0000 (14:17 +0200)
committerWilly Tarreau <w@1wt.eu>
Sun, 21 Aug 2016 21:22:29 +0000 (23:22 +0200)
commit 13631bfc604161a9d69cd68991dff8603edd66f9 upstream.

Validate that all matches (if any) add up to the beginning of
the target and that each match covers at least the base structure size.

The compat path should be able to safely re-use the function
as the structures only differ in alignment; added a
BUILD_BUG_ON just in case we have an arch that adds padding as well.

Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: Willy Tarreau <w@1wt.eu>
net/netfilter/x_tables.c

index ea147468df9c25f36700fe3ff134b3ebc41f30bb..c4826337866b56e421a0486a217c12d7fae05286 100644 (file)
@@ -435,6 +435,47 @@ int xt_check_match(struct xt_mtchk_param *par,
 }
 EXPORT_SYMBOL_GPL(xt_check_match);
 
+/** xt_check_entry_match - check that matches end before start of target
+ *
+ * @match: beginning of xt_entry_match
+ * @target: beginning of this rules target (alleged end of matches)
+ * @alignment: alignment requirement of match structures
+ *
+ * Validates that all matches add up to the beginning of the target,
+ * and that each match covers at least the base structure size.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+static int xt_check_entry_match(const char *match, const char *target,
+                               const size_t alignment)
+{
+       const struct xt_entry_match *pos;
+       int length = target - match;
+
+       if (length == 0) /* no matches */
+               return 0;
+
+       pos = (struct xt_entry_match *)match;
+       do {
+               if ((unsigned long)pos % alignment)
+                       return -EINVAL;
+
+               if (length < (int)sizeof(struct xt_entry_match))
+                       return -EINVAL;
+
+               if (pos->u.match_size < sizeof(struct xt_entry_match))
+                       return -EINVAL;
+
+               if (pos->u.match_size > length)
+                       return -EINVAL;
+
+               length -= pos->u.match_size;
+               pos = ((void *)((char *)(pos) + (pos)->u.match_size));
+       } while (length > 0);
+
+       return 0;
+}
+
 #ifdef CONFIG_COMPAT
 int xt_compat_add_offset(u_int8_t af, unsigned int offset, int delta)
 {
@@ -590,7 +631,14 @@ int xt_compat_check_entry_offsets(const void *base, const char *elems,
            target_offset + sizeof(struct compat_xt_standard_target) != next_offset)
                return -EINVAL;
 
-       return 0;
+       /* compat_xt_entry match has less strict aligment requirements,
+        * otherwise they are identical.  In case of padding differences
+        * we need to add compat version of xt_check_entry_match.
+        */
+       BUILD_BUG_ON(sizeof(struct compat_xt_entry_match) != sizeof(struct xt_entry_match));
+
+       return xt_check_entry_match(elems, base + target_offset,
+                                   __alignof__(struct compat_xt_entry_match));
 }
 EXPORT_SYMBOL(xt_compat_check_entry_offsets);
 #endif /* CONFIG_COMPAT */
@@ -603,17 +651,39 @@ EXPORT_SYMBOL(xt_compat_check_entry_offsets);
  * @target_offset: the arp/ip/ip6_t->target_offset
  * @next_offset: the arp/ip/ip6_t->next_offset
  *
- * validates that target_offset and next_offset are sane.
- * Also see xt_compat_check_entry_offsets for CONFIG_COMPAT version.
+ * validates that target_offset and next_offset are sane and that all
+ * match sizes (if any) align with the target offset.
  *
  * This function does not validate the targets or matches themselves, it
- * only tests that all the offsets and sizes are correct.
+ * only tests that all the offsets and sizes are correct, that all
+ * match structures are aligned, and that the last structure ends where
+ * the target structure begins.
+ *
+ * Also see xt_compat_check_entry_offsets for CONFIG_COMPAT version.
  *
  * The arp/ip/ip6t_entry structure @base must have passed following tests:
  * - it must point to a valid memory location
  * - base to base + next_offset must be accessible, i.e. not exceed allocated
  *   length.
  *
+ * A well-formed entry looks like this:
+ *
+ * ip(6)t_entry   match [mtdata]  match [mtdata] target [tgdata] ip(6)t_entry
+ * e->elems[]-----'                              |               |
+ *                matchsize                      |               |
+ *                                matchsize      |               |
+ *                                               |               |
+ * target_offset---------------------------------'               |
+ * next_offset---------------------------------------------------'
+ *
+ * elems[]: flexible array member at end of ip(6)/arpt_entry struct.
+ *          This is where matches (if any) and the target reside.
+ * target_offset: beginning of target.
+ * next_offset: start of the next rule; also: size of this rule.
+ * Since targets have a minimum size, target_offset + minlen <= next_offset.
+ *
+ * Every match stores its size, sum of sizes must not exceed target_offset.
+ *
  * Return: 0 on success, negative errno on failure.
  */
 int xt_check_entry_offsets(const void *base,
@@ -643,7 +713,8 @@ int xt_check_entry_offsets(const void *base,
            target_offset + sizeof(struct xt_standard_target) != next_offset)
                return -EINVAL;
 
-       return 0;
+       return xt_check_entry_match(elems, base + target_offset,
+                                   __alignof__(struct xt_entry_match));
 }
 EXPORT_SYMBOL(xt_check_entry_offsets);