diff --git a/doc/userguide/rules/vlan-id.rst b/doc/userguide/rules/vlan-id.rst new file mode 100644 index 000000000000..c57df78a46ab --- /dev/null +++ b/doc/userguide/rules/vlan-id.rst @@ -0,0 +1,21 @@ +Vlan.id Keyword +============== + +Suricata has a ``vlan.id`` keyword that can be used in signatures to identify +and filter network packets based on Virtual Local Area Network IDs. + +Syntax:: + + vlan.id: id[,layer]; + +Signature examples:: + + alert ip any any -> any any (msg:"Vlan ID is equal to 300"; vlan.id:300; sid:1;) + +:: + + alert ip any any -> any any (msg:"Vlan ID is equal to 300"; vlan.id:300,1; sid:1;) + +:: + + alert ip any any -> any any (msg:"Vlan ID is equal to 200"; vlan.id:200,any; sid:1;) \ No newline at end of file diff --git a/src/Makefile.am b/src/Makefile.am index 7d05751ec254..acb82cbd0973 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -313,6 +313,7 @@ noinst_HEADERS = \ detect-urilen.h \ detect-within.h \ detect-xbits.h \ + detect-vlan-id.h \ device-storage.h \ feature.h \ flow-bit.h \ @@ -871,6 +872,7 @@ libsuricata_c_a_SOURCES = \ detect-urilen.c \ detect-within.c \ detect-xbits.c \ + detect-vlan-id.c \ device-storage.c \ feature.c \ flow-bit.c \ diff --git a/src/detect-engine-register.c b/src/detect-engine-register.c index 37fbc98d8597..f908a569f09d 100644 --- a/src/detect-engine-register.c +++ b/src/detect-engine-register.c @@ -249,6 +249,7 @@ #include "detect-ike-nonce-payload-length.h" #include "detect-ike-nonce-payload.h" #include "detect-ike-key-exchange-payload.h" +#include "detect-vlan-id.h" #include "action-globals.h" #include "tm-threads.h" @@ -675,6 +676,8 @@ void SigTableSetup(void) DetectFileHandlerRegister(); + DetectVlanIdRegister(); + ScDetectSNMPRegister(); ScDetectDHCPRegister(); ScDetectWebsocketRegister(); diff --git a/src/detect-engine-register.h b/src/detect-engine-register.h index c9134c77b83a..59bf3dd1cd0a 100644 --- a/src/detect-engine-register.h +++ b/src/detect-engine-register.h @@ -328,6 +328,8 @@ enum DetectKeywordId { DETECT_AL_JA4_HASH, + DETECT_VLAN_ID, + /* make sure this stays last */ DETECT_TBLSIZE_STATIC, }; diff --git a/src/detect-vlan-id.c b/src/detect-vlan-id.c new file mode 100644 index 000000000000..30338c4bad91 --- /dev/null +++ b/src/detect-vlan-id.c @@ -0,0 +1,274 @@ +/* Copyright (C) 2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "suricata-common.h" +#include "rust.h" +#include "detect-vlan-id.h" +#include "detect-engine.h" +#include "detect-engine-prefilter.h" +#include "detect-engine-uint.h" +#include "detect-parse.h" +#include "util-byte.h" + +#ifdef UNITTESTS +static void DetectVlanIdRegisterTests(void); +#endif + +#define PARSE_REGEX "^([0-9]+)(?:,\\s*([0-9]|any))?$" + +static DetectParseRegex parse_regex; + +static int DetectVlanIdMatch( + DetectEngineThreadCtx *det_ctx, Packet *p, const Signature *s, const SigMatchCtx *ctx) +{ + if (p->vlan_idx == 0) { + return 0; + } + + const DetectVlanIdData *vdata = (const DetectVlanIdData *)ctx; + for (int i = 0; i < p->vlan_idx; i++) { + if (p->vlan_id[i] == vdata->id && (vdata->layer == ANY || vdata->layer - 1 == i)) + return 1; + } + + return 0; +} + +static void DetectVlanIdFree(DetectEngineCtx *de_ctx, void *ptr) +{ + DetectVlanIdData *data = ptr; + SCFree(data); +} + +static DetectVlanIdData *DetectVlanIdParse(DetectEngineCtx *de_ctx, const char *rawstr) +{ + DetectVlanIdData *vdata = NULL; + int res = 0; + size_t pcre2_len; + pcre2_match_data *match = NULL; + + int count = DetectParsePcreExec(&parse_regex, &match, rawstr, 0, 0); + if (count != 2 && count != 3) { + SCLogError("\"%s\" is not a valid setting for vlan-id.", rawstr); + goto error; + } + + const char *str_ptr; + res = SC_Pcre2SubstringGet(match, 1, (PCRE2_UCHAR8 **)&str_ptr, &pcre2_len); + if (res < 0) { + SCLogError("pcre2_substring_get_bynumber failed"); + goto error; + } + + vdata = SCMalloc(sizeof(DetectVlanIdData)); + if (unlikely(vdata == NULL)) + goto error; + + if (StringParseUint16(&vdata->id, 10, 0, str_ptr) < 0) { + SCLogError("specified vlan id %s is not valid", str_ptr); + goto error; + } + vdata->layer = ANY; + + if (count == 3) { + res = SC_Pcre2SubstringGet(match, 2, (PCRE2_UCHAR8 **)&str_ptr, &pcre2_len); + if (res < 0) { + SCLogError("pcre2_substring_get_bynumber failed"); + goto error; + } + + if (strcasecmp(str_ptr, "any") != 0) { + if (StringParseUint8(&vdata->layer, 10, 0, str_ptr) < 0) { + SCLogError("specified vlan layer %s is not valid", str_ptr); + goto error; + } + } + } + + if (vdata->layer > VLAN_MAX_LAYERS) { + SCLogError("specified vlan layer %s is not valid", str_ptr); + goto error; + } + + if (vdata->id == 0 || vdata->id >= 4095) { + SCLogError("specified vlan id %s is not valid. Valid range 1-4094", str_ptr); + goto error; + } + + pcre2_match_data_free(match); + return vdata; + +error: + if (match) { + pcre2_match_data_free(match); + } + if (vdata != NULL) { + DetectVlanIdFree(de_ctx, vdata); + } + return NULL; +} + +static int DetectVlanIdSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rawstr) +{ + DetectVlanIdData *vdata = DetectVlanIdParse(de_ctx, rawstr); + if (vdata == NULL) + return -1; + + if (SigMatchAppendSMToList( + de_ctx, s, DETECT_VLAN_ID, (SigMatchCtx *)vdata, DETECT_SM_LIST_MATCH) == NULL) { + DetectVlanIdFree(de_ctx, vdata); + return -1; + } + s->flags |= SIG_FLAG_REQUIRE_PACKET; + + return 0; +} + +static void PrefilterPacketVlanIdMatch(DetectEngineThreadCtx *det_ctx, Packet *p, const void *pectx) +{ + const PrefilterPacketHeaderCtx *ctx = pectx; + + for (int i = 0; i < p->vlan_idx; i++) { + if (p->vlan_id[i] == ctx->v1.u16[0] && (ctx->v1.u8[0] == ANY || ctx->v1.u8[0] - 1 == i)) + PrefilterAddSids(&det_ctx->pmq, ctx->sigs_array, ctx->sigs_cnt); + } +} + +static void PrefilterPacketVlanIdSet(PrefilterPacketHeaderValue *v, void *smctx) +{ + const DetectVlanIdData *a = smctx; + v->u16[0] = a->id; + v->u8[0] = a->layer; +} + +static bool PrefilterPacketVlanIdCompare(PrefilterPacketHeaderValue v, void *smctx) +{ + const DetectVlanIdData *a = smctx; + if (v.u16[0] == a->id && v.u8[0] == a->layer) + return true; + return false; +} + +static int PrefilterSetupVlanId(DetectEngineCtx *de_ctx, SigGroupHead *sgh) +{ + return PrefilterSetupPacketHeader(de_ctx, sgh, DETECT_VLAN_ID, SIG_MASK_REQUIRE_FLOW, + PrefilterPacketVlanIdSet, PrefilterPacketVlanIdCompare, PrefilterPacketVlanIdMatch); +} + +static bool PrefilterVlanIdIsPrefilterable(const Signature *s) +{ + return PrefilterIsPrefilterableById(s, DETECT_VLAN_ID); +} + +void DetectVlanIdRegister(void) +{ + sigmatch_table[DETECT_VLAN_ID].name = "vlan.id"; + sigmatch_table[DETECT_VLAN_ID].desc = "match vlan id"; + sigmatch_table[DETECT_VLAN_ID].url = "/rules/vlan-id-keyword.html"; + sigmatch_table[DETECT_VLAN_ID].Match = DetectVlanIdMatch; + sigmatch_table[DETECT_VLAN_ID].Setup = DetectVlanIdSetup; + sigmatch_table[DETECT_VLAN_ID].Free = DetectVlanIdFree; +#ifdef UNITTESTS + sigmatch_table[DETECT_VLAN_ID].RegisterTests = DetectVlanIdRegisterTests; +#endif + sigmatch_table[DETECT_VLAN_ID].SupportsPrefilter = PrefilterVlanIdIsPrefilterable; + sigmatch_table[DETECT_VLAN_ID].SetupPrefilter = PrefilterSetupVlanId; + DetectSetupParseRegexes(PARSE_REGEX, &parse_regex); +} + +#ifdef UNITTESTS +#include "detect-engine.h" +#include "detect-engine-mpm.h" + +/** + * \test DetectVlanIdParseTest01 is a test for setting a valid vlan id value + */ +static int DetectVlanIdParseTest01 (void) +{ + DetectVlanIdData *vdata = DetectVlanIdParse(NULL, "300"); + FAIL_IF_NULL(vdata); + FAIL_IF_NOT(vdata->id == 300); + DetectVlanIdFree(NULL, vdata); + PASS; +} + +/** + * \test DetectVlanIdParseTest02 is a test for setting a valid vlan id value and a specific vlan layer + */ +static int DetectVlanIdParseTest02 (void) +{ + DetectVlanIdData *vdata = DetectVlanIdParse(NULL, "200,1"); + FAIL_IF_NULL(vdata); + FAIL_IF_NOT(vdata->id == 200); + FAIL_IF_NOT(vdata->layer == 1); + DetectVlanIdFree(NULL, vdata); + PASS; +} + +/** + * \test DetectVlanIdParseTest03 is a test for setting a valid vlan id value and explicit "any" vlan layer + */ +static int DetectVlanIdParseTest03 (void) +{ + DetectVlanIdData *vdata = DetectVlanIdParse(NULL, "200,any"); + FAIL_IF_NULL(vdata); + FAIL_IF_NOT(vdata->id == 200); + FAIL_IF_NOT(vdata->layer == 0); + DetectVlanIdFree(NULL, vdata); + PASS; +} + +/** + * \test DetectVlanIdParseTest04 is a test for setting a invalid vlan id value + */ +static int DetectVlanIdParseTest04 (void) +{ + DetectVlanIdData *vdata = DetectVlanIdParse(NULL, "200abc"); + FAIL_IF_NOT_NULL(vdata); + PASS; +} + +/** + * \test DetectVlanIdParseTest05 is a test for setting a invalid vlan id value that is out of range + */ +static int DetectVlanIdParseTest05 (void) +{ + DetectVlanIdData *vdata = DetectVlanIdParse(NULL, "4096"); + FAIL_IF_NOT_NULL(vdata); + PASS; +} + +/** + * \test DetectVlanIdParseTest06 is a test for setting a invalid vlan layer + */ +static int DetectVlanIdParseTest06 (void) +{ + DetectVlanIdData *vdata = DetectVlanIdParse(NULL, "600,abc"); + FAIL_IF_NOT_NULL(vdata); + PASS; +} + +static void DetectVlanIdRegisterTests (void) +{ + UtRegisterTest("DetectVlanIdParseTest01", DetectVlanIdParseTest01); + UtRegisterTest("DetectVlanIdParseTest02", DetectVlanIdParseTest02); + UtRegisterTest("DetectVlanIdParseTest03", DetectVlanIdParseTest03); + UtRegisterTest("DetectVlanIdParseTest04", DetectVlanIdParseTest04); + UtRegisterTest("DetectVlanIdParseTest05", DetectVlanIdParseTest05); + UtRegisterTest("DetectVlanIdParseTest06", DetectVlanIdParseTest06); +} +#endif /* UNITTESTS */ \ No newline at end of file diff --git a/src/detect-vlan-id.h b/src/detect-vlan-id.h new file mode 100644 index 000000000000..2ff15b0f4f80 --- /dev/null +++ b/src/detect-vlan-id.h @@ -0,0 +1,30 @@ +/* Copyright (C) 2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#ifndef SURICATA_DETECT_VLAN_ID_H +#define SURICATA_DETECT_VLAN_ID_H + +#define ANY 0 + +typedef struct DetectVlanIdData_ { + uint16_t id; /* id to match */ + uint8_t layer; /* id layer to match */ +} DetectVlanIdData; + +void DetectVlanIdRegister(void); + +#endif /* SURICATA_DETECT_VLAN_ID_H */ \ No newline at end of file