Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

detect: allow rule which need both directions to match #11561

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 50 additions & 3 deletions doc/userguide/rules/intro.rst
Original file line number Diff line number Diff line change
@@ -228,11 +228,14 @@ Direction

The directional arrow indicates which way the signature will be evaluated.
In most signatures an arrow to the right (``->``) is used. This means that only
packets with the same direction can match. However, it is also possible to
have a rule match both directions (``<>``)::
packets with the same direction can match.
There is also the double arrow (``=>``), which respects the directionality as ``->``,
but allows matching on bidirectional transactions, used with keywords matching each direction.
Finally, it is also possible to have a rule match either directions (``<>``)::

source -> destination
source <> destination (both directions)
source => destination
source <> destination (either directions)

The following example illustrates direction. In this example there is a client
with IP address 1.2.3.4 using port 1024. A server with IP address 5.6.7.8,
@@ -248,10 +251,54 @@ Now, let's say we have a rule with the following header::
Only the traffic from the client to the server will be matched by this rule,
as the direction specifies that we do not want to evaluate the response packet.

Now,if we have a rule with the following header::

alert tcp 1.2.3.4 any <> 5.6.7.8 80

Suricata will duplicate it and use the same rule with headers in both directions :

alert tcp 1.2.3.4 any -> 5.6.7.8 80
alert tcp 5.6.7.8 80 -> 1.2.3.4 any

.. warning::

There is no 'reverse' style direction, i.e. there is no ``<-``.

Bidirectional rules
~~~~~~~~~~~~~~~~~~~

Here is an example of a bidirectional rule:

.. container:: example-rule

alert http any any :example-rule-emphasis:`=>` 5.6.7.8 80 (msg:"matching both uri and status"; sid: 1; http.uri; content: "/download"; http.stat_code; content: "200";)

It will match on flows to 5.6.7.8 and port 80.
And it will match on a full transaction, using both the uri from the request,
and the stat_code from the response.
As such, it will match only when Suricata got both request and response.

Bidirectional rules can use direction-ambiguous keywords, by first using
``bidir.toclient`` or ``bidir.toserver`` keywords.

.. container:: example-rule

alert http any any => 5.6.7.8 80 (msg:"matching json to server and xml to client"; sid: 1; :example-rule-emphasis:`bidir.toserver;` http.content_type; content: "json"; :example-rule-emphasis:`bidir.toclient;` http.content_type; content: "xml";)

Bidirectional rules have some limitations :

* They are only meant to work on transactions with first a request to the server,
and then a response to the client, and not the other way around (not tested).
* They cannot have ``fast_pattern`` or ``prefilter`` the direction to client
if they also have a streaming buffer on the direction to server, see example below.
* They will refuse to load if a single directional rule is enough.

This rule cannot have the ``fast_pattern`` to client, as ``file.data`` is a streaming buffer.

.. container:: example-rule

alert http any any => any any (bidir.toserver; file.data; content: "123"; http.stat_code; content: "500"; fast_patten;)

Rule options
------------
The rest of the rule consists of options. These are enclosed by parenthesis
2 changes: 2 additions & 0 deletions src/app-layer-htp.c
Original file line number Diff line number Diff line change
@@ -1167,6 +1167,8 @@ static void FlagDetectStateNewFile(HtpTxUserData *tx, int dir)
SCLogDebug("DETECT_ENGINE_STATE_FLAG_FILE_NEW set");
tx->tx_data.de_state->dir_state[1].flags |= DETECT_ENGINE_STATE_FLAG_FILE_NEW;
}
tx->tx_data.de_state->dir_state[DETECT_ENGINE_STATE_DIRECTION_BOTHDIR].flags |=
DETECT_ENGINE_STATE_FLAG_FILE_NEW;
}
}

2 changes: 2 additions & 0 deletions src/app-layer-smtp.c
Original file line number Diff line number Diff line change
@@ -495,6 +495,8 @@ static void FlagDetectStateNewFile(SMTPTransaction *tx)
if (tx && tx->tx_data.de_state) {
SCLogDebug("DETECT_ENGINE_STATE_FLAG_FILE_NEW set");
tx->tx_data.de_state->dir_state[0].flags |= DETECT_ENGINE_STATE_FLAG_FILE_NEW;
tx->tx_data.de_state->dir_state[DETECT_ENGINE_STATE_DIRECTION_BOTHDIR].flags |=
DETECT_ENGINE_STATE_FLAG_FILE_NEW;
} else if (tx == NULL) {
SCLogDebug("DETECT_ENGINE_STATE_FLAG_FILE_NEW NOT set, no TX");
} else if (tx->tx_data.de_state == NULL) {
24 changes: 24 additions & 0 deletions src/detect-engine-mpm.c
Original file line number Diff line number Diff line change
@@ -1071,6 +1071,24 @@ static SigMatch *GetMpmForList(const Signature *s, SigMatch *list, SigMatch *mpm

int g_skip_prefilter = 0;

bool DetectBufferToClient(const DetectEngineCtx *de_ctx, int buf_id, AppProto alproto)
{
bool r = false;
const DetectEngineAppInspectionEngine *app = de_ctx->app_inspect_engines;
for (; app != NULL; app = app->next) {
if (app->sm_list == buf_id && (AppProtoEquals(alproto, app->alproto) || alproto == 0)) {
if (app->dir == 1) {
// do not return yet in case we have app engines on both sides
r = true;
} else {
// ambiguous keywords have a app-engine to server
return false;
}
}
}
return r;
}

void RetrieveFPForSig(const DetectEngineCtx *de_ctx, Signature *s)
{
if (g_skip_prefilter)
@@ -1173,6 +1191,12 @@ void RetrieveFPForSig(const DetectEngineCtx *de_ctx, Signature *s)
tmp != NULL && priority == tmp->priority;
tmp = tmp->next)
{
if (s->flags & SIG_FLAG_BOTHDIR) {
// prefer to choose a fast_pattern to server by default
if (DetectBufferToClient(de_ctx, tmp->list_id, s->alproto)) {
continue;
}
}
SCLogDebug("tmp->list_id %d tmp->priority %d", tmp->list_id, tmp->priority);
if (tmp->list_id >= nlists)
continue;
2 changes: 2 additions & 0 deletions src/detect-engine-mpm.h
Original file line number Diff line number Diff line change
@@ -136,4 +136,6 @@ struct MpmListIdDataArgs {

void EngineAnalysisAddAllRulePatterns(DetectEngineCtx *de_ctx, const Signature *s);

bool DetectBufferToClient(const DetectEngineCtx *de_ctx, int buf_id, AppProto alproto);

#endif /* SURICATA_DETECT_ENGINE_MPM_H */
2 changes: 2 additions & 0 deletions src/detect-engine-prefilter.h
Original file line number Diff line number Diff line change
@@ -33,6 +33,8 @@ typedef struct DetectTransaction_ {
const uint64_t tx_id;
struct AppLayerTxData *tx_data_ptr;
DetectEngineStateDirection *de_state;
// state for bidirectional signatures
DetectEngineStateDirection *de_state_bidir;
const uint64_t detect_flags; /* detect flags get/set from/to applayer */
uint64_t prefilter_flags; /* prefilter flags for direction, to be updated by prefilter code */
const uint64_t
1 change: 1 addition & 0 deletions src/detect-engine-register.c
Original file line number Diff line number Diff line change
@@ -576,6 +576,7 @@ void SigTableSetup(void)
DetectOffsetRegister();
DetectReplaceRegister();
DetectFlowRegister();
DetectBidirRegister();
DetectFlowAgeRegister();
DetectFlowPktsToClientRegister();
DetectFlowPktsToServerRegister();
3 changes: 3 additions & 0 deletions src/detect-engine-register.h
Original file line number Diff line number Diff line change
@@ -305,6 +305,9 @@ enum DetectKeywordId {

DETECT_PREFILTER,

DETECT_BIDIR_TOCLIENT,
DETECT_BIDIR_TOSERVER,

DETECT_TRANSFORM_COMPRESS_WHITESPACE,
DETECT_TRANSFORM_STRIP_WHITESPACE,
DETECT_TRANSFORM_STRIP_PSEUDO_HEADERS,
34 changes: 17 additions & 17 deletions src/detect-engine-state.c
Original file line number Diff line number Diff line change
@@ -89,9 +89,9 @@ static DeStateStore *DeStateStoreAlloc(void)
}

#ifdef DEBUG_VALIDATION
static int DeStateSearchState(DetectEngineState *state, uint8_t direction, SigIntId num)
static int DeStateSearchState(DetectEngineState *state, uint8_t dsi, SigIntId num)
{
DetectEngineStateDirection *dir_state = &state->dir_state[direction & STREAM_TOSERVER ? 0 : 1];
DetectEngineStateDirection *dir_state = &state->dir_state[dsi];
DeStateStore *tx_store = dir_state->head;
SigIntId store_cnt;
SigIntId state_cnt = 0;
@@ -120,11 +120,15 @@ static void DeStateSignatureAppend(DetectEngineState *state,
{
SCEnter();

DetectEngineStateDirection *dir_state =
&state->dir_state[(direction & STREAM_TOSERVER) ? 0 : 1];
uint8_t dsi = (direction & STREAM_TOSERVER) ? 0 : 1;
if (s->flags & SIG_FLAG_BOTHDIR) {
dsi = DETECT_ENGINE_STATE_DIRECTION_BOTHDIR;
}

DetectEngineStateDirection *dir_state = &state->dir_state[dsi];

#ifdef DEBUG_VALIDATION
BUG_ON(DeStateSearchState(state, direction, s->num));
BUG_ON(DeStateSearchState(state, dsi, s->num));
#endif
DeStateStore *store = dir_state->tail;
if (store == NULL) {
@@ -172,7 +176,7 @@ void DetectEngineStateFree(DetectEngineState *state)
DeStateStore *store_next;
int i = 0;

for (i = 0; i < 2; i++) {
for (i = 0; i < DETECT_ENGINE_STATE_DIRECTIONS; i++) {
store = state->dir_state[i].head;
while (store != NULL) {
store_next = store->next;
@@ -239,17 +243,13 @@ void DetectRunStoreStateTx(
static inline void ResetTxState(DetectEngineState *s)
{
if (s) {
s->dir_state[0].cnt = 0;
s->dir_state[0].filestore_cnt = 0;
s->dir_state[0].flags = 0;
/* reset 'cur' back to the list head */
s->dir_state[0].cur = s->dir_state[0].head;

s->dir_state[1].cnt = 0;
s->dir_state[1].filestore_cnt = 0;
s->dir_state[1].flags = 0;
/* reset 'cur' back to the list head */
s->dir_state[1].cur = s->dir_state[1].head;
for (int i = 0; i < DETECT_ENGINE_STATE_DIRECTIONS; i++) {
s->dir_state[i].cnt = 0;
s->dir_state[i].filestore_cnt = 0;
s->dir_state[i].flags = 0;
/* reset 'cur' back to the list head */
s->dir_state[i].cur = s->dir_state[i].head;
}
}
}

11 changes: 10 additions & 1 deletion src/detect-engine-state.h
Original file line number Diff line number Diff line change
@@ -89,8 +89,17 @@ typedef struct DetectEngineStateDirection_ {
/* coccinelle: DetectEngineStateDirection:flags:DETECT_ENGINE_STATE_FLAG_ */
} DetectEngineStateDirection;

#define DETECT_ENGINE_STATE_DIRECTIONS 3

enum {
DETECT_ENGINE_STATE_DIRECTION_TOSERVER = 0,
DETECT_ENGINE_STATE_DIRECTION_TOCLIENT = 1,
DETECT_ENGINE_STATE_DIRECTION_BOTHDIR = 2,
};

typedef struct DetectEngineState_ {
DetectEngineStateDirection dir_state[2];
// to server, to client, and bidirectional
DetectEngineStateDirection dir_state[DETECT_ENGINE_STATE_DIRECTIONS];
} DetectEngineState;

/**
45 changes: 45 additions & 0 deletions src/detect-engine.c
Original file line number Diff line number Diff line change
@@ -761,6 +761,15 @@ int DetectEngineAppInspectionEngine2Signature(DetectEngineCtx *de_ctx, Signature
for (const DetectEngineAppInspectionEngine *t = de_ctx->app_inspect_engines; t != NULL;
t = t->next) {
if (t->sm_list == s->init_data->buffers[x].id) {
if (s->flags & SIG_FLAG_BOTHDIR) {
// ambiguous keywords have app engines in both directions
// so we skip the wrong direction for this buffer
if (s->init_data->buffers[x].only_tc && t->dir == 0) {
continue;
} else if (s->init_data->buffers[x].only_ts && t->dir == 1) {
continue;
}
}
AppendAppInspectEngine(
de_ctx, t, s, smd, mpm_list, files_id, &last_id, &head_is_mpm);
}
@@ -1351,6 +1360,30 @@ bool DetectBufferIsPresent(const Signature *s, const uint32_t buf_id)
return false;
}

static bool DetectEngineBufferAmbiguousDir(
DetectEngineCtx *de_ctx, const int list, AppProto alproto)
{
bool has_ts = false;
bool has_tc = false;
const DetectEngineAppInspectionEngine *app = de_ctx->app_inspect_engines;
for (; app != NULL; app = app->next) {
if (app->sm_list == list && (AppProtoEquals(alproto, app->alproto) || alproto == 0)) {
if (app->dir == 0) {
if (has_tc) {
return true;
}
has_ts = true;
} else if (app->dir == 1) {
if (has_ts) {
return true;
}
has_tc = true;
}
}
}
return false;
}

int DetectBufferSetActiveList(DetectEngineCtx *de_ctx, Signature *s, const int list)
{
BUG_ON(s->init_data == NULL);
@@ -1389,7 +1422,12 @@ int DetectBufferSetActiveList(DetectEngineCtx *de_ctx, Signature *s, const int l

} else if (DetectEngineBufferTypeSupportsMultiInstanceGetById(de_ctx, list)) {
// fall through
} else if (b->only_tc && (s->init_data->init_flags & SIG_FLAG_INIT_BIDIR_TOSERVER)) {
// fall through
} else if (b->only_ts && (s->init_data->init_flags & SIG_FLAG_INIT_BIDIR_TOCLIENT)) {
// fall through
} else {
// we create a new buffer for the same id but forced different direction
SCLogWarning("duplicate instance for %s in '%s'",
DetectEngineBufferTypeGetNameById(de_ctx, list), s->sig_str);
s->init_data->curbuf = b;
@@ -1413,6 +1451,13 @@ int DetectBufferSetActiveList(DetectEngineCtx *de_ctx, Signature *s, const int l
s->init_data->curbuf->tail = NULL;
s->init_data->curbuf->multi_capable =
DetectEngineBufferTypeSupportsMultiInstanceGetById(de_ctx, list);
if (s->init_data->init_flags & SIG_FLAG_INIT_BIDIR_TOCLIENT) {
s->init_data->curbuf->only_tc = DetectEngineBufferAmbiguousDir(de_ctx, list, s->alproto);
}
if (s->init_data->init_flags & SIG_FLAG_INIT_BIDIR_TOSERVER) {
s->init_data->curbuf->only_ts = DetectEngineBufferAmbiguousDir(de_ctx, list, s->alproto);
}

SCLogDebug("new: idx %u list %d set up curbuf %p", s->init_data->buffer_index - 1, list,
s->init_data->curbuf);

13 changes: 13 additions & 0 deletions src/detect-fast-pattern.c
Original file line number Diff line number Diff line change
@@ -237,6 +237,19 @@ static int DetectFastPatternSetup(DetectEngineCtx *de_ctx, Signature *s, const c
pm = pm2;
}

if (s->flags & SIG_FLAG_BOTHDIR && s->init_data->curbuf != NULL) {
if (DetectBufferToClient(de_ctx, s->init_data->curbuf->id, s->alproto)) {
if (s->init_data->init_flags & SIG_FLAG_INIT_BIDIR_STREAMING_TOSERVER) {
SCLogError("fast_pattern cannot be used on to_client keyword for "
"bidirectional rule with a streaming buffer to server %u",
s->id);
goto error;
} else {
s->init_data->init_flags |= SIG_FLAG_INIT_BIDIR_FAST_TOCLIENT;
}
}
}

cd = (DetectContentData *)pm->ctx;
if ((cd->flags & DETECT_CONTENT_NEGATED) &&
((cd->flags & DETECT_CONTENT_DISTANCE) ||
3 changes: 3 additions & 0 deletions src/detect-file-data.c
Original file line number Diff line number Diff line change
@@ -151,6 +151,9 @@ static int DetectFiledataSetup (DetectEngineCtx *de_ctx, Signature *s, const cha
return -1;

s->init_data->init_flags |= SIG_FLAG_INIT_FILEDATA;
if ((s->init_data->init_flags & SIG_FLAG_INIT_BIDIR_TOCLIENT) == 0) {
s->init_data->init_flags |= SIG_FLAG_INIT_BIDIR_STREAMING_TOSERVER;
}
SetupDetectEngineConfig(de_ctx);
return 0;
}
3 changes: 3 additions & 0 deletions src/detect-file-hash-common.c
Original file line number Diff line number Diff line change
@@ -332,6 +332,9 @@ int DetectFileHashSetup(
}

s->file_flags |= FILE_SIG_NEED_FILE;
if ((s->init_data->init_flags & SIG_FLAG_INIT_BIDIR_TOCLIENT) == 0) {
s->init_data->init_flags |= SIG_FLAG_INIT_BIDIR_STREAMING_TOSERVER;
}

// Setup the file flags depending on the hashing algorithm
if (type == DETECT_FILEMD5) {
3 changes: 3 additions & 0 deletions src/detect-filemagic.c
Original file line number Diff line number Diff line change
@@ -211,6 +211,9 @@ static int DetectFilemagicSetup (DetectEngineCtx *de_ctx, Signature *s, const ch
}
s->init_data->list = DETECT_SM_LIST_NOTSET;
s->file_flags |= (FILE_SIG_NEED_FILE | FILE_SIG_NEED_MAGIC);
if ((s->init_data->init_flags & SIG_FLAG_INIT_BIDIR_TOCLIENT) == 0) {
s->init_data->init_flags |= SIG_FLAG_INIT_BIDIR_STREAMING_TOSERVER;
}

if (DetectContentSetup(de_ctx, s, str) < 0) {
return -1;
9 changes: 9 additions & 0 deletions src/detect-filename.c
Original file line number Diff line number Diff line change
@@ -128,6 +128,9 @@ static int DetectFileextSetup(DetectEngineCtx *de_ctx, Signature *s, const char
}
s->init_data->list = DETECT_SM_LIST_NOTSET;
s->file_flags |= (FILE_SIG_NEED_FILE | FILE_SIG_NEED_FILENAME);
if ((s->init_data->init_flags & SIG_FLAG_INIT_BIDIR_TOCLIENT) == 0) {
s->init_data->init_flags |= SIG_FLAG_INIT_BIDIR_STREAMING_TOSERVER;
}

size_t dotstr_len = strlen(str) + 2;
char *dotstr = SCCalloc(1, dotstr_len);
@@ -175,6 +178,9 @@ static int DetectFilenameSetup (DetectEngineCtx *de_ctx, Signature *s, const cha
}
s->init_data->list = DETECT_SM_LIST_NOTSET;
s->file_flags |= (FILE_SIG_NEED_FILE | FILE_SIG_NEED_FILENAME);
if ((s->init_data->init_flags & SIG_FLAG_INIT_BIDIR_TOCLIENT) == 0) {
s->init_data->init_flags |= SIG_FLAG_INIT_BIDIR_STREAMING_TOSERVER;
}

if (DetectContentSetup(de_ctx, s, str) < 0) {
return -1;
@@ -210,6 +216,9 @@ static int DetectFilenameSetupSticky(DetectEngineCtx *de_ctx, Signature *s, cons
if (DetectBufferSetActiveList(de_ctx, s, g_file_name_buffer_id) < 0)
return -1;
s->file_flags |= (FILE_SIG_NEED_FILE | FILE_SIG_NEED_FILENAME);
if ((s->init_data->init_flags & SIG_FLAG_INIT_BIDIR_TOCLIENT) == 0) {
s->init_data->init_flags |= SIG_FLAG_INIT_BIDIR_STREAMING_TOSERVER;
}
return 0;
}

3 changes: 3 additions & 0 deletions src/detect-filesize.c
Original file line number Diff line number Diff line change
@@ -133,6 +133,9 @@ static int DetectFilesizeSetup (DetectEngineCtx *de_ctx, Signature *s, const cha
}

s->file_flags |= (FILE_SIG_NEED_FILE|FILE_SIG_NEED_SIZE);
if ((s->init_data->init_flags & SIG_FLAG_INIT_BIDIR_TOCLIENT) == 0) {
s->init_data->init_flags |= SIG_FLAG_INIT_BIDIR_STREAMING_TOSERVER;
}
SCReturnInt(0);
}

52 changes: 52 additions & 0 deletions src/detect-flow.c
Original file line number Diff line number Diff line change
@@ -79,6 +79,48 @@ void DetectFlowRegister (void)
DetectSetupParseRegexes(PARSE_REGEX, &parse_regex);
}

static int DetectBidirToClientSetup(DetectEngineCtx *de_ctx, Signature *s, const char *flowstr)
{
if (!(s->flags & SIG_FLAG_BOTHDIR)) {
SCLogError("Cannot have bidir keyword in a non bidirectional signature");
return -1;
}
s->init_data->init_flags |= SIG_FLAG_INIT_BIDIR_TOCLIENT;
s->init_data->init_flags &= ~SIG_FLAG_INIT_BIDIR_TOSERVER;
return 0;
}

static int DetectBidirToServerSetup(DetectEngineCtx *de_ctx, Signature *s, const char *flowstr)
{
if (!(s->flags & SIG_FLAG_BOTHDIR)) {
SCLogError("Cannot have bidir keyword in a non bidirectional signature");
return -1;
}
s->init_data->init_flags |= SIG_FLAG_INIT_BIDIR_TOSERVER;
s->init_data->init_flags &= ~SIG_FLAG_INIT_BIDIR_TOCLIENT;
return 0;
}

/**
* \brief Registration function for flow: keyword
*/
void DetectBidirRegister(void)
{
sigmatch_table[DETECT_BIDIR_TOCLIENT].name = "bidir.toclient";
sigmatch_table[DETECT_BIDIR_TOCLIENT].desc =
"match next keywords only toclient side for bidirectional rules";
sigmatch_table[DETECT_BIDIR_TOCLIENT].url = "/rules/intro.html#bidirectional-rules";
sigmatch_table[DETECT_BIDIR_TOCLIENT].Setup = DetectBidirToClientSetup;
sigmatch_table[DETECT_BIDIR_TOCLIENT].flags |= SIGMATCH_NOOPT;

sigmatch_table[DETECT_BIDIR_TOSERVER].name = "bidir.toserver";
sigmatch_table[DETECT_BIDIR_TOSERVER].desc =
"match next keywords only toclient side for bidirectional rules";
sigmatch_table[DETECT_BIDIR_TOSERVER].url = "/rules/intro.html#bidirectional-rules";
sigmatch_table[DETECT_BIDIR_TOSERVER].Setup = DetectBidirToServerSetup;
sigmatch_table[DETECT_BIDIR_TOSERVER].flags |= SIGMATCH_NOOPT;
}

/**
* \param pflags packet flags (p->flags)
* \param pflowflags packet flow flags (p->flowflags)
@@ -391,8 +433,18 @@ int DetectFlowSetup (DetectEngineCtx *de_ctx, Signature *s, const char *flowstr)
bool appendsm = true;
/* set the signature direction flags */
if (fd->flags & DETECT_FLOW_FLAG_TOSERVER) {
if (s->flags & SIG_FLAG_BOTHDIR) {
SCLogError(
"rule %u means to use both directions, cannot specify a flow direction", s->id);
goto error;
}
s->flags |= SIG_FLAG_TOSERVER;
} else if (fd->flags & DETECT_FLOW_FLAG_TOCLIENT) {
if (s->flags & SIG_FLAG_BOTHDIR) {
SCLogError(
"rule %u means to use both directions, cannot specify a flow direction", s->id);
goto error;
}
s->flags |= SIG_FLAG_TOCLIENT;
} else {
s->flags |= SIG_FLAG_TOSERVER;
2 changes: 2 additions & 0 deletions src/detect-flow.h
Original file line number Diff line number Diff line change
@@ -44,4 +44,6 @@ int DetectFlowSetupImplicit(Signature *s, uint32_t flags);
/* prototypes */
void DetectFlowRegister (void);

void DetectBidirRegister(void);

#endif /* SURICATA_DETECT_FLOW_H */
2 changes: 2 additions & 0 deletions src/detect-http-client-body.c
Original file line number Diff line number Diff line change
@@ -167,6 +167,8 @@ static int DetectHttpClientBodySetupSticky(DetectEngineCtx *de_ctx, Signature *s
return -1;
if (DetectSignatureSetAppProto(s, ALPROTO_HTTP) < 0)
return -1;
// we cannot use a bidirectional rule with a fast pattern to client and this
s->init_data->init_flags |= SIG_FLAG_INIT_BIDIR_STREAMING_TOSERVER;
return 0;
}

36 changes: 30 additions & 6 deletions src/detect-parse.c
Original file line number Diff line number Diff line change
@@ -1378,6 +1378,8 @@ static int SigParseBasics(DetectEngineCtx *de_ctx, Signature *s, const char *sig

if (strcmp(parser->direction, "<>") == 0) {
s->init_data->init_flags |= SIG_FLAG_INIT_BIDIREC;
} else if (strcmp(parser->direction, "=>") == 0) {
s->flags |= SIG_FLAG_BOTHDIR;
} else if (strcmp(parser->direction, "->") != 0) {
SCLogError("\"%s\" is not a valid direction modifier, "
"\"->\" and \"<>\" are supported.",
@@ -1922,6 +1924,9 @@ static int SigValidate(DetectEngineCtx *de_ctx, Signature *s)
} bufdir[nlists + 1];
memset(&bufdir, 0, (nlists + 1) * sizeof(struct BufferVsDir));

int ts_excl = 0;
int tc_excl = 0;

for (uint32_t x = 0; x < s->init_data->buffer_index; x++) {
SignatureInitDataBuffer *b = &s->init_data->buffers[x];
const DetectBufferType *bt = DetectEngineBufferTypeGetById(de_ctx, b->id);
@@ -1959,8 +1964,16 @@ static int SigValidate(DetectEngineCtx *de_ctx, Signature *s)
DetectEngineBufferTypeGetNameById(de_ctx, app->sm_list), app->dir,
app->alproto);
SCLogDebug("b->id %d nlists %d", b->id, nlists);
bufdir[b->id].ts += (app->dir == 0);
bufdir[b->id].tc += (app->dir == 1);
if (b->only_tc) {
if (app->dir == 1)
tc_excl++;
} else if (b->only_ts) {
if (app->dir == 0)
ts_excl++;
} else {
bufdir[b->id].ts += (app->dir == 0);
bufdir[b->id].tc += (app->dir == 1);
}
}
}

@@ -1973,8 +1986,6 @@ static int SigValidate(DetectEngineCtx *de_ctx, Signature *s)
}
}

int ts_excl = 0;
int tc_excl = 0;
int dir_amb = 0;
for (int x = 0; x < nlists; x++) {
if (bufdir[x].ts == 0 && bufdir[x].tc == 0)
@@ -1986,8 +1997,21 @@ static int SigValidate(DetectEngineCtx *de_ctx, Signature *s)
SCLogDebug("%s/%d: %d/%d", DetectEngineBufferTypeGetNameById(de_ctx, x), x, bufdir[x].ts,
bufdir[x].tc);
}
if (ts_excl && tc_excl) {
SCLogError("rule %u mixes keywords with conflicting directions", s->id);
if (s->flags & SIG_FLAG_BOTHDIR) {
if (!ts_excl || !tc_excl) {
SCLogError("rule %u should use both directions, but does not", s->id);
SCReturnInt(0);
}
if (dir_amb) {
SCLogError("rule %u means to use both directions, cannot have keywords ambiguous about "
"directions",
s->id);
SCReturnInt(0);
}
} else if (ts_excl && tc_excl) {
SCLogError("rule %u mixes keywords with conflicting directions, a bidirection rule with => "
"should be used",
s->id);
SCReturnInt(0);
} else if (ts_excl) {
SCLogDebug("%u: implied rule direction is toserver", s->id);
14 changes: 14 additions & 0 deletions src/detect-prefilter.c
Original file line number Diff line number Diff line change
@@ -29,6 +29,7 @@
#include "detect.h"
#include "detect-parse.h"
#include "detect-content.h"
#include "detect-engine-mpm.h"
#include "detect-prefilter.h"
#include "util-debug.h"

@@ -75,6 +76,19 @@ static int DetectPrefilterSetup (DetectEngineCtx *de_ctx, Signature *s, const ch
/* if the sig match is content, prefilter should act like
* 'fast_pattern' w/o options. */
if (sm->type == DETECT_CONTENT) {
if (s->flags & SIG_FLAG_BOTHDIR && s->init_data->curbuf != NULL) {
if (s->init_data->init_flags & SIG_FLAG_INIT_BIDIR_STREAMING_TOSERVER) {
if (DetectBufferToClient(de_ctx, s->init_data->curbuf->id, s->alproto)) {
SCLogError("prefilter cannot be used on to_client keyword for "
"bidirectional rule %u",
s->id);
SCReturnInt(-1);
} else {
s->init_data->init_flags |= SIG_FLAG_INIT_BIDIR_FAST_TOCLIENT;
}
}
}

DetectContentData *cd = (DetectContentData *)sm->ctx;
if ((cd->flags & DETECT_CONTENT_NEGATED) &&
((cd->flags & DETECT_CONTENT_DISTANCE) ||
139 changes: 84 additions & 55 deletions src/detect.c
Original file line number Diff line number Diff line change
@@ -1125,9 +1125,11 @@ static bool DetectRunTxInspectRule(ThreadVars *tv,
const DetectEngineAppInspectionEngine *engine = s->app_inspect;
do {
TRACE_SID_TXS(s->id, tx, "engine %p inspect_flags %x", engine, inspect_flags);
// also if it is not the same direction, but
// this is a bidrectional signature, and we are toclient
if (!(inspect_flags & BIT_U32(engine->id)) &&
direction == engine->dir)
{
(direction == engine->dir || ((s->flags & SIG_FLAG_BOTHDIR) && direction == 1))) {

void *tx_ptr = DetectGetInnerTx(tx->tx_ptr, f->alproto, engine->alproto, flow_flags);
if (tx_ptr == NULL) {
if (engine->alproto != ALPROTO_UNKNOWN) {
@@ -1163,6 +1165,10 @@ static bool DetectRunTxInspectRule(ThreadVars *tv,
}
}

uint8_t engine_flags = flow_flags;
if (direction != engine->dir) {
engine_flags = flow_flags ^ (STREAM_TOCLIENT | STREAM_TOSERVER);
}
/* run callback: but bypass stream callback if we can */
uint8_t match;
if (unlikely(engine->stream && can->stream_stored)) {
@@ -1172,7 +1178,7 @@ static bool DetectRunTxInspectRule(ThreadVars *tv,
KEYWORD_PROFILING_SET_LIST(det_ctx, engine->sm_list);
DEBUG_VALIDATE_BUG_ON(engine->v2.Callback == NULL);
match = engine->v2.Callback(
de_ctx, det_ctx, engine, s, f, flow_flags, alstate, tx_ptr, tx->tx_id);
de_ctx, det_ctx, engine, s, f, engine_flags, alstate, tx_ptr, tx->tx_id);
TRACE_SID_TXS(s->id, tx, "engine %p match %d", engine, match);
if (engine->stream) {
can->stream_stored = true;
@@ -1206,7 +1212,19 @@ static bool DetectRunTxInspectRule(ThreadVars *tv,
inspect_flags |= BIT_U32(engine->id);
}
break;
} else if (!(inspect_flags & BIT_U32(engine->id)) && s->flags & SIG_FLAG_BOTHDIR &&
direction != engine->dir) {
// for bidirectional rules, the engines on the opposite direction
// are ordered by progress on the different side
// so we have a two mixed-up lists, and we skip the elements
if (direction == 0 && engine->next == NULL) {
// do not match yet on request only
break;
}
engine = engine->next;
continue;
}

engine = engine->next;
} while (engine != NULL);
TRACE_SID_TXS(s->id, tx, "inspect_flags %x, total_matches %u, engine %p",
@@ -1265,7 +1283,7 @@ static bool DetectRunTxInspectRule(ThreadVars *tv,

#define NO_TX \
{ \
NULL, 0, NULL, NULL, 0, 0, 0, 0, 0, \
NULL, 0, NULL, NULL, NULL, 0, 0, 0, 0, 0, \
}

/** \internal
@@ -1305,16 +1323,18 @@ static DetectTransaction GetDetectTx(const uint8_t ipproto, const AppProto alpro
DEBUG_VALIDATE_BUG_ON(prefilter_flags & APP_LAYER_TX_RESERVED_FLAGS);

DetectTransaction tx = {
.tx_ptr = tx_ptr,
.tx_id = tx_id,
.tx_data_ptr = (struct AppLayerTxData *)txd,
.de_state = tx_dir_state,
.detect_flags = detect_flags,
.prefilter_flags = prefilter_flags,
.prefilter_flags_orig = prefilter_flags,
.tx_progress = tx_progress,
.tx_end_state = tx_end_state,
};
.tx_ptr = tx_ptr,
.tx_id = tx_id,
.tx_data_ptr = (struct AppLayerTxData *)txd,
.de_state = tx_dir_state,
.de_state_bidir =
tx_de_state ? &tx_de_state->dir_state[DETECT_ENGINE_STATE_DIRECTION_BOTHDIR] : NULL,
.detect_flags = detect_flags,
.prefilter_flags = prefilter_flags,
.prefilter_flags_orig = prefilter_flags,
.tx_progress = tx_progress,
.tx_end_state = tx_end_state,
};
return tx;
}

@@ -1331,6 +1351,52 @@ static inline void StoreDetectFlags(DetectTransaction *tx, const uint8_t flow_fl
}
}

static bool RuleMatchCandidateMergeStoredState(DetectEngineCtx *de_ctx,
DetectEngineThreadCtx *det_ctx, DetectTransaction tx, DetectEngineStateDirection *de_state,
uint32_t *array_idx)
{
if (de_state == NULL) {
return false;
}
const uint32_t old = *array_idx;

/* if de_state->flags has 'new file' set and sig below has
* 'file inspected' flag, reset the file part of the state */
const bool have_new_file = (de_state->flags & DETECT_ENGINE_STATE_FLAG_FILE_NEW);
if (have_new_file) {
SCLogDebug("%p/%" PRIu64 " destate: need to consider new file", tx.tx_ptr, tx.tx_id);
de_state->flags &= ~DETECT_ENGINE_STATE_FLAG_FILE_NEW;
}

SigIntId state_cnt = 0;
DeStateStore *tx_store = de_state->head;
for (; tx_store != NULL; tx_store = tx_store->next) {
SCLogDebug("tx_store %p", tx_store);

SigIntId store_cnt = 0;
for (store_cnt = 0; store_cnt < DE_STATE_CHUNK_SIZE && state_cnt < de_state->cnt;
store_cnt++, state_cnt++) {
DeStateStoreItem *item = &tx_store->store[store_cnt];
SCLogDebug("rule id %u, inspect_flags %u", item->sid, item->flags);
if (have_new_file && (item->flags & DE_STATE_FLAG_FILE_INSPECT)) {
/* remove part of the state. File inspect engine will now
* be able to run again */
item->flags &= ~(DE_STATE_FLAG_SIG_CANT_MATCH | DE_STATE_FLAG_FULL_INSPECT |
DE_STATE_FLAG_FILE_INSPECT);
SCLogDebug("rule id %u, post file reset inspect_flags %u", item->sid, item->flags);
}
det_ctx->tx_candidates[*array_idx].s = de_ctx->sig_array[item->sid];
det_ctx->tx_candidates[*array_idx].id = item->sid;
det_ctx->tx_candidates[*array_idx].flags = &item->flags;
det_ctx->tx_candidates[*array_idx].stream_reset = 0;
(*array_idx)++;
}
}
SCLogDebug("%p/%" PRIu64 " rules added from 'continue' list: %u", tx.tx_ptr, tx.tx_id,
*array_idx - old);
return (old && old != *array_idx); // sort if continue list adds sids
}

// Merge 'state' rules from the regular prefilter
// updates array_idx on the way
static inline void RuleMatchCandidateMergeStateRules(
@@ -1447,6 +1513,7 @@ static void DetectRunTx(ThreadVars *tv,
uint32_t array_idx = 0;
uint32_t total_rules = det_ctx->match_array_cnt;
total_rules += (tx.de_state ? tx.de_state->cnt : 0);
total_rules += (tx.de_state_bidir ? tx.de_state_bidir->cnt : 0);

/* run prefilter engines and merge results into a candidates array */
if (sgh->tx_engines) {
@@ -1485,47 +1552,9 @@ static void DetectRunTx(ThreadVars *tv,
RuleMatchCandidateMergeStateRules(det_ctx, &array_idx);

/* merge stored state into results */
if (tx.de_state != NULL) {
const uint32_t old = array_idx;

/* if tx.de_state->flags has 'new file' set and sig below has
* 'file inspected' flag, reset the file part of the state */
const bool have_new_file = (tx.de_state->flags & DETECT_ENGINE_STATE_FLAG_FILE_NEW);
if (have_new_file) {
SCLogDebug("%p/%"PRIu64" destate: need to consider new file",
tx.tx_ptr, tx.tx_id);
tx.de_state->flags &= ~DETECT_ENGINE_STATE_FLAG_FILE_NEW;
}

SigIntId state_cnt = 0;
DeStateStore *tx_store = tx.de_state->head;
for (; tx_store != NULL; tx_store = tx_store->next) {
SCLogDebug("tx_store %p", tx_store);

SigIntId store_cnt = 0;
for (store_cnt = 0;
store_cnt < DE_STATE_CHUNK_SIZE && state_cnt < tx.de_state->cnt;
store_cnt++, state_cnt++)
{
DeStateStoreItem *item = &tx_store->store[store_cnt];
SCLogDebug("rule id %u, inspect_flags %u", item->sid, item->flags);
if (have_new_file && (item->flags & DE_STATE_FLAG_FILE_INSPECT)) {
/* remove part of the state. File inspect engine will now
* be able to run again */
item->flags &= ~(DE_STATE_FLAG_SIG_CANT_MATCH|DE_STATE_FLAG_FULL_INSPECT|DE_STATE_FLAG_FILE_INSPECT);
SCLogDebug("rule id %u, post file reset inspect_flags %u", item->sid, item->flags);
}
det_ctx->tx_candidates[array_idx].s = de_ctx->sig_array[item->sid];
det_ctx->tx_candidates[array_idx].id = item->sid;
det_ctx->tx_candidates[array_idx].flags = &item->flags;
det_ctx->tx_candidates[array_idx].stream_reset = 0;
array_idx++;
}
}
do_sort |= (old && old != array_idx); // sort if continue list adds sids
SCLogDebug("%p/%" PRIu64 " rules added from 'continue' list: %u", tx.tx_ptr, tx.tx_id,
array_idx - old);
}
do_sort |= RuleMatchCandidateMergeStoredState(de_ctx, det_ctx, tx, tx.de_state, &array_idx);
do_sort |= RuleMatchCandidateMergeStoredState(
de_ctx, det_ctx, tx, tx.de_state_bidir, &array_idx);
if (do_sort) {
qsort(det_ctx->tx_candidates, array_idx, sizeof(RuleMatchCandidateTx),
DetectRunTxSortHelper);
10 changes: 10 additions & 0 deletions src/detect.h
Original file line number Diff line number Diff line change
@@ -244,6 +244,7 @@ typedef struct DetectPort_ {

#define SIG_FLAG_DSIZE BIT_U32(5) /**< signature has a dsize setting */
#define SIG_FLAG_APPLAYER BIT_U32(6) /**< signature applies to app layer instead of packets */
#define SIG_FLAG_BOTHDIR BIT_U32(7) /**< signature needs both directions to match */

// vacancy

@@ -295,6 +296,13 @@ typedef struct DetectPort_ {
BIT_U32(8) /**< priority is explicitly set by the priority keyword */
#define SIG_FLAG_INIT_FILEDATA BIT_U32(9) /**< signature has filedata keyword */
#define SIG_FLAG_INIT_JA BIT_U32(10) /**< signature has ja3/ja4 keyword */
#define SIG_FLAG_INIT_BIDIR_TOCLIENT BIT_U32(11) /**< signature now takes keywords toclient */
#define SIG_FLAG_INIT_BIDIR_TOSERVER BIT_U32(12) /**< signature now takes keywords toserver */
// Two following flags are meant to be mutually exclusive
#define SIG_FLAG_INIT_BIDIR_STREAMING_TOSERVER \
BIT_U32(13) /**< bidirectional signature uses a streaming buffer to server */
#define SIG_FLAG_INIT_BIDIR_FAST_TOCLIENT \
BIT_U32(14) /**< bidirectional signature uses a fast pattern to client */

/* signature mask flags */
/** \note: additions should be added to the rule analyzer as well */
@@ -531,6 +539,8 @@ typedef struct SignatureInitDataBuffer_ {
set up. */
bool multi_capable; /**< true if we can have multiple instances of this buffer, so e.g. for
http.uri. */
bool only_tc; /**< true if we can only used toclient. */
bool only_ts; /**< true if we can only used toserver. */
/* sig match list */
SigMatch *head;
SigMatch *tail;