From: Philippe Antoine Date: Fri, 4 Oct 2024 12:53:02 +0000 (+0200) Subject: detect: clean support for multi-protocol keywords X-Git-Tag: suricata-8.0.0-beta1~215 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f97767043f99bd6100ac0209a3bcf915e282271f;p=thirdparty%2Fsuricata.git detect: clean support for multi-protocol keywords such as ja4. Why ? We do not want to see hard-coded protocol constants such as ALPROTO_QUIC directly used in generic code in detect-parse.c How ? From the keyword point of view, this commit adds the function DetectSignatureSetMultiAppProto which is similar to DetectSignatureSetAppProto but takes multiple alprotos. It restricts the signature alprotos to a set of possible alprotos and errors out if the interstion gets empty. The data structure SignatureInitData gets extended with a fixed-length array, as the use case is a sparse number of protocols Ticket: 7304 --- diff --git a/src/detect-parse.c b/src/detect-parse.c index 2675b4369f..c67f342aaf 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -1779,6 +1779,94 @@ int DetectSignatureAddTransform(Signature *s, int transform, void *options) SCReturnInt(0); } +/** + * \brief this function is used to set multiple possible app-layer protos + * \brief into the current signature (for example ja4 for both tls and quic) + * + * \param s pointer to the Current Signature + * \param alprotos an array terminated by ALPROTO_UNKNOWN + * + * \retval 0 on Success + * \retval -1 on Failure + */ +int DetectSignatureSetMultiAppProto(Signature *s, const AppProto *alprotos) +{ + if (s->alproto != ALPROTO_UNKNOWN) { + // One alproto was set, check if it matches the new ones proposed + while (*alprotos != ALPROTO_UNKNOWN) { + if (s->alproto == *alprotos) { + // alproto already set to only one + return 0; + } + alprotos++; + } + // alproto already set and not matching the new set of alprotos + return -1; + } + if (s->init_data->alprotos[0] != ALPROTO_UNKNOWN) { + // check intersection of already used alprotos and new ones + for (AppProto i = 0; i < SIG_ALPROTO_MAX; i++) { + if (s->init_data->alprotos[i] == ALPROTO_UNKNOWN) { + break; + } + // first disable the ones that do not match + bool found = false; + const AppProto *args = alprotos; + while (*args != ALPROTO_UNKNOWN) { + if (s->init_data->alprotos[i] == *args) { + found = true; + break; + } + args++; + } + if (!found) { + s->init_data->alprotos[i] = ALPROTO_UNKNOWN; + } + } + // Then put at the beginning every defined protocol + for (AppProto i = 0; i < SIG_ALPROTO_MAX; i++) { + if (s->init_data->alprotos[i] == ALPROTO_UNKNOWN) { + for (AppProto j = SIG_ALPROTO_MAX - 1; j > i; j--) { + if (s->init_data->alprotos[j] != ALPROTO_UNKNOWN) { + s->init_data->alprotos[i] = s->init_data->alprotos[j]; + s->init_data->alprotos[j] = ALPROTO_UNKNOWN; + break; + } + } + if (s->init_data->alprotos[i] == ALPROTO_UNKNOWN) { + if (i == 0) { + // there was no intersection + return -1; + } else if (i == 1) { + // intersection is singleton, set it as usual + AppProto alproto = s->init_data->alprotos[0]; + s->init_data->alprotos[0] = ALPROTO_UNKNOWN; + return DetectSignatureSetAppProto(s, alproto); + } + break; + } + } + } + } else { + if (alprotos[0] == ALPROTO_UNKNOWN) { + // do not allow empty set + return -1; + } + if (alprotos[1] == ALPROTO_UNKNOWN) { + // allow singleton, but call traditional setter + return DetectSignatureSetAppProto(s, alprotos[0]); + } + // first time we enforce alprotos + for (AppProto i = 0; i < SIG_ALPROTO_MAX; i++) { + if (alprotos[i] == ALPROTO_UNKNOWN) { + break; + } + s->init_data->alprotos[i] = alprotos[i]; + } + } + return 0; +} + int DetectSignatureSetAppProto(Signature *s, AppProto alproto) { if (!AppProtoIsValid(alproto)) { @@ -1786,6 +1874,24 @@ int DetectSignatureSetAppProto(Signature *s, AppProto alproto) return -1; } + if (s->init_data->alprotos[0] != ALPROTO_UNKNOWN) { + // Multiple alprotos were set, check if we restrict to one + bool found = false; + for (AppProto i = 0; i < SIG_ALPROTO_MAX; i++) { + if (s->init_data->alprotos[i] == alproto) { + found = true; + break; + } + } + if (!found) { + // fail if we set to a alproto which was not in the set + return -1; + } + // we will use s->alproto if there is a single alproto and + // we reset s->init_data->alprotos to signal there are no longer multiple alprotos + s->init_data->alprotos[0] = ALPROTO_UNKNOWN; + } + if (s->alproto != ALPROTO_UNKNOWN) { alproto = AppProtoCommon(s->alproto, alproto); if (alproto == ALPROTO_FAILED) { @@ -4554,6 +4660,81 @@ static int SigParseTestActionDrop(void) PASS; } +static int SigSetMultiAppProto(void) +{ + Signature *s = SigAlloc(); + FAIL_IF_NULL(s); + + AppProto alprotos[] = { 1, 2, 3, ALPROTO_UNKNOWN }; + FAIL_IF(DetectSignatureSetMultiAppProto(s, alprotos) < 0); + + // check intersection gives multiple entries + alprotos[0] = 3; + alprotos[1] = 2; + alprotos[2] = ALPROTO_UNKNOWN; + FAIL_IF(DetectSignatureSetMultiAppProto(s, alprotos) < 0); + FAIL_IF(s->init_data->alprotos[0] != 3); + FAIL_IF(s->init_data->alprotos[1] != 2); + FAIL_IF(s->init_data->alprotos[2] != ALPROTO_UNKNOWN); + + // check single after multiple + FAIL_IF(DetectSignatureSetAppProto(s, 3) < 0); + FAIL_IF(s->init_data->alprotos[0] != ALPROTO_UNKNOWN); + FAIL_IF(s->alproto != 3); + alprotos[0] = 4; + alprotos[1] = 3; + alprotos[2] = ALPROTO_UNKNOWN; + // check multiple containing singleton + FAIL_IF(DetectSignatureSetMultiAppProto(s, alprotos) < 0); + FAIL_IF(s->alproto != 3); + + // reset + s->alproto = ALPROTO_UNKNOWN; + alprotos[0] = 1; + alprotos[1] = 2; + alprotos[2] = 3; + alprotos[3] = ALPROTO_UNKNOWN; + FAIL_IF(DetectSignatureSetMultiAppProto(s, alprotos) < 0); + // fail if set single not in multiple + FAIL_IF(DetectSignatureSetAppProto(s, 4) >= 0); + + s->init_data->alprotos[0] = ALPROTO_UNKNOWN; + s->alproto = ALPROTO_UNKNOWN; + alprotos[0] = 1; + alprotos[1] = 2; + alprotos[2] = 3; + alprotos[3] = ALPROTO_UNKNOWN; + FAIL_IF(DetectSignatureSetMultiAppProto(s, alprotos) < 0); + alprotos[0] = 4; + alprotos[1] = 5; + alprotos[2] = ALPROTO_UNKNOWN; + // fail if multiple do not have intersection + FAIL_IF(DetectSignatureSetMultiAppProto(s, alprotos) >= 0); + + s->init_data->alprotos[0] = ALPROTO_UNKNOWN; + s->alproto = ALPROTO_UNKNOWN; + alprotos[0] = 1; + alprotos[1] = 2; + alprotos[2] = 3; + alprotos[3] = ALPROTO_UNKNOWN; + FAIL_IF(DetectSignatureSetMultiAppProto(s, alprotos) < 0); + alprotos[0] = 3; + alprotos[1] = 4; + alprotos[2] = 5; + alprotos[3] = ALPROTO_UNKNOWN; + // check multiple intersect to singleton + FAIL_IF(DetectSignatureSetMultiAppProto(s, alprotos) < 0); + FAIL_IF(s->alproto != 3); + alprotos[0] = 5; + alprotos[1] = 4; + alprotos[2] = ALPROTO_UNKNOWN; + // fail if multiple do not belong to singleton + FAIL_IF(DetectSignatureSetMultiAppProto(s, alprotos) >= 0); + + SigFree(NULL, s); + PASS; +} + #endif /* UNITTESTS */ #ifdef UNITTESTS @@ -4629,5 +4810,8 @@ void SigParseRegisterTests(void) SigParseBidirWithSameSrcAndDest02); UtRegisterTest("SigParseTestActionReject", SigParseTestActionReject); UtRegisterTest("SigParseTestActionDrop", SigParseTestActionDrop); + + UtRegisterTest("SigSetMultiAppProto", SigSetMultiAppProto); + #endif /* UNITTESTS */ } diff --git a/src/detect-parse.h b/src/detect-parse.h index ec2c204c0f..c6b69a1bb2 100644 --- a/src/detect-parse.h +++ b/src/detect-parse.h @@ -102,6 +102,7 @@ SigMatch *DetectGetLastSMByListId(const Signature *s, int list_id, ...); int DetectSignatureAddTransform(Signature *s, int transform, void *options); int WARN_UNUSED DetectSignatureSetAppProto(Signature *s, AppProto alproto); +int WARN_UNUSED DetectSignatureSetMultiAppProto(Signature *s, const AppProto *alprotos); /* parse regex setup and free util funcs */ diff --git a/src/detect.h b/src/detect.h index 90507ff031..89838b9368 100644 --- a/src/detect.h +++ b/src/detect.h @@ -540,6 +540,8 @@ typedef struct SignatureInitDataBuffer_ { SigMatch *tail; } SignatureInitDataBuffer; +#define SIG_ALPROTO_MAX 4 + typedef struct SignatureInitData_ { /** Number of sigmatches. Used for assigning SigMatch::idx */ uint16_t sm_cnt; @@ -560,6 +562,9 @@ typedef struct SignatureInitData_ { uint32_t init_flags; /* coccinelle: SignatureInitData:init_flags:SIG_FLAG_INIT_ */ + /* alproto mask if multiple protocols are possible */ + AppProto alprotos[SIG_ALPROTO_MAX]; + /* used at init to determine max dsize */ SigMatch *dsize_sm;