Blame SOURCES/0018-extensions-Add-string-filter-to-ebtables.patch

05e71a
From 746a409113ab837c55b8cfaf819c7905c8f9e295 Mon Sep 17 00:00:00 2001
05e71a
From: Bernie Harris <bernie.harris@alliedtelesis.co.nz>
05e71a
Date: Wed, 21 Mar 2018 15:42:29 +1300
05e71a
Subject: [PATCH] extensions: Add string filter to ebtables
05e71a
05e71a
This patch is part of a proposal to add a string filter to
05e71a
ebtables, which would be similar to the string filter in
05e71a
iptables.
05e71a
05e71a
Like iptables, the ebtables filter uses the xt_string module,
05e71a
however some modifications have been made for this to work
05e71a
correctly.
05e71a
05e71a
Currently ebtables assumes that the revision number of all match
05e71a
modules is 0. The xt_string module doesn't register a match with
05e71a
revision 0 so the solution is to modify ebtables to allow
05e71a
extensions to specify a revision number, similar to iptables.
05e71a
This gets passed down to the kernel, which is then able to find
05e71a
the match module correctly.
05e71a
05e71a
Signed-off-by: Bernie Harris <bernie.harris@alliedtelesis.co.nz>
05e71a
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
05e71a
Signed-off-by: Phil Sutter <psutter@redhat.com>
05e71a
---
05e71a
 ebtables.8              |  20 +++
05e71a
 extensions/Makefile     |   2 +-
05e71a
 extensions/ebt_string.c | 319 ++++++++++++++++++++++++++++++++++++++++
05e71a
 include/ebtables.h      |  16 +-
05e71a
 include/ebtables_u.h    |   1 +
05e71a
 libebtc.c               |   6 +-
05e71a
 6 files changed, 359 insertions(+), 5 deletions(-)
05e71a
 create mode 100644 extensions/ebt_string.c
05e71a
05e71a
diff --git a/ebtables.8 b/ebtables.8
05e71a
index 45a88b2347de6..00c4562d20036 100644
05e71a
--- a/ebtables.8
05e71a
+++ b/ebtables.8
05e71a
@@ -810,6 +810,26 @@ The hello time timer (0-65535) range.
05e71a
 .TP
05e71a
 .BR "--stp-forward-delay " "[!] [\fIdelay\fP][:\fIdelay\fP]"
05e71a
 The forward delay timer (0-65535) range.
05e71a
+.SS string
05e71a
+This module matches on a given string using some pattern matching strategy.
05e71a
+.TP
05e71a
+.BR "--string-algo " "\fIalgorithm\fP"
05e71a
+The pattern matching strategy. (bm = Boyer-Moore, kmp = Knuth-Pratt-Morris)
05e71a
+.TP
05e71a
+.BR "--string-from " "\fIoffset\fP"
05e71a
+The lowest offset from which a match can start. (default: 0)
05e71a
+.TP
05e71a
+.BR "--string-to " "\fIoffset\fP"
05e71a
+The highest offset from which a match can start. (default: size of frame)
05e71a
+.TP
05e71a
+.BR "--string " "[!] \fIpattern\fP"
05e71a
+Matches the given pattern.
05e71a
+.TP
05e71a
+.BR "--string-hex " "[!] \fIpattern\fP"
05e71a
+Matches the given pattern in hex notation, e.g. '|0D 0A|', '|0D0A|', 'www|09|netfilter|03|org|00|'
05e71a
+.TP
05e71a
+.BR "--string-icase"
05e71a
+Ignore case when searching.
05e71a
 .SS vlan
05e71a
 Specify 802.1Q Tag Control Information fields.
05e71a
 The protocol must be specified as
05e71a
diff --git a/extensions/Makefile b/extensions/Makefile
05e71a
index b3548e81eca85..60a70a2298357 100644
05e71a
--- a/extensions/Makefile
05e71a
+++ b/extensions/Makefile
05e71a
@@ -1,7 +1,7 @@
05e71a
 #! /usr/bin/make
05e71a
 
05e71a
 EXT_FUNC+=802_3 nat arp arpreply ip ip6 standard log redirect vlan mark_m mark \
05e71a
-          pkttype stp among limit ulog nflog
05e71a
+          pkttype stp among limit ulog nflog string
05e71a
 EXT_TABLES+=filter nat broute
05e71a
 EXT_OBJS+=$(foreach T,$(EXT_FUNC), extensions/ebt_$(T).o)
05e71a
 EXT_OBJS+=$(foreach T,$(EXT_TABLES), extensions/ebtable_$(T).o)
05e71a
diff --git a/extensions/ebt_string.c b/extensions/ebt_string.c
05e71a
new file mode 100644
05e71a
index 0000000000000..793f5df312f10
05e71a
--- /dev/null
05e71a
+++ b/extensions/ebt_string.c
05e71a
@@ -0,0 +1,319 @@
05e71a
+/* ebt_string
05e71a
+ *
05e71a
+ * Author:
05e71a
+ * Bernie Harris <bernie.harris@alliedtelesis.co.nz>
05e71a
+ *
05e71a
+ * February, 2018
05e71a
+ *
05e71a
+ * Based on:
05e71a
+ *  libxt_string.c, Copyright (C) 2000 Emmanuel Roger  <winfield@freegates.be>
05e71a
+ */
05e71a
+
05e71a
+#include <stdio.h>
05e71a
+#include <stdlib.h>
05e71a
+#include <string.h>
05e71a
+#include <stdint.h>
05e71a
+#include <getopt.h>
05e71a
+#include <netdb.h>
05e71a
+#include <ctype.h>
05e71a
+#include "../include/ebtables_u.h"
05e71a
+#include <linux/if_packet.h>
05e71a
+#include <linux/netfilter/xt_string.h>
05e71a
+
05e71a
+#define STRING_FROM  '1'
05e71a
+#define STRING_TO    '2'
05e71a
+#define STRING_ALGO  '3'
05e71a
+#define STRING_ICASE '4'
05e71a
+#define STRING       '5'
05e71a
+#define STRING_HEX   '6'
05e71a
+#define OPT_STRING_FROM  (1 << 0)
05e71a
+#define OPT_STRING_TO    (1 << 1)
05e71a
+#define OPT_STRING_ALGO  (1 << 2)
05e71a
+#define OPT_STRING_ICASE (1 << 3)
05e71a
+#define OPT_STRING       (1 << 4)
05e71a
+#define OPT_STRING_HEX   (1 << 5)
05e71a
+
05e71a
+static const struct option opts[] =
05e71a
+{
05e71a
+	{ "string-from"             , required_argument, 0, STRING_FROM },
05e71a
+	{ "string-to"               , required_argument, 0, STRING_TO },
05e71a
+	{ "string-algo"             , required_argument, 0, STRING_ALGO },
05e71a
+	{ "string-icase"            , no_argument,       0, STRING_ICASE },
05e71a
+	{ "string"                  , required_argument, 0, STRING },
05e71a
+	{ "string-hex"              , required_argument, 0, STRING_HEX },
05e71a
+	{ 0 }
05e71a
+};
05e71a
+
05e71a
+static void print_help()
05e71a
+{
05e71a
+	printf(
05e71a
+"string options:\n"
05e71a
+"--string-from offset    : Offset to start searching from (default: 0)\n"
05e71a
+"--string-to   offset    : Offset to stop searching (default: packet size)\n"
05e71a
+"--string-algo algorithm : Algorithm (bm = Boyer-Moore, kmp = Knuth-Pratt-Morris)\n"
05e71a
+"--string-icase          : Ignore case when searching\n"
05e71a
+"--string     [!] string : Match a string in a packet\n"
05e71a
+"--string-hex [!] string : Match a hex string in a packet, e.g. |0D 0A|, |0D0A|, netfilter|03|org\n");
05e71a
+}
05e71a
+
05e71a
+static void init(struct ebt_entry_match *match)
05e71a
+{
05e71a
+	struct xt_string_info *info = (struct xt_string_info *)match->data;
05e71a
+
05e71a
+	info->to_offset = UINT16_MAX;
05e71a
+}
05e71a
+
05e71a
+static void parse_string(const char *s, struct xt_string_info *info)
05e71a
+{
05e71a
+	/* xt_string does not need \0 at the end of the pattern */
05e71a
+	if (strlen(s) <= XT_STRING_MAX_PATTERN_SIZE) {
05e71a
+		strncpy(info->pattern, s, XT_STRING_MAX_PATTERN_SIZE);
05e71a
+		info->patlen = strnlen(s, XT_STRING_MAX_PATTERN_SIZE);
05e71a
+		return;
05e71a
+	}
05e71a
+	ebt_print_error2("STRING too long \"%s\"", s);
05e71a
+}
05e71a
+
05e71a
+static void parse_hex_string(const char *s, struct xt_string_info *info)
05e71a
+{
05e71a
+	int i=0, slen, sindex=0, schar;
05e71a
+	short hex_f = 0, literal_f = 0;
05e71a
+	char hextmp[3];
05e71a
+
05e71a
+	slen = strlen(s);
05e71a
+
05e71a
+	if (slen == 0) {
05e71a
+		ebt_print_error2("STRING must contain at least one char");
05e71a
+	}
05e71a
+
05e71a
+	while (i < slen) {
05e71a
+		if (s[i] == '\\' && !hex_f) {
05e71a
+			literal_f = 1;
05e71a
+		} else if (s[i] == '\\') {
05e71a
+			ebt_print_error2("Cannot include literals in hex data");
05e71a
+		} else if (s[i] == '|') {
05e71a
+			if (hex_f)
05e71a
+				hex_f = 0;
05e71a
+			else {
05e71a
+				hex_f = 1;
05e71a
+				/* get past any initial whitespace just after the '|' */
05e71a
+				while (s[i+1] == ' ')
05e71a
+					i++;
05e71a
+			}
05e71a
+			if (i+1 >= slen)
05e71a
+				break;
05e71a
+			else
05e71a
+				i++;  /* advance to the next character */
05e71a
+		}
05e71a
+
05e71a
+		if (literal_f) {
05e71a
+			if (i+1 >= slen) {
05e71a
+				ebt_print_error2("Bad literal placement at end of string");
05e71a
+			}
05e71a
+			info->pattern[sindex] = s[i+1];
05e71a
+			i += 2;  /* skip over literal char */
05e71a
+			literal_f = 0;
05e71a
+		} else if (hex_f) {
05e71a
+			if (i+1 >= slen) {
05e71a
+				ebt_print_error2("Odd number of hex digits");
05e71a
+			}
05e71a
+			if (i+2 >= slen) {
05e71a
+				/* must end with a "|" */
05e71a
+				ebt_print_error2("Invalid hex block");
05e71a
+			}
05e71a
+			if (! isxdigit(s[i])) /* check for valid hex char */
05e71a
+				ebt_print_error2("Invalid hex char '%c'", s[i]);
05e71a
+			if (! isxdigit(s[i+1])) /* check for valid hex char */
05e71a
+				ebt_print_error2("Invalid hex char '%c'", s[i+1]);
05e71a
+			hextmp[0] = s[i];
05e71a
+			hextmp[1] = s[i+1];
05e71a
+			hextmp[2] = '\0';
05e71a
+			if (! sscanf(hextmp, "%x", &schar))
05e71a
+				ebt_print_error2("Invalid hex char `%c'", s[i]);
05e71a
+			info->pattern[sindex] = (char) schar;
05e71a
+			if (s[i+2] == ' ')
05e71a
+				i += 3;  /* spaces included in the hex block */
05e71a
+			else
05e71a
+				i += 2;
05e71a
+		} else {  /* the char is not part of hex data, so just copy */
05e71a
+			info->pattern[sindex] = s[i];
05e71a
+			i++;
05e71a
+		}
05e71a
+		if (sindex > XT_STRING_MAX_PATTERN_SIZE)
05e71a
+			ebt_print_error2("STRING too long \"%s\"", s);
05e71a
+		sindex++;
05e71a
+	}
05e71a
+	info->patlen = sindex;
05e71a
+}
05e71a
+
05e71a
+static int parse(int c, char **argv, int argc, const struct ebt_u_entry *entry,
05e71a
+		 unsigned int *flags, struct ebt_entry_match **match)
05e71a
+{
05e71a
+	struct xt_string_info *info = (struct xt_string_info *)(*match)->data;
05e71a
+	int i;
05e71a
+	int input_string_length = 0;
05e71a
+	char buf[3] = { 0 };
05e71a
+
05e71a
+	switch (c) {
05e71a
+	case STRING_FROM:
05e71a
+		ebt_check_option2(flags, OPT_STRING_FROM);
05e71a
+		if (ebt_check_inverse2(optarg))
05e71a
+			ebt_print_error2("Unexpected `!' after --string-from");
05e71a
+		info->from_offset = (__u16)strtoul(optarg, NULL, 10);
05e71a
+		break;
05e71a
+	case STRING_TO:
05e71a
+		ebt_check_option2(flags, OPT_STRING_TO);
05e71a
+		if (ebt_check_inverse2(optarg))
05e71a
+			ebt_print_error2("Unexpected `!' after --string-to");
05e71a
+		info->to_offset = (__u16)strtoul(optarg, NULL, 10);
05e71a
+		break;
05e71a
+	case STRING_ALGO:
05e71a
+		ebt_check_option2(flags, OPT_STRING_ALGO);
05e71a
+		if (ebt_check_inverse2(optarg))
05e71a
+			ebt_print_error2("Unexpected `!' after --string-algo");
05e71a
+		strncpy(info->algo, optarg, XT_STRING_MAX_ALGO_NAME_SIZE);
05e71a
+		break;
05e71a
+	case STRING_ICASE:
05e71a
+		ebt_check_option2(flags, OPT_STRING_ICASE);
05e71a
+		if (ebt_check_inverse2(optarg))
05e71a
+			ebt_print_error2("Unexpected `!' after --string-icase");
05e71a
+		info->u.v1.flags |= XT_STRING_FLAG_IGNORECASE;
05e71a
+		break;
05e71a
+	case STRING:
05e71a
+		ebt_check_option2(flags, OPT_STRING);
05e71a
+		parse_string(optarg, info);
05e71a
+		if (ebt_check_inverse2(optarg)) {
05e71a
+			info->u.v1.flags |= XT_STRING_FLAG_INVERT;
05e71a
+		}
05e71a
+		break;
05e71a
+	case STRING_HEX:
05e71a
+		ebt_check_option2(flags, OPT_STRING_HEX);
05e71a
+		parse_hex_string(optarg, info);
05e71a
+		if (ebt_check_inverse2(optarg)) {
05e71a
+			info->u.v1.flags |= XT_STRING_FLAG_INVERT;
05e71a
+		}
05e71a
+		break;
05e71a
+	default:
05e71a
+		return 0;
05e71a
+	}
05e71a
+	return 1;
05e71a
+}
05e71a
+
05e71a
+static void final_check(const struct ebt_u_entry *entry,
05e71a
+			const struct ebt_entry_match *match, const char *name,
05e71a
+			unsigned int hookmask, unsigned int time)
05e71a
+{
05e71a
+	struct xt_string_info *info = (struct xt_string_info *)match->data;
05e71a
+
05e71a
+	if (info->to_offset < info->from_offset) {
05e71a
+		ebt_print_error2("'to' offset should not be less than 'from' "
05e71a
+				 "offset");
05e71a
+	}
05e71a
+}
05e71a
+
05e71a
+/* Test to see if the string contains non-printable chars or quotes */
05e71a
+static unsigned short int is_hex_string(const char *str,
05e71a
+					const unsigned short int len)
05e71a
+{
05e71a
+	unsigned int i;
05e71a
+	for (i=0; i < len; i++) {
05e71a
+		if (! isprint(str[i])) {
05e71a
+			/* string contains at least one non-printable char */
05e71a
+			return 1;
05e71a
+		}
05e71a
+	}
05e71a
+	/* use hex output if the last char is a "\" */
05e71a
+	if (str[len-1] == '\\')
05e71a
+		return 1;
05e71a
+	return 0;
05e71a
+}
05e71a
+
05e71a
+/* Print string with "|" chars included as one would pass to --string-hex */
05e71a
+static void print_hex_string(const char *str, const unsigned short int len)
05e71a
+{
05e71a
+	unsigned int i;
05e71a
+	/* start hex block */
05e71a
+	printf("\"|");
05e71a
+	for (i=0; i < len; i++)
05e71a
+		printf("%02x", (unsigned char)str[i]);
05e71a
+	/* close hex block */
05e71a
+	printf("|\" ");
05e71a
+}
05e71a
+
05e71a
+static void print_string(const char *str, const unsigned short int len)
05e71a
+{
05e71a
+	unsigned int i;
05e71a
+	printf("\"");
05e71a
+	for (i=0; i < len; i++) {
05e71a
+		if (str[i] == '\"' || str[i] == '\\')
05e71a
+			putchar('\\');
05e71a
+		printf("%c", (unsigned char) str[i]);
05e71a
+	}
05e71a
+	printf("\" ");  /* closing quote */
05e71a
+}
05e71a
+
05e71a
+static void print(const struct ebt_u_entry *entry,
05e71a
+		  const struct ebt_entry_match *match)
05e71a
+{
05e71a
+	const struct xt_string_info *info =
05e71a
+		(const struct xt_string_info *) match->data;
05e71a
+	int invert = info->u.v1.flags & XT_STRING_FLAG_INVERT;
05e71a
+
05e71a
+	if (is_hex_string(info->pattern, info->patlen)) {
05e71a
+		printf("--string-hex %s", invert ? "! " : "");
05e71a
+		print_hex_string(info->pattern, info->patlen);
05e71a
+	} else {
05e71a
+		printf("--string %s", invert ? "! " : "");
05e71a
+		print_string(info->pattern, info->patlen);
05e71a
+	}
05e71a
+	printf("--string-algo %s ", info->algo);
05e71a
+	if (info->from_offset != 0)
05e71a
+		printf("--string-from %u ", info->from_offset);
05e71a
+	if (info->to_offset != 0)
05e71a
+		printf("--string-to %u ", info->to_offset);
05e71a
+	if (info->u.v1.flags & XT_STRING_FLAG_IGNORECASE)
05e71a
+		printf("--string-icase ");
05e71a
+}
05e71a
+
05e71a
+static int compare(const struct ebt_entry_match *m1,
05e71a
+		   const struct ebt_entry_match *m2)
05e71a
+{
05e71a
+	const struct xt_string_info *info1 =
05e71a
+		(const struct xt_string_info *) m1->data;
05e71a
+	const struct xt_string_info *info2 =
05e71a
+		(const struct xt_string_info *) m2->data;
05e71a
+
05e71a
+	if (info1->from_offset != info2->from_offset)
05e71a
+		return 0;
05e71a
+	if (info1->to_offset != info2->to_offset)
05e71a
+		return 0;
05e71a
+	if (info1->u.v1.flags != info2->u.v1.flags)
05e71a
+		return 0;
05e71a
+	if (info1->patlen != info2->patlen)
05e71a
+		return 0;
05e71a
+	if (strncmp (info1->algo, info2->algo, XT_STRING_MAX_ALGO_NAME_SIZE) != 0)
05e71a
+		return 0;
05e71a
+	if (strncmp (info1->pattern, info2->pattern, info1->patlen) != 0)
05e71a
+		return 0;
05e71a
+
05e71a
+	return 1;
05e71a
+}
05e71a
+
05e71a
+static struct ebt_u_match string_match =
05e71a
+{
05e71a
+	.name		= "string",
05e71a
+	.revision	= 1,
05e71a
+	.size		= sizeof(struct xt_string_info),
05e71a
+	.help		= print_help,
05e71a
+	.init		= init,
05e71a
+	.parse		= parse,
05e71a
+	.final_check	= final_check,
05e71a
+	.print		= print,
05e71a
+	.compare	= compare,
05e71a
+	.extra_ops	= opts,
05e71a
+};
05e71a
+
05e71a
+void _init(void)
05e71a
+{
05e71a
+	ebt_register_match(&string_match);
05e71a
+}
05e71a
diff --git a/include/ebtables.h b/include/ebtables.h
05e71a
index 8f520c600b356..9bbedbb72eea5 100644
05e71a
--- a/include/ebtables.h
05e71a
+++ b/include/ebtables.h
05e71a
@@ -20,6 +20,7 @@
05e71a
 #define EBT_TABLE_MAXNAMELEN 32
05e71a
 #define EBT_CHAIN_MAXNAMELEN EBT_TABLE_MAXNAMELEN
05e71a
 #define EBT_FUNCTION_MAXNAMELEN EBT_TABLE_MAXNAMELEN
05e71a
+#define EBT_EXTENSION_MAXNAMELEN 31
05e71a
 
05e71a
 /* verdicts >0 are "branches" */
05e71a
 #define EBT_ACCEPT   -1
05e71a
@@ -113,7 +114,10 @@ struct ebt_entries {
05e71a
 struct ebt_entry_match
05e71a
 {
05e71a
 	union {
05e71a
-		char name[EBT_FUNCTION_MAXNAMELEN];
05e71a
+		struct {
05e71a
+			char name[EBT_EXTENSION_MAXNAMELEN];
05e71a
+			uint8_t revision;
05e71a
+		};
05e71a
 		struct ebt_match *match;
05e71a
 	} u;
05e71a
 	/* size of data */
05e71a
@@ -127,7 +131,10 @@ struct ebt_entry_match
05e71a
 struct ebt_entry_watcher
05e71a
 {
05e71a
 	union {
05e71a
-		char name[EBT_FUNCTION_MAXNAMELEN];
05e71a
+		struct {
05e71a
+			char name[EBT_EXTENSION_MAXNAMELEN];
05e71a
+			uint8_t revision;
05e71a
+		};
05e71a
 		struct ebt_watcher *watcher;
05e71a
 	} u;
05e71a
 	/* size of data */
05e71a
@@ -141,7 +148,10 @@ struct ebt_entry_watcher
05e71a
 struct ebt_entry_target
05e71a
 {
05e71a
 	union {
05e71a
-		char name[EBT_FUNCTION_MAXNAMELEN];
05e71a
+		struct {
05e71a
+			char name[EBT_EXTENSION_MAXNAMELEN];
05e71a
+			uint8_t revision;
05e71a
+		};
05e71a
 		struct ebt_target *target;
05e71a
 	} u;
05e71a
 	/* size of data */
05e71a
diff --git a/include/ebtables_u.h b/include/ebtables_u.h
05e71a
index 17afa9487f5ad..c8589969bd8e0 100644
05e71a
--- a/include/ebtables_u.h
05e71a
+++ b/include/ebtables_u.h
05e71a
@@ -144,6 +144,7 @@ struct ebt_u_entry
05e71a
 struct ebt_u_match
05e71a
 {
05e71a
 	char name[EBT_FUNCTION_MAXNAMELEN];
05e71a
+	uint8_t revision;
05e71a
 	/* size of the real match data */
05e71a
 	unsigned int size;
05e71a
 	void (*help)(void);
05e71a
diff --git a/libebtc.c b/libebtc.c
05e71a
index d47424872dc51..92fd76485c723 100644
05e71a
--- a/libebtc.c
05e71a
+++ b/libebtc.c
05e71a
@@ -272,6 +272,7 @@ void ebt_reinit_extensions()
05e71a
 			if (!m->m)
05e71a
 				ebt_print_memory();
05e71a
 			strcpy(m->m->u.name, m->name);
05e71a
+			m->m->u.revision = m->revision;
05e71a
 			m->m->match_size = EBT_ALIGN(m->size);
05e71a
 			m->used = 0;
05e71a
 		}
05e71a
@@ -550,8 +551,10 @@ int ebt_check_rule_exists(struct ebt_u_replace *replace,
05e71a
 		while (m_l) {
05e71a
 			m = (struct ebt_u_match *)(m_l->m);
05e71a
 			m_l2 = u_e->m_list;
05e71a
-			while (m_l2 && strcmp(m_l2->m->u.name, m->m->u.name))
05e71a
+			while (m_l2 && (strcmp(m_l2->m->u.name, m->m->u.name) ||
05e71a
+			       m_l2->m->u.revision != m->m->u.revision)) {
05e71a
 				m_l2 = m_l2->next;
05e71a
+			}
05e71a
 			if (!m_l2 || !m->compare(m->m, m_l2->m))
05e71a
 				goto letscontinue;
05e71a
 			j++;
05e71a
@@ -1209,6 +1212,7 @@ void ebt_register_match(struct ebt_u_match *m)
05e71a
 	if (!m->m)
05e71a
 		ebt_print_memory();
05e71a
 	strcpy(m->m->u.name, m->name);
05e71a
+	m->m->u.revision = m->revision;
05e71a
 	m->m->match_size = EBT_ALIGN(m->size);
05e71a
 	m->init(m->m);
05e71a
 
05e71a
-- 
05e71a
2.21.0
05e71a