Skip to content

Commit

Permalink
detect: clean support for multi-protocol keywords
Browse files Browse the repository at this point in the history
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
  • Loading branch information
catenacyber committed Dec 19, 2024
1 parent 2c0d3b8 commit c85c7a8
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 0 deletions.
172 changes: 172 additions & 0 deletions src/detect-parse.c
Original file line number Diff line number Diff line change
Expand Up @@ -1735,6 +1735,85 @@ int DetectSignatureAddTransform(Signature *s, int transform, void *options)
SCReturnInt(0);
}

// alprotos parameter is expected as an array terminated by ALPROTO_UNKNOWN
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 (size_t 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 (size_t i = 0; i < SIG_ALPROTO_MAX; i++) {
if (s->init_data->alprotos[i] == ALPROTO_UNKNOWN) {
for (size_t 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 (size_t 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 (alproto == ALPROTO_UNKNOWN ||
Expand All @@ -1743,6 +1822,21 @@ int DetectSignatureSetAppProto(Signature *s, AppProto alproto)
return -1;
}

if (s->init_data->alprotos[0] != ALPROTO_UNKNOWN) {
// Multiple protocols were set, check if we restrict to one
bool found = false;
for (size_t i = 0; i < SIG_ALPROTO_MAX; i++) {
if (s->init_data->alprotos[i] == alproto) {
found = true;
break;
}
}
if (!found) {
return -1;
}
s->init_data->alprotos[0] = ALPROTO_UNKNOWN;
}

if (s->alproto != ALPROTO_UNKNOWN) {
alproto = AppProtoCommon(s->alproto, alproto);
if (alproto == ALPROTO_FAILED) {
Expand Down Expand Up @@ -4439,6 +4533,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
Expand Down Expand Up @@ -4514,5 +4683,8 @@ void SigParseRegisterTests(void)
SigParseBidirWithSameSrcAndDest02);
UtRegisterTest("SigParseTestActionReject", SigParseTestActionReject);
UtRegisterTest("SigParseTestActionDrop", SigParseTestActionDrop);

UtRegisterTest("SigSetMultiAppProto", SigSetMultiAppProto);

#endif /* UNITTESTS */
}
1 change: 1 addition & 0 deletions src/detect-parse.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */

Expand Down
5 changes: 5 additions & 0 deletions src/detect.h
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,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;
Expand All @@ -555,6 +557,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;

Expand Down

0 comments on commit c85c7a8

Please sign in to comment.