From: Victor Julien Date: Fri, 14 Mar 2025 15:20:15 +0000 (+0100) Subject: detect/app-layer-state: keyword for protocol state X-Git-Tag: suricata-8.0.0-beta1~23 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6f5fd77cb9271dd9bebe304d73a433e17f891f93;p=thirdparty%2Fsuricata.git detect/app-layer-state: keyword for protocol state Allow matching on the app-layer protocol state. --- diff --git a/doc/userguide/rules/app-layer.rst b/doc/userguide/rules/app-layer.rst index 2804fcb5d1..330b38cf75 100644 --- a/doc/userguide/rules/app-layer.rst +++ b/doc/userguide/rules/app-layer.rst @@ -97,3 +97,16 @@ applayer_proto_detection_skipped Protocol detection was skipped because of :ref:`proto-detect-bail-out`. +app-layer-state +--------------- + +Match on the detected app-layer protocol transaction state. + +Syntax:: + + app-layer-state:[<>]; + +Examples:: + + app-layer-state:request_headers; + app-layer-state:>request_body; diff --git a/src/Makefile.am b/src/Makefile.am index 50eeb79c31..6fd83f11b5 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -88,6 +88,7 @@ noinst_HEADERS = \ defrag-timeout.h \ detect-app-layer-event.h \ detect-app-layer-protocol.h \ + detect-app-layer-state.h \ detect-asn1.h \ detect-base64-data.h \ detect-base64-decode.h \ @@ -673,6 +674,7 @@ libsuricata_c_a_SOURCES = \ defrag-timeout.c \ detect-app-layer-event.c \ detect-app-layer-protocol.c \ + detect-app-layer-state.c \ detect-asn1.c \ detect-base64-data.c \ detect-base64-decode.c \ diff --git a/src/detect-app-layer-state.c b/src/detect-app-layer-state.c new file mode 100644 index 0000000000..fc3c73e715 --- /dev/null +++ b/src/detect-app-layer-state.c @@ -0,0 +1,229 @@ +/* Copyright (C) 2007-2025 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. + */ + +/** + * \file + * + * \author Victor Julien + */ + +#include "suricata-common.h" +#include "threads.h" +#include "decode.h" + +#include "app-layer.h" +#include "app-layer-protos.h" +#include "app-layer-parser.h" +#include "app-layer-smtp.h" +#include "detect.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-state.h" +#include "detect-engine-build.h" +#include "detect-app-layer-state.h" + +#include "flow.h" +#include "flow-var.h" +#include "flow-util.h" + +#include "decode-events.h" +#include "util-byte.h" +#include "util-debug.h" +#include "util-enum.h" +#include "util-profiling.h" +#include "util-unittest.h" +#include "util-unittest-helper.h" +#include "stream-tcp-util.h" + +typedef struct DetectAppLayerStateData_ { + uint8_t progress; + int8_t mode; +} DetectAppLayerStateData; + +static int DetectAppLayerStateSetup(DetectEngineCtx *, Signature *, const char *); +static void DetectAppLayerStateFree(DetectEngineCtx *, void *); +static uint8_t DetectEngineAptStateInspect(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, + const struct DetectEngineAppInspectionEngine_ *engine, const Signature *s, Flow *f, + uint8_t flags, void *alstate, void *tx, uint64_t tx_id); +static int g_applayer_state_list_id = 0; + +void DetectAppLayerStateRegister(void) +{ + sigmatch_table[DETECT_APP_LAYER_STATE].name = "app-layer-state"; + sigmatch_table[DETECT_APP_LAYER_STATE].desc = + "match on events generated by the App Layer Parsers and the protocol detection engine"; + sigmatch_table[DETECT_APP_LAYER_STATE].url = "/rules/app-layer.html#app-layer-event"; + sigmatch_table[DETECT_APP_LAYER_STATE].Setup = DetectAppLayerStateSetup; + sigmatch_table[DETECT_APP_LAYER_STATE].Free = DetectAppLayerStateFree; + + DetectAppLayerInspectEngineRegister("app-layer-state", ALPROTO_UNKNOWN, SIG_FLAG_TOSERVER, 0, + DetectEngineAptStateInspect, NULL); + DetectAppLayerInspectEngineRegister("app-layer-state", ALPROTO_UNKNOWN, SIG_FLAG_TOCLIENT, 0, + DetectEngineAptStateInspect, NULL); + + g_applayer_state_list_id = DetectBufferTypeGetByName("app-layer-state"); +} + +static uint8_t DetectEngineAptStateInspect(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, + const struct DetectEngineAppInspectionEngine_ *engine, const Signature *s, Flow *f, + uint8_t flags, void *alstate, void *tx, uint64_t tx_id) +{ + int r = 0; + const AppProto alproto = f->alproto; + const uint8_t tx_progress = + (uint8_t)AppLayerParserGetStateProgress(f->proto, alproto, tx, flags); + + const SigMatchData *smd = engine->smd; + while (1) { + const DetectAppLayerStateData *data = (const DetectAppLayerStateData *)smd->ctx; + KEYWORD_PROFILING_START; + + bool match = false; + if (data->mode == -1) { + SCLogDebug("sid:%u tx_progress %u < keyword progress %u ?", s->id, tx_progress, + data->progress); + if (tx_progress < data->progress) { + match = true; + } + } else if (data->mode == 1) { + SCLogDebug("sid:%u tx_progress %u > keyword progress %u ?", s->id, tx_progress, + data->progress); + if (tx_progress > data->progress) { + match = true; + } + } else { + BUG_ON(1); + } + + if (match) { + KEYWORD_PROFILING_END(det_ctx, smd->type, 1); + + if (smd->is_last) + break; + smd++; + continue; + } + + KEYWORD_PROFILING_END(det_ctx, smd->type, 0); + goto end; + } + r = 1; + +end: + if (r == 1) { + SCLogDebug("DETECT_ENGINE_INSPECT_SIG_MATCH"); + return DETECT_ENGINE_INSPECT_SIG_MATCH; + } else { + if (AppLayerParserGetStateProgress(f->proto, alproto, tx, flags) == + AppLayerParserGetStateProgressCompletionStatus(alproto, flags)) { + SCLogDebug("DETECT_ENGINE_INSPECT_SIG_CANT_MATCH"); + return DETECT_ENGINE_INSPECT_SIG_CANT_MATCH; + } else { + SCLogDebug("DETECT_ENGINE_INSPECT_SIG_NO_MATCH"); + return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; + } + } +} + +// TODO dedup with detect-parse.c +static SignatureHook SetAppHook(const AppProto alproto, int progress) +{ + SignatureHook h = { + .type = SIGNATURE_HOOK_TYPE_APP, + .t.app.alproto = alproto, + .t.app.app_progress = progress, + }; + return h; +} + +static int DetectAppLayerStateSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg) +{ + if (s->alproto == ALPROTO_UNKNOWN) { + return -1; + } + + int mode = 0; + + if (strlen(arg) > 0) { + if (arg[0] == '<') { + mode = -1; + arg++; + } else if (arg[0] == '>') { + mode = 1; + arg++; + } + } + + if (mode == 0) { + const char *h = arg; + + const int progress_ts = AppLayerParserGetStateIdByName( + IPPROTO_TCP /* TODO */, s->alproto, h, STREAM_TOSERVER); + if (progress_ts >= 0) { + s->flags |= SIG_FLAG_TOSERVER; + s->init_data->hook = SetAppHook(s->alproto, progress_ts); + } else { + const int progress_tc = AppLayerParserGetStateIdByName( + IPPROTO_TCP /* TODO */, s->alproto, h, STREAM_TOCLIENT); + if (progress_tc < 0) { + return -1; + } + s->flags |= SIG_FLAG_TOCLIENT; + s->init_data->hook = SetAppHook(s->alproto, progress_tc); + } + SCLogDebug("hook %u", s->init_data->hook.t.app.app_progress); + return 0; + } + + int progress = 0; + const char *h = arg; + const int progress_ts = + AppLayerParserGetStateIdByName(IPPROTO_TCP /* TODO */, s->alproto, h, STREAM_TOSERVER); + if (progress_ts >= 0) { + s->flags |= SIG_FLAG_TOSERVER; + progress = progress_ts; + } else { + const int progress_tc = AppLayerParserGetStateIdByName( + IPPROTO_TCP /* TODO */, s->alproto, h, STREAM_TOCLIENT); + if (progress_tc < 0) { + return -1; + } + s->flags |= SIG_FLAG_TOCLIENT; + progress = progress_tc; + } + + DetectAppLayerStateData *data = SCCalloc(1, sizeof(*data)); + if (data == NULL) + return -1; + + data->progress = (uint8_t)progress; + data->mode = (int8_t)mode; + + if (SigMatchAppendSMToList(de_ctx, s, DETECT_APP_LAYER_STATE, (SigMatchCtx *)data, + g_applayer_state_list_id) == NULL) { + SCFree(data); + return -1; + } + s->flags |= SIG_FLAG_APPLAYER; + + return 0; +} + +static void DetectAppLayerStateFree(DetectEngineCtx *de_ctx, void *ptr) +{ + SCFree(ptr); +} diff --git a/src/detect-app-layer-state.h b/src/detect-app-layer-state.h new file mode 100644 index 0000000000..04281113e0 --- /dev/null +++ b/src/detect-app-layer-state.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2007-2025 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. + */ + +/** + * \file + * + * \author Victor Julien + */ + +#ifndef SURICATA_DETECT_APP_LAYER_STATE_H +#define SURICATA_DETECT_APP_LAYER_STATE_H + +void DetectAppLayerStateRegister(void); + +#endif /* SURICATA_DETECT_APP_LAYER_STATE_H */ diff --git a/src/detect-engine-prefilter.c b/src/detect-engine-prefilter.c index 5f6ab3f017..8bb5306054 100644 --- a/src/detect-engine-prefilter.c +++ b/src/detect-engine-prefilter.c @@ -682,6 +682,8 @@ struct TxNonPFData { AppProto alproto; int dir; /**< 0: toserver, 1: toclient */ int progress; /**< progress state value to register at */ + int sig_list; /**< special handling: normally 0, but for special cases (app-layer-state, + app-layer-event) use the list id to create separate engines */ uint32_t sigs_cnt; struct PrefilterNonPFDataSig *sigs; const char *engine_name; /**< pointer to name owned by DetectEngineCtx::non_pf_engine_names */ @@ -690,14 +692,15 @@ struct TxNonPFData { static uint32_t TxNonPFHash(HashListTable *h, void *data, uint16_t _len) { struct TxNonPFData *d = data; - return (d->alproto + d->progress + d->dir) % h->array_size; + return (d->alproto + d->progress + d->dir + d->sig_list) % h->array_size; } static char TxNonPFCompare(void *data1, uint16_t _len1, void *data2, uint16_t len2) { struct TxNonPFData *d1 = data1; struct TxNonPFData *d2 = data2; - return d1->alproto == d2->alproto && d1->progress == d2->progress && d1->dir == d2->dir; + return d1->alproto == d2->alproto && d1->progress == d2->progress && d1->dir == d2->dir && + d1->sig_list == d2->sig_list; } static void TxNonPFFree(void *data) @@ -708,8 +711,8 @@ static void TxNonPFFree(void *data) } static int TxNonPFAddSig(DetectEngineCtx *de_ctx, HashListTable *tx_engines_hash, - const AppProto alproto, const int dir, const int16_t progress, const char *name, - const Signature *s) + const AppProto alproto, const int dir, const int16_t progress, const int sig_list, + const char *name, const Signature *s) { const uint32_t max_sids = DetectEngineGetMaxSigId(de_ctx); @@ -717,6 +720,7 @@ static int TxNonPFAddSig(DetectEngineCtx *de_ctx, HashListTable *tx_engines_hash .alproto = alproto, .dir = dir, .progress = progress, + .sig_list = sig_list, .sigs_cnt = 0, .sigs = NULL, .engine_name = NULL, @@ -747,6 +751,7 @@ static int TxNonPFAddSig(DetectEngineCtx *de_ctx, HashListTable *tx_engines_hash add->dir = dir; add->alproto = alproto; add->progress = progress; + add->sig_list = sig_list; add->sigs = SCCalloc(max_sids, sizeof(struct PrefilterNonPFDataSig)); if (add->sigs == NULL) { SCFree(add); @@ -852,6 +857,8 @@ static int SetupNonPrefilter(DetectEngineCtx *de_ctx, SigGroupHead *sgh) #endif const int app_events_list_id = DetectBufferTypeGetByName("app-layer-events"); SCLogDebug("app_events_list_id %d", app_events_list_id); + const int app_state_list_id = DetectBufferTypeGetByName("app-layer-state"); + SCLogDebug("app_state_list_id %d", app_state_list_id); for (uint32_t sig = 0; sig < sgh->init->sig_cnt; sig++) { Signature *s = sgh->init->match_array[sig]; if (s == NULL) @@ -958,8 +965,11 @@ static int SetupNonPrefilter(DetectEngineCtx *de_ctx, SigGroupHead *sgh) AppProtoEquals(s->alproto, app->alproto)))) continue; + int sig_list = 0; + if (list_id == app_state_list_id) + sig_list = app_state_list_id; if (TxNonPFAddSig(de_ctx, tx_engines_hash, app->alproto, app->dir, - app->progress, buf->name, s) != 0) { + app->progress, sig_list, buf->name, s) != 0) { goto error; } tx_non_pf = true; @@ -1036,6 +1046,13 @@ static int SetupNonPrefilter(DetectEngineCtx *de_ctx, SigGroupHead *sgh) continue; } + /* register special progress value to indicate we need to run it all the time */ + int engine_progress = t->progress; + if (t->sig_list == app_state_list_id) { + SCLogDebug("engine %s for state list", t->engine_name); + engine_progress = -1; + } + struct PrefilterNonPFDataTx *data = SCCalloc(1, sizeof(*data) + t->sigs_cnt * sizeof(data->array[0])); if (data == NULL) @@ -1044,7 +1061,7 @@ static int SetupNonPrefilter(DetectEngineCtx *de_ctx, SigGroupHead *sgh) for (uint32_t i = 0; i < t->sigs_cnt; i++) { data->array[i] = t->sigs[i].sid; } - if (PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxNonPF, t->alproto, t->progress, + if (PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxNonPF, t->alproto, engine_progress, (void *)data, PrefilterNonPFDataFree, t->engine_name) < 0) { SCFree(data); goto error; diff --git a/src/detect-engine-register.c b/src/detect-engine-register.c index 61f5d33bde..51ea64403b 100644 --- a/src/detect-engine-register.c +++ b/src/detect-engine-register.c @@ -184,6 +184,7 @@ #include "detect-replace.h" #include "detect-tos.h" #include "detect-app-layer-event.h" +#include "detect-app-layer-state.h" #include "detect-lua.h" #include "detect-iprep.h" #include "detect-geoip.h" @@ -593,6 +594,7 @@ void SigTableSetup(void) DetectTlsJa3SStringRegister(); DetectAppLayerEventRegister(); + DetectAppLayerStateRegister(); /* end of order dependent regs */ DetectFrameRegister(); diff --git a/src/detect-engine-register.h b/src/detect-engine-register.h index 2f19793a8a..dd7fefb58b 100644 --- a/src/detect-engine-register.h +++ b/src/detect-engine-register.h @@ -201,6 +201,7 @@ enum DetectKeywordId { DETECT_FILE_DATA, DETECT_PKT_DATA, DETECT_APP_LAYER_EVENT, + DETECT_APP_LAYER_STATE, DETECT_HTTP2_FRAMETYPE, DETECT_HTTP2_ERRORCODE, diff --git a/src/flow.h b/src/flow.h index 3e07ee8e11..6b30909e52 100644 --- a/src/flow.h +++ b/src/flow.h @@ -565,7 +565,6 @@ void FlowSwap(Flow *); void FlowRegisterTests(void); int FlowSetProtoFreeFunc(uint8_t, void (*Free)(void *)); -static inline void FlowSetNoPacketInspectionFlag(Flow *); static inline void FlowSetNoPayloadInspectionFlag(Flow *); int FlowGetPacketDirection(const Flow *, const Packet *);