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