From 746a409113ab837c55b8cfaf819c7905c8f9e295 Mon Sep 17 00:00:00 2001 From: Bernie Harris 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 Signed-off-by: Pablo Neira Ayuso Signed-off-by: Phil Sutter --- 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 + * + * February, 2018 + * + * Based on: + * libxt_string.c, Copyright (C) 2000 Emmanuel Roger + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../include/ebtables_u.h" +#include +#include + +#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