From de4617595ec36b1831eea26077d32924b668bd33 Mon Sep 17 00:00:00 2001 From: Jason Ish Date: Tue, 30 Jun 2026 12:16:26 -0600 Subject: [PATCH 01/69] smtp: add firewall progress states Add minimal SMTP progress states to support envelope validation before moving to data. Update SMTP, file and email keywords to hook into the appropriate states. Purposefully kept minimal for now as to not break the current idea of an SMTP transaction, which is probably not ideal for firewall mode. Ticket: #8393 (cherry picked from commit c2728eee017c26f639e230394651d5377c2836b6) --- src/app-layer-smtp.c | 61 ++++++++++++++++++++++++++++++++++++++++-- src/app-layer-smtp.h | 16 +++++++++++ src/detect-email.c | 43 ++++++++++++++++------------- src/detect-file-data.c | 5 +++- 4 files changed, 104 insertions(+), 21 deletions(-) diff --git a/src/app-layer-smtp.c b/src/app-layer-smtp.c index 7938a94a0429..3c58bce83b74 100644 --- a/src/app-layer-smtp.c +++ b/src/app-layer-smtp.c @@ -186,6 +186,52 @@ static const char *SMTPGetFrameNameById(const uint8_t frame_id) return name; } +static SCEnumCharMap smtp_state_client_table[] = { + { "request_started", SMTP_REQUEST_STARTED }, + { "request_data", SMTP_REQUEST_DATA }, + { "request_complete", SMTP_REQUEST_COMPLETE }, + { NULL, -1 }, +}; + +static SCEnumCharMap smtp_state_server_table[] = { + { "response_started", SMTP_RESPONSE_STARTED }, + { "response_data", SMTP_RESPONSE_DATA }, + { "response_complete", SMTP_RESPONSE_COMPLETE }, + { NULL, -1 }, +}; + +static int SMTPStateGetStateIdByName(const char *name, const uint8_t direction) +{ + SCEnumCharMap *map = + direction == STREAM_TOSERVER ? smtp_state_client_table : smtp_state_server_table; + int id = SCMapEnumNameToValue(name, map); + if (id < 0) { + return -1; + } + return id; +} + +static const char *SMTPStateGetStateNameById(const int id, const uint8_t direction) +{ + SCEnumCharMap *map = + direction == STREAM_TOSERVER ? smtp_state_client_table : smtp_state_server_table; + return SCMapEnumValueToName(id, map); +} + +static inline void SMTPSetProgressTS(SMTPTransaction *tx, uint8_t progress) +{ + if (tx != NULL && tx->progress_ts < progress) { + tx->progress_ts = progress; + } +} + +static inline void SMTPSetProgressTC(SMTPTransaction *tx, uint8_t progress) +{ + if (tx != NULL && tx->progress_tc < progress) { + tx->progress_tc = progress; + } +} + typedef struct SMTPThreadCtx_ { MpmThreadCtx *smtp_mpm_thread_ctx; PrefilterRuleStore *pmq; @@ -940,6 +986,7 @@ static int SMTPProcessReply( } } else if (IsReplyToCommand(state, SMTP_COMMAND_DATA)) { if (reply_code == SMTP_REPLY_354) { + SMTPSetProgressTC(state->curr_tx, SMTP_RESPONSE_DATA); /* Next comes the mail for the DATA command in toserver direction */ state->parser_state |= SMTP_PARSER_STATE_COMMAND_DATA_MODE; } else { @@ -950,6 +997,8 @@ static int SMTPProcessReply( } SMTPSetEvent(state, SMTP_DECODER_EVENT_DATA_COMMAND_REJECTED); } + } else if (IsReplyToCommand(state, SMTP_COMMAND_BDAT)) { + SMTPSetProgressTC(state->curr_tx, SMTP_RESPONSE_DATA); } else if (IsReplyToCommand(state, SMTP_COMMAND_RSET)) { if (reply_code == SMTP_REPLY_250 && state->curr_tx && !(state->parser_state & SMTP_PARSER_STATE_PARSING_MULTILINE_REPLY)) { @@ -1185,6 +1234,7 @@ static int SMTPProcessRequest( state->current_command = SMTP_COMMAND_STARTTLS; } else if (line->len >= 4 && SCMemcmpLowercase("data", line->buf, 4) == 0) { state->current_command = SMTP_COMMAND_DATA; + SMTPSetProgressTS(tx, SMTP_REQUEST_DATA); if (state->curr_tx->is_data) { // We did not receive a confirmation from server // And now client sends a next DATA @@ -1225,6 +1275,7 @@ static int SMTPProcessRequest( SCReturnInt(-1); } state->current_command = SMTP_COMMAND_BDAT; + SMTPSetProgressTS(tx, SMTP_REQUEST_DATA); state->parser_state |= SMTP_PARSER_STATE_COMMAND_DATA_MODE; } else if (line->len >= 4 && ((SCMemcmpLowercase("helo", line->buf, 4) == 0) || SCMemcmpLowercase("ehlo", line->buf, 4) == 0)) { @@ -1790,7 +1841,10 @@ static void *SMTPStateGetTx(void *state, uint64_t id) static int SMTPStateGetAlstateProgress(void *vtx, uint8_t direction) { SMTPTransaction *tx = vtx; - return tx->done; + if (direction & STREAM_TOSERVER) { + return tx->done ? SMTP_REQUEST_COMPLETE : tx->progress_ts; + } + return tx->done ? SMTP_RESPONSE_COMPLETE : tx->progress_tc; } static AppLayerGetFileState SMTPGetTxFiles(void *txv, uint8_t direction) @@ -1893,9 +1947,12 @@ void RegisterSMTPParsers(void) AppLayerParserRegisterGetTxIterator(IPPROTO_TCP, ALPROTO_SMTP, SMTPGetTxIterator); AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_SMTP, SMTPGetTxData); AppLayerParserRegisterStateDataFunc(IPPROTO_TCP, ALPROTO_SMTP, SMTPGetStateData); - AppLayerParserRegisterStateProgressCompletionStatus(ALPROTO_SMTP, 1, 1); + AppLayerParserRegisterStateProgressCompletionStatus( + ALPROTO_SMTP, SMTP_REQUEST_COMPLETE, SMTP_RESPONSE_COMPLETE); AppLayerParserRegisterGetFrameFuncs( IPPROTO_TCP, ALPROTO_SMTP, SMTPGetFrameIdByName, SMTPGetFrameNameById); + AppLayerParserRegisterGetStateFuncs( + IPPROTO_TCP, ALPROTO_SMTP, SMTPStateGetStateIdByName, SMTPStateGetStateNameById); } else { SCLogInfo("Parser disabled for %s protocol. Protocol detection still on.", proto_name); } diff --git a/src/app-layer-smtp.h b/src/app-layer-smtp.h index cd9c614b966a..3054ba1b761d 100644 --- a/src/app-layer-smtp.h +++ b/src/app-layer-smtp.h @@ -69,6 +69,18 @@ typedef struct SMTPString_ { TAILQ_ENTRY(SMTPString_) next; } SMTPString; +enum SMTPRequestProgress { + SMTP_REQUEST_STARTED = 0, + SMTP_REQUEST_DATA = 1, + SMTP_REQUEST_COMPLETE = 2, +}; + +enum SMTPResponseProgress { + SMTP_RESPONSE_STARTED = 0, + SMTP_RESPONSE_DATA = 1, + SMTP_RESPONSE_COMPLETE = 2, +}; + typedef struct SMTPTransaction_ { /** id of this tx, starting at 0 */ uint64_t tx_id; @@ -77,6 +89,10 @@ typedef struct SMTPTransaction_ { /** the tx is complete and can be logged and cleaned */ bool done; + /** to-server firewall progress state. */ + uint8_t progress_ts; + /** to-client firewall progress state. */ + uint8_t progress_tc; /** the tx has seen a DATA command */ // another DATA command within the same context // will trigger an app-layer event. diff --git a/src/detect-email.c b/src/detect-email.c index 26bd4974ce53..f5538c83dd5d 100644 --- a/src/detect-email.c +++ b/src/detect-email.c @@ -235,8 +235,9 @@ void DetectEmailRegister(void) kw.Setup = DetectMimeEmailFromSetup; kw.flags = SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER; SCDetectHelperKeywordRegister(&kw); - g_mime_email_from_buffer_id = SCDetectHelperBufferMpmRegister( - "email.from", "MIME EMAIL FROM", ALPROTO_SMTP, STREAM_TOSERVER, GetMimeEmailFromData); + g_mime_email_from_buffer_id = + SCDetectHelperBufferProgressMpmRegister("email.from", "MIME EMAIL FROM", ALPROTO_SMTP, + STREAM_TOSERVER, GetMimeEmailFromData, SMTP_REQUEST_DATA); kw.name = "email.subject"; kw.desc = "'Subject' field from an email"; @@ -244,8 +245,9 @@ void DetectEmailRegister(void) kw.Setup = DetectMimeEmailSubjectSetup; kw.flags = SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER; SCDetectHelperKeywordRegister(&kw); - g_mime_email_subject_buffer_id = SCDetectHelperBufferMpmRegister("email.subject", - "MIME EMAIL SUBJECT", ALPROTO_SMTP, STREAM_TOSERVER, GetMimeEmailSubjectData); + g_mime_email_subject_buffer_id = + SCDetectHelperBufferProgressMpmRegister("email.subject", "MIME EMAIL SUBJECT", + ALPROTO_SMTP, STREAM_TOSERVER, GetMimeEmailSubjectData, SMTP_REQUEST_DATA); kw.name = "email.to"; kw.desc = "'To' field from an email"; @@ -253,8 +255,8 @@ void DetectEmailRegister(void) kw.Setup = DetectMimeEmailToSetup; kw.flags = SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER; SCDetectHelperKeywordRegister(&kw); - g_mime_email_to_buffer_id = SCDetectHelperBufferMpmRegister( - "email.to", "MIME EMAIL TO", ALPROTO_SMTP, STREAM_TOSERVER, GetMimeEmailToData); + g_mime_email_to_buffer_id = SCDetectHelperBufferProgressMpmRegister("email.to", "MIME EMAIL TO", + ALPROTO_SMTP, STREAM_TOSERVER, GetMimeEmailToData, SMTP_REQUEST_DATA); kw.name = "email.cc"; kw.desc = "'Cc' field from an email"; @@ -262,8 +264,8 @@ void DetectEmailRegister(void) kw.Setup = DetectMimeEmailCcSetup; kw.flags = SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER; SCDetectHelperKeywordRegister(&kw); - g_mime_email_cc_buffer_id = SCDetectHelperBufferMpmRegister( - "email.cc", "MIME EMAIL CC", ALPROTO_SMTP, STREAM_TOSERVER, GetMimeEmailCcData); + g_mime_email_cc_buffer_id = SCDetectHelperBufferProgressMpmRegister("email.cc", "MIME EMAIL CC", + ALPROTO_SMTP, STREAM_TOSERVER, GetMimeEmailCcData, SMTP_REQUEST_DATA); kw.name = "email.date"; kw.desc = "'Date' field from an email"; @@ -271,8 +273,9 @@ void DetectEmailRegister(void) kw.Setup = DetectMimeEmailDateSetup; kw.flags = SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER; SCDetectHelperKeywordRegister(&kw); - g_mime_email_date_buffer_id = SCDetectHelperBufferMpmRegister( - "email.date", "MIME EMAIL DATE", ALPROTO_SMTP, STREAM_TOSERVER, GetMimeEmailDateData); + g_mime_email_date_buffer_id = + SCDetectHelperBufferProgressMpmRegister("email.date", "MIME EMAIL DATE", ALPROTO_SMTP, + STREAM_TOSERVER, GetMimeEmailDateData, SMTP_REQUEST_DATA); kw.name = "email.message_id"; kw.desc = "'Message-Id' field from an email"; @@ -280,8 +283,9 @@ void DetectEmailRegister(void) kw.Setup = DetectMimeEmailMessageIdSetup; kw.flags = SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER; SCDetectHelperKeywordRegister(&kw); - g_mime_email_message_id_buffer_id = SCDetectHelperBufferMpmRegister("email.message_id", - "MIME EMAIL Message-Id", ALPROTO_SMTP, STREAM_TOSERVER, GetMimeEmailMessageIdData); + g_mime_email_message_id_buffer_id = + SCDetectHelperBufferProgressMpmRegister("email.message_id", "MIME EMAIL Message-Id", + ALPROTO_SMTP, STREAM_TOSERVER, GetMimeEmailMessageIdData, SMTP_REQUEST_DATA); kw.name = "email.x_mailer"; kw.desc = "'X-Mailer' field from an email"; @@ -289,8 +293,9 @@ void DetectEmailRegister(void) kw.Setup = DetectMimeEmailXMailerSetup; kw.flags = SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER; SCDetectHelperKeywordRegister(&kw); - g_mime_email_x_mailer_buffer_id = SCDetectHelperBufferMpmRegister("email.x_mailer", - "MIME EMAIL X-Mailer", ALPROTO_SMTP, STREAM_TOSERVER, GetMimeEmailXMailerData); + g_mime_email_x_mailer_buffer_id = + SCDetectHelperBufferProgressMpmRegister("email.x_mailer", "MIME EMAIL X-Mailer", + ALPROTO_SMTP, STREAM_TOSERVER, GetMimeEmailXMailerData, SMTP_REQUEST_DATA); kw.name = "email.url"; kw.desc = "'Url' extracted from an email"; @@ -298,8 +303,9 @@ void DetectEmailRegister(void) kw.Setup = DetectMimeEmailUrlSetup; kw.flags = SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER; SCDetectHelperKeywordRegister(&kw); - g_mime_email_url_buffer_id = SCDetectHelperMultiBufferMpmRegister( - "email.url", "MIME EMAIL URL", ALPROTO_SMTP, STREAM_TOSERVER, GetMimeEmailUrlData); + g_mime_email_url_buffer_id = + SCDetectHelperMultiBufferProgressMpmRegister("email.url", "MIME EMAIL URL", + ALPROTO_SMTP, STREAM_TOSERVER, GetMimeEmailUrlData, SMTP_REQUEST_DATA); kw.name = "email.received"; kw.desc = "'Received' field from an email"; @@ -307,6 +313,7 @@ void DetectEmailRegister(void) kw.Setup = DetectMimeEmailReceivedSetup; kw.flags = SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER; SCDetectHelperKeywordRegister(&kw); - g_mime_email_received_buffer_id = SCDetectHelperMultiBufferMpmRegister("email.received", - "MIME EMAIL RECEIVED", ALPROTO_SMTP, STREAM_TOSERVER, GetMimeEmailReceivedData); + g_mime_email_received_buffer_id = + SCDetectHelperMultiBufferProgressMpmRegister("email.received", "MIME EMAIL RECEIVED", + ALPROTO_SMTP, STREAM_TOSERVER, GetMimeEmailReceivedData, SMTP_REQUEST_DATA); } diff --git a/src/detect-file-data.c b/src/detect-file-data.c index e5f28d8b9f4b..77f68de01ca1 100644 --- a/src/detect-file-data.c +++ b/src/detect-file-data.c @@ -93,7 +93,10 @@ DetectFileHandlerProtocol_t al_protocols[ALPROTO_WITHFILES_MAX] = { .direction = SIG_FLAG_TOSERVER | SIG_FLAG_TOCLIENT, .to_client_progress = HTTP2StateDataServer, .to_server_progress = HTTP2StateDataClient }, - { .alproto = ALPROTO_SMTP, .direction = SIG_FLAG_TOSERVER }, { .alproto = ALPROTO_UNKNOWN } + { .alproto = ALPROTO_SMTP, + .direction = SIG_FLAG_TOSERVER, + .to_server_progress = SMTP_REQUEST_DATA }, + { .alproto = ALPROTO_UNKNOWN } }; void DetectFileRegisterProto( From ebbe9a72cc53f6ffd9b945dc2e5e96371e279519 Mon Sep 17 00:00:00 2001 From: Jason Ish Date: Fri, 3 Jul 2026 13:06:04 -0600 Subject: [PATCH 02/69] smtp: complete transactions by progress state Add directionality to completion states, and replace tx->done by checking for both directions being complete. This means that the transaction is now not complete until the server responds to the clients of data marker, previously the tx was completed when the client send end of data without waiting for the server response. This keeps smtp:response_complete from being exposed before the server response is parsed. Ticket: #8393 (cherry picked from commit 7b31f41878b557f121d471fe56f3ca0e94ec4a36) --- src/app-layer-smtp.c | 44 +++++++++++++++++++++++++++++++++++++------- src/app-layer-smtp.h | 2 -- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/app-layer-smtp.c b/src/app-layer-smtp.c index 3c58bce83b74..dd230621332c 100644 --- a/src/app-layer-smtp.c +++ b/src/app-layer-smtp.c @@ -232,6 +232,12 @@ static inline void SMTPSetProgressTC(SMTPTransaction *tx, uint8_t progress) } } +static bool SMTPTransactionIsComplete(const SMTPTransaction *tx) +{ + return tx && tx->progress_ts == SMTP_REQUEST_COMPLETE && + tx->progress_tc == SMTP_RESPONSE_COMPLETE; +} + typedef struct SMTPThreadCtx_ { MpmThreadCtx *smtp_mpm_thread_ctx; PrefilterRuleStore *pmq; @@ -755,8 +761,28 @@ static void SetMimeEvents(SMTPState *state, uint32_t events) static inline void SMTPTransactionComplete(SMTPState *state) { DEBUG_VALIDATE_BUG_ON(state->curr_tx == NULL); - if (state->curr_tx) - state->curr_tx->done = true; + if (state->curr_tx) { + SMTPSetProgressTS(state->curr_tx, SMTP_REQUEST_COMPLETE); + SMTPSetProgressTC(state->curr_tx, SMTP_RESPONSE_COMPLETE); + } +} + +static inline void SMTPTransactionCompleteTS(SMTPState *state) +{ + DEBUG_VALIDATE_BUG_ON(state->curr_tx == NULL); + if (state->curr_tx) { + SMTPSetProgressTS(state->curr_tx, SMTP_REQUEST_COMPLETE); + SCLogDebug("marked tx as ts complete"); + } +} + +static inline void SMTPTransactionCompleteTC(SMTPState *state) +{ + DEBUG_VALIDATE_BUG_ON(state->curr_tx == NULL); + if (state->curr_tx) { + SMTPSetProgressTC(state->curr_tx, SMTP_RESPONSE_COMPLETE); + SCLogDebug("marked tx as tc complete"); + } } /** @@ -793,8 +819,7 @@ static int SMTPProcessCommandDATA( FileFlowToFlags(f, STREAM_TOSERVER)); } } - SMTPTransactionComplete(state); - SCLogDebug("marked tx as done"); + SMTPTransactionCompleteTS(state); } else if (smtp_config.raw_extraction) { // message not over, store the line. This is a substitution of // ProcessDataChunk @@ -999,6 +1024,10 @@ static int SMTPProcessReply( } } else if (IsReplyToCommand(state, SMTP_COMMAND_BDAT)) { SMTPSetProgressTC(state->curr_tx, SMTP_RESPONSE_DATA); + } else if (IsReplyToCommand(state, SMTP_COMMAND_DATA_MODE)) { + if (!(state->parser_state & SMTP_PARSER_STATE_PARSING_MULTILINE_REPLY)) { + SMTPTransactionCompleteTC(state); + } } else if (IsReplyToCommand(state, SMTP_COMMAND_RSET)) { if (reply_code == SMTP_REPLY_250 && state->curr_tx && !(state->parser_state & SMTP_PARSER_STATE_PARSING_MULTILINE_REPLY)) { @@ -1200,7 +1229,8 @@ static int SMTPProcessRequest( if (line->len == 0 && line->delim_len == 0) { return 0; } - if (state->curr_tx == NULL || (state->curr_tx->done && !NoNewTx(state, line))) { + if (state->curr_tx == NULL || + (SMTPTransactionIsComplete(state->curr_tx) && !NoNewTx(state, line))) { tx = SMTPTransactionCreate(state); if (tx == NULL) return -1; @@ -1842,9 +1872,9 @@ static int SMTPStateGetAlstateProgress(void *vtx, uint8_t direction) { SMTPTransaction *tx = vtx; if (direction & STREAM_TOSERVER) { - return tx->done ? SMTP_REQUEST_COMPLETE : tx->progress_ts; + return tx->progress_ts; } - return tx->done ? SMTP_RESPONSE_COMPLETE : tx->progress_tc; + return tx->progress_tc; } static AppLayerGetFileState SMTPGetTxFiles(void *txv, uint8_t direction) diff --git a/src/app-layer-smtp.h b/src/app-layer-smtp.h index 3054ba1b761d..7dd05d1235eb 100644 --- a/src/app-layer-smtp.h +++ b/src/app-layer-smtp.h @@ -87,8 +87,6 @@ typedef struct SMTPTransaction_ { AppLayerTxData tx_data; - /** the tx is complete and can be logged and cleaned */ - bool done; /** to-server firewall progress state. */ uint8_t progress_ts; /** to-client firewall progress state. */ From 0118ba292f84adccafd6c666c911f54e7fff5428 Mon Sep 17 00:00:00 2001 From: Jason Ish Date: Tue, 7 Jul 2026 15:41:02 -0600 Subject: [PATCH 03/69] smtp: handle pipelined replies on owning tx Track the transaction id for each queued SMTP command so replies can update the transaction that created the command instead of always using the current transaction. Ticket: #8393 (cherry picked from commit e2a62dd1c086fa2ed6ddf544366d5f2a1d6d4c93) --- src/app-layer-smtp.c | 121 +++++++++++++++++++++++++++++++------------ src/app-layer-smtp.h | 2 + 2 files changed, 91 insertions(+), 32 deletions(-) diff --git a/src/app-layer-smtp.c b/src/app-layer-smtp.c index dd230621332c..84072aeb0d1e 100644 --- a/src/app-layer-smtp.c +++ b/src/app-layer-smtp.c @@ -98,6 +98,9 @@ #define SMTP_DEFAULT_MAX_TX 256 +/* command buffer tx id for commands with no owning transaction */ +#define SMTP_NO_TX_ID UINT64_MAX + typedef struct SMTPInput_ { /* current input that is being parsed */ const uint8_t *buf; @@ -229,13 +232,13 @@ static inline void SMTPSetProgressTC(SMTPTransaction *tx, uint8_t progress) { if (tx != NULL && tx->progress_tc < progress) { tx->progress_tc = progress; + tx->tx_data.updated_tc = true; } } -static bool SMTPTransactionIsComplete(const SMTPTransaction *tx) +static bool SMTPTransactionRequestIsComplete(const SMTPTransaction *tx) { - return tx && tx->progress_ts == SMTP_REQUEST_COMPLETE && - tx->progress_tc == SMTP_RESPONSE_COMPLETE; + return tx && tx->progress_ts == SMTP_REQUEST_COMPLETE; } typedef struct SMTPThreadCtx_ { @@ -665,7 +668,8 @@ static AppLayerResult SMTPGetLine(Flow *f, StreamSlice *slice, SMTPState *state, } } -static int SMTPInsertCommandIntoCommandBuffer(uint8_t command, SMTPState *state) +static int SMTPInsertCommandIntoCommandBuffer( + SMTPState *state, uint8_t command, const SMTPTransaction *tx) { SCEnter(); void *ptmp; @@ -680,12 +684,26 @@ static int SMTPInsertCommandIntoCommandBuffer(uint8_t command, SMTPState *state) sizeof(uint8_t) * (state->cmds_buffer_len + increment)); if (ptmp == NULL) { SCFree(state->cmds); + SCFree(state->cmds_tx_ids); state->cmds = NULL; + state->cmds_tx_ids = NULL; SCLogDebug("SCRealloc failure"); return -1; } state->cmds = ptmp; + ptmp = SCRealloc( + state->cmds_tx_ids, sizeof(uint64_t) * (state->cmds_buffer_len + increment)); + if (ptmp == NULL) { + SCFree(state->cmds); + SCFree(state->cmds_tx_ids); + state->cmds = NULL; + state->cmds_tx_ids = NULL; + SCLogDebug("SCRealloc failure"); + return -1; + } + state->cmds_tx_ids = ptmp; + state->cmds_buffer_len += increment; } if (state->cmds_cnt >= 1 && @@ -704,6 +722,7 @@ static int SMTPInsertCommandIntoCommandBuffer(uint8_t command, SMTPState *state) } state->cmds[state->cmds_cnt] = command; + state->cmds_tx_ids[state->cmds_cnt] = tx != NULL ? tx->tx_id : SMTP_NO_TX_ID; state->cmds_cnt++; return 0; @@ -758,29 +777,29 @@ static void SetMimeEvents(SMTPState *state, uint32_t events) } } -static inline void SMTPTransactionComplete(SMTPState *state) +static inline void SMTPTransactionComplete(SMTPTransaction *tx) { - DEBUG_VALIDATE_BUG_ON(state->curr_tx == NULL); - if (state->curr_tx) { - SMTPSetProgressTS(state->curr_tx, SMTP_REQUEST_COMPLETE); - SMTPSetProgressTC(state->curr_tx, SMTP_RESPONSE_COMPLETE); + DEBUG_VALIDATE_BUG_ON(tx == NULL); + if (tx) { + SMTPSetProgressTS(tx, SMTP_REQUEST_COMPLETE); + SMTPSetProgressTC(tx, SMTP_RESPONSE_COMPLETE); } } -static inline void SMTPTransactionCompleteTS(SMTPState *state) +static inline void SMTPTransactionCompleteTS(SMTPTransaction *tx) { - DEBUG_VALIDATE_BUG_ON(state->curr_tx == NULL); - if (state->curr_tx) { - SMTPSetProgressTS(state->curr_tx, SMTP_REQUEST_COMPLETE); + DEBUG_VALIDATE_BUG_ON(tx == NULL); + if (tx) { + SMTPSetProgressTS(tx, SMTP_REQUEST_COMPLETE); SCLogDebug("marked tx as ts complete"); } } -static inline void SMTPTransactionCompleteTC(SMTPState *state) +static inline void SMTPTransactionCompleteTC(SMTPTransaction *tx) { - DEBUG_VALIDATE_BUG_ON(state->curr_tx == NULL); - if (state->curr_tx) { - SMTPSetProgressTC(state->curr_tx, SMTP_RESPONSE_COMPLETE); + DEBUG_VALIDATE_BUG_ON(tx == NULL); + if (tx) { + SMTPSetProgressTC(tx, SMTP_RESPONSE_COMPLETE); SCLogDebug("marked tx as tc complete"); } } @@ -807,7 +826,7 @@ static int SMTPProcessCommandDATA( * acknowledged with a reply. We insert a dummy command to * the command buffer to be used by the reply handler to match * the reply received */ - SMTPInsertCommandIntoCommandBuffer(SMTP_COMMAND_DATA_MODE, state); + SMTPInsertCommandIntoCommandBuffer(state, SMTP_COMMAND_DATA_MODE, tx); if (smtp_config.raw_extraction) { /* we use this as the signal that message data is complete. */ FileCloseFile(&tx->files_ts, &smtp_config.sbcfg, NULL, 0, 0); @@ -819,7 +838,7 @@ static int SMTPProcessCommandDATA( FileFlowToFlags(f, STREAM_TOSERVER)); } } - SMTPTransactionCompleteTS(state); + SMTPTransactionCompleteTS(tx); } else if (smtp_config.raw_extraction) { // message not over, store the line. This is a substitution of // ProcessDataChunk @@ -916,8 +935,35 @@ static int SMTPProcessCommandDATA( static inline bool IsReplyToCommand(const SMTPState *state, const uint8_t cmd) { - return (state->cmds_idx < state->cmds_buffer_len && - state->cmds[state->cmds_idx] == cmd); + return (state->cmds_idx < state->cmds_cnt && state->cmds[state->cmds_idx] == cmd); +} + +static SMTPTransaction *SMTPStateGetTxById(SMTPState *state, uint64_t tx_id) +{ + SMTPTransaction *tx = NULL; + TAILQ_FOREACH (tx, &state->tx_list, next) { + if (tx->tx_id == tx_id) { + return tx; + } + if (tx->tx_id > tx_id) { + break; + } + } + return NULL; +} + +static SMTPTransaction *SMTPGetReplyTx(SMTPState *state) +{ + if (state->cmds_idx >= state->cmds_cnt) { + return state->curr_tx; + } + + /* a command with no owning tx, or whose tx is gone, must not resolve + * to another tx */ + if (state->cmds_tx_ids[state->cmds_idx] == SMTP_NO_TX_ID) { + return NULL; + } + return SMTPStateGetTxById(state, state->cmds_tx_ids[state->cmds_idx]); } static int SMTPProcessReply( @@ -930,8 +976,9 @@ static int SMTPProcessReply( return 0; // to continue processing further } - if (state->curr_tx) { - state->curr_tx->tx_data.updated_tc = true; + SMTPTransaction *reply_tx = SMTPGetReplyTx(state); + if (reply_tx != NULL) { + reply_tx->tx_data.updated_tc = true; } /* the reply code has to contain at least 3 bytes, to hold the 3 digit * reply code */ @@ -1002,8 +1049,8 @@ static int SMTPProcessReply( if (!SCAppLayerRequestProtocolTLSUpgrade(f)) { SMTPSetEvent(state, SMTP_DECODER_EVENT_FAILED_PROTOCOL_CHANGE); } - if (state->curr_tx) { - SMTPTransactionComplete(state); + if (reply_tx) { + SMTPTransactionComplete(reply_tx); } } else { /* decoder event */ @@ -1011,7 +1058,7 @@ static int SMTPProcessReply( } } else if (IsReplyToCommand(state, SMTP_COMMAND_DATA)) { if (reply_code == SMTP_REPLY_354) { - SMTPSetProgressTC(state->curr_tx, SMTP_RESPONSE_DATA); + SMTPSetProgressTC(reply_tx, SMTP_RESPONSE_DATA); /* Next comes the mail for the DATA command in toserver direction */ state->parser_state |= SMTP_PARSER_STATE_COMMAND_DATA_MODE; } else { @@ -1023,15 +1070,15 @@ static int SMTPProcessReply( SMTPSetEvent(state, SMTP_DECODER_EVENT_DATA_COMMAND_REJECTED); } } else if (IsReplyToCommand(state, SMTP_COMMAND_BDAT)) { - SMTPSetProgressTC(state->curr_tx, SMTP_RESPONSE_DATA); + SMTPSetProgressTC(reply_tx, SMTP_RESPONSE_DATA); } else if (IsReplyToCommand(state, SMTP_COMMAND_DATA_MODE)) { if (!(state->parser_state & SMTP_PARSER_STATE_PARSING_MULTILINE_REPLY)) { - SMTPTransactionCompleteTC(state); + SMTPTransactionCompleteTC(reply_tx); } } else if (IsReplyToCommand(state, SMTP_COMMAND_RSET)) { - if (reply_code == SMTP_REPLY_250 && state->curr_tx && + if (reply_code == SMTP_REPLY_250 && reply_tx && !(state->parser_state & SMTP_PARSER_STATE_PARSING_MULTILINE_REPLY)) { - SMTPTransactionComplete(state); + SMTPTransactionComplete(reply_tx); } } else { /* we don't care for any other command for now */ @@ -1230,7 +1277,7 @@ static int SMTPProcessRequest( return 0; } if (state->curr_tx == NULL || - (SMTPTransactionIsComplete(state->curr_tx) && !NoNewTx(state, line))) { + (SMTPTransactionRequestIsComplete(state->curr_tx) && !NoNewTx(state, line))) { tx = SMTPTransactionCreate(state); if (tx == NULL) return -1; @@ -1336,7 +1383,7 @@ static int SMTPProcessRequest( /* Every command is inserted into a command buffer, to be matched * against reply(ies) sent by the server */ - if (SMTPInsertCommandIntoCommandBuffer(state->current_command, state) == -1) { + if (SMTPInsertCommandIntoCommandBuffer(state, state->current_command, tx) == -1) { SCReturnInt(-1); } @@ -1586,6 +1633,12 @@ void *SMTPStateAlloc(void *orig_state, AppProto proto_orig) SCFree(smtp_state); return NULL; } + smtp_state->cmds_tx_ids = SCMalloc(sizeof(uint64_t) * SMTP_COMMAND_BUFFER_STEPS); + if (smtp_state->cmds_tx_ids == NULL) { + SCFree(smtp_state->cmds); + SCFree(smtp_state); + return NULL; + } smtp_state->cmds_buffer_len = SMTP_COMMAND_BUFFER_STEPS; TAILQ_INIT(&smtp_state->tx_list); @@ -1683,6 +1736,9 @@ static void SMTPStateFree(void *p) if (smtp_state->cmds != NULL) { SCFree(smtp_state->cmds); } + if (smtp_state->cmds_tx_ids != NULL) { + SCFree(smtp_state->cmds_tx_ids); + } if (smtp_state->helo) { SCFree(smtp_state->helo); @@ -4327,6 +4383,7 @@ static int SMTPParserTest14(void) FLOW_DESTROY(&f); return result; } + #endif /* UNITTESTS */ void SMTPParserRegisterTests(void) diff --git a/src/app-layer-smtp.h b/src/app-layer-smtp.h index 7dd05d1235eb..c455eca777a3 100644 --- a/src/app-layer-smtp.h +++ b/src/app-layer-smtp.h @@ -152,6 +152,8 @@ typedef struct SMTPState_ { * stored command in the buffer to match the reply(ies) with the command */ /** the command buffer */ uint8_t *cmds; + /** tx id for each stored command */ + uint64_t *cmds_tx_ids; /** the buffer length */ uint16_t cmds_buffer_len; /** no of commands stored in the above buffer */ From fddc9e208f0781aa93d9691e4705ad3515ee85dc Mon Sep 17 00:00:00 2001 From: Jason Ish Date: Wed, 8 Jul 2026 16:40:25 -0600 Subject: [PATCH 04/69] smtp: don't create transaction for trailing quit Also ensures that a quit or rset without a helo still creates a tx. Ticket: #8728 (cherry picked from commit 842b14ee1f716874a134cd9266944978b22a59c2) --- src/app-layer-smtp.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/app-layer-smtp.c b/src/app-layer-smtp.c index 84072aeb0d1e..8ef9f6031913 100644 --- a/src/app-layer-smtp.c +++ b/src/app-layer-smtp.c @@ -95,6 +95,7 @@ /* All other commands are represented by this var */ #define SMTP_COMMAND_OTHER_CMD 5 #define SMTP_COMMAND_RSET 6 +#define SMTP_COMMAND_QUIT 7 #define SMTP_DEFAULT_MAX_TX 256 @@ -1080,6 +1081,11 @@ static int SMTPProcessReply( !(state->parser_state & SMTP_PARSER_STATE_PARSING_MULTILINE_REPLY)) { SMTPTransactionComplete(reply_tx); } + } else if (IsReplyToCommand(state, SMTP_COMMAND_QUIT)) { + if (reply_code == SMTP_REPLY_221 && reply_tx && + !(state->parser_state & SMTP_PARSER_STATE_PARSING_MULTILINE_REPLY)) { + SMTPTransactionComplete(reply_tx); + } } else { /* we don't care for any other command for now */ } @@ -1276,8 +1282,9 @@ static int SMTPProcessRequest( if (line->len == 0 && line->delim_len == 0) { return 0; } - if (state->curr_tx == NULL || - (SMTPTransactionRequestIsComplete(state->curr_tx) && !NoNewTx(state, line))) { + const bool no_new_tx = NoNewTx(state, line); + if ((state->curr_tx == NULL && (state->tx_cnt == 0 || !no_new_tx)) || + (SMTPTransactionRequestIsComplete(state->curr_tx) && !no_new_tx)) { tx = SMTPTransactionCreate(state); if (tx == NULL) return -1; @@ -1293,7 +1300,9 @@ static int SMTPProcessRequest( if (frame != NULL && state->curr_tx) { AppLayerFrameSetTxId(frame, state->curr_tx->tx_id); } - tx->tx_data.updated_ts = true; + if (tx != NULL) { + tx->tx_data.updated_ts = true; + } state->toserver_data_count += (line->len + line->delim_len); @@ -1377,6 +1386,8 @@ static int SMTPProcessRequest( // Resets chunk index in case of connection reuse state->bdat_chunk_idx = 0; state->current_command = SMTP_COMMAND_RSET; + } else if (line->len >= 4 && SCMemcmpLowercase("quit", line->buf, 4) == 0) { + state->current_command = SMTP_COMMAND_QUIT; } else { state->current_command = SMTP_COMMAND_OTHER_CMD; } @@ -2851,7 +2862,7 @@ static int SMTPParserTest02(void) goto end; } if (smtp_state->cmds_cnt != 1 || smtp_state->cmds_idx != 0 || - smtp_state->cmds[0] != SMTP_COMMAND_OTHER_CMD || + smtp_state->cmds[0] != SMTP_COMMAND_QUIT || smtp_state->parser_state != SMTP_PARSER_STATE_FIRST_REPLY_SEEN) { printf("smtp parser in inconsistent state\n"); goto end; @@ -3333,7 +3344,7 @@ static int SMTPParserTest05(void) goto end; } if (smtp_state->cmds_cnt != 1 || smtp_state->cmds_idx != 0 || - smtp_state->cmds[0] != SMTP_COMMAND_OTHER_CMD || + smtp_state->cmds[0] != SMTP_COMMAND_QUIT || smtp_state->parser_state != (SMTP_PARSER_STATE_FIRST_REPLY_SEEN | SMTP_PARSER_STATE_PIPELINING_SERVER)) { printf("smtp parser in inconsistent state\n"); @@ -4356,7 +4367,7 @@ static int SMTPParserTest14(void) goto end; } if (smtp_state->cmds_cnt != 1 || smtp_state->cmds_idx != 0 || - smtp_state->cmds[0] != SMTP_COMMAND_OTHER_CMD || + smtp_state->cmds[0] != SMTP_COMMAND_QUIT || smtp_state->parser_state != SMTP_PARSER_STATE_FIRST_REPLY_SEEN) { printf("smtp parser in inconsistent state l.%d\n", __LINE__); goto end; From 2f075e8c60150a1f2cf7c598725d30d27b577eec Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Sat, 13 Jun 2026 09:55:41 +0200 Subject: [PATCH 05/69] htp: remove duplicate entries in the event table (cherry picked from commit d81be73ba305018818c7267552266ee6e0ae3e0f) --- src/app-layer-htp.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/app-layer-htp.c b/src/app-layer-htp.c index 88050bd29967..b1a18a5ff71e 100644 --- a/src/app-layer-htp.c +++ b/src/app-layer-htp.c @@ -165,19 +165,13 @@ SCEnumCharMap http_decoder_event_table[] = { "CONTENT_LENGTH_EXTRA_DATA_END", HTP_LOG_CODE_CONTENT_LENGTH_EXTRA_DATA_END, }, - { - "CONTENT_LENGTH_EXTRA_DATA_END", - HTP_LOG_CODE_CONTENT_LENGTH_EXTRA_DATA_END, - }, { "SWITCHING_PROTO_WITH_CONTENT_LENGTH", HTP_LOG_CODE_SWITCHING_PROTO_WITH_CONTENT_LENGTH }, { "DEFORMED_EOL", HTP_LOG_CODE_DEFORMED_EOL }, { "PARSER_STATE_ERROR", HTP_LOG_CODE_PARSER_STATE_ERROR }, { "MISSING_OUTBOUND_TRANSACTION_DATA", HTP_LOG_CODE_MISSING_OUTBOUND_TRANSACTION_DATA }, { "MISSING_INBOUND_TRANSACTION_DATA", HTP_LOG_CODE_MISSING_INBOUND_TRANSACTION_DATA }, - { "MISSING_INBOUND_TRANSACTION_DATA", HTP_LOG_CODE_MISSING_INBOUND_TRANSACTION_DATA }, { "ZERO_LENGTH_DATA_CHUNKS", HTP_LOG_CODE_ZERO_LENGTH_DATA_CHUNKS }, { "REQUEST_LINE_UNKNOWN_METHOD", HTP_LOG_CODE_REQUEST_LINE_UNKNOWN_METHOD }, - { "REQUEST_LINE_UNKNOWN_METHOD", HTP_LOG_CODE_REQUEST_LINE_UNKNOWN_METHOD }, { "REQUEST_LINE_UNKNOWN_METHOD_NO_PROTOCOL", HTP_LOG_CODE_REQUEST_LINE_UNKNOWN_METHOD_NO_PROTOCOL }, { "REQUEST_LINE_UNKNOWN_METHOD_INVALID_PROTOCOL", From 56b3ecefbcdcb7dea8bca6635947864eed166d80 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Mon, 8 Jun 2026 20:33:19 +0200 Subject: [PATCH 06/69] detect/file: remove unused registration fields (cherry picked from commit 0e3c946836ce7fb9b5f5ae5755bf87dc8a25081a) --- src/detect-file-data.c | 10 ++++------ src/detect-file-data.h | 4 ---- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/detect-file-data.c b/src/detect-file-data.c index 77f68de01ca1..185680f721bd 100644 --- a/src/detect-file-data.c +++ b/src/detect-file-data.c @@ -130,17 +130,15 @@ void DetectFileRegisterFileProtocols(DetectFileHandlerTableElmt *reg) if (direction & SIG_FLAG_TOCLIENT) { DetectAppLayerMpmRegister(reg->name, SIG_FLAG_TOCLIENT, reg->priority, reg->PrefilterFn, - reg->GetData, al_protocols[i].alproto, al_protocols[i].to_client_progress); + NULL, al_protocols[i].alproto, al_protocols[i].to_client_progress); DetectAppLayerInspectEngineRegister(reg->name, al_protocols[i].alproto, - SIG_FLAG_TOCLIENT, al_protocols[i].to_client_progress, reg->Callback, - reg->GetData); + SIG_FLAG_TOCLIENT, al_protocols[i].to_client_progress, reg->Callback, NULL); } if (direction & SIG_FLAG_TOSERVER) { DetectAppLayerMpmRegister(reg->name, SIG_FLAG_TOSERVER, reg->priority, reg->PrefilterFn, - reg->GetData, al_protocols[i].alproto, al_protocols[i].to_server_progress); + NULL, al_protocols[i].alproto, al_protocols[i].to_server_progress); DetectAppLayerInspectEngineRegister(reg->name, al_protocols[i].alproto, - SIG_FLAG_TOSERVER, al_protocols[i].to_server_progress, reg->Callback, - reg->GetData); + SIG_FLAG_TOSERVER, al_protocols[i].to_server_progress, reg->Callback, NULL); } } } diff --git a/src/detect-file-data.h b/src/detect-file-data.h index e78f2eb8e11c..d31478fa0fd6 100644 --- a/src/detect-file-data.h +++ b/src/detect-file-data.h @@ -34,10 +34,6 @@ typedef struct DetectFileHandlerTableElmt_ { int priority; PrefilterRegisterFunc PrefilterFn; InspectEngineFuncPtr Callback; - InspectionBufferGetDataPtr GetData; - int al_protocols[MAX_DETECT_ALPROTO_CNT]; - int tx_progress; - int progress; } DetectFileHandlerTableElmt; void DetectFileRegisterFileProtocols(DetectFileHandlerTableElmt *entry); From d8b869f9525cdc71b5fb0873cf647a3b403c5708 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Sat, 13 Jun 2026 13:34:04 +0200 Subject: [PATCH 07/69] detect/file.data: reduce scope for MPM datatype (cherry picked from commit 0123cfd9ca4efce4b2ae1e441f2fce0af93c70c5) --- src/detect-file-data.c | 7 +++++++ src/detect-file-data.h | 7 ------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/detect-file-data.c b/src/detect-file-data.c index 185680f721bd..439a707bc7ce 100644 --- a/src/detect-file-data.c +++ b/src/detect-file-data.c @@ -525,6 +525,13 @@ uint8_t DetectEngineInspectFiledata(DetectEngineCtx *de_ctx, DetectEngineThreadC return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; } +typedef struct PrefilterMpmFiledata { + int list_id; + int base_list_id; + const MpmCtx *mpm_ctx; + const DetectEngineTransforms *transforms; +} PrefilterMpmFiledata; + /** \brief Filedata Filedata Mpm prefilter callback * * \param det_ctx detection engine thread ctx diff --git a/src/detect-file-data.h b/src/detect-file-data.h index d31478fa0fd6..635635d8de06 100644 --- a/src/detect-file-data.h +++ b/src/detect-file-data.h @@ -40,13 +40,6 @@ void DetectFileRegisterFileProtocols(DetectFileHandlerTableElmt *entry); /* File registration table */ extern DetectFileHandlerTableElmt filehandler_table[DETECT_TBLSIZE_STATIC]; -typedef struct PrefilterMpmFiledata { - int list_id; - int base_list_id; - const MpmCtx *mpm_ctx; - const DetectEngineTransforms *transforms; -} PrefilterMpmFiledata; - uint8_t DetectEngineInspectFiledata(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, const DetectEngineAppInspectionEngine *engine, const Signature *s, Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id); From 628b0b2c3b08c636dbcc2800d2f9689fe0dbf0e2 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Wed, 10 Jun 2026 06:55:06 +0200 Subject: [PATCH 08/69] detect/parse: tighten hook parsing Don't allow trailing : (cherry picked from commit 7fe8f63a892d5199a869c1ca17172cc7bad7a7ba) --- src/detect-parse.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/detect-parse.c b/src/detect-parse.c index 6320ad40ab06..6ea964275314 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -1391,12 +1391,12 @@ static int SigParseProto(Signature *s, const char *protostr) bool has_hook = strchr(proto, ':') != NULL; if (has_hook) { - char *xsaveptr = NULL; - p = strtok_r(proto, ":", &xsaveptr); - h = strtok_r(NULL, ":", &xsaveptr); + char *rem = NULL; + p = strtok_r(proto, ":", &rem); + h = rem; SCLogDebug("p: '%s' h: '%s'", p, h); } - if (p == NULL) { + if (p == NULL || strlen(p) == 0) { SCLogError("invalid protocol specification '%s'", proto); return -1; } @@ -1411,6 +1411,11 @@ static int SigParseProto(Signature *s, const char *protostr) AppLayerProtoDetectSupportedIpprotos(s->alproto, s->proto.proto); if (h) { + if (strlen(h) == 0) { + SCLogError("invalid protocol specification '%s'", proto); + return -1; + } + /* FW hook LTE mode */ SCLogDebug("hook '%s'", h); if (*h == '<') { From 52577dfae0be52f617f79b69e1071eb016d26312 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Fri, 12 Jun 2026 14:57:08 +0200 Subject: [PATCH 09/69] app-layer: minor code cleanup for GetStateProgress It used the alstate name where it meant tx. (cherry picked from commit 280a1e5a59dbc5441aec28d95877af0a87ae7a19) --- src/app-layer-parser.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/app-layer-parser.c b/src/app-layer-parser.c index 717aa4882599..1734840a149d 100644 --- a/src/app-layer-parser.c +++ b/src/app-layer-parser.c @@ -1077,17 +1077,15 @@ static inline int StateGetProgressCompletionStatus(const AppProto alproto, const * * If the stream is disrupted, we return the 'completion' value. */ -int AppLayerParserGetStateProgress(uint8_t ipproto, AppProto alproto, - void *alstate, uint8_t flags) +int AppLayerParserGetStateProgress(uint8_t ipproto, AppProto alproto, void *tx, uint8_t flags) { SCEnter(); int r; if (unlikely(IS_DISRUPTED(flags))) { r = StateGetProgressCompletionStatus(alproto, flags); } else { - uint8_t direction = flags & (STREAM_TOCLIENT | STREAM_TOSERVER); - r = alp_ctx.ctxs[alproto][FlowGetProtoMapping(ipproto)].StateGetProgress( - alstate, direction); + const uint8_t direction = flags & (STREAM_TOCLIENT | STREAM_TOSERVER); + r = alp_ctx.ctxs[alproto][FlowGetProtoMapping(ipproto)].StateGetProgress(tx, direction); } SCReturnInt(r); } From df4bacc06ff34f73ffdd1bf638093eee7788ab62 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Fri, 12 Jun 2026 20:58:55 +0200 Subject: [PATCH 10/69] app-layer/parser: remove misleading comment Fixes: 833a738dd142 ("http: fail tx creation if we cannot allocate user data") (cherry picked from commit 5a770adc273c8de25cacc95ce791b216173f577a) --- src/app-layer-parser.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app-layer-parser.c b/src/app-layer-parser.c index 1734840a149d..c0a1a21b45d4 100644 --- a/src/app-layer-parser.c +++ b/src/app-layer-parser.c @@ -816,7 +816,6 @@ void AppLayerParserSetTransactionInspectId(const Flow *f, AppLayerParserState *p if (state_progress < state_done_progress) break; - /* txd can be NULL for HTTP sessions where the user data alloc failed */ AppLayerTxData *txd = AppLayerParserGetTxData(ipproto, alproto, tx); const uint8_t inspected_flag = (flags & STREAM_TOSERVER) ? APP_LAYER_TX_INSPECTED_TS : APP_LAYER_TX_INSPECTED_TC; From b7c86362d9c985078adf853bd4ef424c50e2461c Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Sat, 13 Jun 2026 14:19:52 +0200 Subject: [PATCH 11/69] app-layer: cleanup inspect id getter Since pstate can't be NULL, remove the conditional logic. (cherry picked from commit f4f521272adc9fac2da1efb84ae8516290dec79d) --- src/app-layer-parser.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/app-layer-parser.c b/src/app-layer-parser.c index c0a1a21b45d4..a7e68bbb4162 100644 --- a/src/app-layer-parser.c +++ b/src/app-layer-parser.c @@ -725,12 +725,8 @@ void AppLayerParserSetTransactionLogId(AppLayerParserState *pstate, uint64_t tx_ uint64_t AppLayerParserGetTransactionInspectId(AppLayerParserState *pstate, uint8_t direction) { SCEnter(); - - if (pstate != NULL) - SCReturnCT(pstate->inspect_id[(direction & STREAM_TOSERVER) ? 0 : 1], "uint64_t"); - - DEBUG_VALIDATE_BUG_ON(1); - SCReturnCT(0ULL, "uint64_t"); + DEBUG_VALIDATE_BUG_ON(pstate == NULL); + SCReturnCT(pstate->inspect_id[(direction & STREAM_TOSERVER) ? 0 : 1], "uint64_t"); } inline uint8_t AppLayerParserGetTxDetectProgress(AppLayerTxData *txd, const uint8_t dir) From 46d39d001e630ca64108b0561f7fa17ddcfb010e Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Thu, 25 Jun 2026 21:18:29 +0200 Subject: [PATCH 12/69] http/xff: harden code against http in detection-only (cherry picked from commit c1392d99a30c742f73dd74c89637efcf6fdb62ab) --- src/app-layer-htp-xff.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/app-layer-htp-xff.c b/src/app-layer-htp-xff.c index 24c6c3153679..2a9bd18fec21 100644 --- a/src/app-layer-htp-xff.c +++ b/src/app-layer-htp-xff.c @@ -182,17 +182,14 @@ int HttpXFFGetIPFromTx( */ int HttpXFFGetIP(const Flow *f, HttpXFFCfg *xff_cfg, char *dstbuf, int dstbuflen) { - HtpState *htp_state = NULL; - uint64_t tx_id = AppLayerParserGetMinId(f->alparser); - uint64_t total_txs = 0; - - htp_state = (HtpState *)FlowGetAppState(f); + HtpState *htp_state = (HtpState *)FlowGetAppState(f); if (htp_state == NULL) { SCLogDebug("no http state, XFF IP cannot be retrieved"); goto end; } - total_txs = AppLayerParserGetTxCnt(f, htp_state); + uint64_t tx_id = AppLayerParserGetMinId(f->alparser); + const uint64_t total_txs = AppLayerParserGetTxCnt(f, htp_state); AppLayerGetTxIteratorFunc IterFunc = AppLayerGetTxIterator(f->proto, f->alproto); AppLayerGetTxIterState state; memset(&state, 0, sizeof(state)); From 5011437daf37825e98361e261bf4f0bd8c262f1c Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Fri, 26 Jun 2026 10:11:27 +0200 Subject: [PATCH 13/69] detect/alert: split append func per packet/tx (cherry picked from commit 695c9d8d20e533da2848ba46c5f48c50e670adbb) --- src/detect-engine-alert.c | 35 ++++++++++++++++++++++++++++-- src/detect-engine-alert.h | 8 +++++-- src/detect-engine-iponly.c | 2 +- src/detect.c | 44 ++++++++++++++++++++------------------ 4 files changed, 63 insertions(+), 26 deletions(-) diff --git a/src/detect-engine-alert.c b/src/detect-engine-alert.c index 9beedf93ea4f..29fb424eca97 100644 --- a/src/detect-engine-alert.c +++ b/src/detect-engine-alert.c @@ -390,8 +390,8 @@ static inline PacketAlert PacketAlertSet( /** * \brief Append signature to local packet alert queue for later preprocessing */ -void AlertQueueAppend(DetectEngineThreadCtx *det_ctx, const Signature *s, Packet *p, uint64_t tx_id, - uint8_t alert_flags) +static void AlertQueueAppend(DetectEngineThreadCtx *det_ctx, const Signature *s, Packet *p, + uint64_t tx_id, uint8_t alert_flags) { /* first time we see a drop action signature, set that in the packet */ /* we do that even before inserting into the queue, so we save it even if appending fails */ @@ -416,6 +416,37 @@ void AlertQueueAppend(DetectEngineThreadCtx *det_ctx, const Signature *s, Packet det_ctx->alert_queue_size++; } +/** + * \brief Append signature to local packet alert queue for later preprocessing + */ +void AlertQueueAppendAppTx(DetectEngineThreadCtx *det_ctx, const Signature *s, Packet *p, + uint64_t tx_id, uint8_t alert_flags) +{ + alert_flags |= (PACKET_ALERT_FLAG_TX | PACKET_ALERT_FLAG_STATE_MATCH); + return AlertQueueAppend(det_ctx, s, p, tx_id, alert_flags); +} + +/** + * \brief Append signature to local packet alert queue for later preprocessing + * This does not automatically set PACKET_ALERT_FLAG_STATE_MATCH as this + * comes from the packet alert path. + */ +void AlertQueueAppendAppTxFromPacket(DetectEngineThreadCtx *det_ctx, const Signature *s, Packet *p, + uint64_t tx_id, uint8_t alert_flags) +{ + alert_flags |= PACKET_ALERT_FLAG_TX; + return AlertQueueAppend(det_ctx, s, p, tx_id, alert_flags); +} + +/** + * \brief Append signature to local packet alert queue for later preprocessing + */ +void AlertQueueAppendPacket( + DetectEngineThreadCtx *det_ctx, const Signature *s, Packet *p, uint8_t alert_flags) +{ + return AlertQueueAppend(det_ctx, s, p, PACKET_ALERT_NOTX, alert_flags); +} + /** \internal * \brief sort helper for sorting alerts by priority * diff --git a/src/detect-engine-alert.h b/src/detect-engine-alert.h index 1a8c8b81c3ae..259444d5e9f7 100644 --- a/src/detect-engine-alert.h +++ b/src/detect-engine-alert.h @@ -30,8 +30,12 @@ void AlertQueueInit(DetectEngineThreadCtx *det_ctx); void AlertQueueFree(DetectEngineThreadCtx *det_ctx); -void AlertQueueAppend(DetectEngineThreadCtx *det_ctx, const Signature *s, Packet *p, uint64_t tx_id, - uint8_t alert_flags); +void AlertQueueAppendPacket( + DetectEngineThreadCtx *det_ctx, const Signature *s, Packet *p, uint8_t alert_flags); +void AlertQueueAppendAppTxFromPacket(DetectEngineThreadCtx *det_ctx, const Signature *s, Packet *p, + uint64_t tx_id, uint8_t alert_flags); +void AlertQueueAppendAppTx(DetectEngineThreadCtx *det_ctx, const Signature *s, Packet *p, + uint64_t tx_id, uint8_t alert_flags); void PacketAlertFinalize(const DetectEngineCtx *, DetectEngineThreadCtx *, Packet *); #ifdef UNITTESTS int PacketAlertCheck(Packet *, uint32_t); diff --git a/src/detect-engine-iponly.c b/src/detect-engine-iponly.c index b498103b388f..a2a9ee8fbfb3 100644 --- a/src/detect-engine-iponly.c +++ b/src/detect-engine-iponly.c @@ -1109,7 +1109,7 @@ void IPOnlyMatchPacket(ThreadVars *tv, const DetectEngineCtx *de_ctx, } } } - AlertQueueAppend(det_ctx, s, p, 0, 0); + AlertQueueAppendPacket(det_ctx, s, p, 0); } } } diff --git a/src/detect.c b/src/detect.c index c88cd56835b5..4636f3158c00 100644 --- a/src/detect.c +++ b/src/detect.c @@ -717,7 +717,7 @@ static uint8_t DetectRunApplyPacketPolicy(const DetectEngineCtx *de_ctx, } Signature *s = de_ctx->fw_policies->pkt_policy_signatures[policy]; if (s != NULL) { - AlertQueueAppend(det_ctx, s, p, 0, PACKET_ALERT_FLAG_APPLY_ACTION_TO_PACKET); + AlertQueueAppendPacket(det_ctx, s, p, PACKET_ALERT_FLAG_APPLY_ACTION_TO_PACKET); } return p->action; } @@ -854,7 +854,11 @@ static inline uint8_t DetectRulePacketRules(ThreadVars *const tv, } } } - AlertQueueAppend(det_ctx, s, p, txid, alert_flags); + if (alert_flags & PACKET_ALERT_FLAG_TX) { + AlertQueueAppendAppTx(det_ctx, s, p, txid, alert_flags); + } else { + AlertQueueAppendPacket(det_ctx, s, p, alert_flags); + } if (det_ctx->post_rule_work_queue.len > 0) { /* run post match prefilter engines on work queue */ @@ -1649,8 +1653,7 @@ static inline void DetectRunAppendDefaultAppPolicyAlert(DetectEngineThreadCtx *d det_ctx->de_ctx->fw_policies, alproto, direction, hook); BUG_ON(s == NULL); uint8_t alert_flags = apply_to_packet ? PACKET_ALERT_FLAG_APPLY_ACTION_TO_PACKET : 0; - alert_flags |= PACKET_ALERT_FLAG_TX; - AlertQueueAppend(det_ctx, s, p, tx_id, alert_flags); + AlertQueueAppendAppTx(det_ctx, s, p, tx_id, alert_flags); } } @@ -1983,7 +1986,7 @@ static void DetectRunAppendDefaultAccept(DetectEngineThreadCtx *det_ctx, Packet default_accept.flags = SIG_FLAG_FIREWALL; default_accept.detect_table = DETECT_TABLE_APP_FILTER; // TODO review, hope this makes it last in sorting - AlertQueueAppend(det_ctx, &default_accept, p, 0, PACKET_ALERT_FLAG_APPLY_ACTION_TO_PACKET); + AlertQueueAppendPacket(det_ctx, &default_accept, p, PACKET_ALERT_FLAG_APPLY_ACTION_TO_PACKET); } /** \internal @@ -2137,8 +2140,10 @@ static void DetectRunTxFirewallRuleFullMatch(DetectEngineThreadCtx *det_ctx, con DetectTransaction *tx, struct DetectFirewallAppTxState *fw_state, Flow *f, Packet *p, const uint8_t flow_flags) { - uint8_t alert_flags = (PACKET_ALERT_FLAG_STATE_MATCH | PACKET_ALERT_FLAG_TX); if (s->action & ACTION_ACCEPT) { + /* add alert now, as ApplyAccept may also trigger + * policy matches that could add alerts. */ + SCLogDebug("append alert"); /* see if we need to apply tx/hook accept to the packet. This can be needed * when we've completed the inspection so far for an incomplete tx, and an * accept:tx or accept:hook is the last match.*/ @@ -2146,14 +2151,11 @@ static void DetectRunTxFirewallRuleFullMatch(DetectEngineThreadCtx *det_ctx, con if (fw_accept_to_packet) { SCLogDebug("packet %" PRIu64 ": apply accept to packet", p->pcap_cnt); SCLogDebug("accept:(tx|hook): should be applied to the packet"); - alert_flags |= PACKET_ALERT_FLAG_APPLY_ACTION_TO_PACKET; + AlertQueueAppendAppTx( + det_ctx, s, p, tx->tx_id, PACKET_ALERT_FLAG_APPLY_ACTION_TO_PACKET); + } else { + AlertQueueAppendAppTx(det_ctx, s, p, tx->tx_id, 0); } - SCLogDebug("append alert"); - - /* add alert now, as ApplyAccept may also trigger - * policy matches that could add alerts. */ - AlertQueueAppend(det_ctx, s, p, tx->tx_id, alert_flags); - DetectRunTxFirewallApplyAccept(det_ctx, p, flow_flags, s, tx, fw_state); } else if (s->action & ACTION_DROP) { SCLogDebug("drop packet because of rule with drop action"); @@ -2164,10 +2166,10 @@ static void DetectRunTxFirewallRuleFullMatch(DetectEngineThreadCtx *det_ctx, con f->aux_flags |= FLOW_AUX_ACTION_BY_FIREWALL; } SCLogDebug("append alert"); - AlertQueueAppend(det_ctx, s, p, tx->tx_id, alert_flags); + AlertQueueAppendAppTx(det_ctx, s, p, tx->tx_id, 0); } else { SCLogDebug("append alert"); - AlertQueueAppend(det_ctx, s, p, tx->tx_id, alert_flags); + AlertQueueAppendAppTx(det_ctx, s, p, tx->tx_id, 0); } } @@ -2544,8 +2546,7 @@ static void DetectRunTx(ThreadVars *tv, "%p/%" PRIu64 " sig %u (%u) matched", tx.tx_ptr, tx.tx_id, s->id, s->iid); if ((s->flags & SIG_FLAG_FIREWALL) == 0) { - AlertQueueAppend(det_ctx, s, p, tx.tx_id, - (PACKET_ALERT_FLAG_STATE_MATCH | PACKET_ALERT_FLAG_TX)); + AlertQueueAppendAppTx(det_ctx, s, p, tx.tx_id, 0); } else { DetectRunTxFirewallRuleFullMatch(det_ctx, s, &tx, &fw_state, f, p, flow_flags); } @@ -2756,15 +2757,16 @@ static void DetectRunFrames(ThreadVars *tv, DetectEngineCtx *de_ctx, DetectEngin if (r) { /* match */ DetectRunPostMatch(tv, det_ctx, p, s); - - uint8_t alert_flags = (PACKET_ALERT_FLAG_STATE_MATCH | PACKET_ALERT_FLAG_FRAME); det_ctx->frame_id = frame->id; SCLogDebug( "%p/%" PRIi64 " sig %u (%u) matched", frame, frame->id, s->id, s->iid); + const uint8_t alert_flags = + (PACKET_ALERT_FLAG_STATE_MATCH | PACKET_ALERT_FLAG_FRAME); if (frame->flags & FRAME_FLAG_TX_ID_SET) { - alert_flags |= PACKET_ALERT_FLAG_TX; + AlertQueueAppendAppTx(det_ctx, s, p, frame->tx_id, alert_flags); + } else { + AlertQueueAppendPacket(det_ctx, s, p, alert_flags); } - AlertQueueAppend(det_ctx, s, p, frame->tx_id, alert_flags); } } DetectVarProcessList(det_ctx, p->flow, p); From 7b71e22c65cc873f3bf0f7394c9cc10eb34b404c Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Tue, 30 Jun 2026 15:18:45 +0200 Subject: [PATCH 14/69] detect: move packet alert logic into helper (cherry picked from commit 3b351e257401edccc68e6df6c41c8f51ce49ea17) --- src/detect.c | 64 +++++++++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/src/detect.c b/src/detect.c index 4636f3158c00..be673d0713cb 100644 --- a/src/detect.c +++ b/src/detect.c @@ -722,6 +722,41 @@ static uint8_t DetectRunApplyPacketPolicy(const DetectEngineCtx *de_ctx, return p->action; } +/** \internal + * \brief helper for appending a packet alert + * Tries to find (guess) a TX to add to the alert. + */ +static void DetectRulePacketAppendAlert(const DetectEngineCtx *de_ctx, + DetectEngineThreadCtx *det_ctx, const Signature *s, Packet *p, Flow *f, + const uint8_t alert_flags_in) +{ + DEBUG_VALIDATE_BUG_ON(alert_flags_in & PACKET_ALERT_FLAG_TX); + + if (f && f->alstate) { + const uint8_t dir = (p->flowflags & FLOW_PKT_TOCLIENT) ? STREAM_TOCLIENT : STREAM_TOSERVER; + const uint64_t tx_id = AppLayerParserGetTransactionInspectId(f->alparser, dir); + if ((s->alproto != ALPROTO_UNKNOWN && f->proto == IPPROTO_UDP) || + (de_ctx->guess_applayer && IsOnlyTxInDirection(f, tx_id, dir))) { + // if there is a UDP specific app-layer signature, + // or only one live transaction + // try to use the good tx for the packet direction + void *tx_ptr = AppLayerParserGetTx(f->proto, f->alproto, f->alstate, tx_id); + AppLayerTxData *txd = + tx_ptr ? AppLayerParserGetTxData(f->proto, f->alproto, tx_ptr) : NULL; + if (txd && txd->guessed_applayer_logged < de_ctx->guess_applayer_log_limit) { + uint8_t alert_flags = alert_flags_in; + if (f->proto != IPPROTO_UDP) { + alert_flags |= PACKET_ALERT_FLAG_TX_GUESSED; + } + txd->guessed_applayer_logged++; + AlertQueueAppendAppTxFromPacket(det_ctx, s, p, tx_id, alert_flags); + return; + } + } + } + AlertQueueAppendPacket(det_ctx, s, p, alert_flags_in); +} + static inline uint8_t DetectRulePacketRules(ThreadVars *const tv, const DetectEngineCtx *const de_ctx, DetectEngineThreadCtx *const det_ctx, Packet *const p, Flow *const pflow, const DetectRunScratchpad *scratch) @@ -831,34 +866,7 @@ static inline uint8_t DetectRulePacketRules(ThreadVars *const tv, #endif DetectRunPostMatch(tv, det_ctx, p, s); - uint64_t txid = PACKET_ALERT_NOTX; - if (pflow && pflow->alstate) { - uint8_t dir = (p->flowflags & FLOW_PKT_TOCLIENT) ? STREAM_TOCLIENT : STREAM_TOSERVER; - txid = AppLayerParserGetTransactionInspectId(pflow->alparser, dir); - if ((s->alproto != ALPROTO_UNKNOWN && pflow->proto == IPPROTO_UDP) || - (de_ctx->guess_applayer && IsOnlyTxInDirection(pflow, txid, dir))) { - // if there is a UDP specific app-layer signature, - // or only one live transaction - // try to use the good tx for the packet direction - void *tx_ptr = - AppLayerParserGetTx(pflow->proto, pflow->alproto, pflow->alstate, txid); - AppLayerTxData *txd = - tx_ptr ? AppLayerParserGetTxData(pflow->proto, pflow->alproto, tx_ptr) - : NULL; - if (txd && txd->guessed_applayer_logged < de_ctx->guess_applayer_log_limit) { - alert_flags |= PACKET_ALERT_FLAG_TX; - if (pflow->proto != IPPROTO_UDP) { - alert_flags |= PACKET_ALERT_FLAG_TX_GUESSED; - } - txd->guessed_applayer_logged++; - } - } - } - if (alert_flags & PACKET_ALERT_FLAG_TX) { - AlertQueueAppendAppTx(det_ctx, s, p, txid, alert_flags); - } else { - AlertQueueAppendPacket(det_ctx, s, p, alert_flags); - } + DetectRulePacketAppendAlert(de_ctx, det_ctx, s, p, pflow, alert_flags); if (det_ctx->post_rule_work_queue.len > 0) { /* run post match prefilter engines on work queue */ From c3bef9258b652b42baa77ff1ca9b4fa3da010d67 Mon Sep 17 00:00:00 2001 From: Yash Datre Date: Wed, 8 Jul 2026 02:37:03 +0000 Subject: [PATCH 15/69] detect: extend app-layer-protocol to accept a pipe-separated value list Extend the app-layer-protocol keyword to accept a pipe-separated list of protocol values, so a single rule can match any of several protocols: app-layer-protocol:[!]|[|...][,]...; A non-negated list matches when the flow's protocol equals any listed value (OR); a negated list matches when it equals none of them (NOR). The single-value form and the trailing mode qualifier are unchanged. Matching keeps the historical AppProtoEquals() equivalences by default (dns/doh2, http/http1/http2, dcerpc/smb, ...). An `exact` qualifier selects strict identity matching with no equivalences and no http umbrella; it combines with a direction mode in any order. Because a flow is never the generic ALPROTO_HTTP, `http,exact` is rejected at load. Values are expanded once at rule load into an effective match-set bitmask, so the per-packet match is a single bitmask test. Single-value rules remain prefilterable; multi-value rules are excluded from prefiltering and an explicit prefilter on them is rejected. Conflicting keyword combinations (duplicate or overlapping negations, mixed positive/negated) are rejected at load. Engine-analysis reports the effective match set. Ticket: 7705 (cherry picked from commit 43bc2db41e4f7356f6cadb3247750d66e27ea8fe) --- doc/userguide/rules/app-layer.rst | 92 ++- src/detect-app-layer-protocol.c | 895 ++++++++++++++++++++++++------ src/detect-app-layer-protocol.h | 25 + src/detect-engine-analyzer.c | 16 + src/detect-prefilter.c | 11 + 5 files changed, 856 insertions(+), 183 deletions(-) diff --git a/doc/userguide/rules/app-layer.rst b/doc/userguide/rules/app-layer.rst index 26be0a2739dd..e2aedf0b77a9 100644 --- a/doc/userguide/rules/app-layer.rst +++ b/doc/userguide/rules/app-layer.rst @@ -10,7 +10,11 @@ Match on the detected app-layer protocol. Syntax:: - app-layer-protocol:[!](,); + app-layer-protocol:[!][,]...; + app-layer-protocol:[!]|[|...|][,]...; + +Each ```` is either a ```` (at most one, see below) or the +``exact`` option, in any order. Examples:: @@ -21,6 +25,12 @@ Examples:: app-layer-protocol:http,to_server; app-layer-protocol:tls,to_client; app-layer-protocol:http2,final; app-layer-protocol:http1,original; app-layer-protocol:unknown; + app-layer-protocol:unknown|tls; + app-layer-protocol:unknown|tls|http; + app-layer-protocol:!tls|http; + app-layer-protocol:tls|http,either; + app-layer-protocol:dns,exact; + app-layer-protocol:tls|dns,either,exact; A special value 'failed' can be used for matching on flows in which protocol detection failed. This can happen if Suricata doesn't know @@ -42,12 +52,92 @@ By default, (if no mode is specified), the mode is ``direction``. .. note:: when negation is used, like ``!http``, it will not match on the "unknown" state in the flow. +Protocol equivalences and the ``exact`` option +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default a value matches its related protocols as well as itself. For +example ``http`` matches ``http1`` and ``http2``, ``dns`` also matches +``doh2`` (DNS over HTTP/2), and ``dcerpc`` also matches ``smb``. This is the +long-standing behaviour and keeps existing rules working. + +Add the ``exact`` qualifier to match strictly, with no equivalences: the +flow's protocol must equal the configured value exactly. ``exact`` applies to +all values in the list and can be combined with a mode:: + + app-layer-protocol:dns,exact; # matches dns only, not doh2 + app-layer-protocol:tls|dns,either,exact; + +Because ``exact`` disables all equivalences, the generic ``http`` value is not +expanded to ``http1``/``http2`` either. A flow is never classified as the +generic ``http``, so ``app-layer-protocol:http,exact`` can never match and is +rejected at rule load; use ``http1`` or ``http2`` instead. + Here is an example of a rule matching non-http traffic on port 80: .. container:: example-rule alert tcp any any -> any 80 (msg:"non-HTTP traffic over HTTP standard port"; flow:to_server; app-layer-protocol:!http,final; sid:1; ) +Multi-value form +~~~~~~~~~~~~~~~~ + +The ``app-layer-protocol`` keyword also accepts a pipe-separated (``|``) list +of protocol values. A rule matches when the flow's resolved application-layer +protocol equals **any** value in the list (logical OR). + +Syntax:: + + app-layer-protocol:[!]|[|...|](,); + +Using ``|`` for the list keeps the optional trailing ``,`` qualifier +unambiguous, so the single-value ``,`` form is unchanged. + +Examples:: + + app-layer-protocol:unknown|tls; + app-layer-protocol:unknown|tls|http; + app-layer-protocol:tls|http,either; + app-layer-protocol:!tls|http; + +The ``unknown|`` detection-window idiom +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When Suricata has not yet classified a flow's protocol (the "detection +window"), the flow's app-layer protocol is ``unknown``. Once protocol +detection completes, the protocol transitions to its classified value +(e.g., ``tls``, ``http``). Including ``unknown`` in a multi-value list +allows a single rule to cover both the detection window and the confirmed +protocol:: + + app-layer-protocol:unknown|tls; + +This rule matches during the detection window (while the protocol is still +``unknown``) **and** after classification (when the protocol is ``tls``). +If the flow is classified to a protocol not in the list (e.g., ``http``), +the rule stops matching once the protocol is classified; in firewall mode the +flow is then handled by the default policy if no other rule accepts it. + +Negated multi-value (NOR semantics) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When the multi-value form is negated with ``!``, it implements NOR semantics +across the entire list: the rule matches when the resolved application-layer +protocol is **known** AND matches **none** of the listed values. + +Example:: + + app-layer-protocol:!tls|http; + +This matches when the flow's protocol is known and is neither ``tls`` nor +``http`` (e.g., it matches ``dns``, ``ssh``, ``smtp``, etc.). + +.. note:: Negated multi-value rules do not match during the detection window + (when the protocol is still ``unknown``). This prevents false positives + before protocol classification is complete. + +.. note:: The value ``unknown`` cannot appear in a negated list. The parser + rejects ``!unknown`` and ``!unknown|tls`` at rule-load time. + .. _proto-detect-bail-out: Bail out conditions diff --git a/src/detect-app-layer-protocol.c b/src/detect-app-layer-protocol.c index fab270ae6ff6..339ae59ee324 100644 --- a/src/detect-app-layer-protocol.c +++ b/src/detect-app-layer-protocol.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2025 Open Information Security Foundation +/* Copyright (C) 2007-2026 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 @@ -30,6 +30,7 @@ #include "detect-app-layer-protocol.h" #include "app-layer.h" #include "app-layer-parser.h" +#include "app-layer-detect-proto.h" #include "util-debug.h" #include "util-unittest.h" #include "util-unittest-helper.h" @@ -47,11 +48,49 @@ enum { DETECT_ALPROTO_ORIG = 5, }; -typedef struct DetectAppLayerProtocolData_ { - AppProto alproto; - uint8_t negated; - uint8_t mode; -} DetectAppLayerProtocolData; +static void DetectAppLayerProtocolFree(DetectEngineCtx *de_ctx, void *ptr); + +/** \internal + * \brief size in bytes of an alproto bitmask (g_alproto_max bits). */ +static inline uint32_t AlprotoBitmaskSize(void) +{ + return (uint32_t)((g_alproto_max + 7) / 8); +} + +static inline void AlprotoBitmaskSet(uint8_t *bm, AppProto a) +{ + bm[a >> 3] |= (uint8_t)(1u << (a & 7)); +} + +static inline bool AlprotoBitmaskTest(const uint8_t *bm, AppProto a) +{ + return (bm[a >> 3] & (uint8_t)(1u << (a & 7))) != 0; +} + +/** \internal + * \brief Compare a configured value against a flow protocol under the rule's + * matching policy. + * + * By default the historical AppProtoEquals() equivalences apply (dns/doh2, + * http/http1/http2, dcerpc/smb, ...). With the `exact` option the match is a + * strict identity, with no equivalences and no http umbrella. Used to expand + * the match set at rule load and by the single-value prefilter comparator. */ +static inline bool DetectAppLayerProtocolCompare(AppProto sigproto, AppProto alproto, bool exact) +{ + return exact ? (sigproto == alproto) : AppProtoEquals(sigproto, alproto); +} + +/** \internal + * \brief Expand one configured value into the match bitmask: set a bit for + * every flow protocol that should match it. Done once at rule load so + * the per-packet match is a single bitmask test. */ +static void DetectAppLayerProtocolExpand(uint8_t *bm, AppProto sigproto, bool exact) +{ + for (AppProto a = 0; a < g_alproto_max; a++) { + if (DetectAppLayerProtocolCompare(sigproto, a, exact)) + AlprotoBitmaskSet(bm, a); + } +} static int DetectAppLayerProtocolPacketMatch( DetectEngineThreadCtx *det_ctx, @@ -59,7 +98,6 @@ static int DetectAppLayerProtocolPacketMatch( { SCEnter(); - bool r = false; const DetectAppLayerProtocolData *data = (const DetectAppLayerProtocolData *)ctx; /* if the sig is PD-only we only match when PD packet flags are set */ @@ -75,75 +113,57 @@ static int DetectAppLayerProtocolPacketMatch( SCReturnInt(0); } + /* Resolve the flow's alproto for the configured mode. */ + AppProto resolved_alproto = ALPROTO_UNKNOWN; switch (data->mode) { case DETECT_ALPROTO_DIRECTION: - if (data->negated) { - if (p->flowflags & FLOW_PKT_TOSERVER) { - if (f->alproto_ts == ALPROTO_UNKNOWN) - SCReturnInt(0); - r = AppProtoEquals(data->alproto, f->alproto_ts); - } else { - if (f->alproto_tc == ALPROTO_UNKNOWN) - SCReturnInt(0); - r = AppProtoEquals(data->alproto, f->alproto_tc); - } + if (p->flowflags & FLOW_PKT_TOSERVER) { + resolved_alproto = f->alproto_ts; } else { - if (p->flowflags & FLOW_PKT_TOSERVER) { - r = AppProtoEquals(data->alproto, f->alproto_ts); - } else { - r = AppProtoEquals(data->alproto, f->alproto_tc); - } + resolved_alproto = f->alproto_tc; } break; case DETECT_ALPROTO_ORIG: - if (data->negated) { - if (f->alproto_orig == ALPROTO_UNKNOWN) - SCReturnInt(0); - r = AppProtoEquals(data->alproto, f->alproto_orig); - } else { - r = AppProtoEquals(data->alproto, f->alproto_orig); - } + resolved_alproto = f->alproto_orig; break; case DETECT_ALPROTO_FINAL: - if (data->negated) { - if (f->alproto == ALPROTO_UNKNOWN) - SCReturnInt(0); - r = AppProtoEquals(data->alproto, f->alproto); - } else { - r = AppProtoEquals(data->alproto, f->alproto); - } + resolved_alproto = f->alproto; break; case DETECT_ALPROTO_TOSERVER: - if (data->negated) { - if (f->alproto_ts == ALPROTO_UNKNOWN) - SCReturnInt(0); - r = AppProtoEquals(data->alproto, f->alproto_ts); - } else { - r = AppProtoEquals(data->alproto, f->alproto_ts); - } + resolved_alproto = f->alproto_ts; break; case DETECT_ALPROTO_TOCLIENT: - if (data->negated) { - if (f->alproto_tc == ALPROTO_UNKNOWN) - SCReturnInt(0); - r = AppProtoEquals(data->alproto, f->alproto_tc); - } else { - r = AppProtoEquals(data->alproto, f->alproto_tc); - } + resolved_alproto = f->alproto_tc; break; case DETECT_ALPROTO_EITHER: - if (data->negated) { - if (f->alproto_ts == ALPROTO_UNKNOWN && f->alproto_tc == ALPROTO_UNKNOWN) - SCReturnInt(0); - r = AppProtoEquals(data->alproto, f->alproto_tc) || - AppProtoEquals(data->alproto, f->alproto_ts); - } else { - r = AppProtoEquals(data->alproto, f->alproto_tc) || - AppProtoEquals(data->alproto, f->alproto_ts); - } + /* Handled separately below against both directions. */ break; } + + /* Negated rules never match when alproto is still unknown. */ + if (data->negated) { + if (data->mode == DETECT_ALPROTO_EITHER) { + if (f->alproto_ts == ALPROTO_UNKNOWN && f->alproto_tc == ALPROTO_UNKNOWN) { + SCReturnInt(0); + } + } else { + if (resolved_alproto == ALPROTO_UNKNOWN) { + SCReturnInt(0); + } + } + } + + bool r = false; + if (data->mode == DETECT_ALPROTO_EITHER) { + r = AlprotoBitmaskTest(data->alprotos, f->alproto_ts) || + AlprotoBitmaskTest(data->alprotos, f->alproto_tc); + } else { + r = AlprotoBitmaskTest(data->alprotos, resolved_alproto); + } + + /* XOR with negated for NOR semantics. */ r = r ^ data->negated; + if (r) { SCReturnInt(1); } @@ -151,92 +171,322 @@ static int DetectAppLayerProtocolPacketMatch( } #define MAX_ALPROTO_NAME 50 -static DetectAppLayerProtocolData *DetectAppLayerProtocolParse(const char *arg, bool negate) + +/** \internal + * \brief Map a textual mode-qualifier token to its DETECT_ALPROTO_* value. + */ +static int DetectAppLayerProtocolMapModeName(const char *name) { - DetectAppLayerProtocolData *data; - AppProto alproto = ALPROTO_UNKNOWN; + if (strcmp(name, "final") == 0) + return DETECT_ALPROTO_FINAL; + if (strcmp(name, "original") == 0) + return DETECT_ALPROTO_ORIG; + if (strcmp(name, "either") == 0) + return DETECT_ALPROTO_EITHER; + if (strcmp(name, "to_server") == 0) + return DETECT_ALPROTO_TOSERVER; + if (strcmp(name, "to_client") == 0) + return DETECT_ALPROTO_TOCLIENT; + if (strcmp(name, "direction") == 0) + return DETECT_ALPROTO_DIRECTION; + return -1; +} - char alproto_copy[MAX_ALPROTO_NAME]; - const char *sep = strchr(arg, ','); - char *alproto_name; - if (sep && sep - arg < MAX_ALPROTO_NAME) { - strlcpy(alproto_copy, arg, sep - arg + 1); - alproto_name = alproto_copy; - } else { - alproto_name = (char *)arg; +/** \brief Map a DETECT_ALPROTO_* mode value to its textual qualifier. */ +const char *DetectAppLayerProtocolModeName(uint8_t mode) +{ + switch (mode) { + case DETECT_ALPROTO_FINAL: + return "final"; + case DETECT_ALPROTO_ORIG: + return "original"; + case DETECT_ALPROTO_EITHER: + return "either"; + case DETECT_ALPROTO_TOSERVER: + return "to_server"; + case DETECT_ALPROTO_TOCLIENT: + return "to_client"; + case DETECT_ALPROTO_DIRECTION: + default: + return "direction"; + } +} + +/** \brief Fill out[] with the keyword's set protocol values. + * \retval number of values written (capped at max). */ +uint16_t DetectAppLayerProtocolGetValues( + const DetectAppLayerProtocolData *data, AppProto *out, uint16_t max) +{ + uint16_t n = 0; + for (AppProto a = 0; a < g_alproto_max && n < max; a++) { + if (AlprotoBitmaskTest(data->alprotos, a)) + out[n++] = a; + } + return n; +} + +/** \internal + * \brief Build a comma-separated list of supported app-layer protocol names. + */ +static void DetectAppLayerProtocolBuildSupportedList(char *buf, size_t buflen) +{ + if (buflen == 0) + return; + buf[0] = '\0'; + + AppProto alprotos[g_alproto_max]; + AppLayerProtoDetectSupportedAppProtocols(alprotos); + + size_t offset = 0; + for (AppProto a = 0; a < g_alproto_max; a++) { + if (alprotos[a] != 1) + continue; + const char *name = AppProtoToString(a); + if (name == NULL) + continue; + int w = snprintf(buf + offset, buflen - offset, "%s%s", (offset == 0) ? "" : ", ", name); + if (w < 0 || (size_t)w >= buflen - offset) + break; /* truncated; stop appending */ + offset += (size_t)w; + } +} + +/** \internal + * \brief Resolve a single protocol token to its AppProto value. + * \retval 0 on success, -1 on error (logs the reason). */ +static int DetectAppLayerProtocolResolveToken( + const char *token, const char *arg, bool negate, AppProto *out) +{ + size_t tlen = strlen(token); + if (tlen == 0) { + SCLogError("app-layer-protocol keyword value \"%s\" contains an empty token", arg); + return -1; } - if (strcmp(alproto_name, "failed") == 0) { - alproto = ALPROTO_FAILED; - } else if (strcmp(alproto_name, "unknown") == 0) { + if (tlen >= MAX_ALPROTO_NAME) { + SCLogError("app-layer-protocol keyword token \"%s\" in \"%s\" exceeds the " + "maximum token length of %d characters", + token, arg, MAX_ALPROTO_NAME - 1); + return -1; + } + if (strcmp(token, "failed") == 0) { + *out = ALPROTO_FAILED; + return 0; + } + if (strcmp(token, "unknown") == 0) { if (negate) { - SCLogError("app-layer-protocol " - "keyword can't use negation with protocol 'unknown'"); - return NULL; - } - alproto = ALPROTO_UNKNOWN; - } else { - alproto = AppLayerGetProtoByName(alproto_name); - if (alproto == ALPROTO_UNKNOWN) { - SCLogError("app-layer-protocol " - "keyword supplied with unknown protocol \"%s\"", - alproto_name); - return NULL; + SCLogError("app-layer-protocol keyword can't use negation with protocol 'unknown'"); + return -1; } + *out = ALPROTO_UNKNOWN; + return 0; + } + AppProto ap = AppLayerGetProtoByName(token); + if (ap == ALPROTO_UNKNOWN) { + char supported[1024]; + DetectAppLayerProtocolBuildSupportedList(supported, sizeof(supported)); + SCLogError("app-layer-protocol keyword supplied with unknown protocol " + "\"%s\" in \"%s\"; supported protocols: %s", + token, arg, supported); + return -1; + } + *out = ap; + return 0; +} + +static DetectAppLayerProtocolData *DetectAppLayerProtocolParse(const char *arg, bool negate) +{ + if (arg == NULL) { + SCLogError("app-layer-protocol keyword requires a value"); + return NULL; + } + + /* Total-length validation. The limit bounds the on-stack copy below + * (buf[1025]) and is far larger than any realistic protocol value list. */ + size_t arglen = strlen(arg); + if (arglen > 1024) { + SCLogError("app-layer-protocol keyword argument too long (\"%s\"): maximum " + "supported length is 1024 characters", + arg); + return NULL; + } + if (arglen == 0) { + SCLogError("app-layer-protocol keyword value is empty (an empty value list " + "is not permitted)"); + return NULL; } + + char buf[1025]; + strlcpy(buf, arg, sizeof(buf)); + + /* Split the protocol list from the trailing comma-separated qualifiers. + * The list itself is pipe-separated; each qualifier is a direction mode + * (at most one) or the `exact` option, in any order. */ uint8_t mode = DETECT_ALPROTO_DIRECTION; - if (sep) { - if (strcmp(sep + 1, "final") == 0) { - mode = DETECT_ALPROTO_FINAL; - } else if (strcmp(sep + 1, "original") == 0) { - mode = DETECT_ALPROTO_ORIG; - } else if (strcmp(sep + 1, "either") == 0) { - mode = DETECT_ALPROTO_EITHER; - } else if (strcmp(sep + 1, "to_server") == 0) { - mode = DETECT_ALPROTO_TOSERVER; - } else if (strcmp(sep + 1, "to_client") == 0) { - mode = DETECT_ALPROTO_TOCLIENT; - } else if (strcmp(sep + 1, "direction") == 0) { - mode = DETECT_ALPROTO_DIRECTION; - } else { - SCLogError("app-layer-protocol " - "keyword supplied with unknown mode \"%s\"", - sep + 1); - return NULL; + bool exact = false; + char *qualifiers = strchr(buf, ','); + if (qualifiers != NULL) { + *qualifiers = '\0'; + qualifiers++; + bool mode_set = false; + char *q = qualifiers; + while (q != NULL && *q != '\0') { + char *next = strchr(q, ','); + if (next != NULL) + *next++ = '\0'; + if (strcmp(q, "exact") == 0) { + exact = true; + } else { + int m = DetectAppLayerProtocolMapModeName(q); + if (m < 0) { + SCLogError("app-layer-protocol keyword supplied with unknown " + "qualifier \"%s\" in \"%s\"", + q, arg); + return NULL; + } + if (mode_set) { + SCLogError("app-layer-protocol keyword supplied with multiple " + "mode qualifiers in \"%s\"", + arg); + return NULL; + } + mode = (uint8_t)m; + mode_set = true; + } + q = next; } } - data = SCMalloc(sizeof(DetectAppLayerProtocolData)); + DetectAppLayerProtocolData *data = SCCalloc(1, sizeof(*data)); if (unlikely(data == NULL)) return NULL; - data->alproto = alproto; + data->alprotos = SCCalloc(1, AlprotoBitmaskSize()); + if (unlikely(data->alprotos == NULL)) { + SCFree(data); + return NULL; + } data->negated = negate; data->mode = mode; + data->exact = exact; + data->alproto = ALPROTO_UNKNOWN; + + /* Tokenize the protocol list on '|' and expand each value into the + * effective match set under the chosen policy, so the per-packet match is + * a single bitmask test. */ + int value_count = 0; + char *cur = buf; + while (1) { + char *pipe = strchr(cur, '|'); + if (pipe != NULL) + *pipe = '\0'; + + AppProto value; + if (DetectAppLayerProtocolResolveToken(cur, arg, negate, &value) < 0) + goto error; + + /* The generic ALPROTO_HTTP is never a flow's classified protocol, so + * an exact http match can never fire; steer users to http1/http2. */ + if (exact && value == ALPROTO_HTTP) { + SCLogError("app-layer-protocol keyword: 'http' with 'exact' never " + "matches (flows are classified as http1/http2); use " + "http1 or http2"); + goto error; + } + + if (value_count == 0) + data->alproto = value; + DetectAppLayerProtocolExpand(data->alprotos, value, exact); + value_count++; + + if (pipe == NULL) + break; + cur = pipe + 1; + } + + data->is_list = (value_count > 1); + if (data->is_list) + data->alproto = ALPROTO_UNKNOWN; /* lists are not prefilterable: no single-value key */ return data; + +error: + DetectAppLayerProtocolFree(NULL, data); + return NULL; } -static bool HasConflicts(const DetectAppLayerProtocolData *us, - const DetectAppLayerProtocolData *them) +/** + * \brief Check whether two app-layer-protocol SigMatches conflict. + */ +static bool DetectAppLayerProtocolsConflict( + const DetectAppLayerProtocolData *us, const DetectAppLayerProtocolData *them) { - /* mixing negated and non negated is illegal */ - if ((them->negated ^ us->negated) && them->mode == us->mode) - return true; - /* multiple non-negated is illegal */ - if (!us->negated && them->mode == us->mode) - return true; - /* duplicate option */ - if (us->alproto == them->alproto && them->mode == us->mode) + /* Different modes never conflict. */ + if (us->mode != them->mode) + return false; + + /* Both negated under the same mode: only a conflict when the value sets + * intersect. Identical or overlapping negated lists are redundant, while + * disjoint negated lists (e.g. !http; !dns;) are a valid NOR combination. */ + if (us->negated && them->negated) { + for (AppProto a = 0; a < g_alproto_max; a++) { + if (AlprotoBitmaskTest(us->alprotos, a) && AlprotoBitmaskTest(them->alprotos, a)) { + SCLogError("conflicting app-layer-protocol rules: " + "duplicate or overlapping negated entries under the same mode"); + return true; + } + } + return false; + } + + /* Two non-negated under the same mode: always conflict. */ + if (!us->negated && !them->negated) { + SCLogError("conflicting app-layer-protocol rules: " + "multiple non-negated entries under the same mode"); return true; + } + + /* Mixed negation under the same mode: conflict. Collect the intersecting + * values for the error message. */ + char conflict_buf[512]; + size_t buf_offset = 0; + bool has_intersection = false; + + for (AppProto a = 0; a < g_alproto_max; a++) { + if (!AlprotoBitmaskTest(us->alprotos, a) || !AlprotoBitmaskTest(them->alprotos, a)) + continue; + has_intersection = true; + const char *name = AppProtoToString(a); + if (name == NULL) + name = "unknown"; + if (buf_offset > 0 && buf_offset < sizeof(conflict_buf) - 2) { + conflict_buf[buf_offset++] = ','; + conflict_buf[buf_offset++] = ' '; + } + size_t name_len = strlen(name); + if (buf_offset + name_len < sizeof(conflict_buf) - 1) { + memcpy(conflict_buf + buf_offset, name, name_len); + buf_offset += name_len; + } + } + conflict_buf[buf_offset] = '\0'; - /* all good */ - return false; + if (has_intersection) { + SCLogError("conflicting app-layer-protocol rules: " + "can't mix positive match with negated match under the same " + "mode; intersecting protocol value(s): %s", + conflict_buf); + } else { + SCLogError("conflicting app-layer-protocol rules: " + "can't mix positive app-layer-protocol match with negated " + "match or match for 'failed'"); + } + return true; } -static int DetectAppLayerProtocolSetup(DetectEngineCtx *de_ctx, - Signature *s, const char *arg) +static int DetectAppLayerProtocolSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg) { DetectAppLayerProtocolData *data = NULL; + /* Early rejection: rule already bound to a protocol. */ if (s->alproto != ALPROTO_UNKNOWN) { SCLogError("Either we already " "have the rule match on an app layer protocol set through " @@ -250,14 +500,13 @@ static int DetectAppLayerProtocolSetup(DetectEngineCtx *de_ctx, goto error; SigMatch *tsm = s->init_data->smlists[DETECT_SM_LIST_MATCH]; - for ( ; tsm != NULL; tsm = tsm->next) { + for (; tsm != NULL; tsm = tsm->next) { if (tsm->type == DETECT_APP_LAYER_PROTOCOL) { const DetectAppLayerProtocolData *them = (const DetectAppLayerProtocolData *)tsm->ctx; - if (HasConflicts(data, them)) { - SCLogError("can't mix " - "positive app-layer-protocol match with negated " - "match or match for 'failed'."); + if (DetectAppLayerProtocolsConflict(data, them)) { + SCLogError("conflicting app-layer-protocol options detected " + "(see preceding error for details)."); goto error; } } @@ -270,21 +519,25 @@ static int DetectAppLayerProtocolSetup(DetectEngineCtx *de_ctx, return 0; error: - if (data != NULL) - SCFree(data); + DetectAppLayerProtocolFree(de_ctx, data); return -1; } static void DetectAppLayerProtocolFree(DetectEngineCtx *de_ctx, void *ptr) { - SCFree(ptr); + DetectAppLayerProtocolData *data = (DetectAppLayerProtocolData *)ptr; + if (data == NULL) + return; + if (data->alprotos != NULL) + SCFree(data->alprotos); + SCFree(data); } /** \internal * \brief prefilter function for protocol detect matching */ -static void -PrefilterPacketAppProtoMatch(DetectEngineThreadCtx *det_ctx, Packet *p, const void *pectx) +static void PrefilterPacketAppProtoMatch( + DetectEngineThreadCtx *det_ctx, Packet *p, const void *pectx) { const PrefilterPacketHeaderCtx *ctx = pectx; @@ -306,6 +559,7 @@ PrefilterPacketAppProtoMatch(DetectEngineThreadCtx *det_ctx, Packet *p, const vo Flow *f = p->flow; AppProto alproto = ALPROTO_UNKNOWN; bool negated = (bool)ctx->v1.u8[2]; + bool exact = (bool)ctx->v1.u8[4]; switch (ctx->v1.u8[3]) { case DETECT_ALPROTO_DIRECTION: if (p->flowflags & FLOW_PKT_TOSERVER) { @@ -331,15 +585,15 @@ PrefilterPacketAppProtoMatch(DetectEngineThreadCtx *det_ctx, Packet *p, const vo // the one in the signature ctx if (negated) { if (f->alproto_tc != ALPROTO_UNKNOWN && - !AppProtoEquals(ctx->v1.u16[0], f->alproto_tc)) { + !DetectAppLayerProtocolCompare(ctx->v1.u16[0], f->alproto_tc, exact)) { PrefilterAddSids(&det_ctx->pmq, ctx->sigs_array, ctx->sigs_cnt); } else if (f->alproto_ts != ALPROTO_UNKNOWN && - !AppProtoEquals(ctx->v1.u16[0], f->alproto_ts)) { + !DetectAppLayerProtocolCompare(ctx->v1.u16[0], f->alproto_ts, exact)) { PrefilterAddSids(&det_ctx->pmq, ctx->sigs_array, ctx->sigs_cnt); } } else { - if (AppProtoEquals(ctx->v1.u16[0], f->alproto_tc) || - AppProtoEquals(ctx->v1.u16[0], f->alproto_ts)) { + if (DetectAppLayerProtocolCompare(ctx->v1.u16[0], f->alproto_tc, exact) || + DetectAppLayerProtocolCompare(ctx->v1.u16[0], f->alproto_ts, exact)) { PrefilterAddSids(&det_ctx->pmq, ctx->sigs_array, ctx->sigs_cnt); } } @@ -349,12 +603,12 @@ PrefilterPacketAppProtoMatch(DetectEngineThreadCtx *det_ctx, Packet *p, const vo if (negated) { if (alproto != ALPROTO_UNKNOWN) { - if (!AppProtoEquals(ctx->v1.u16[0], alproto)) { + if (!DetectAppLayerProtocolCompare(ctx->v1.u16[0], alproto, exact)) { PrefilterAddSids(&det_ctx->pmq, ctx->sigs_array, ctx->sigs_cnt); } } } else { - if (AppProtoEquals(ctx->v1.u16[0], alproto)) { + if (DetectAppLayerProtocolCompare(ctx->v1.u16[0], alproto, exact)) { PrefilterAddSids(&det_ctx->pmq, ctx->sigs_array, ctx->sigs_cnt); } } @@ -364,18 +618,19 @@ static void PrefilterPacketAppProtoSet(PrefilterPacketHeaderValue *v, void *smctx) { const DetectAppLayerProtocolData *a = smctx; + /* Only single-value rules are prefilterable; alproto is that value. */ v->u16[0] = a->alproto; v->u8[2] = (uint8_t)a->negated; v->u8[3] = a->mode; + v->u8[4] = (uint8_t)a->exact; } static bool PrefilterPacketAppProtoCompare(PrefilterPacketHeaderValue v, void *smctx) { const DetectAppLayerProtocolData *a = smctx; - if (v.u16[0] == a->alproto && v.u8[2] == (uint8_t)a->negated && v.u8[3] == a->mode) - return true; - return false; + return v.u16[0] == a->alproto && v.u8[2] == (uint8_t)a->negated && v.u8[3] == a->mode && + v.u8[4] == (uint8_t)a->exact; } static int PrefilterSetupAppProto(DetectEngineCtx *de_ctx, SigGroupHead *sgh) @@ -387,11 +642,22 @@ static int PrefilterSetupAppProto(DetectEngineCtx *de_ctx, SigGroupHead *sgh) static bool PrefilterAppProtoIsPrefilterable(const Signature *s) { - if (s->type == SIG_TYPE_PDONLY) { - SCLogDebug("prefilter on PD %u", s->id); - return true; + if (s->type != SIG_TYPE_PDONLY) { + return false; } - return false; + + /* Multi-value rules cannot be prefiltered (single-valued bucket key). */ + const SigMatch *sm; + for (sm = s->init_data->smlists[DETECT_SM_LIST_MATCH]; sm != NULL; sm = sm->next) { + if (sm->type == DETECT_APP_LAYER_PROTOCOL) { + const DetectAppLayerProtocolData *data = (const DetectAppLayerProtocolData *)sm->ctx; + if (data->is_list) { + return false; + } + break; + } + } + return true; } void DetectAppLayerProtocolRegister(void) @@ -420,7 +686,7 @@ static int DetectAppLayerProtocolTest01(void) { DetectAppLayerProtocolData *data = DetectAppLayerProtocolParse("http", false); FAIL_IF_NULL(data); - FAIL_IF(data->alproto != ALPROTO_HTTP); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_HTTP)); FAIL_IF(data->negated != 0); DetectAppLayerProtocolFree(NULL, data); PASS; @@ -430,7 +696,7 @@ static int DetectAppLayerProtocolTest02(void) { DetectAppLayerProtocolData *data = DetectAppLayerProtocolParse("http", true); FAIL_IF_NULL(data); - FAIL_IF(data->alproto != ALPROTO_HTTP); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_HTTP)); FAIL_IF(data->negated == 0); DetectAppLayerProtocolFree(NULL, data); PASS; @@ -454,7 +720,7 @@ static int DetectAppLayerProtocolTest03(void) FAIL_IF_NULL(s->init_data->smlists[DETECT_SM_LIST_MATCH]->ctx); data = (DetectAppLayerProtocolData *)s->init_data->smlists[DETECT_SM_LIST_MATCH]->ctx; - FAIL_IF(data->alproto != ALPROTO_HTTP); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_HTTP)); FAIL_IF(data->negated); DetectEngineCtxFree(de_ctx); PASS; @@ -479,7 +745,7 @@ static int DetectAppLayerProtocolTest04(void) data = (DetectAppLayerProtocolData *)s->init_data->smlists[DETECT_SM_LIST_MATCH]->ctx; FAIL_IF_NULL(data); - FAIL_IF(data->alproto != ALPROTO_HTTP); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_HTTP)); FAIL_IF(data->negated == 0); DetectEngineCtxFree(de_ctx); @@ -505,12 +771,12 @@ static int DetectAppLayerProtocolTest05(void) data = (DetectAppLayerProtocolData *)s->init_data->smlists[DETECT_SM_LIST_MATCH]->ctx; FAIL_IF_NULL(data); - FAIL_IF(data->alproto != ALPROTO_HTTP); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_HTTP)); FAIL_IF(data->negated == 0); data = (DetectAppLayerProtocolData *)s->init_data->smlists[DETECT_SM_LIST_MATCH]->next->ctx; FAIL_IF_NULL(data); - FAIL_IF(data->alproto != ALPROTO_SMTP); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_SMTP)); FAIL_IF(data->negated == 0); DetectEngineCtxFree(de_ctx); @@ -591,7 +857,7 @@ static int DetectAppLayerProtocolTest11(void) { DetectAppLayerProtocolData *data = DetectAppLayerProtocolParse("failed", false); FAIL_IF_NULL(data); - FAIL_IF(data->alproto != ALPROTO_FAILED); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_FAILED)); FAIL_IF(data->negated != 0); DetectAppLayerProtocolFree(NULL, data); PASS; @@ -601,7 +867,7 @@ static int DetectAppLayerProtocolTest12(void) { DetectAppLayerProtocolData *data = DetectAppLayerProtocolParse("failed", true); FAIL_IF_NULL(data); - FAIL_IF(data->alproto != ALPROTO_FAILED); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_FAILED)); FAIL_IF(data->negated == 0); DetectAppLayerProtocolFree(NULL, data); PASS; @@ -625,7 +891,7 @@ static int DetectAppLayerProtocolTest13(void) FAIL_IF_NULL(s->init_data->smlists[DETECT_SM_LIST_MATCH]->ctx); data = (DetectAppLayerProtocolData *)s->init_data->smlists[DETECT_SM_LIST_MATCH]->ctx; - FAIL_IF(data->alproto != ALPROTO_FAILED); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_FAILED)); FAIL_IF(data->negated); DetectEngineCtxFree(de_ctx); PASS; @@ -645,7 +911,7 @@ static int DetectAppLayerProtocolTest14(void) FAIL_IF_NULL(s1->init_data->smlists[DETECT_SM_LIST_MATCH]); FAIL_IF_NULL(s1->init_data->smlists[DETECT_SM_LIST_MATCH]->ctx); data = (DetectAppLayerProtocolData *)s1->init_data->smlists[DETECT_SM_LIST_MATCH]->ctx; - FAIL_IF(data->alproto != ALPROTO_HTTP); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_HTTP)); FAIL_IF(data->negated); Signature *s2 = DetectEngineAppendSig(de_ctx, "alert tcp any any -> any any " @@ -655,7 +921,7 @@ static int DetectAppLayerProtocolTest14(void) FAIL_IF_NULL(s2->init_data->smlists[DETECT_SM_LIST_MATCH]); FAIL_IF_NULL(s2->init_data->smlists[DETECT_SM_LIST_MATCH]->ctx); data = (DetectAppLayerProtocolData *)s2->init_data->smlists[DETECT_SM_LIST_MATCH]->ctx; - FAIL_IF(data->alproto != ALPROTO_HTTP); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_HTTP)); FAIL_IF(data->negated); /* flow:established and other options not supported for PD-only */ @@ -666,7 +932,7 @@ static int DetectAppLayerProtocolTest14(void) FAIL_IF_NULL(s3->init_data->smlists[DETECT_SM_LIST_MATCH]); FAIL_IF_NULL(s3->init_data->smlists[DETECT_SM_LIST_MATCH]->ctx); data = (DetectAppLayerProtocolData *)s3->init_data->smlists[DETECT_SM_LIST_MATCH]->ctx; - FAIL_IF(data->alproto != ALPROTO_HTTP); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_HTTP)); FAIL_IF(data->negated); SigGroupBuild(de_ctx); @@ -678,36 +944,301 @@ static int DetectAppLayerProtocolTest14(void) PASS; } +static int DetectAppLayerProtocolTest15(void) +{ + DetectAppLayerProtocolData *data = DetectAppLayerProtocolParse("http,final", false); + FAIL_IF_NULL(data); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_HTTP)); + FAIL_IF(data->negated != 0); + FAIL_IF(data->mode != DETECT_ALPROTO_FINAL); + FAIL_IF(data->is_list); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_HTTP)); + DetectAppLayerProtocolFree(NULL, data); + PASS; +} + +/** \test Multi-value without mode qualifier. */ +static int DetectAppLayerProtocolTest16(void) +{ + DetectAppLayerProtocolData *data = DetectAppLayerProtocolParse("tls|http", false); + FAIL_IF_NULL(data); + FAIL_IF_NOT(data->is_list); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_TLS)); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_HTTP)); + FAIL_IF(data->mode != DETECT_ALPROTO_DIRECTION); /* default */ + FAIL_IF(data->negated != 0); + DetectAppLayerProtocolFree(NULL, data); + PASS; +} + +/** \test Multi-value with mode qualifier. */ +static int DetectAppLayerProtocolTest17(void) +{ + DetectAppLayerProtocolData *data = DetectAppLayerProtocolParse("tls|http,either", false); + FAIL_IF_NULL(data); + FAIL_IF_NOT(data->is_list); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_TLS)); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_HTTP)); + FAIL_IF(data->mode != DETECT_ALPROTO_EITHER); + DetectAppLayerProtocolFree(NULL, data); + PASS; +} + +/** \test A bare mode name with no protocol is treated as a protocol lookup (fails). */ +static int DetectAppLayerProtocolTest18(void) +{ + /* "final" alone is treated as a protocol name (which won't resolve). */ + DetectAppLayerProtocolData *data = DetectAppLayerProtocolParse("final", false); + FAIL_IF_NOT_NULL(data); + PASS; +} + +/** \test Multi-value list with an explicit 'direction' mode qualifier. */ +static int DetectAppLayerProtocolTest19(void) +{ + DetectAppLayerProtocolData *data = DetectAppLayerProtocolParse("tls|http,direction", false); + FAIL_IF_NULL(data); + FAIL_IF_NOT(data->is_list); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_TLS)); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_HTTP)); + FAIL_IF(data->mode != DETECT_ALPROTO_DIRECTION); + DetectAppLayerProtocolFree(NULL, data); + PASS; +} + +/** \test Empty value list rejected. */ +static int DetectAppLayerProtocolTest20(void) +{ + DetectAppLayerProtocolData *data = DetectAppLayerProtocolParse("", false); + FAIL_IF_NOT_NULL(data); + PASS; +} + +/** \test Negation of 'unknown' rejected. */ +static int DetectAppLayerProtocolTest21(void) +{ + DetectAppLayerProtocolData *data = DetectAppLayerProtocolParse("unknown", true); + FAIL_IF_NOT_NULL(data); + PASS; +} + +/** \test Oversized argument length rejected. */ +static int DetectAppLayerProtocolTest22(void) +{ + /* Build a string >1024 characters. */ + char big[1030]; + memset(big, 'a', sizeof(big) - 1); + big[sizeof(big) - 1] = '\0'; + DetectAppLayerProtocolData *data = DetectAppLayerProtocolParse(big, false); + FAIL_IF_NOT_NULL(data); + PASS; +} + +/** \test Empty token in pipe-separated list rejected. */ +static int DetectAppLayerProtocolTest23(void) +{ + DetectAppLayerProtocolData *data = DetectAppLayerProtocolParse("tls||http", false); + FAIL_IF_NOT_NULL(data); + PASS; +} + +/** \test Negated multi-value parses correctly (!tls|http). */ +static int DetectAppLayerProtocolTest24(void) +{ + DetectAppLayerProtocolData *data = DetectAppLayerProtocolParse("tls|http", true); + FAIL_IF_NULL(data); + FAIL_IF(data->negated != 1); + FAIL_IF_NOT(data->is_list); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_TLS)); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_HTTP)); + DetectAppLayerProtocolFree(NULL, data); + PASS; +} + +/** \test Unknown protocol name in list rejected. */ +static int DetectAppLayerProtocolTest25(void) +{ + DetectAppLayerProtocolData *data = DetectAppLayerProtocolParse("tls|bogus_proto_xyz", false); + FAIL_IF_NOT_NULL(data); + PASS; +} + +/** \test Negated single-value against unclassified flow returns 0. */ +static int DetectAppLayerProtocolTest26(void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + + Signature *s = DetectEngineAppendSig(de_ctx, "alert tcp any any -> any any " + "(app-layer-protocol:!tls; sid:1;)"); + FAIL_IF_NULL(s); + + /* Check data BEFORE SigGroupBuild (init_data is freed by build). */ + SigMatch *sm = s->init_data->smlists[DETECT_SM_LIST_MATCH]; + FAIL_IF_NULL(sm); + DetectAppLayerProtocolData *data = (DetectAppLayerProtocolData *)sm->ctx; + FAIL_IF_NULL(data); + FAIL_IF(data->negated != 1); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_TLS)); + + SigGroupBuild(de_ctx); + DetectEngineCtxFree(de_ctx); + PASS; +} + +/** \test Multi-value rule is NOT prefiltered. */ +static int DetectAppLayerProtocolTest27(void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + + Signature *s = DetectEngineAppendSig(de_ctx, "alert tcp any any -> any any " + "(app-layer-protocol:tls|dns; sid:1;)"); + FAIL_IF_NULL(s); + + /* Verify the parsed data is list-valued BEFORE SigGroupBuild. */ + SigMatch *sm = s->init_data->smlists[DETECT_SM_LIST_MATCH]; + FAIL_IF_NULL(sm); + DetectAppLayerProtocolData *data = (DetectAppLayerProtocolData *)sm->ctx; + FAIL_IF_NULL(data); + FAIL_IF_NOT(data->is_list); + + /* A single-valued packet-detect-only rule is prefilter-eligible; the + * multi-value guard must exclude this one. init_data is read by the + * predicate, so check before SigGroupBuild frees it. */ + s->type = SIG_TYPE_PDONLY; + FAIL_IF(PrefilterAppProtoIsPrefilterable(s)); + + DetectEngineCtxFree(de_ctx); + PASS; +} + +/** \test Multi-value rule combined with buffer-keyword that pre-binds s->alproto is rejected. */ +static int DetectAppLayerProtocolTest28(void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + + /* tls.sni binds s->alproto = TLS, so app-layer-protocol:tls|dns is rejected. */ + Signature *s = DetectEngineAppendSig(de_ctx, + "alert tcp any any -> any any " + "(tls.sni; content:\"example.com\"; app-layer-protocol:tls|dns; sid:1;)"); + FAIL_IF_NOT_NULL(s); + + DetectEngineCtxFree(de_ctx); + PASS; +} + +/** \test Default matching keeps AppProtoEquals equivalences (dns covers doh2). */ +static int DetectAppLayerProtocolTest29(void) +{ + DetectAppLayerProtocolData *data = DetectAppLayerProtocolParse("dns", false); + FAIL_IF_NULL(data); + FAIL_IF(data->exact); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_DNS)); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_DOH2)); + DetectAppLayerProtocolFree(NULL, data); + PASS; +} + +/** \test The exact option drops equivalences (dns no longer covers doh2). */ +static int DetectAppLayerProtocolTest30(void) +{ + DetectAppLayerProtocolData *data = DetectAppLayerProtocolParse("dns,exact", false); + FAIL_IF_NULL(data); + FAIL_IF_NOT(data->exact); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_DNS)); + FAIL_IF(AlprotoBitmaskTest(data->alprotos, ALPROTO_DOH2)); + DetectAppLayerProtocolFree(NULL, data); + PASS; +} + +/** \test 'http' with 'exact' is rejected (can never match a real flow). */ +static int DetectAppLayerProtocolTest31(void) +{ + DetectAppLayerProtocolData *data = DetectAppLayerProtocolParse("http,exact", false); + FAIL_IF_NOT_NULL(data); + PASS; +} + +/** \test exact combines with a direction mode, order-independent. */ +static int DetectAppLayerProtocolTest32(void) +{ + DetectAppLayerProtocolData *data = DetectAppLayerProtocolParse("tls|dns,either,exact", false); + FAIL_IF_NULL(data); + FAIL_IF_NOT(data->exact); + FAIL_IF_NOT(data->is_list); + FAIL_IF(data->mode != DETECT_ALPROTO_EITHER); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_TLS)); + FAIL_IF_NOT(AlprotoBitmaskTest(data->alprotos, ALPROTO_DNS)); + FAIL_IF(AlprotoBitmaskTest(data->alprotos, ALPROTO_DOH2)); + DetectAppLayerProtocolFree(NULL, data); + PASS; +} + +/** \test Single-value rule IS prefilter-eligible (mirror of Test27). */ +static int DetectAppLayerProtocolTest33(void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + + Signature *s = DetectEngineAppendSig(de_ctx, "alert tcp any any -> any any " + "(app-layer-protocol:tls; sid:1;)"); + FAIL_IF_NULL(s); + + SigMatch *sm = s->init_data->smlists[DETECT_SM_LIST_MATCH]; + FAIL_IF_NULL(sm); + DetectAppLayerProtocolData *data = (DetectAppLayerProtocolData *)sm->ctx; + FAIL_IF_NULL(data); + FAIL_IF(data->is_list); + + /* A single-valued packet-detect-only rule is prefilter-eligible. init_data + * is read by the predicate, so check before SigGroupBuild frees it. */ + s->type = SIG_TYPE_PDONLY; + FAIL_IF_NOT(PrefilterAppProtoIsPrefilterable(s)); + + DetectEngineCtxFree(de_ctx); + PASS; +} static void DetectAppLayerProtocolRegisterTests(void) { - UtRegisterTest("DetectAppLayerProtocolTest01", - DetectAppLayerProtocolTest01); - UtRegisterTest("DetectAppLayerProtocolTest02", - DetectAppLayerProtocolTest02); - UtRegisterTest("DetectAppLayerProtocolTest03", - DetectAppLayerProtocolTest03); - UtRegisterTest("DetectAppLayerProtocolTest04", - DetectAppLayerProtocolTest04); - UtRegisterTest("DetectAppLayerProtocolTest05", - DetectAppLayerProtocolTest05); - UtRegisterTest("DetectAppLayerProtocolTest06", - DetectAppLayerProtocolTest06); - UtRegisterTest("DetectAppLayerProtocolTest07", - DetectAppLayerProtocolTest07); - UtRegisterTest("DetectAppLayerProtocolTest08", - DetectAppLayerProtocolTest08); - UtRegisterTest("DetectAppLayerProtocolTest09", - DetectAppLayerProtocolTest09); - UtRegisterTest("DetectAppLayerProtocolTest10", - DetectAppLayerProtocolTest10); - UtRegisterTest("DetectAppLayerProtocolTest11", - DetectAppLayerProtocolTest11); - UtRegisterTest("DetectAppLayerProtocolTest12", - DetectAppLayerProtocolTest12); - UtRegisterTest("DetectAppLayerProtocolTest13", - DetectAppLayerProtocolTest13); - UtRegisterTest("DetectAppLayerProtocolTest14", - DetectAppLayerProtocolTest14); + UtRegisterTest("DetectAppLayerProtocolTest01", DetectAppLayerProtocolTest01); + UtRegisterTest("DetectAppLayerProtocolTest02", DetectAppLayerProtocolTest02); + UtRegisterTest("DetectAppLayerProtocolTest03", DetectAppLayerProtocolTest03); + UtRegisterTest("DetectAppLayerProtocolTest04", DetectAppLayerProtocolTest04); + UtRegisterTest("DetectAppLayerProtocolTest05", DetectAppLayerProtocolTest05); + UtRegisterTest("DetectAppLayerProtocolTest06", DetectAppLayerProtocolTest06); + UtRegisterTest("DetectAppLayerProtocolTest07", DetectAppLayerProtocolTest07); + UtRegisterTest("DetectAppLayerProtocolTest08", DetectAppLayerProtocolTest08); + UtRegisterTest("DetectAppLayerProtocolTest09", DetectAppLayerProtocolTest09); + UtRegisterTest("DetectAppLayerProtocolTest10", DetectAppLayerProtocolTest10); + UtRegisterTest("DetectAppLayerProtocolTest11", DetectAppLayerProtocolTest11); + UtRegisterTest("DetectAppLayerProtocolTest12", DetectAppLayerProtocolTest12); + UtRegisterTest("DetectAppLayerProtocolTest13", DetectAppLayerProtocolTest13); + UtRegisterTest("DetectAppLayerProtocolTest14", DetectAppLayerProtocolTest14); + UtRegisterTest("DetectAppLayerProtocolTest15", DetectAppLayerProtocolTest15); + UtRegisterTest("DetectAppLayerProtocolTest16", DetectAppLayerProtocolTest16); + UtRegisterTest("DetectAppLayerProtocolTest17", DetectAppLayerProtocolTest17); + UtRegisterTest("DetectAppLayerProtocolTest18", DetectAppLayerProtocolTest18); + UtRegisterTest("DetectAppLayerProtocolTest19", DetectAppLayerProtocolTest19); + UtRegisterTest("DetectAppLayerProtocolTest20", DetectAppLayerProtocolTest20); + UtRegisterTest("DetectAppLayerProtocolTest21", DetectAppLayerProtocolTest21); + UtRegisterTest("DetectAppLayerProtocolTest22", DetectAppLayerProtocolTest22); + UtRegisterTest("DetectAppLayerProtocolTest23", DetectAppLayerProtocolTest23); + UtRegisterTest("DetectAppLayerProtocolTest24", DetectAppLayerProtocolTest24); + UtRegisterTest("DetectAppLayerProtocolTest25", DetectAppLayerProtocolTest25); + UtRegisterTest("DetectAppLayerProtocolTest26", DetectAppLayerProtocolTest26); + UtRegisterTest("DetectAppLayerProtocolTest27", DetectAppLayerProtocolTest27); + UtRegisterTest("DetectAppLayerProtocolTest28", DetectAppLayerProtocolTest28); + UtRegisterTest("DetectAppLayerProtocolTest29", DetectAppLayerProtocolTest29); + UtRegisterTest("DetectAppLayerProtocolTest30", DetectAppLayerProtocolTest30); + UtRegisterTest("DetectAppLayerProtocolTest31", DetectAppLayerProtocolTest31); + UtRegisterTest("DetectAppLayerProtocolTest32", DetectAppLayerProtocolTest32); + UtRegisterTest("DetectAppLayerProtocolTest33", DetectAppLayerProtocolTest33); } #endif /* UNITTESTS */ diff --git a/src/detect-app-layer-protocol.h b/src/detect-app-layer-protocol.h index 02f13968a0e8..82378dcddee6 100644 --- a/src/detect-app-layer-protocol.h +++ b/src/detect-app-layer-protocol.h @@ -24,6 +24,31 @@ #ifndef SURICATA_DETECT_APP_LAYER_PROTOCOL__H #define SURICATA_DETECT_APP_LAYER_PROTOCOL__H +#include "app-layer-protos.h" + void DetectAppLayerProtocolRegister(void); +const char *DetectAppLayerProtocolModeName(uint8_t mode); +struct DetectAppLayerProtocolData_; +uint16_t DetectAppLayerProtocolGetValues( + const struct DetectAppLayerProtocolData_ *data, AppProto *out, uint16_t max); + +/** + * \brief Per-rule keyword data for `app-layer-protocol:`. + * + * `alprotos` is the effective match set: a bitmask (one bit per AppProto, + * sized g_alproto_max) holding every flow protocol that should match, with the + * AppProtoEquals() equivalences (or, with the `exact` option, only the exact + * values) already expanded in at rule load. The per-packet match is then a + * single bitmask test. `alproto` is the first configured value, used as the + * prefilter bucket key for single-value (prefilterable) rules. + */ +typedef struct DetectAppLayerProtocolData_ { + AppProto alproto; /**< first configured value; single-value prefilter key */ + bool negated; + bool exact; /**< `exact` option: strict identity, no equivalences/umbrella */ + bool is_list; /**< more than one value configured (not prefilterable) */ + uint8_t mode; + uint8_t *alprotos; /**< effective match set (g_alproto_max bits) */ +} DetectAppLayerProtocolData; #endif /* SURICATA_DETECT_APP_LAYER_PROTOCOL__H */ diff --git a/src/detect-engine-analyzer.c b/src/detect-engine-analyzer.c index 742e34f02696..32e7382c49cb 100644 --- a/src/detect-engine-analyzer.c +++ b/src/detect-engine-analyzer.c @@ -54,6 +54,7 @@ #include "util-var-name.h" #include "detect-icmp-id.h" #include "detect-tcp-window.h" +#include "detect-app-layer-protocol.h" static int rule_warnings_only = 0; @@ -975,6 +976,21 @@ static void DumpMatches(RuleAnalyzer *ctx, SCJsonBuilder *js, const SigMatchData SCJbClose(js); break; } + case DETECT_APP_LAYER_PROTOCOL: { + const DetectAppLayerProtocolData *ad = (const DetectAppLayerProtocolData *)smd->ctx; + SCJbOpenObject(js, "app_layer_protocol"); + AppProto vals[256]; + uint16_t n = DetectAppLayerProtocolGetValues(ad, vals, ARRAY_SIZE(vals)); + SCJbOpenArray(js, "protocols"); + for (uint16_t i = 0; i < n; i++) { + SCJbAppendString(js, AppProtoToString(vals[i])); + } + SCJbClose(js); + SCJbSetString(js, "mode", DetectAppLayerProtocolModeName(ad->mode)); + SCJbSetBool(js, "negated", ad->negated); + SCJbClose(js); + break; + } } SCJbClose(js); diff --git a/src/detect-prefilter.c b/src/detect-prefilter.c index c73363b1eb04..286e1d502c4d 100644 --- a/src/detect-prefilter.c +++ b/src/detect-prefilter.c @@ -29,6 +29,7 @@ #include "detect.h" #include "detect-parse.h" #include "detect-content.h" +#include "detect-app-layer-protocol.h" #include "detect-engine-mpm.h" #include "detect-prefilter.h" #include "util-debug.h" @@ -107,6 +108,16 @@ static int DetectPrefilterSetup (DetectEngineCtx *de_ctx, Signature *s, const ch SCReturnInt(-1); } + /* A multi-value app-layer-protocol keyword stores its values in a + * bitmask that the single-valued prefilter bucket key can't carry, so + * forcing prefilter would bucket the rule under ALPROTO_UNKNOWN and + * silently never match. */ + if (sm->type == DETECT_APP_LAYER_PROTOCOL && + ((const DetectAppLayerProtocolData *)sm->ctx)->is_list) { + SCLogError("prefilter is not supported for multi-value app-layer-protocol"); + SCReturnInt(-1); + } + /* make sure setup function runs for this type. */ de_ctx->sm_types_prefilter[sm->type] = true; } From e379d174245576effd72f4fbc90b0ab02703ca0b Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Mon, 1 Jun 2026 20:48:01 +0200 Subject: [PATCH 16/69] http2: split progress per direction Ticket: 8518 Keywords that work for HTTP2 headers match now as soon as possible A push promise is now considered like a headers frame with regards to the progress (no dedicated "reserved" progress/state) http.protocol and http.stat_msg keywords are now registered at earliest progress, since these are synthetic like "HTTP/2" and not really seen on the wire. http.request_line and http.response_line match only on data, and not on headers, since we must wait the end of headers to be sure to have the full line http2.size_update now matches at headers progress as it should http2.frametype, http2.errorcode, http2.priority now match like http2.window, when the tx is complete from both sides, as a half-closed client may still send priority, rst_stream or window_update frames (cherry picked from commit daf68dc36f06fd5d1726038b86ca2005235224cc) --- .../extending/app-layer/transactions.rst | 8 +- doc/userguide/upgrade.rst | 8 ++ rust/cbindgen.toml | 2 +- rust/src/http2/detect.rs | 4 +- rust/src/http2/http2.rs | 113 +++++++++--------- src/detect-file-data.c | 4 +- src/detect-http-client-body.c | 4 +- src/detect-http-cookie.c | 8 +- src/detect-http-header-names.c | 8 +- src/detect-http-header.c | 12 +- src/detect-http-headers-stub.h | 8 +- src/detect-http-host.c | 8 +- src/detect-http-method.c | 4 +- src/detect-http-protocol.c | 8 +- src/detect-http-raw-header.c | 8 +- src/detect-http-request-line.c | 4 +- src/detect-http-response-line.c | 4 +- src/detect-http-stat-code.c | 4 +- src/detect-http-stat-msg.c | 4 +- src/detect-http-ua.c | 4 +- src/detect-http-uri.c | 8 +- src/detect-http2.c | 24 ++-- src/output.c | 4 +- 23 files changed, 137 insertions(+), 126 deletions(-) diff --git a/doc/userguide/devguide/extending/app-layer/transactions.rst b/doc/userguide/devguide/extending/app-layer/transactions.rst index a1dc89552038..dd19461dcc6c 100644 --- a/doc/userguide/devguide/extending/app-layer/transactions.rst +++ b/doc/userguide/devguide/extending/app-layer/transactions.rst @@ -73,7 +73,7 @@ Rule Matching Transaction progress is also used for certain keywords to know what is the minimum state before we can expect a match: until that, Suricata won't even try to look for the patterns. As seen in ``DetectAppLayerMpmRegister`` that has ``int progress`` as parameter, and ``DetectAppLayerInspectEngineRegister``, which expects ``int tx_min_progress``, for instance. In the code snippet, -``HTTP2StateDataClient``, ``HTTP2StateDataServer`` and ``0`` are the values passed to the functions - in the last +``HTTP2ProgData``, ``HTTP2ProgData`` and ``0`` are the values passed to the functions - in the last example, for ``FTPDATA``, the existence of a transaction implies that a file is being transferred. Hence the ``0`` value. @@ -86,14 +86,14 @@ the existence of a transaction implies that a file is being transferred. Hence t . DetectAppLayerMpmRegister("file_data", SIG_FLAG_TOSERVER, 2, PrefilterMpmFiledataRegister, NULL, - ALPROTO_HTTP2, HTTP2StateDataClient); + ALPROTO_HTTP2, HTTP2ProgData); DetectAppLayerMpmRegister("file_data", SIG_FLAG_TOCLIENT, 2, PrefilterMpmFiledataRegister, NULL, - ALPROTO_HTTP2, HTTP2StateDataServer); + ALPROTO_HTTP2, HTTP2ProgData); . . DetectAppLayerInspectEngineRegister("file_data", - ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, HTTP2StateDataServer, + ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, HTTP2ProgData, DetectEngineInspectFiledata, NULL); DetectAppLayerInspectEngineRegister( "file_data", ALPROTO_FTPDATA, SIG_FLAG_TOSERVER, 0, DetectEngineInspectFiledata, NULL); diff --git a/doc/userguide/upgrade.rst b/doc/userguide/upgrade.rst index 46ed6d530446..228fba4865db 100644 --- a/doc/userguide/upgrade.rst +++ b/doc/userguide/upgrade.rst @@ -46,6 +46,14 @@ Deprecations Upgrading to 8.0.5 ------------------ +Keyword Changes +~~~~~~~~~~~~~~~ + +- HTTP2 keywords have now better progress defined, with the http2 transaction progress + being split per direction. This means that some rules should match sooner, + some rules will have less false negatives, and some rules will trigger once per transaction + instead of twice (one time for each direction) + Other Changes ~~~~~~~~~~~~~ diff --git a/rust/cbindgen.toml b/rust/cbindgen.toml index 3009d45e6e1b..1a7b5ce9c3ef 100644 --- a/rust/cbindgen.toml +++ b/rust/cbindgen.toml @@ -88,7 +88,7 @@ include = [ "FtpRequestCommand", "FtpStateValues", "FtpDataStateValues", - "HTTP2TransactionState", + "HTTP2TxProgress", "DataRepType", ] diff --git a/rust/src/http2/detect.rs b/rust/src/http2/detect.rs index 182fc7811fc6..e82cec1367fe 100644 --- a/rust/src/http2/detect.rs +++ b/rust/src/http2/detect.rs @@ -16,7 +16,7 @@ */ use super::http2::{ - HTTP2Event, HTTP2Frame, HTTP2FrameTypeData, HTTP2State, HTTP2Transaction, HTTP2TransactionState, + HTTP2Event, HTTP2Frame, HTTP2FrameTypeData, HTTP2State, HTTP2Transaction, HTTP2TxProgress, }; use super::parser; use crate::detect::uint::{detect_match_uint, DetectUintData}; @@ -1040,7 +1040,7 @@ fn http2_tx_set_header(state: &mut HTTP2State, name: &[u8], input: &[u8]) { data: txdata, }); //we do not expect more data from client - tx.state = HTTP2TransactionState::HTTP2StateHalfClosedClient; + tx.progress_ts = HTTP2TxProgress::HTTP2ProgClosed; } #[no_mangle] diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs index d92cfe7599f3..844b3c01b37f 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -126,19 +126,16 @@ pub enum HTTP2FrameTypeData { #[repr(u8)] #[derive(Copy, Clone, PartialOrd, PartialEq, Eq, Debug)] -pub enum HTTP2TransactionState { - HTTP2StateIdle = 0, - HTTP2StateOpen = 1, - HTTP2StateReserved = 2, - HTTP2StateDataClient = 3, - HTTP2StateHalfClosedClient = 4, - HTTP2StateDataServer = 5, - HTTP2StateHalfClosedServer = 6, - HTTP2StateClosed = 7, +pub enum HTTP2TxProgress { + HTTP2ProgStart = 0, + HTTP2ProgHeaders = 1, + HTTP2ProgData = 2, + HTTP2ProgClosed = 3, + HTTP2ProgComplete = 4, //not a RFC-defined state, used for stream 0 frames applying to the global connection - HTTP2StateGlobal = 8, + HTTP2ProgGlobal = 5, //not a RFC-defined state, dropping this old tx because we have too many - HTTP2StateTodrop = 9, + HTTP2ProgTodrop = 6, } #[derive(Debug)] @@ -164,7 +161,8 @@ pub struct DohHttp2Tx { pub struct HTTP2Transaction { tx_id: u64, pub stream_id: u32, - pub state: HTTP2TransactionState, + pub progress_tc: HTTP2TxProgress, + pub progress_ts: HTTP2TxProgress, child_stream_id: u32, pub frames_tc: Vec, @@ -201,7 +199,8 @@ impl HTTP2Transaction { tx_id: 0, stream_id: 0, child_stream_id: 0, - state: HTTP2TransactionState::HTTP2StateIdle, + progress_tc: HTTP2TxProgress::HTTP2ProgStart, + progress_ts: HTTP2TxProgress::HTTP2ProgStart, frames_tc: Vec::new(), frames_ts: Vec::new(), decoder: decompression::HTTP2Decoder::new(), @@ -406,7 +405,9 @@ impl HTTP2Transaction { if header.flags & parser::HTTP2_FLAG_HEADER_END_HEADERS == 0 { self.child_stream_id = hs.stream_id; } - self.state = HTTP2TransactionState::HTTP2StateReserved; + if self.progress_tc < HTTP2TxProgress::HTTP2ProgHeaders { + self.progress_tc = HTTP2TxProgress::HTTP2ProgHeaders; + } } r = self.handle_headers(&hs.blocks, dir); } @@ -430,41 +431,30 @@ impl HTTP2Transaction { _ => {} } //handle closing state changes + let state = if dir == Direction::ToServer { + &mut self.progress_ts + } else { + &mut self.progress_tc + }; match data { HTTP2FrameTypeData::HEADERS(_) | HTTP2FrameTypeData::DATA => { if header.flags & parser::HTTP2_FLAG_HEADER_EOS != 0 { - match self.state { - HTTP2TransactionState::HTTP2StateHalfClosedClient - | HTTP2TransactionState::HTTP2StateDataServer => { - if dir == Direction::ToClient { - self.state = HTTP2TransactionState::HTTP2StateClosed; - } - } - HTTP2TransactionState::HTTP2StateHalfClosedServer => { - if dir == Direction::ToServer { - self.state = HTTP2TransactionState::HTTP2StateClosed; - } - } - // do not revert back to a half closed state - HTTP2TransactionState::HTTP2StateClosed => {} - HTTP2TransactionState::HTTP2StateGlobal => {} - _ => { - if dir == Direction::ToClient { - self.state = HTTP2TransactionState::HTTP2StateHalfClosedServer; - } else { - self.state = HTTP2TransactionState::HTTP2StateHalfClosedClient; - } + if *state < HTTP2TxProgress::HTTP2ProgClosed { + *state = HTTP2TxProgress::HTTP2ProgClosed; + if self.progress_ts == HTTP2TxProgress::HTTP2ProgClosed + && self.progress_tc == HTTP2TxProgress::HTTP2ProgClosed + { + self.progress_ts = HTTP2TxProgress::HTTP2ProgComplete; + self.progress_tc = HTTP2TxProgress::HTTP2ProgComplete; } } } else if header.ftype == parser::HTTP2FrameType::Data as u8 { //not end of stream - if dir == Direction::ToServer { - if self.state < HTTP2TransactionState::HTTP2StateDataClient { - self.state = HTTP2TransactionState::HTTP2StateDataClient; - } - } else if self.state < HTTP2TransactionState::HTTP2StateDataServer { - self.state = HTTP2TransactionState::HTTP2StateDataServer; + if *state < HTTP2TxProgress::HTTP2ProgData { + *state = HTTP2TxProgress::HTTP2ProgData; } + } else if *state < HTTP2TxProgress::HTTP2ProgHeaders { + *state = HTTP2TxProgress::HTTP2ProgHeaders; } } _ => {} @@ -756,13 +746,15 @@ impl HTTP2State { return sid; } - fn create_global_tx(&mut self) -> &mut HTTP2Transaction { + fn create_global_tx(&mut self, _dir: Direction) -> &mut HTTP2Transaction { //special transaction with only one frame //as it affects the global connection, there is no end to it let mut tx = HTTP2Transaction::new(); + //tx.tx_data = AppLayerTxData::for_direction(dir); self.tx_id += 1; tx.tx_id = self.tx_id; - tx.state = HTTP2TransactionState::HTTP2StateGlobal; + tx.progress_tc = HTTP2TxProgress::HTTP2ProgGlobal; + tx.progress_ts = HTTP2TxProgress::HTTP2ProgGlobal; // a global tx (stream id 0) does not hold files cf RFC 9113 section 5.1.1 self.transactions.push_back(tx); return self.transactions.back_mut().unwrap(); @@ -774,19 +766,20 @@ impl HTTP2State { if header.stream_id == 0 { if self.transactions.len() >= unsafe { HTTP2_MAX_STREAMS } { for tx_old in &mut self.transactions { - if tx_old.state == HTTP2TransactionState::HTTP2StateTodrop { + if tx_old.progress_ts == HTTP2TxProgress::HTTP2ProgTodrop { // loop was already run break; } tx_old.set_event(HTTP2Event::TooManyStreams); // use a distinct state, even if we do not log it - tx_old.state = HTTP2TransactionState::HTTP2StateTodrop; + tx_old.progress_ts = HTTP2TxProgress::HTTP2ProgTodrop; + tx_old.progress_tc = HTTP2TxProgress::HTTP2ProgTodrop; tx_old.tx_data.updated_tc = true; tx_old.tx_data.updated_ts = true; } return None; } - return Some(self.create_global_tx()); + return Some(self.create_global_tx(dir)); } let sid = match data { //yes, the right stream_id for Suricata is not the header one @@ -803,7 +796,9 @@ impl HTTP2State { }; let index = self.find_tx_index(sid); if index > 0 { - if self.transactions[index - 1].state == HTTP2TransactionState::HTTP2StateClosed { + if self.transactions[index - 1].progress_tc >= HTTP2TxProgress::HTTP2ProgClosed + && self.transactions[index - 1].progress_ts >= HTTP2TxProgress::HTTP2ProgClosed + { //these frames can be received in this state for a short period if header.ftype != parser::HTTP2FrameType::RstStream as u8 && header.ftype != parser::HTTP2FrameType::WindowUpdate as u8 @@ -823,13 +818,14 @@ impl HTTP2State { // do not use SETTINGS_MAX_CONCURRENT_STREAMS as it can grow too much if self.transactions.len() >= unsafe { HTTP2_MAX_STREAMS } { for tx_old in &mut self.transactions { - if tx_old.state == HTTP2TransactionState::HTTP2StateTodrop { + if tx_old.progress_ts == HTTP2TxProgress::HTTP2ProgTodrop { // loop was already run break; } tx_old.set_event(HTTP2Event::TooManyStreams); // use a distinct state, even if we do not log it - tx_old.state = HTTP2TransactionState::HTTP2StateTodrop; + tx_old.progress_ts = HTTP2TxProgress::HTTP2ProgTodrop; + tx_old.progress_tc = HTTP2TxProgress::HTTP2ProgTodrop; tx_old.tx_data.updated_tc = true; tx_old.tx_data.updated_ts = true; } @@ -839,7 +835,6 @@ impl HTTP2State { self.tx_id += 1; tx.tx_id = self.tx_id; tx.stream_id = sid; - tx.state = HTTP2TransactionState::HTTP2StateOpen; tx.tx_data.update_file_flags(self.state_data.file_flags); tx.update_file_flags(tx.tx_data.file_flags); tx.tx_data.file_tx = STREAM_TOSERVER | STREAM_TOCLIENT; // might hold files in both directions @@ -1533,15 +1528,15 @@ unsafe extern "C" fn http2_state_get_tx_count(state: *mut std::os::raw::c_void) return state.tx_id; } -unsafe extern "C" fn http2_tx_get_state(tx: *mut std::os::raw::c_void) -> HTTP2TransactionState { - let tx = cast_pointer!(tx, HTTP2Transaction); - return tx.state; -} - unsafe extern "C" fn http2_tx_get_alstate_progress( - tx: *mut std::os::raw::c_void, _direction: u8, + tx: *mut std::os::raw::c_void, direction: u8, ) -> std::os::raw::c_int { - return http2_tx_get_state(tx) as i32; + let tx = cast_pointer!(tx, HTTP2Transaction); + if direction == STREAM_TOSERVER { + return tx.progress_ts as i32; + } else { + return tx.progress_tc as i32; + } } unsafe extern "C" fn http2_getfiles( @@ -1585,8 +1580,8 @@ pub unsafe extern "C" fn SCRegisterHttp2Parser() { parse_tc: http2_parse_tc, get_tx_count: http2_state_get_tx_count, get_tx: http2_state_get_tx, - tx_comp_st_ts: HTTP2TransactionState::HTTP2StateClosed as i32, - tx_comp_st_tc: HTTP2TransactionState::HTTP2StateClosed as i32, + tx_comp_st_ts: HTTP2TxProgress::HTTP2ProgComplete as i32, + tx_comp_st_tc: HTTP2TxProgress::HTTP2ProgComplete as i32, tx_get_progress: http2_tx_get_alstate_progress, get_eventinfo: Some(HTTP2Event::get_event_info), get_eventinfo_byid: Some(HTTP2Event::get_event_info_by_id), diff --git a/src/detect-file-data.c b/src/detect-file-data.c index 439a707bc7ce..48d5b0d249dc 100644 --- a/src/detect-file-data.c +++ b/src/detect-file-data.c @@ -91,8 +91,8 @@ DetectFileHandlerProtocol_t al_protocols[ALPROTO_WITHFILES_MAX] = { .to_server_progress = HTP_REQUEST_PROGRESS_BODY }, { .alproto = ALPROTO_HTTP2, .direction = SIG_FLAG_TOSERVER | SIG_FLAG_TOCLIENT, - .to_client_progress = HTTP2StateDataServer, - .to_server_progress = HTTP2StateDataClient }, + .to_client_progress = HTTP2ProgData, + .to_server_progress = HTTP2ProgData }, { .alproto = ALPROTO_SMTP, .direction = SIG_FLAG_TOSERVER, .to_server_progress = SMTP_REQUEST_DATA }, diff --git a/src/detect-http-client-body.c b/src/detect-http-client-body.c index d1304fbe9180..074a892ecdf0 100644 --- a/src/detect-http-client-body.c +++ b/src/detect-http-client-body.c @@ -112,9 +112,9 @@ void DetectHttpClientBodyRegister(void) PrefilterMpmHttpRequestBodyRegister, NULL, ALPROTO_HTTP1, HTP_REQUEST_PROGRESS_BODY); DetectAppLayerInspectEngineRegister("http_client_body", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2StateDataClient, DetectEngineInspectFiledata, NULL); + HTTP2ProgData, DetectEngineInspectFiledata, NULL); DetectAppLayerMpmRegister("http_client_body", SIG_FLAG_TOSERVER, 2, - PrefilterMpmFiledataRegister, NULL, ALPROTO_HTTP2, HTTP2StateDataClient); + PrefilterMpmFiledataRegister, NULL, ALPROTO_HTTP2, HTTP2ProgData); DetectBufferTypeSetDescriptionByName("http_client_body", "http request body"); diff --git a/src/detect-http-cookie.c b/src/detect-http-cookie.c index 0644e58076b5..857d85ce7fd7 100644 --- a/src/detect-http-cookie.c +++ b/src/detect-http-cookie.c @@ -120,14 +120,14 @@ void DetectHttpCookieRegister(void) GetResponseData, ALPROTO_HTTP1, HTP_REQUEST_PROGRESS_HEADERS); DetectAppLayerInspectEngineRegister("http_cookie", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2StateOpen, DetectEngineInspectBufferGeneric, GetRequestData2); + HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetRequestData2); DetectAppLayerInspectEngineRegister("http_cookie", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, - HTTP2StateDataServer, DetectEngineInspectBufferGeneric, GetResponseData2); + HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetResponseData2); DetectAppLayerMpmRegister("http_cookie", SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, - GetRequestData2, ALPROTO_HTTP2, HTTP2StateOpen); + GetRequestData2, ALPROTO_HTTP2, HTTP2ProgHeaders); DetectAppLayerMpmRegister("http_cookie", SIG_FLAG_TOCLIENT, 2, PrefilterGenericMpmRegister, - GetResponseData2, ALPROTO_HTTP2, HTTP2StateDataServer); + GetResponseData2, ALPROTO_HTTP2, HTTP2ProgHeaders); DetectBufferTypeSetDescriptionByName("http_cookie", "http cookie header"); diff --git a/src/detect-http-header-names.c b/src/detect-http-header-names.c index e1bf0c0a0705..5f905f4c3aa2 100644 --- a/src/detect-http-header-names.c +++ b/src/detect-http-header-names.c @@ -236,14 +236,14 @@ void DetectHttpHeaderNamesRegister(void) /* http2 */ DetectAppLayerMpmRegister(BUFFER_NAME, SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, - GetBuffer2ForTX, ALPROTO_HTTP2, HTTP2StateOpen); + GetBuffer2ForTX, ALPROTO_HTTP2, HTTP2ProgHeaders); DetectAppLayerMpmRegister(BUFFER_NAME, SIG_FLAG_TOCLIENT, 2, PrefilterGenericMpmRegister, - GetBuffer2ForTX, ALPROTO_HTTP2, HTTP2StateDataServer); + GetBuffer2ForTX, ALPROTO_HTTP2, HTTP2ProgHeaders); DetectAppLayerInspectEngineRegister(BUFFER_NAME, ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2StateOpen, DetectEngineInspectBufferGeneric, GetBuffer2ForTX); + HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetBuffer2ForTX); DetectAppLayerInspectEngineRegister(BUFFER_NAME, ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, - HTTP2StateDataServer, DetectEngineInspectBufferGeneric, GetBuffer2ForTX); + HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetBuffer2ForTX); DetectBufferTypeSetDescriptionByName(BUFFER_NAME, BUFFER_DESC); diff --git a/src/detect-http-header.c b/src/detect-http-header.c index 3021dd745986..bb3e4ee37ff2 100644 --- a/src/detect-http-header.c +++ b/src/detect-http-header.c @@ -441,14 +441,14 @@ void DetectHttpHeaderRegister(void) 0); /* not used, registered twice: HEADERS/TRAILER */ DetectAppLayerInspectEngineRegister("http_header", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2StateOpen, DetectEngineInspectBufferGeneric, GetBuffer2ForTX); + HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetBuffer2ForTX); DetectAppLayerMpmRegister("http_header", SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, - GetBuffer2ForTX, ALPROTO_HTTP2, HTTP2StateOpen); + GetBuffer2ForTX, ALPROTO_HTTP2, HTTP2ProgHeaders); DetectAppLayerInspectEngineRegister("http_header", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, - HTTP2StateDataServer, DetectEngineInspectBufferGeneric, GetBuffer2ForTX); + HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetBuffer2ForTX); DetectAppLayerMpmRegister("http_header", SIG_FLAG_TOCLIENT, 2, PrefilterGenericMpmRegister, - GetBuffer2ForTX, ALPROTO_HTTP2, HTTP2StateDataServer); + GetBuffer2ForTX, ALPROTO_HTTP2, HTTP2ProgHeaders); DetectBufferTypeSetDescriptionByName("http_header", "http headers"); @@ -616,7 +616,7 @@ void DetectHttpRequestHeaderRegister(void) SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER; DetectAppLayerMultiRegister("http_request_header", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2StateOpen, GetHttp2HeaderData, 2); + HTTP2ProgHeaders, GetHttp2HeaderData, 2); DetectAppLayerMultiRegister("http_request_header", ALPROTO_HTTP1, SIG_FLAG_TOSERVER, HTP_REQUEST_PROGRESS_HEADERS, GetHttp1HeaderData, 2); @@ -651,7 +651,7 @@ void DetectHttpResponseHeaderRegister(void) SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER; DetectAppLayerMultiRegister("http_response_header", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, - HTTP2StateOpen, GetHttp2HeaderData, 2); + HTTP2ProgHeaders, GetHttp2HeaderData, 2); DetectAppLayerMultiRegister("http_response_header", ALPROTO_HTTP1, SIG_FLAG_TOCLIENT, HTP_RESPONSE_PROGRESS_HEADERS, GetHttp1HeaderData, 2); diff --git a/src/detect-http-headers-stub.h b/src/detect-http-headers-stub.h index 4b7f62ff2b49..10eff07cc8d0 100644 --- a/src/detect-http-headers-stub.h +++ b/src/detect-http-headers-stub.h @@ -198,25 +198,25 @@ static void DetectHttpHeadersRegisterStub(void) DetectAppLayerMpmRegister(BUFFER_NAME, SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, GetRequestData, ALPROTO_HTTP1, HTP_REQUEST_PROGRESS_HEADERS); DetectAppLayerMpmRegister(BUFFER_NAME, SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, - GetRequestData2, ALPROTO_HTTP2, HTTP2StateOpen); + GetRequestData2, ALPROTO_HTTP2, HTTP2ProgHeaders); #endif #ifdef KEYWORD_TOCLIENT DetectAppLayerMpmRegister(BUFFER_NAME, SIG_FLAG_TOCLIENT, 2, PrefilterGenericMpmRegister, GetResponseData, ALPROTO_HTTP1, HTP_RESPONSE_PROGRESS_HEADERS); DetectAppLayerMpmRegister(BUFFER_NAME, SIG_FLAG_TOCLIENT, 2, PrefilterGenericMpmRegister, - GetResponseData2, ALPROTO_HTTP2, HTTP2StateDataServer); + GetResponseData2, ALPROTO_HTTP2, HTTP2ProgHeaders); #endif #ifdef KEYWORD_TOSERVER DetectAppLayerInspectEngineRegister(BUFFER_NAME, ALPROTO_HTTP1, SIG_FLAG_TOSERVER, HTP_REQUEST_PROGRESS_HEADERS, DetectEngineInspectBufferGeneric, GetRequestData); DetectAppLayerInspectEngineRegister(BUFFER_NAME, ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2StateOpen, DetectEngineInspectBufferGeneric, GetRequestData2); + HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetRequestData2); #endif #ifdef KEYWORD_TOCLIENT DetectAppLayerInspectEngineRegister(BUFFER_NAME, ALPROTO_HTTP1, SIG_FLAG_TOCLIENT, HTP_RESPONSE_PROGRESS_HEADERS, DetectEngineInspectBufferGeneric, GetResponseData); DetectAppLayerInspectEngineRegister(BUFFER_NAME, ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, - HTTP2StateDataServer, DetectEngineInspectBufferGeneric, GetResponseData2); + HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetResponseData2); #endif DetectBufferTypeSetDescriptionByName(BUFFER_NAME, BUFFER_DESC); diff --git a/src/detect-http-host.c b/src/detect-http-host.c index a9fe3562e43f..1b8777bf169f 100644 --- a/src/detect-http-host.c +++ b/src/detect-http-host.c @@ -117,10 +117,10 @@ void DetectHttpHHRegister(void) GetData, ALPROTO_HTTP1, HTP_REQUEST_PROGRESS_HEADERS); DetectAppLayerInspectEngineRegister("http_host", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2StateOpen, DetectEngineInspectBufferGeneric, GetData2); + HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetData2); DetectAppLayerMpmRegister("http_host", SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, - GetData2, ALPROTO_HTTP2, HTTP2StateOpen); + GetData2, ALPROTO_HTTP2, HTTP2ProgHeaders); DetectBufferTypeRegisterValidateCallback("http_host", DetectHttpHostValidateCallback); @@ -157,10 +157,10 @@ void DetectHttpHHRegister(void) GetRawData, ALPROTO_HTTP1, HTP_REQUEST_PROGRESS_HEADERS); DetectAppLayerInspectEngineRegister("http_raw_host", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2StateOpen, DetectEngineInspectBufferGeneric, GetRawData2); + HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetRawData2); DetectAppLayerMpmRegister("http_raw_host", SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, - GetRawData2, ALPROTO_HTTP2, HTTP2StateOpen); + GetRawData2, ALPROTO_HTTP2, HTTP2ProgHeaders); DetectBufferTypeSetDescriptionByName("http_raw_host", "http raw host header"); diff --git a/src/detect-http-method.c b/src/detect-http-method.c index 3c321829ff21..99061a49a426 100644 --- a/src/detect-http-method.c +++ b/src/detect-http-method.c @@ -107,10 +107,10 @@ void DetectHttpMethodRegister(void) GetData, ALPROTO_HTTP1, HTP_REQUEST_PROGRESS_LINE); DetectAppLayerInspectEngineRegister("http_method", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2StateOpen, DetectEngineInspectBufferGeneric, GetData2); + HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetData2); DetectAppLayerMpmRegister("http_method", SIG_FLAG_TOSERVER, 4, PrefilterGenericMpmRegister, - GetData2, ALPROTO_HTTP2, HTTP2StateOpen); + GetData2, ALPROTO_HTTP2, HTTP2ProgHeaders); DetectBufferTypeSetDescriptionByName("http_method", "http request method"); diff --git a/src/detect-http-protocol.c b/src/detect-http-protocol.c index c5c5dcfd5795..c2423b552a7e 100644 --- a/src/detect-http-protocol.c +++ b/src/detect-http-protocol.c @@ -173,13 +173,13 @@ void DetectHttpProtocolRegister(void) HTP_RESPONSE_PROGRESS_LINE, DetectEngineInspectBufferGeneric, GetData); DetectAppLayerInspectEngineRegister(BUFFER_NAME, ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2StateOpen, DetectEngineInspectBufferGeneric, GetData2); + HTTP2ProgStart, DetectEngineInspectBufferGeneric, GetData2); DetectAppLayerMpmRegister(BUFFER_NAME, SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, - GetData2, ALPROTO_HTTP2, HTTP2StateOpen); + GetData2, ALPROTO_HTTP2, HTTP2ProgStart); DetectAppLayerInspectEngineRegister(BUFFER_NAME, ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, - HTTP2StateDataServer, DetectEngineInspectBufferGeneric, GetData2); + HTTP2ProgStart, DetectEngineInspectBufferGeneric, GetData2); DetectAppLayerMpmRegister(BUFFER_NAME, SIG_FLAG_TOCLIENT, 2, PrefilterGenericMpmRegister, - GetData2, ALPROTO_HTTP2, HTTP2StateDataServer); + GetData2, ALPROTO_HTTP2, HTTP2ProgStart); DetectBufferTypeSetDescriptionByName(BUFFER_NAME, BUFFER_DESC); diff --git a/src/detect-http-raw-header.c b/src/detect-http-raw-header.c index d99334b4de13..9f79178ba86c 100644 --- a/src/detect-http-raw-header.c +++ b/src/detect-http-raw-header.c @@ -115,14 +115,14 @@ void DetectHttpRawHeaderRegister(void) 0); /* progress handled in register */ DetectAppLayerInspectEngineRegister("http_raw_header", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2StateOpen, DetectEngineInspectBufferGeneric, GetData2); + HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetData2); DetectAppLayerInspectEngineRegister("http_raw_header", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, - HTTP2StateDataServer, DetectEngineInspectBufferGeneric, GetData2); + HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetData2); DetectAppLayerMpmRegister("http_raw_header", SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, - GetData2, ALPROTO_HTTP2, HTTP2StateOpen); + GetData2, ALPROTO_HTTP2, HTTP2ProgHeaders); DetectAppLayerMpmRegister("http_raw_header", SIG_FLAG_TOCLIENT, 2, PrefilterGenericMpmRegister, - GetData2, ALPROTO_HTTP2, HTTP2StateDataServer); + GetData2, ALPROTO_HTTP2, HTTP2ProgHeaders); DetectBufferTypeSetDescriptionByName("http_raw_header", "raw http headers"); diff --git a/src/detect-http-request-line.c b/src/detect-http-request-line.c index d38d1d3376f9..0403350076a7 100644 --- a/src/detect-http-request-line.c +++ b/src/detect-http-request-line.c @@ -117,9 +117,9 @@ void DetectHttpRequestLineRegister(void) PrefilterGenericMpmRegister, GetData, ALPROTO_HTTP1, HTP_REQUEST_PROGRESS_LINE); DetectAppLayerInspectEngineRegister("http_request_line", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2StateOpen, DetectEngineInspectBufferGeneric, GetData2); + HTTP2ProgData, DetectEngineInspectBufferGeneric, GetData2); DetectAppLayerMpmRegister("http_request_line", SIG_FLAG_TOSERVER, 2, - PrefilterGenericMpmRegister, GetData2, ALPROTO_HTTP2, HTTP2StateOpen); + PrefilterGenericMpmRegister, GetData2, ALPROTO_HTTP2, HTTP2ProgData); DetectBufferTypeSetDescriptionByName("http_request_line", "http request line"); diff --git a/src/detect-http-response-line.c b/src/detect-http-response-line.c index 0611e4cc750c..307dd55bdc21 100644 --- a/src/detect-http-response-line.c +++ b/src/detect-http-response-line.c @@ -116,9 +116,9 @@ void DetectHttpResponseLineRegister(void) PrefilterGenericMpmRegister, GetData, ALPROTO_HTTP1, HTP_RESPONSE_PROGRESS_LINE); DetectAppLayerInspectEngineRegister("http_response_line", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, - HTTP2StateDataServer, DetectEngineInspectBufferGeneric, GetData2); + HTTP2ProgData, DetectEngineInspectBufferGeneric, GetData2); DetectAppLayerMpmRegister("http_response_line", SIG_FLAG_TOCLIENT, 2, - PrefilterGenericMpmRegister, GetData2, ALPROTO_HTTP2, HTTP2StateDataServer); + PrefilterGenericMpmRegister, GetData2, ALPROTO_HTTP2, HTTP2ProgData); DetectBufferTypeSetDescriptionByName("http_response_line", "http response line"); diff --git a/src/detect-http-stat-code.c b/src/detect-http-stat-code.c index 5119a60a204d..92e51f3281cf 100644 --- a/src/detect-http-stat-code.c +++ b/src/detect-http-stat-code.c @@ -108,10 +108,10 @@ void DetectHttpStatCodeRegister (void) GetData, ALPROTO_HTTP1, HTP_RESPONSE_PROGRESS_LINE); DetectAppLayerInspectEngineRegister("http_stat_code", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, - HTTP2StateDataServer, DetectEngineInspectBufferGeneric, GetData2); + HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetData2); DetectAppLayerMpmRegister("http_stat_code", SIG_FLAG_TOCLIENT, 4, PrefilterGenericMpmRegister, - GetData2, ALPROTO_HTTP2, HTTP2StateDataServer); + GetData2, ALPROTO_HTTP2, HTTP2ProgHeaders); DetectBufferTypeSetDescriptionByName("http_stat_code", "http response status code"); diff --git a/src/detect-http-stat-msg.c b/src/detect-http-stat-msg.c index d08c4123706d..70cd3edd3677 100644 --- a/src/detect-http-stat-msg.c +++ b/src/detect-http-stat-msg.c @@ -118,9 +118,9 @@ void DetectHttpStatMsgRegister (void) GetData, ALPROTO_HTTP1, HTP_RESPONSE_PROGRESS_LINE); DetectAppLayerInspectEngineRegister("http_stat_msg", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, - HTTP2StateDataServer, DetectEngineInspectBufferGeneric, GetData2); + HTTP2ProgStart, DetectEngineInspectBufferGeneric, GetData2); DetectAppLayerMpmRegister("http_stat_msg", SIG_FLAG_TOCLIENT, 2, PrefilterGenericMpmRegister, - GetData2, ALPROTO_HTTP2, HTTP2StateDataServer); + GetData2, ALPROTO_HTTP2, HTTP2ProgStart); DetectBufferTypeSetDescriptionByName("http_stat_msg", "http response status message"); diff --git a/src/detect-http-ua.c b/src/detect-http-ua.c index 4115eb1b5980..f5ce3075b64e 100644 --- a/src/detect-http-ua.c +++ b/src/detect-http-ua.c @@ -108,10 +108,10 @@ void DetectHttpUARegister(void) GetData, ALPROTO_HTTP1, HTP_REQUEST_PROGRESS_HEADERS); DetectAppLayerInspectEngineRegister("http_user_agent", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2StateOpen, DetectEngineInspectBufferGeneric, GetData2); + HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetData2); DetectAppLayerMpmRegister("http_user_agent", SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, - GetData2, ALPROTO_HTTP2, HTTP2StateOpen); + GetData2, ALPROTO_HTTP2, HTTP2ProgHeaders); DetectBufferTypeSetDescriptionByName("http_user_agent", "http user agent"); diff --git a/src/detect-http-uri.c b/src/detect-http-uri.c index 70ee97a3eb45..5638ae5d8884 100644 --- a/src/detect-http-uri.c +++ b/src/detect-http-uri.c @@ -112,10 +112,10 @@ void DetectHttpUriRegister (void) GetData, ALPROTO_HTTP1, HTP_REQUEST_PROGRESS_LINE); DetectAppLayerInspectEngineRegister("http_uri", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2StateOpen, DetectEngineInspectBufferGeneric, GetData2); + HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetData2); DetectAppLayerMpmRegister("http_uri", SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, - GetData2, ALPROTO_HTTP2, HTTP2StateOpen); + GetData2, ALPROTO_HTTP2, HTTP2ProgHeaders); DetectBufferTypeSetDescriptionByName("http_uri", "http request uri"); @@ -150,10 +150,10 @@ void DetectHttpUriRegister (void) // no difference between raw and decoded uri for HTTP2 DetectAppLayerInspectEngineRegister("http_raw_uri", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2StateOpen, DetectEngineInspectBufferGeneric, GetData2); + HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetData2); DetectAppLayerMpmRegister("http_raw_uri", SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, - GetData2, ALPROTO_HTTP2, HTTP2StateOpen); + GetData2, ALPROTO_HTTP2, HTTP2ProgHeaders); DetectBufferTypeSetDescriptionByName("http_raw_uri", "raw http uri"); diff --git a/src/detect-http2.c b/src/detect-http2.c index 181fdf537fa2..c1bd7f53faf4 100644 --- a/src/detect-http2.c +++ b/src/detect-http2.c @@ -94,7 +94,8 @@ void DetectHTTP2RegisterTests (void); #endif static int g_http2_match_buffer_id = 0; -static int g_http2_header_name_buffer_id = 0; +static int g_http2_complete_buffer_id = 0; +static int g_http2_header_buffer_id = 0; /** * \brief Registration function for HTTP2 keywords @@ -175,14 +176,14 @@ void DetectHttp2Register(void) sigmatch_table[DETECT_HTTP2_HEADERNAME].flags |= SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER; DetectAppLayerMultiRegister("http2_header_name", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, - HTTP2StateOpen, SCHttp2TxGetHeaderName, 2); + HTTP2ProgHeaders, SCHttp2TxGetHeaderName, 2); DetectAppLayerMultiRegister("http2_header_name", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2StateOpen, SCHttp2TxGetHeaderName, 2); + HTTP2ProgHeaders, SCHttp2TxGetHeaderName, 2); DetectBufferTypeSupportsMultiInstance("http2_header_name"); DetectBufferTypeSetDescriptionByName("http2_header_name", "HTTP2 header name"); - g_http2_header_name_buffer_id = DetectBufferTypeGetByName("http2_header_name"); + g_http2_header_buffer_id = DetectBufferTypeGetByName("http2_header_name"); DetectAppLayerInspectEngineRegister( "http2", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, 0, DetectEngineInspectGenericList, NULL); @@ -190,6 +191,13 @@ void DetectHttp2Register(void) "http2", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, 0, DetectEngineInspectGenericList, NULL); g_http2_match_buffer_id = DetectBufferTypeRegister("http2"); + + DetectAppLayerInspectEngineRegister("http2_complete", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, + HTTP2ProgComplete, DetectEngineInspectGenericList, NULL); + DetectAppLayerInspectEngineRegister("http2_complete", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, + HTTP2ProgComplete, DetectEngineInspectGenericList, NULL); + + g_http2_complete_buffer_id = DetectBufferTypeRegister("http2_complete"); } /** @@ -253,7 +261,7 @@ static int DetectHTTP2frametypeSetup (DetectEngineCtx *de_ctx, Signature *s, con *http2ft = frame_type; if (SCSigMatchAppendSMToList(de_ctx, s, DETECT_HTTP2_FRAMETYPE, (SigMatchCtx *)http2ft, - g_http2_match_buffer_id) == NULL) { + g_http2_complete_buffer_id) == NULL) { DetectHTTP2frametypeFree(NULL, http2ft); return -1; } @@ -333,7 +341,7 @@ static int DetectHTTP2errorcodeSetup (DetectEngineCtx *de_ctx, Signature *s, con *http2ec = error_code; if (SCSigMatchAppendSMToList(de_ctx, s, DETECT_HTTP2_ERRORCODE, (SigMatchCtx *)http2ec, - g_http2_match_buffer_id) == NULL) { + g_http2_complete_buffer_id) == NULL) { DetectHTTP2errorcodeFree(NULL, http2ec); return -1; } @@ -395,7 +403,7 @@ static int DetectHTTP2prioritySetup (DetectEngineCtx *de_ctx, Signature *s, cons return -1; if (SCSigMatchAppendSMToList(de_ctx, s, DETECT_HTTP2_PRIORITY, (SigMatchCtx *)prio, - g_http2_match_buffer_id) == NULL) { + g_http2_complete_buffer_id) == NULL) { SCDetectU8Free(prio); return -1; } @@ -581,7 +589,7 @@ void DetectHTTP2settingsFree(DetectEngineCtx *de_ctx, void *ptr) static int DetectHTTP2headerNameSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg) { - if (SCDetectBufferSetActiveList(de_ctx, s, g_http2_header_name_buffer_id) < 0) + if (SCDetectBufferSetActiveList(de_ctx, s, g_http2_header_buffer_id) < 0) return -1; if (SCDetectSignatureSetAppProto(s, ALPROTO_HTTP2) != 0) diff --git a/src/output.c b/src/output.c index f870eec39e69..a608ff58e4ac 100644 --- a/src/output.c +++ b/src/output.c @@ -1091,8 +1091,8 @@ void OutputRegisterLoggers(void) LogHttpLogRegister(); JsonHttpLogRegister(); OutputRegisterTxSubModuleWithProgress(LOGGER_JSON_TX, "eve-log", "LogHttp2Log", "eve-log.http2", - OutputJsonLogInitSub, ALPROTO_HTTP2, JsonGenericDirFlowLogger, HTTP2StateClosed, - HTTP2StateClosed, JsonLogThreadInit, JsonLogThreadDeinit); + OutputJsonLogInitSub, ALPROTO_HTTP2, JsonGenericDirFlowLogger, HTTP2ProgClosed, + HTTP2ProgClosed, JsonLogThreadInit, JsonLogThreadDeinit); /* tls log */ LogTlsLogRegister(); JsonTlsLogRegister(); From 7bfb577bec0c918c83f51ee619c89714e9970f71 Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Tue, 2 Jun 2026 09:03:35 +0200 Subject: [PATCH 17/69] http2: global txs are unidirectional Ticket: 8518 Meaning they will now match only once per tx instead of twice: once for each direction (cherry picked from commit 8eed90ca9d0d3103c90cb5530616df60cbddf732) --- rust/src/http2/http2.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs index 844b3c01b37f..ccf04381410b 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -746,11 +746,11 @@ impl HTTP2State { return sid; } - fn create_global_tx(&mut self, _dir: Direction) -> &mut HTTP2Transaction { + fn create_global_tx(&mut self, dir: Direction) -> &mut HTTP2Transaction { //special transaction with only one frame //as it affects the global connection, there is no end to it let mut tx = HTTP2Transaction::new(); - //tx.tx_data = AppLayerTxData::for_direction(dir); + tx.tx_data = AppLayerTxData::for_direction(dir); self.tx_id += 1; tx.tx_id = self.tx_id; tx.progress_tc = HTTP2TxProgress::HTTP2ProgGlobal; From 2ea3afc29680c472319d17e1f6b8251e942bf9ad Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Tue, 2 Jun 2026 13:27:59 +0200 Subject: [PATCH 18/69] http2: replace state todrop with a dedicated boolean Ticket: 8518 (cherry picked from commit 26bb18cfaaa76eb728b61b763139aad5b8ae842d) --- rust/src/http2/http2.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs index ccf04381410b..f8ee0c54748c 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -134,8 +134,6 @@ pub enum HTTP2TxProgress { HTTP2ProgComplete = 4, //not a RFC-defined state, used for stream 0 frames applying to the global connection HTTP2ProgGlobal = 5, - //not a RFC-defined state, dropping this old tx because we have too many - HTTP2ProgTodrop = 6, } #[derive(Debug)] @@ -163,6 +161,7 @@ pub struct HTTP2Transaction { pub stream_id: u32, pub progress_tc: HTTP2TxProgress, pub progress_ts: HTTP2TxProgress, + to_drop: bool, child_stream_id: u32, pub frames_tc: Vec, @@ -201,6 +200,7 @@ impl HTTP2Transaction { child_stream_id: 0, progress_tc: HTTP2TxProgress::HTTP2ProgStart, progress_ts: HTTP2TxProgress::HTTP2ProgStart, + to_drop: false, frames_tc: Vec::new(), frames_ts: Vec::new(), decoder: decompression::HTTP2Decoder::new(), @@ -766,14 +766,15 @@ impl HTTP2State { if header.stream_id == 0 { if self.transactions.len() >= unsafe { HTTP2_MAX_STREAMS } { for tx_old in &mut self.transactions { - if tx_old.progress_ts == HTTP2TxProgress::HTTP2ProgTodrop { + if tx_old.to_drop { // loop was already run break; } tx_old.set_event(HTTP2Event::TooManyStreams); // use a distinct state, even if we do not log it - tx_old.progress_ts = HTTP2TxProgress::HTTP2ProgTodrop; - tx_old.progress_tc = HTTP2TxProgress::HTTP2ProgTodrop; + tx_old.progress_ts = HTTP2TxProgress::HTTP2ProgComplete; + tx_old.progress_tc = HTTP2TxProgress::HTTP2ProgComplete; + tx_old.to_drop = true; tx_old.tx_data.updated_tc = true; tx_old.tx_data.updated_ts = true; } @@ -818,14 +819,15 @@ impl HTTP2State { // do not use SETTINGS_MAX_CONCURRENT_STREAMS as it can grow too much if self.transactions.len() >= unsafe { HTTP2_MAX_STREAMS } { for tx_old in &mut self.transactions { - if tx_old.progress_ts == HTTP2TxProgress::HTTP2ProgTodrop { + if tx_old.to_drop { // loop was already run break; } tx_old.set_event(HTTP2Event::TooManyStreams); // use a distinct state, even if we do not log it - tx_old.progress_ts = HTTP2TxProgress::HTTP2ProgTodrop; - tx_old.progress_tc = HTTP2TxProgress::HTTP2ProgTodrop; + tx_old.progress_ts = HTTP2TxProgress::HTTP2ProgComplete; + tx_old.progress_tc = HTTP2TxProgress::HTTP2ProgComplete; + tx_old.to_drop = true; tx_old.tx_data.updated_tc = true; tx_old.tx_data.updated_ts = true; } From 8933283efb2679a80d349200556d64e0c8ab015c Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Fri, 10 Jul 2026 12:01:29 +0200 Subject: [PATCH 19/69] bindgen: add stddef --- src/bindgen.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/bindgen.h b/src/bindgen.h index d11078523795..a8dc20b38abb 100644 --- a/src/bindgen.h +++ b/src/bindgen.h @@ -26,8 +26,9 @@ #ifndef SURICATA_BINDGEN_H #define SURICATA_BINDGEN_H -#include "stdint.h" -#include "stdbool.h" +#include +#include +#include #define WARN_UNUSED From 3316da407e1ca55a5f89a028e588fc547806f87e Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Tue, 16 Jun 2026 15:29:10 +0200 Subject: [PATCH 20/69] app-layer: start of substate API support Allow registration of per substate progress name mappings. Implement logic for getting sub-state names, id's. Add transaction type and end of progress values to AppLayerTxData. Introduce helpers to keep logic clean. (cherry picked from commit b6bc0257895c98be488a0da27f70bc9b9a9503a6) --- rust/src/applayer.rs | 12 +++ rust/sys/src/sys.rs | 26 +++++ src/app-layer-parser.c | 235 +++++++++++++++++++++++++++++++++++++++-- src/app-layer-parser.h | 15 +++ 4 files changed, 281 insertions(+), 7 deletions(-) diff --git a/rust/src/applayer.rs b/rust/src/applayer.rs index 2171e01db0e1..6765fa203443 100644 --- a/rust/src/applayer.rs +++ b/rust/src/applayer.rs @@ -144,6 +144,12 @@ pub struct AppLayerTxData { /// detect_progress_ts: u8, detect_progress_tc: u8, + #[doc = " Type of transaction. Meaning is defined by the parser. Used to\n select a state machine. 0 means it is not used."] + pub tx_type: u8, + #[doc = " End of TX progress values\n\n toserver end of tx progress value"] + pub tx_type_eop_ts: u8, + #[doc = " toclient end of tx progress value"] + pub tx_type_eop_tc: u8, de_state: *mut DetectEngineState, pub events: *mut core::AppLayerDecoderEvents, @@ -198,6 +204,9 @@ impl AppLayerTxData { flags: 0, detect_progress_ts: 0, detect_progress_tc: 0, + tx_type: 0, + tx_type_eop_ts: 0, + tx_type_eop_tc: 0, de_state: std::ptr::null_mut(), events: std::ptr::null_mut(), txbits: std::ptr::null_mut(), @@ -225,6 +234,9 @@ impl AppLayerTxData { detect_progress_ts: 0, detect_progress_tc: 0, flags, + tx_type: 0, + tx_type_eop_ts: 0, + tx_type_eop_tc: 0, de_state: std::ptr::null_mut(), events: std::ptr::null_mut(), txbits: std::ptr::null_mut(), diff --git a/rust/sys/src/sys.rs b/rust/sys/src/sys.rs index 1945c99077f5..4a06dc133339 100644 --- a/rust/sys/src/sys.rs +++ b/rust/sys/src/sys.rs @@ -721,12 +721,31 @@ pub struct AppLayerParserState_ { _unused: [u8; 0], } pub type AppLayerParserState = AppLayerParserState_; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct AppLayerTxData { + _unused: [u8; 0], +} extern "C" { #[doc = " \\brief Given a protocol name, checks if the parser is enabled in\n the conf file.\n\n \\param alproto_name Name of the app layer protocol.\n\n \\retval 1 If enabled.\n \\retval 0 If disabled."] pub fn SCAppLayerParserConfParserEnabled( ipproto: *const ::std::os::raw::c_char, alproto_name: *const ::std::os::raw::c_char, ) -> ::std::os::raw::c_int; } +extern "C" { + pub fn SCAppLayerTxDataCleanup(txd: *mut AppLayerTxData); +} +#[doc = " \\param name progress name to get the id for\n \\param direction STREAM_TOSERVER/STREAM_TOCLIENT"] +pub type AppLayerParserGetStateIdByNameFn = ::std::option::Option< + unsafe extern "C" fn( + name: *const ::std::os::raw::c_char, + direction: u8, + ) -> ::std::os::raw::c_int, +>; +#[doc = " \\param id progress value id to get the name for\n \\param direction STREAM_TOSERVER/STREAM_TOCLIENT"] +pub type AppLayerParserGetStateNameByIdFn = ::std::option::Option< + unsafe extern "C" fn(id: ::std::os::raw::c_int, direction: u8) -> *const ::std::os::raw::c_char, +>; extern "C" { pub fn SCAppLayerParserReallocCtx(alproto: AppProto) -> ::std::os::raw::c_int; } @@ -738,6 +757,13 @@ extern "C" { extern "C" { pub fn SCAppLayerParserRegisterLogger(ipproto: u8, alproto: AppProto); } +extern "C" { + #[doc = " \\brief register state<>name funcs for a substate"] + pub fn SCAppLayerParserRegisterGetTxSubStateFuncs( + alproto: AppProto, sub_state: u8, GetIdByNameFunc: AppLayerParserGetStateIdByNameFn, + GetNameByIdFunc: AppLayerParserGetStateNameByIdFn, + ); +} extern "C" { pub fn SCAppLayerParserSetStreamDepth(ipproto: u8, alproto: AppProto, stream_depth: u32); } diff --git a/src/app-layer-parser.c b/src/app-layer-parser.c index a7e68bbb4162..1b807d73622b 100644 --- a/src/app-layer-parser.c +++ b/src/app-layer-parser.c @@ -61,6 +61,12 @@ struct AppLayerParserThreadCtx_ { void *(*alproto_local_storage)[FLOW_PROTO_MAX]; }; +struct AppLayerParserSubStateMapping { + uint8_t sub_state; + AppLayerParserGetStateIdByNameFn GetStateIdByName; + AppLayerParserGetStateNameByIdFn GetStateNameById; + struct AppLayerParserSubStateMapping *next; +}; /** * \brief App layer protocol parser context. @@ -125,6 +131,13 @@ typedef struct AppLayerParserProtoCtx_ #ifdef UNITTESTS void (*RegisterUnittests)(void); #endif + + /* list of mappings per sub state + * only set for FLOW_PROTO_DEFAULT */ + struct AppLayerParserSubStateMapping *sub_state_mappings; + /* max value of a sub state + * only set for FLOW_PROTO_DEFAULT */ + uint8_t max_sub_state; } AppLayerParserProtoCtx; typedef struct AppLayerParserCtx_ { @@ -152,6 +165,10 @@ struct AppLayerParserState_ { FramesContainer *frames; }; +static inline uint8_t GetTxEndProgress(uint8_t ipproto, AppProto alproto, void *tx, uint8_t flags); +static inline uint8_t GetTxdEndProgress(uint8_t ipproto, AppProto alproto, + const AppLayerTxData *txd, uint8_t flags, uint8_t complete); + enum ExceptionPolicy g_applayerparser_error_policy = EXCEPTION_POLICY_NOT_SET; static void AppLayerConfig(void) @@ -286,6 +303,21 @@ int AppLayerParserDeSetup(void) { SCEnter(); + /* inclusive loop as some parsers use FLOW_PROTO_DEFAULT */ + for (int flow_proto = 0; flow_proto <= FLOW_PROTO_DEFAULT; flow_proto++) { + for (AppProto a = 0; a < g_alproto_max; a++) { + if (alp_ctx.ctxs[a][flow_proto].sub_state_mappings == NULL) + continue; + + while (alp_ctx.ctxs[a][flow_proto].sub_state_mappings) { + struct AppLayerParserSubStateMapping *next = + alp_ctx.ctxs[a][flow_proto].sub_state_mappings->next; + SCFree(alp_ctx.ctxs[a][flow_proto].sub_state_mappings); + alp_ctx.ctxs[a][flow_proto].sub_state_mappings = next; + } + } + } + SCFree(alp_ctx.ctxs); FTPParserCleanup(); @@ -558,6 +590,38 @@ void AppLayerParserRegisterGetEventInfoById(uint8_t ipproto, AppProto alproto, SCReturn; } +void SCAppLayerParserRegisterGetTxSubStateFuncs(AppProto alproto, const uint8_t sub_state, + AppLayerParserGetStateIdByNameFn GetIdByNameFunc, + AppLayerParserGetStateNameByIdFn GetNameByIdFunc) +{ + SCEnter(); + /* validate input */ + BUG_ON(sub_state == 0); + BUG_ON(GetIdByNameFunc == NULL); + BUG_ON(GetNameByIdFunc == NULL); + + AppLayerParserProtoCtx *p = &alp_ctx.ctxs[alproto][FLOW_PROTO_DEFAULT]; + /* double registration not allowed */ + for (struct AppLayerParserSubStateMapping *m = p->sub_state_mappings; m != NULL; m = m->next) { + BUG_ON(sub_state == m->sub_state); + } + struct AppLayerParserSubStateMapping *m = SCCalloc(1, sizeof(*m)); + if (m == NULL) + FatalError("failed to register substate"); + + m->sub_state = sub_state; + m->GetStateIdByName = GetIdByNameFunc; + m->GetStateNameById = GetNameByIdFunc; + m->next = p->sub_state_mappings; + p->sub_state_mappings = m; + + p->max_sub_state = MAX(p->max_sub_state, sub_state); + SCLogDebug("alproto %u:%s, sub_state:%u max:%u %p:%p", alproto, AppProtoToString(alproto), + sub_state, p->max_sub_state, m->GetStateIdByName, m->GetStateNameById); + + SCReturn; +} + void AppLayerParserRegisterGetStateFuncs(uint8_t ipproto, AppProto alproto, AppLayerParserGetStateIdByNameFn GetIdByNameFunc, AppLayerParserGetStateNameByIdFn GetNameByIdFunc) @@ -769,11 +833,13 @@ void AppLayerParserSetTransactionInspectId(const Flow *f, AppLayerParserState *p void *tx = ires.tx_ptr; idx = ires.tx_id; + AppLayerTxData *txd = AppLayerParserGetTxData(ipproto, alproto, tx); + const int tx_end_state = + GetTxdEndProgress(ipproto, alproto, txd, flags, (uint8_t)state_done_progress); int state_progress = AppLayerParserGetStateProgress(ipproto, alproto, tx, flags); - if (state_progress < state_done_progress) + if (state_progress < tx_end_state) break; - AppLayerTxData *txd = AppLayerParserGetTxData(ipproto, alproto, tx); if (tag_txs_as_inspected) { const uint8_t inspected_flag = (flags & STREAM_TOSERVER) ? APP_LAYER_TX_INSPECTED_TS : APP_LAYER_TX_INSPECTED_TC; @@ -808,11 +874,13 @@ void AppLayerParserSetTransactionInspectId(const Flow *f, AppLayerParserState *p } idx = ires.tx_id; + AppLayerTxData *txd = AppLayerParserGetTxData(ipproto, alproto, tx); + const int tx_end_state = + GetTxdEndProgress(ipproto, alproto, txd, flags, (uint8_t)state_done_progress); const int state_progress = AppLayerParserGetStateProgress(ipproto, alproto, tx, flags); - if (state_progress < state_done_progress) + if (state_progress < tx_end_state) break; - AppLayerTxData *txd = AppLayerParserGetTxData(ipproto, alproto, tx); const uint8_t inspected_flag = (flags & STREAM_TOSERVER) ? APP_LAYER_TX_INSPECTED_TS : APP_LAYER_TX_INSPECTED_TC; if (!(txd->flags & inspected_flag)) { @@ -950,14 +1018,18 @@ void AppLayerParserTransactionsCleanup(Flow *f, const uint8_t pkt_dir) } const int tx_progress_tc = AppLayerParserGetStateProgress(ipproto, alproto, tx, tc_disrupt_flags); - if (tx_progress_tc < tx_end_state_tc) { + const int end_state_tc = + GetTxdEndProgress(ipproto, alproto, txd, STREAM_TOCLIENT, (uint8_t)tx_end_state_tc); + if (tx_progress_tc < end_state_tc) { SCLogDebug("%p/%"PRIu64" skipping: tc parser not done", tx, i); skipped = true; goto next; } + const int end_state_ts = + GetTxdEndProgress(ipproto, alproto, txd, STREAM_TOSERVER, (uint8_t)tx_end_state_ts); const int tx_progress_ts = AppLayerParserGetStateProgress(ipproto, alproto, tx, ts_disrupt_flags); - if (tx_progress_ts < tx_end_state_ts) { + if (tx_progress_ts < end_state_ts) { SCLogDebug("%p/%"PRIu64" skipping: ts parser not done", tx, i); skipped = true; goto next; @@ -1067,6 +1139,55 @@ static inline int StateGetProgressCompletionStatus(const AppProto alproto, const } } +/** \internal + * \brief get the end state (progress) for a TX + * If the TX is supporting sub-states, return the value from the txd. + * \param tx pointer to the transaction + */ +static inline uint8_t GetTxEndProgress(uint8_t ipproto, AppProto alproto, void *tx, uint8_t flags) +{ + const AppLayerTxData *txd = AppLayerParserGetTxData(ipproto, alproto, tx); + DEBUG_VALIDATE_BUG_ON(txd == NULL); + uint8_t tx_end_state; + if (txd->tx_type == 0) { + tx_end_state = (uint8_t)AppLayerParserGetStateProgressCompletionStatus(alproto, flags); + } else { + if (flags & STREAM_TOSERVER) + tx_end_state = txd->tx_type_eop_ts; + else + tx_end_state = txd->tx_type_eop_tc; + } + return tx_end_state; +} + +/** \internal + * \brief get the end state (progress) for a TX(D) + * If the TX is supporting sub-states, return the value from the txd. + * \param txd pointer to the transactions txd + * \param complete optional final progress for the protocol + * + * `complete` can be passed in as it is often already looked up + * outside of the tx loop. + */ +static inline uint8_t GetTxdEndProgress(uint8_t ipproto, AppProto alproto, + const AppLayerTxData *txd, uint8_t flags, uint8_t complete) +{ + DEBUG_VALIDATE_BUG_ON(txd == NULL); + uint8_t tx_end_state; + if (txd->tx_type == 0) { + if (complete) + tx_end_state = complete; + else + tx_end_state = (uint8_t)AppLayerParserGetStateProgressCompletionStatus(alproto, flags); + } else { + if (flags & STREAM_TOSERVER) + tx_end_state = txd->tx_type_eop_ts; + else + tx_end_state = txd->tx_type_eop_tc; + } + return tx_end_state; +} + /** * \brief get the progress value for a tx/protocol * @@ -1077,7 +1198,7 @@ int AppLayerParserGetStateProgress(uint8_t ipproto, AppProto alproto, void *tx, SCEnter(); int r; if (unlikely(IS_DISRUPTED(flags))) { - r = StateGetProgressCompletionStatus(alproto, flags); + r = (int)GetTxEndProgress(ipproto, alproto, tx, flags); } else { const uint8_t direction = flags & (STREAM_TOCLIENT | STREAM_TOSERVER); r = alp_ctx.ctxs[alproto][FlowGetProtoMapping(ipproto)].StateGetProgress(tx, direction); @@ -1107,6 +1228,106 @@ int AppLayerParserGetStateProgressCompletionStatus(AppProto alproto, SCReturnInt(r); } +/** + * \brief + * + * Translate name to progress value for a substate `sub_state`. Calls the + * registered callbacks. + * + * \retval -1 not found + * \retval id value belonging to the state name + */ +int8_t AppLayerParserGetSubStateProgressId( + const AppProto alproto, const uint8_t sub_state, const char *state, const uint8_t dir_flag) +{ + BUG_ON(alp_ctx.ctxs[alproto][FLOW_PROTO_DEFAULT].max_sub_state == 0); + BUG_ON(dir_flag != STREAM_TOSERVER && dir_flag != STREAM_TOCLIENT); + + for (struct AppLayerParserSubStateMapping *m = + alp_ctx.ctxs[alproto][FLOW_PROTO_DEFAULT].sub_state_mappings; + m != NULL; m = m->next) { + if (m->sub_state == sub_state) { + BUG_ON(m->GetStateNameById == NULL); + BUG_ON(m->GetStateIdByName == NULL); + + int v = m->GetStateIdByName(state, dir_flag); + if (v < 0) { + /* name not found */ + return -1; + } + SCLogDebug("state:%s v:%u", state, v); + BUG_ON(v > 48); + return (int8_t)v; + } + } + + return -1; +} + +const char *AppLayerParserGetSubStateProgressName(const AppProto alproto, const uint8_t sub_state, + const uint8_t state, const uint8_t dir_flag) +{ + BUG_ON(alp_ctx.ctxs[alproto][FLOW_PROTO_DEFAULT].max_sub_state == 0); + BUG_ON(dir_flag != STREAM_TOSERVER && dir_flag != STREAM_TOCLIENT); + + for (struct AppLayerParserSubStateMapping *m = + alp_ctx.ctxs[alproto][FLOW_PROTO_DEFAULT].sub_state_mappings; + m != NULL; m = m->next) { + if (m->sub_state == sub_state) { + BUG_ON(m->GetStateNameById == NULL); + BUG_ON(m->GetStateIdByName == NULL); + + return m->GetStateNameById(state, dir_flag); + } + } + + return NULL; +} + +uint8_t AppLayerParserGetSubStateCompletion(const AppProto alproto, const uint8_t sub_state) +{ + BUG_ON(alp_ctx.ctxs[alproto][FLOW_PROTO_DEFAULT].max_sub_state == 0); + + /* TODO hard coded for now */ + BUG_ON(alproto != ALPROTO_HTTP2); + + if (sub_state == 1) { + return 4; + } else if (sub_state == 2) { + return 1; + } else { + BUG_ON(1); + } + return 0; +} + +const char *AppLayerParserGetSubStateName(const AppProto alproto, const uint8_t sub_state) +{ + BUG_ON(alp_ctx.ctxs[alproto][FLOW_PROTO_DEFAULT].max_sub_state == 0); + + /* TODO hard coded for now */ + BUG_ON(alproto != ALPROTO_HTTP2); + + if (sub_state == 1) { + return "stream"; + } else if (sub_state == 2) { + return "global"; + } else { + BUG_ON(1); + } + return NULL; +} + +uint8_t AppLayerParserGetMaxSubState(const AppProto alproto) +{ + return alp_ctx.ctxs[alproto][FLOW_PROTO_DEFAULT].max_sub_state; +} + +bool AppLayerParserSupportsSubStates(const AppProto alproto) +{ + return AppLayerParserGetMaxSubState(alproto) != 0; +} + int AppLayerParserGetEventInfo(uint8_t ipproto, AppProto alproto, const char *event_name, uint8_t *event_id, AppLayerEventType *event_type) { diff --git a/src/app-layer-parser.h b/src/app-layer-parser.h index 624874c7d3de..7aa79b1cb567 100644 --- a/src/app-layer-parser.h +++ b/src/app-layer-parser.h @@ -138,6 +138,8 @@ typedef struct AppLayerGetTxIterState { } un; } AppLayerGetTxIterState; +void SCAppLayerTxDataCleanup(AppLayerTxData *txd); + /** \brief tx iterator prototype */ typedef AppLayerGetTxIterTuple (*AppLayerGetTxIteratorFunc) (const uint8_t ipproto, const AppProto alproto, @@ -212,6 +214,11 @@ void AppLayerParserRegisterGetStateFuncs(uint8_t ipproto, AppProto alproto, AppLayerParserGetStateIdByNameFn GetStateIdByName, AppLayerParserGetStateNameByIdFn GetStateNameById); +/** \brief register state<>name funcs for a substate */ +void SCAppLayerParserRegisterGetTxSubStateFuncs(AppProto alproto, const uint8_t sub_state, + AppLayerParserGetStateIdByNameFn GetIdByNameFunc, + AppLayerParserGetStateNameByIdFn GetNameByIdFunc); + void AppLayerParserRegisterTxDataFunc(uint8_t ipproto, AppProto alproto, AppLayerTxData *(*GetTxData)(void *tx)); void AppLayerParserRegisterApplyTxConfigFunc(uint8_t ipproto, AppProto alproto, @@ -245,6 +252,14 @@ int AppLayerParserGetStateProgress(uint8_t ipproto, AppProto alproto, uint64_t AppLayerParserGetTxCnt(const Flow *, void *alstate); void *AppLayerParserGetTx(uint8_t ipproto, AppProto alproto, void *alstate, uint64_t tx_id); int AppLayerParserGetStateProgressCompletionStatus(AppProto alproto, uint8_t direction); +int8_t AppLayerParserGetSubStateProgressId( + const AppProto alproto, const uint8_t sub_state, const char *state, const uint8_t dir_flag); +const char *AppLayerParserGetSubStateProgressName(const AppProto alproto, const uint8_t sub_state, + const uint8_t state, const uint8_t dir_flag); +uint8_t AppLayerParserGetSubStateCompletion(const AppProto alproto, const uint8_t sub_state); +const char *AppLayerParserGetSubStateName(const AppProto alproto, const uint8_t sub_state); +uint8_t AppLayerParserGetMaxSubState(const AppProto alproto); +bool AppLayerParserSupportsSubStates(const AppProto alproto); int AppLayerParserGetEventInfo(uint8_t ipproto, AppProto alproto, const char *event_name, uint8_t *event_id, AppLayerEventType *event_type); int AppLayerParserGetEventInfoById(uint8_t ipproto, AppProto alproto, uint8_t event_id, From 8b26d6383dea5f63cd2e9642a40a99420ea7aef5 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Sat, 6 Jun 2026 14:29:57 +0200 Subject: [PATCH 21/69] detect: support per tx sub states Support for per transaction sub states: different state machines per transaction type. Skip engines not belonging to our substate. Store tx_type in DetectTransaction. Each protocol supporting sub states will register states from 1 and up. To support: Ticket: #8386. (cherry picked from commit 95365ce05b5305fc5c37978f477c702e918e03cd) --- rust/sys/src/sys.rs | 37 +++++- src/detect-engine-analyzer.c | 4 + src/detect-engine-helper.c | 34 +++++- src/detect-engine-helper.h | 10 +- src/detect-engine-mpm.c | 35 ++++-- src/detect-engine-mpm.h | 11 +- src/detect-engine-prefilter.c | 206 +++++++++++++++++++++------------- src/detect-engine-prefilter.h | 8 +- src/detect-engine.c | 69 +++++++++--- src/detect-engine.h | 13 ++- src/detect.c | 40 ++++++- src/detect.h | 19 +++- 12 files changed, 369 insertions(+), 117 deletions(-) diff --git a/rust/sys/src/sys.rs b/rust/sys/src/sys.rs index 4a06dc133339..92c5bf749112 100644 --- a/rust/sys/src/sys.rs +++ b/rust/sys/src/sys.rs @@ -234,6 +234,11 @@ pub struct DetectEngineThreadCtx_ { _unused: [u8; 0], } pub type DetectEngineThreadCtx = DetectEngineThreadCtx_; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct DetectEngineTransforms { + _unused: [u8; 0], +} extern "C" { pub fn SCInspectionBufferCheckAndExpand( buffer: *mut InspectionBuffer, min_size: u32, @@ -257,6 +262,16 @@ pub struct SigMatchCtx_ { _unused: [u8; 0], } pub type SigMatchCtx = SigMatchCtx_; +pub type InspectionBufferGetDataPtr = ::std::option::Option< + unsafe extern "C" fn( + det_ctx: *mut DetectEngineThreadCtx_, + transforms: *const DetectEngineTransforms, + f: *mut Flow, + flow_flags: u8, + txv: *mut ::std::os::raw::c_void, + list_id: ::std::os::raw::c_int, + ) -> *mut InspectionBuffer, +>; pub type InspectionMultiBufferGetDataPtr = ::std::option::Option< unsafe extern "C" fn( det_ctx: *mut DetectEngineThreadCtx_, @@ -377,6 +392,18 @@ extern "C" { alproto: AppProto, direction: u8, GetData: InspectionSingleBufferGetDataPtr, ) -> ::std::os::raw::c_int; } +extern "C" { + pub fn SCDetectHelperBufferProgressRegisterSubState( + name: *const ::std::os::raw::c_char, alproto: AppProto, direction: u8, sub_state: u8, + progress: u8, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn SCDetectRegisterMpmGeneric( + name: *const ::std::os::raw::c_char, desc: *const ::std::os::raw::c_char, + alproto: AppProto, direction: u8, GetData: InspectionBufferGetDataPtr, progress: u8, + ) -> ::std::os::raw::c_int; +} extern "C" { pub fn SCDetectHelperBufferProgressMpmRegister( name: *const ::std::os::raw::c_char, desc: *const ::std::os::raw::c_char, @@ -393,8 +420,14 @@ extern "C" { extern "C" { pub fn SCDetectHelperMultiBufferProgressMpmRegister( name: *const ::std::os::raw::c_char, desc: *const ::std::os::raw::c_char, - alproto: AppProto, direction: u8, GetData: InspectionMultiBufferGetDataPtr, - progress: ::std::os::raw::c_int, + alproto: AppProto, direction: u8, GetData: InspectionMultiBufferGetDataPtr, progress: u8, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn SCDetectHelperMultiBufferProgressMpmRegisterSubState( + name: *const ::std::os::raw::c_char, desc: *const ::std::os::raw::c_char, + alproto: AppProto, direction: u8, GetData: InspectionMultiBufferGetDataPtr, sub_state: u8, + progress: u8, ) -> ::std::os::raw::c_int; } extern "C" { diff --git a/src/detect-engine-analyzer.c b/src/detect-engine-analyzer.c index 32e7382c49cb..fe398e3bd41f 100644 --- a/src/detect-engine-analyzer.c +++ b/src/detect-engine-analyzer.c @@ -55,6 +55,7 @@ #include "detect-icmp-id.h" #include "detect-tcp-window.h" #include "detect-app-layer-protocol.h" +#include "app-layer-parser.h" static int rule_warnings_only = 0; @@ -1337,6 +1338,9 @@ void EngineAnalysisRules2(const DetectEngineCtx *de_ctx, const Signature *s) SCJbSetBool(ctx.js, "is_mpm", app->mpm); SCJbSetString(ctx.js, "app_proto", AppProtoToString(app->alproto)); SCJbSetUint(ctx.js, "progress", app->progress); + if (app->sub_state) + SCJbSetString(ctx.js, "sub_state", + AppLayerParserGetSubStateName(app->alproto, app->sub_state)); if (app->v2.transforms != NULL) { SCJbOpenArray(ctx.js, "transforms"); diff --git a/src/detect-engine-helper.c b/src/detect-engine-helper.c index ef44d33555c5..ae83dc067f4f 100644 --- a/src/detect-engine-helper.c +++ b/src/detect-engine-helper.c @@ -58,6 +58,20 @@ int SCDetectHelperBufferProgressRegister( return DetectBufferTypeRegister(name); } +int SCDetectHelperBufferProgressRegisterSubState( + const char *name, AppProto alproto, uint8_t direction, uint8_t sub_state, uint8_t progress) +{ + if (direction & STREAM_TOSERVER) { + DetectAppLayerInspectEngineRegisterSubState(name, alproto, SIG_FLAG_TOSERVER, sub_state, + (uint8_t)progress, DetectEngineInspectGenericList, NULL); + } + if (direction & STREAM_TOCLIENT) { + DetectAppLayerInspectEngineRegisterSubState(name, alproto, SIG_FLAG_TOCLIENT, sub_state, + (uint8_t)progress, DetectEngineInspectGenericList, NULL); + } + return DetectBufferTypeRegister(name); +} + int SCDetectHelperBufferMpmRegister(const char *name, const char *desc, AppProto alproto, uint8_t direction, InspectionSingleBufferGetDataPtr GetData) { @@ -97,7 +111,8 @@ int SCDetectHelperBufferProgressMpmRegister(const char *name, const char *desc, } int SCDetectHelperMultiBufferProgressMpmRegister(const char *name, const char *desc, - AppProto alproto, uint8_t direction, InspectionMultiBufferGetDataPtr GetData, int progress) + AppProto alproto, uint8_t direction, InspectionMultiBufferGetDataPtr GetData, + uint8_t progress) { if (direction & STREAM_TOSERVER) { DetectAppLayerMultiRegister(name, alproto, SIG_FLAG_TOSERVER, progress, GetData, 2); @@ -110,6 +125,23 @@ int SCDetectHelperMultiBufferProgressMpmRegister(const char *name, const char *d return DetectBufferTypeGetByName(name); } +int SCDetectHelperMultiBufferProgressMpmRegisterSubState(const char *name, const char *desc, + AppProto alproto, uint8_t direction, InspectionMultiBufferGetDataPtr GetData, + uint8_t sub_state, uint8_t progress) +{ + if (direction & STREAM_TOSERVER) { + DetectAppLayerMultiRegisterSubState( + name, alproto, SIG_FLAG_TOSERVER, sub_state, progress, GetData, 2); + } + if (direction & STREAM_TOCLIENT) { + DetectAppLayerMultiRegisterSubState( + name, alproto, SIG_FLAG_TOCLIENT, sub_state, progress, GetData, 2); + } + DetectBufferTypeSupportsMultiInstance(name); + DetectBufferTypeSetDescriptionByName(name, desc); + return DetectBufferTypeGetByName(name); +} + int SCDetectHelperMultiBufferMpmRegister(const char *name, const char *desc, AppProto alproto, uint8_t direction, InspectionMultiBufferGetDataPtr GetData) { diff --git a/src/detect-engine-helper.h b/src/detect-engine-helper.h index 7c923f99810b..4fcf340fb660 100644 --- a/src/detect-engine-helper.h +++ b/src/detect-engine-helper.h @@ -86,12 +86,20 @@ int SCDetectHelperBufferProgressRegister( int SCDetectHelperBufferMpmRegister(const char *name, const char *desc, AppProto alproto, uint8_t direction, InspectionSingleBufferGetDataPtr GetData); +int SCDetectHelperBufferProgressRegisterSubState( + const char *name, AppProto alproto, uint8_t direction, uint8_t sub_state, uint8_t progress); +int SCDetectRegisterMpmGeneric(const char *name, const char *desc, AppProto alproto, + uint8_t direction, InspectionBufferGetDataPtr GetData, uint8_t progress); int SCDetectHelperBufferProgressMpmRegister(const char *name, const char *desc, AppProto alproto, uint8_t direction, InspectionSingleBufferGetDataPtr GetData, int progress); int SCDetectHelperMultiBufferMpmRegister(const char *name, const char *desc, AppProto alproto, uint8_t direction, InspectionMultiBufferGetDataPtr GetData); int SCDetectHelperMultiBufferProgressMpmRegister(const char *name, const char *desc, - AppProto alproto, uint8_t direction, InspectionMultiBufferGetDataPtr GetData, int progress); + AppProto alproto, uint8_t direction, InspectionMultiBufferGetDataPtr GetData, + uint8_t progress); +int SCDetectHelperMultiBufferProgressMpmRegisterSubState(const char *name, const char *desc, + AppProto alproto, uint8_t direction, InspectionMultiBufferGetDataPtr GetData, + uint8_t sub_state, uint8_t progress); int SCDetectHelperTransformRegister(const SCTransformTableElmt *kw); diff --git a/src/detect-engine-mpm.c b/src/detect-engine-mpm.c index a082e8ea9c73..03c40c260272 100644 --- a/src/detect-engine-mpm.c +++ b/src/detect-engine-mpm.c @@ -89,7 +89,8 @@ static int g_mpm_list_cnt[DETECT_BUFFER_MPM_TYPE_SIZE] = { 0, 0, 0 }; static void RegisterInternal(const char *name, int direction, int priority, PrefilterRegisterFunc PrefilterRegister, InspectionBufferGetDataPtr GetData, InspectionSingleBufferGetDataPtr GetDataSingle, - InspectionMultiBufferGetDataPtr GetMultiData, AppProto alproto, int tx_min_progress) + InspectionMultiBufferGetDataPtr GetMultiData, AppProto alproto, uint8_t sub_state, + uint8_t tx_min_progress) { SCLogDebug("registering %s/%d/%d/%p/%p/%u/%d", name, direction, priority, PrefilterRegister, GetData, alproto, tx_min_progress); @@ -109,7 +110,7 @@ static void RegisterInternal(const char *name, int direction, int priority, // every HTTP2 can be accessed from DOH2 if (alproto == ALPROTO_HTTP2 || alproto == ALPROTO_DNS) { RegisterInternal(name, direction, priority, PrefilterRegister, GetData, GetDataSingle, - GetMultiData, ALPROTO_DOH2, tx_min_progress); + GetMultiData, ALPROTO_DOH2, sub_state, tx_min_progress); } DetectBufferMpmRegistry *am = SCCalloc(1, sizeof(*am)); BUG_ON(am == NULL); @@ -132,6 +133,7 @@ static void RegisterInternal(const char *name, int direction, int priority, } am->app_v2.alproto = alproto; am->app_v2.tx_min_progress = tx_min_progress; + am->app_v2.sub_state = sub_state; if (g_mpm_list[DETECT_BUFFER_MPM_TYPE_APP] == NULL) { g_mpm_list[DETECT_BUFFER_MPM_TYPE_APP] = am; @@ -147,32 +149,50 @@ static void RegisterInternal(const char *name, int direction, int priority, g_mpm_list_cnt[DETECT_BUFFER_MPM_TYPE_APP]++; SupportFastPatternForSigMatchList(sm_list, priority); + SCLogDebug("%s: sub_state %u", name, am->app_v2.sub_state); } void DetectAppLayerMpmRegister(const char *name, int direction, int priority, PrefilterRegisterFunc PrefilterRegister, InspectionBufferGetDataPtr GetData, - AppProto alproto, int tx_min_progress) + AppProto alproto, uint8_t tx_min_progress) { - RegisterInternal(name, direction, priority, PrefilterRegister, GetData, NULL, NULL, alproto, + RegisterInternal(name, direction, priority, PrefilterRegister, GetData, NULL, NULL, alproto, 0, tx_min_progress); } +void DetectAppLayerMpmRegisterSubState(const char *name, int direction, int priority, + PrefilterRegisterFunc PrefilterRegister, InspectionBufferGetDataPtr GetData, + AppProto alproto, uint8_t sub_state, uint8_t tx_min_progress) +{ + SCLogDebug("%s: sub_state %u", name, sub_state); + RegisterInternal(name, direction, priority, PrefilterRegister, GetData, NULL, NULL, alproto, + sub_state, tx_min_progress); +} + void DetectAppLayerMpmRegisterSingle(const char *name, int direction, int priority, PrefilterRegisterFunc PrefilterRegister, InspectionSingleBufferGetDataPtr GetData, AppProto alproto, int tx_min_progress) { - RegisterInternal(name, direction, priority, PrefilterRegister, NULL, GetData, NULL, alproto, + RegisterInternal(name, direction, priority, PrefilterRegister, NULL, GetData, NULL, alproto, 0, tx_min_progress); } void DetectAppLayerMpmMultiRegister(const char *name, int direction, int priority, PrefilterRegisterFunc PrefilterRegister, InspectionMultiBufferGetDataPtr GetData, - AppProto alproto, int tx_min_progress) + AppProto alproto, uint8_t tx_min_progress) { - RegisterInternal(name, direction, priority, PrefilterRegister, NULL, NULL, GetData, alproto, + RegisterInternal(name, direction, priority, PrefilterRegister, NULL, NULL, GetData, alproto, 0, tx_min_progress); } +void DetectAppLayerMpmMultiRegisterSubState(const char *name, int direction, int priority, + PrefilterRegisterFunc PrefilterRegister, InspectionMultiBufferGetDataPtr GetData, + AppProto alproto, uint8_t sub_state, uint8_t tx_min_progress) +{ + RegisterInternal(name, direction, priority, PrefilterRegister, NULL, NULL, GetData, alproto, + sub_state, tx_min_progress); +} + /** \internal * \brief build basic profiling name (pname) making sure the id is always fully printed */ @@ -260,6 +280,7 @@ void DetectAppLayerMpmRegisterByParentId(DetectEngineCtx *de_ctx, am->app_v2.GetData = t->app_v2.GetData; am->app_v2.alproto = t->app_v2.alproto; am->app_v2.tx_min_progress = t->app_v2.tx_min_progress; + am->app_v2.sub_state = t->app_v2.sub_state; am->priority = t->priority; am->sgh_mpm_context = t->sgh_mpm_context; am->sgh_mpm_context = MpmFactoryRegisterMpmCtxProfile( diff --git a/src/detect-engine-mpm.h b/src/detect-engine-mpm.h index 6bde23a2020c..2681f5d0ce02 100644 --- a/src/detect-engine-mpm.h +++ b/src/detect-engine-mpm.h @@ -86,13 +86,20 @@ typedef int (*PrefilterRegisterFunc)(DetectEngineCtx *de_ctx, SigGroupHead *sgh, */ void DetectAppLayerMpmRegister(const char *name, int direction, int priority, PrefilterRegisterFunc PrefilterRegister, InspectionBufferGetDataPtr GetData, - AppProto alproto, int tx_min_progress); + AppProto alproto, uint8_t tx_min_progress); +void DetectAppLayerMpmRegisterSubState(const char *name, int direction, int priority, + PrefilterRegisterFunc PrefilterRegister, InspectionBufferGetDataPtr GetData, + AppProto alproto, uint8_t sub_state, uint8_t tx_min_progress); void DetectAppLayerMpmRegisterSingle(const char *name, int direction, int priority, PrefilterRegisterFunc PrefilterRegister, InspectionSingleBufferGetDataPtr GetData, AppProto alproto, int tx_min_progress); void DetectAppLayerMpmMultiRegister(const char *name, int direction, int priority, PrefilterRegisterFunc PrefilterRegister, InspectionMultiBufferGetDataPtr GetData, - AppProto alproto, int tx_min_progress); + AppProto alproto, uint8_t tx_min_progress); +/** As DetectAppLayerMpmMultiRegister, but with a sub state argument */ +void DetectAppLayerMpmMultiRegisterSubState(const char *name, int direction, int priority, + PrefilterRegisterFunc PrefilterRegister, InspectionMultiBufferGetDataPtr GetData, + AppProto alproto, uint8_t sub_state, uint8_t tx_min_progress); void DetectAppLayerMpmRegisterByParentId( DetectEngineCtx *de_ctx, const int id, const int parent_id, diff --git a/src/detect-engine-prefilter.c b/src/detect-engine-prefilter.c index 4bb1ae2b1c39..9c330f36359d 100644 --- a/src/detect-engine-prefilter.c +++ b/src/detect-engine-prefilter.c @@ -104,11 +104,21 @@ void DetectRunPrefilterTx(DetectEngineThreadCtx *det_ctx, /* reset rule store */ det_ctx->pmq.rule_id_array_cnt = 0; - SCLogDebug("packet %" PRIu64 " tx %p progress %d tx->detect_progress %02x", p->pcap_cnt, - tx->tx_ptr, tx->tx_progress, tx->detect_progress); + SCLogDebug("packet %" PRIu64 " tx %p id %" PRIu64 " progress %d tx->detect_progress %02x", + p->pcap_cnt, tx->tx_ptr, tx->tx_id, tx->tx_progress, tx->detect_progress); PrefilterEngine *engine = sgh->tx_engines; do { + SCLogDebug("%" PRIu64 ": engine %p for %s progress %u sub_state %u tx %u", p->pcap_cnt, + engine, AppProtoToString(engine->alproto), engine->ctx.app.tx_min_progress, + engine->ctx.app.sub_state, tx->tx_type); + + if (engine->alproto != ALPROTO_UNKNOWN && engine->ctx.app.sub_state != tx->tx_type) { + SCLogDebug("%" PRIu64 ": engine %p sub_state %u mismatch with tx %u", p->pcap_cnt, + engine, engine->ctx.app.sub_state, tx->tx_type); + // not for the tx sub state + goto next; + } // based on flow alproto, and engine, we get right tx_ptr void *tx_ptr = DetectGetInnerTx(tx->tx_ptr, alproto, engine->alproto, flow_flags); if (tx_ptr == NULL) { @@ -116,30 +126,31 @@ void DetectRunPrefilterTx(DetectEngineThreadCtx *det_ctx, goto next; } - if (engine->ctx.tx_min_progress != -1) { + if (engine->ctx.app.tx_min_progress != -1) { #ifdef DEBUG const char *pname = AppLayerParserGetStateNameById(ipproto, engine->alproto, - engine->ctx.tx_min_progress, flow_flags & (STREAM_TOSERVER | STREAM_TOCLIENT)); - SCLogDebug("engine %p min_progress %d %s:%s", engine, engine->ctx.tx_min_progress, + engine->ctx.app.tx_min_progress, + flow_flags & (STREAM_TOSERVER | STREAM_TOCLIENT)); + SCLogDebug("engine %p min_progress %d %s:%s", engine, engine->ctx.app.tx_min_progress, AppProtoToString(engine->alproto), pname); #endif /* if engine needs tx state to be higher, break out. */ - if (engine->ctx.tx_min_progress > tx->tx_progress) + if (engine->ctx.app.tx_min_progress > tx->tx_progress) break; - if (tx->tx_progress > engine->ctx.tx_min_progress) { - SCLogDebug("tx->tx_progress %u > engine->ctx.tx_min_progress %d", tx->tx_progress, - engine->ctx.tx_min_progress); + if (tx->tx_progress > engine->ctx.app.tx_min_progress) { + SCLogDebug("tx->tx_progress %u > engine->ctx.app.tx_min_progress %d", + tx->tx_progress, engine->ctx.app.tx_min_progress); /* if state value is at or beyond engine state, we can skip it. It means we ran at * least once already. */ - if (tx->detect_progress > engine->ctx.tx_min_progress) { + if (tx->detect_progress > engine->ctx.app.tx_min_progress) { SCLogDebug("tx already marked progress as beyond engine: %u > %u", - tx->detect_progress, engine->ctx.tx_min_progress); + tx->detect_progress, engine->ctx.app.tx_min_progress); goto next; } else { - SCLogDebug("tx->tx_progress %u > engine->ctx.tx_min_progress %d: " + SCLogDebug("tx->tx_progress %u > engine->ctx.app.tx_min_progress %d: " "tx->detect_progress %u", - tx->tx_progress, engine->ctx.tx_min_progress, tx->detect_progress); + tx->tx_progress, engine->ctx.app.tx_min_progress, tx->detect_progress); } } #ifdef DEBUG @@ -150,21 +161,21 @@ void DetectRunPrefilterTx(DetectEngineThreadCtx *det_ctx, tx->tx_data_ptr, flow_flags); PREFILTER_PROFILING_END(det_ctx, engine->gid); SCLogDebug("engine %p min_progress %d %s:%s: results %u", engine, - engine->ctx.tx_min_progress, AppProtoToString(engine->alproto), pname, + engine->ctx.app.tx_min_progress, AppProtoToString(engine->alproto), pname, det_ctx->pmq.rule_id_array_cnt - old); - if (tx->tx_progress > engine->ctx.tx_min_progress && engine->is_last_for_progress && + if (tx->tx_progress > engine->ctx.app.tx_min_progress && engine->is_last_for_progress && tx->tx_ptr == tx_ptr) { /* track with an offset of one, so that tx->progress 0 complete is tracked * as 1, progress 1 as 2, etc. This is to allow 0 to mean: nothing tracked, even * though a parser may use 0 as a valid value. */ // tx->tx_ptr == tx_ptr ensures we do not use a dns engine progress // to update a HTTP2 tx detect_progress in case of DOH2 - tx->detect_progress = engine->ctx.tx_min_progress + 1; - SCLogDebug("tx->tx_progress %d engine->ctx.tx_min_progress %d " + tx->detect_progress = engine->ctx.app.tx_min_progress + 1; + SCLogDebug("tx->tx_progress %d engine->ctx.app.tx_min_progress %d " "engine->is_last_for_progress %d => tx->detect_progress updated to %02x", - tx->tx_progress, engine->ctx.tx_min_progress, engine->is_last_for_progress, - tx->detect_progress); + tx->tx_progress, engine->ctx.app.tx_min_progress, + engine->is_last_for_progress, tx->detect_progress); } } else { PREFILTER_PROFILING_START(det_ctx); @@ -352,9 +363,9 @@ int PrefilterAppendPayloadEngine(DetectEngineCtx *de_ctx, SigGroupHead *sgh, return 0; } -int PrefilterAppendTxEngine(DetectEngineCtx *de_ctx, SigGroupHead *sgh, - PrefilterTxFn PrefilterTxFunc, AppProto alproto, int tx_min_progress, void *pectx, - void (*FreeFunc)(void *pectx), const char *name) +int PrefilterAppendTxEngineSubState(DetectEngineCtx *de_ctx, SigGroupHead *sgh, + PrefilterTxFn PrefilterTxFunc, AppProto alproto, uint8_t sub_state, + const int8_t tx_min_progress, void *pectx, void (*FreeFunc)(void *pectx), const char *name) { if (sgh == NULL || PrefilterTxFunc == NULL || pectx == NULL) return -1; @@ -367,9 +378,8 @@ int PrefilterAppendTxEngine(DetectEngineCtx *de_ctx, SigGroupHead *sgh, e->PrefilterTx = PrefilterTxFunc; e->pectx = pectx; e->alproto = alproto; - // TODO change function prototype ? - DEBUG_VALIDATE_BUG_ON(tx_min_progress > INT8_MAX); - e->tx_min_progress = (uint8_t)tx_min_progress; + e->tx_min_progress = tx_min_progress; + e->sub_state = sub_state; e->Free = FreeFunc; if (sgh->init->tx_engines == NULL) { @@ -386,9 +396,18 @@ int PrefilterAppendTxEngine(DetectEngineCtx *de_ctx, SigGroupHead *sgh, e->name = name; e->gid = PrefilterStoreGetId(de_ctx, e->name, e->Free); + SCLogDebug("%s: sub_state %u", name, e->sub_state); return 0; } +int PrefilterAppendTxEngine(DetectEngineCtx *de_ctx, SigGroupHead *sgh, + PrefilterTxFn PrefilterTxFunc, AppProto alproto, const int8_t tx_min_progress, void *pectx, + void (*FreeFunc)(void *pectx), const char *name) +{ + return PrefilterAppendTxEngineSubState( + de_ctx, sgh, PrefilterTxFunc, alproto, 0, tx_min_progress, pectx, FreeFunc, name); +} + int PrefilterAppendFrameEngine(DetectEngineCtx *de_ctx, SigGroupHead *sgh, PrefilterFrameFn PrefilterFrameFunc, AppProto alproto, uint8_t frame_type, void *pectx, void (*FreeFunc)(void *pectx), const char *name) @@ -521,14 +540,18 @@ static int PrefilterSetupRuleGroupSortHelper(const void *a, const void *b) { const PrefilterEngine *s0 = a; const PrefilterEngine *s1 = b; - if (s1->ctx.tx_min_progress == s0->ctx.tx_min_progress) { - if (s1->alproto == s0->alproto) { - return s0->local_id > s1->local_id ? 1 : -1; + if (s1->ctx.app.sub_state == s0->ctx.app.sub_state) { + if (s1->ctx.app.tx_min_progress == s0->ctx.app.tx_min_progress) { + if (s1->alproto == s0->alproto) { + return s0->local_id > s1->local_id ? 1 : -1; + } else { + return s0->alproto > s1->alproto ? 1 : -1; + } } else { - return s0->alproto > s1->alproto ? 1 : -1; + return s0->ctx.app.tx_min_progress > s1->ctx.app.tx_min_progress ? 1 : -1; } } else { - return s0->ctx.tx_min_progress > s1->ctx.tx_min_progress ? 1 : -1; + return s0->ctx.app.sub_state > s1->ctx.app.sub_state ? 1 : -1; } } @@ -702,6 +725,7 @@ static void NonPFNamesFree(void *data) struct TxNonPFData { AppProto alproto; + uint8_t sub_state; 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, @@ -714,15 +738,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 + d->sig_list) % h->array_size; + return (d->alproto + d->sub_state + 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 && - d1->sig_list == d2->sig_list; + return d1->alproto == d2->alproto && d1->sub_state == d2->sub_state && + d1->progress == d2->progress && d1->dir == d2->dir && d1->sig_list == d2->sig_list; } static void TxNonPFFree(void *data) @@ -733,13 +757,14 @@ 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 int sig_list, - const char *name, const Signature *s) + const AppProto alproto, const uint8_t sub_state, const int dir, const uint8_t progress, + const int sig_list, const char *name, const Signature *s) { const uint32_t max_sids = DetectEngineGetMaxSigId(de_ctx); struct TxNonPFData lookup = { .alproto = alproto, + .sub_state = sub_state, .dir = dir, .progress = progress, .sig_list = sig_list, @@ -771,6 +796,7 @@ static int TxNonPFAddSig(DetectEngineCtx *de_ctx, HashListTable *tx_engines_hash return -1; } add->dir = dir; + add->sub_state = sub_state; add->alproto = alproto; add->progress = progress; add->sig_list = sig_list; @@ -951,8 +977,9 @@ static int SetupNonPrefilter(DetectEngineCtx *de_ctx, SigGroupHead *sgh) } const int sm_list = DetectEngineAppHookToSmlist( s->alproto, state, dir == 0 ? STREAM_TOSERVER : STREAM_TOCLIENT); - if (TxNonPFAddSig(de_ctx, tx_engines_hash, s->alproto, dir, (int16_t)state, sm_list, - pname, s) != 0) { + uint8_t sub_state = s->init_data->hook.t.app.sub_state; + if (TxNonPFAddSig(de_ctx, tx_engines_hash, s->alproto, sub_state, dir, state, + sm_list, pname, s) != 0) { goto error; } tx_non_pf = true; @@ -1015,7 +1042,8 @@ static int SetupNonPrefilter(DetectEngineCtx *de_ctx, SigGroupHead *sgh) 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, + const uint8_t sub_state = app->sub_state; + if (TxNonPFAddSig(de_ctx, tx_engines_hash, app->alproto, sub_state, app->dir, app->progress, sig_list, buf->name, s) != 0) { goto error; } @@ -1036,9 +1064,10 @@ static int SetupNonPrefilter(DetectEngineCtx *de_ctx, SigGroupHead *sgh) s->alproto, s->init_data->hook.t.app.app_progress, dir == 0 ? STREAM_TOSERVER : STREAM_TOCLIENT); - if (TxNonPFAddSig(de_ctx, tx_engines_hash, s->alproto, dir, - (int16_t)s->init_data->hook.t.app.app_progress, s->init_data->hook.sm_list, - pname, s) != 0) { + uint8_t sub_state = s->init_data->hook.t.app.sub_state; + if (TxNonPFAddSig(de_ctx, tx_engines_hash, s->alproto, sub_state, dir, + s->init_data->hook.t.app.app_progress, s->init_data->hook.sm_list, pname, + s) != 0) { goto error; } tx_non_pf = true; @@ -1128,8 +1157,8 @@ 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, engine_progress, - (void *)data, PrefilterNonPFDataFree, t->engine_name) < 0) { + if (PrefilterAppendTxEngineSubState(de_ctx, sgh, PrefilterTxNonPF, t->alproto, t->sub_state, + engine_progress, (void *)data, PrefilterNonPFDataFree, t->engine_name) < 0) { SCFree(data); goto error; } @@ -1304,7 +1333,8 @@ int PrefilterSetupRuleGroup(DetectEngineCtx *de_ctx, SigGroupHead *sgh) for (el = sgh->init->tx_engines ; el != NULL; el = el->next) { e->local_id = local_id++; e->alproto = el->alproto; - e->ctx.tx_min_progress = el->tx_min_progress; + e->ctx.app.tx_min_progress = el->tx_min_progress; + e->ctx.app.sub_state = el->sub_state; e->cb.PrefilterTx = el->PrefilterTx; e->pectx = el->pectx; el->pectx = NULL; // e now owns the ctx @@ -1319,45 +1349,58 @@ int PrefilterSetupRuleGroup(DetectEngineCtx *de_ctx, SigGroupHead *sgh) sgh->tx_engines[local_id - 1].is_last_for_progress = true; PrefilterEngine *engine; - /* per alproto to set is_last_for_progress per alproto because the inspect * loop skips over engines that are not the correct alproto */ for (AppProto a = ALPROTO_FAILED + 1; a < g_alproto_max; a++) { - int last_tx_progress = 0; - bool last_tx_progress_set = false; - PrefilterEngine *prev_engine = NULL; - engine = sgh->tx_engines; - do { - if (engine->ctx.tx_min_progress != -1) - BUG_ON(engine->ctx.tx_min_progress < last_tx_progress); - if (engine->alproto == a) { - if (last_tx_progress_set && engine->ctx.tx_min_progress > last_tx_progress) { - if (prev_engine) { - prev_engine->is_last_for_progress = true; + /* loop over sub-states. Protocols not supporting sub-states + * will just use 0. */ + const uint8_t max_sub_state = AppLayerParserGetMaxSubState(a); + for (uint8_t sub = 0; sub <= max_sub_state; sub++) { + int last_tx_progress = 0; + bool last_tx_progress_set = false; + PrefilterEngine *prev_engine = NULL; + engine = sgh->tx_engines; + do { + if (engine->ctx.app.sub_state == sub) { + if (engine->ctx.app.tx_min_progress != -1) + BUG_ON(engine->ctx.app.tx_min_progress < last_tx_progress); + if (engine->alproto == a) { + if (last_tx_progress_set && + engine->ctx.app.tx_min_progress > last_tx_progress) { + if (prev_engine) { + prev_engine->is_last_for_progress = true; + } + } + + last_tx_progress_set = true; + prev_engine = engine; + if (!engine->is_last) { + PrefilterEngine *next_engine = engine + 1; + engine->is_last_for_progress = + (next_engine->ctx.app.sub_state != sub); + } + + } else { + if (prev_engine) { + prev_engine->is_last_for_progress = true; + } } + last_tx_progress = engine->ctx.app.tx_min_progress; } - - last_tx_progress_set = true; - prev_engine = engine; - } else { - if (prev_engine) { - prev_engine->is_last_for_progress = true; - } - } - last_tx_progress = engine->ctx.tx_min_progress; - if (engine->is_last) - break; - engine++; - } while (1); + if (engine->is_last) + break; + engine++; + } while (1); + } } #ifdef DEBUG SCLogDebug("sgh %p", sgh); engine = sgh->tx_engines; do { - SCLogDebug("engine: gid %u alproto %s tx_min_progress %d is_last %s " + SCLogDebug("engine: gid %u alproto %s sub_state %u tx_min_progress %d is_last %s " "is_last_for_progress %s", - engine->gid, AppProtoToString(engine->alproto), engine->ctx.tx_min_progress, - engine->is_last ? "true" : "false", + engine->gid, AppProtoToString(engine->alproto), engine->ctx.app.sub_state, + engine->ctx.app.tx_min_progress, engine->is_last ? "true" : "false", engine->is_last_for_progress ? "true" : "false"); if (engine->is_last) break; @@ -1625,9 +1668,10 @@ int PrefilterGenericMpmRegister(DetectEngineCtx *de_ctx, SigGroupHead *sgh, MpmC pectx->mpm_ctx = mpm_ctx; pectx->transforms = &mpm_reg->transforms; - int r = PrefilterAppendTxEngine(de_ctx, sgh, PrefilterMpm, - mpm_reg->app_v2.alproto, mpm_reg->app_v2.tx_min_progress, - pectx, PrefilterGenericMpmFree, mpm_reg->pname); + SCLogDebug("mpm_reg %s sub_state %u", mpm_reg->name, mpm_reg->app_v2.sub_state); + int r = PrefilterAppendTxEngineSubState(de_ctx, sgh, PrefilterMpm, mpm_reg->app_v2.alproto, + mpm_reg->app_v2.sub_state, mpm_reg->app_v2.tx_min_progress, pectx, + PrefilterGenericMpmFree, mpm_reg->pname); if (r != 0) { SCFree(pectx); } @@ -1646,8 +1690,10 @@ int PrefilterSingleMpmRegister(DetectEngineCtx *de_ctx, SigGroupHead *sgh, MpmCt pectx->mpm_ctx = mpm_ctx; pectx->transforms = &mpm_reg->transforms; - int r = PrefilterAppendTxEngine(de_ctx, sgh, PrefilterMpmTxSingle, mpm_reg->app_v2.alproto, - mpm_reg->app_v2.tx_min_progress, pectx, PrefilterGenericMpmFree, mpm_reg->pname); + SCLogDebug("mpm_reg %s sub_state %u", mpm_reg->name, mpm_reg->app_v2.sub_state); + int r = PrefilterAppendTxEngineSubState(de_ctx, sgh, PrefilterMpmTxSingle, + mpm_reg->app_v2.alproto, mpm_reg->app_v2.sub_state, mpm_reg->app_v2.tx_min_progress, + pectx, PrefilterGenericMpmFree, mpm_reg->pname); if (r != 0) { SCFree(pectx); } @@ -1699,8 +1745,10 @@ int PrefilterMultiGenericMpmRegister(DetectEngineCtx *de_ctx, SigGroupHead *sgh, pectx->mpm_ctx = mpm_ctx; pectx->transforms = &mpm_reg->transforms; - int r = PrefilterAppendTxEngine(de_ctx, sgh, PrefilterMultiMpm, mpm_reg->app_v2.alproto, - mpm_reg->app_v2.tx_min_progress, pectx, PrefilterMultiGenericMpmFree, mpm_reg->pname); + SCLogDebug("mpm_reg %s sub_state %u", mpm_reg->name, mpm_reg->app_v2.sub_state); + int r = PrefilterAppendTxEngineSubState(de_ctx, sgh, PrefilterMultiMpm, mpm_reg->app_v2.alproto, + mpm_reg->app_v2.sub_state, mpm_reg->app_v2.tx_min_progress, pectx, + PrefilterMultiGenericMpmFree, mpm_reg->pname); if (r != 0) { SCFree(pectx); } diff --git a/src/detect-engine-prefilter.h b/src/detect-engine-prefilter.h index 3e6e470f4fbc..53e4aef834eb 100644 --- a/src/detect-engine-prefilter.h +++ b/src/detect-engine-prefilter.h @@ -43,6 +43,9 @@ typedef struct DetectTransaction_ { const uint8_t tx_progress; const uint8_t tx_end_state; bool is_last; /* is this the last tx? */ + + /** app-layer specific transaction type (for sub-state support). 0 if not used. */ + const uint8_t tx_type; } DetectTransaction; typedef struct PrefilterStore_ { @@ -63,8 +66,11 @@ void PrefilterPostRuleMatch( int PrefilterAppendPayloadEngine(DetectEngineCtx *de_ctx, SigGroupHead *sgh, PrefilterPktFn PrefilterFunc, void *pectx, void (*FreeFunc)(void *pectx), const char *name); +int PrefilterAppendTxEngineSubState(DetectEngineCtx *de_ctx, SigGroupHead *sgh, + PrefilterTxFn PrefilterTxFunc, AppProto alproto, uint8_t sub_state, + const int8_t tx_min_progress, void *pectx, void (*FreeFunc)(void *pectx), const char *name); int PrefilterAppendTxEngine(DetectEngineCtx *de_ctx, SigGroupHead *sgh, - PrefilterTxFn PrefilterTxFunc, const AppProto alproto, const int tx_min_progress, + PrefilterTxFn PrefilterTxFunc, const AppProto alproto, const int8_t tx_min_progress, void *pectx, void (*FreeFunc)(void *pectx), const char *name); int PrefilterAppendFrameEngine(DetectEngineCtx *de_ctx, SigGroupHead *sgh, PrefilterFrameFn PrefilterFrameFunc, AppProto alproto, uint8_t frame_type, void *pectx, diff --git a/src/detect-engine.c b/src/detect-engine.c index abcd358cfa53..051a8fc04ff0 100644 --- a/src/detect-engine.c +++ b/src/detect-engine.c @@ -195,8 +195,8 @@ void DetectPktInspectEngineRegister(const char *name, * * \note errors are fatal */ static void AppLayerInspectEngineRegisterInternal(const char *name, AppProto alproto, uint32_t dir, - int progress, InspectEngineFuncPtr Callback, InspectionBufferGetDataPtr GetData, - InspectionSingleBufferGetDataPtr GetDataSingle, + uint8_t sub_state, uint8_t progress, InspectEngineFuncPtr Callback, + InspectionBufferGetDataPtr GetData, InspectionSingleBufferGetDataPtr GetDataSingle, InspectionMultiBufferGetDataPtr GetMultiData) { BUG_ON(progress >= 48); @@ -209,8 +209,8 @@ static void AppLayerInspectEngineRegisterInternal(const char *name, AppProto alp SCLogDebug("name %s id %d", name, sm_list); if ((alproto == ALPROTO_FAILED) || (!(dir == SIG_FLAG_TOSERVER || dir == SIG_FLAG_TOCLIENT)) || - (sm_list < DETECT_SM_LIST_MATCH) || (sm_list >= SHRT_MAX) || - (progress < 0 || progress >= SHRT_MAX) || (Callback == NULL)) { + (sm_list < DETECT_SM_LIST_MATCH) || (sm_list >= SHRT_MAX) || (progress >= 48) || + (Callback == NULL)) { SCLogError("Invalid arguments"); BUG_ON(1); } else if (Callback == DetectEngineInspectBufferGeneric && GetData == NULL) { @@ -235,8 +235,8 @@ static void AppLayerInspectEngineRegisterInternal(const char *name, AppProto alp } // every DNS or HTTP2 can be accessed from DOH2 if (alproto == ALPROTO_HTTP2 || alproto == ALPROTO_DNS) { - AppLayerInspectEngineRegisterInternal( - name, ALPROTO_DOH2, dir, progress, Callback, GetData, GetDataSingle, GetMultiData); + AppLayerInspectEngineRegisterInternal(name, ALPROTO_DOH2, dir, sub_state, progress, + Callback, GetData, GetDataSingle, GetMultiData); } DetectEngineAppInspectionEngine *new_engine = @@ -248,7 +248,8 @@ static void AppLayerInspectEngineRegisterInternal(const char *name, AppProto alp new_engine->dir = direction; new_engine->sm_list = (uint16_t)sm_list; new_engine->sm_list_base = (uint16_t)sm_list; - new_engine->progress = (int16_t)progress; + new_engine->progress = progress; + new_engine->sub_state = sub_state; new_engine->v2.Callback = Callback; if (Callback == DetectEngineInspectBufferGeneric) { new_engine->v2.GetData = GetData; @@ -281,7 +282,8 @@ void DetectAppLayerInspectEngineRegister(const char *name, AppProto alproto, uin const int sm_list = DetectBufferTypeGetByName(name); if (t->sm_list == sm_list && t->alproto == alproto && t_direction == dir && - t->progress == progress && t->v2.Callback == Callback && t->v2.GetData == GetData) { + t->sub_state == 0 && t->progress == progress && t->v2.Callback == Callback && + t->v2.GetData == GetData) { DEBUG_VALIDATE_BUG_ON(1); return; } @@ -289,9 +291,32 @@ void DetectAppLayerInspectEngineRegister(const char *name, AppProto alproto, uin } AppLayerInspectEngineRegisterInternal( - name, alproto, dir, progress, Callback, GetData, NULL, NULL); + name, alproto, dir, 0, (uint8_t)progress, Callback, GetData, NULL, NULL); } +void DetectAppLayerInspectEngineRegisterSubState(const char *name, AppProto alproto, uint32_t dir, + uint8_t sub_state, uint8_t progress, InspectEngineFuncPtr Callback, + InspectionBufferGetDataPtr GetData) +{ + /* before adding, check that we don't add a duplicate entry, which will + * propagate all the way into the packet runtime if allowed. */ + DetectEngineAppInspectionEngine *t = g_app_inspect_engines; + while (t != NULL) { + const uint32_t t_direction = t->dir == 0 ? SIG_FLAG_TOSERVER : SIG_FLAG_TOCLIENT; + const int sm_list = DetectBufferTypeGetByName(name); + + if (t->sm_list == sm_list && t->alproto == alproto && t_direction == dir && + t->sub_state == sub_state && t->progress == progress && + t->v2.Callback == Callback && t->v2.GetData == GetData) { + DEBUG_VALIDATE_BUG_ON(1); + return; + } + t = t->next; + } + + AppLayerInspectEngineRegisterInternal( + name, alproto, dir, sub_state, progress, Callback, GetData, NULL, NULL); +} void DetectAppLayerInspectEngineRegisterSingle(const char *name, AppProto alproto, uint32_t dir, int progress, InspectEngineFuncPtr Callback, InspectionSingleBufferGetDataPtr GetData) { @@ -312,7 +337,7 @@ void DetectAppLayerInspectEngineRegisterSingle(const char *name, AppProto alprot } AppLayerInspectEngineRegisterInternal( - name, alproto, dir, progress, Callback, NULL, GetData, NULL); + name, alproto, dir, 0, (uint8_t)progress, Callback, NULL, GetData, NULL); } /* copy an inspect engine with transforms to a new list id. */ @@ -335,6 +360,7 @@ static void DetectAppLayerInspectEngineCopy( DEBUG_VALIDATE_BUG_ON(sm_list < 0 || sm_list > UINT16_MAX); new_engine->sm_list_base = (uint16_t)sm_list; new_engine->progress = t->progress; + new_engine->sub_state = t->sub_state; new_engine->v2 = t->v2; new_engine->v2.transforms = transforms; /* assign transforms */ @@ -368,6 +394,7 @@ static void DetectAppLayerInspectEngineCopyListToDetectCtx(DetectEngineCtx *de_c new_engine->sm_list = t->sm_list; new_engine->sm_list_base = t->sm_list; new_engine->progress = t->progress; + new_engine->sub_state = t->sub_state; new_engine->v2 = t->v2; if (list == NULL) { @@ -751,6 +778,7 @@ static void AppendAppInspectEngine(DetectEngineCtx *de_ctx, new_engine->smd = smd; new_engine->match_on_null = smd ? DetectContentInspectionMatchOnAbsentBuffer(smd) : false; new_engine->progress = t->progress; + new_engine->sub_state = t->sub_state; new_engine->v2 = t->v2; SCLogDebug("sm_list %d new_engine->v2 %p/%p/%p", new_engine->sm_list, new_engine->v2.Callback, new_engine->v2.GetData, new_engine->v2.transforms); @@ -900,7 +928,8 @@ int DetectEngineAppInspectionEngine2Signature(DetectEngineCtx *de_ctx, Signature DetectEngineAppInspectionEngine t = { .alproto = s->init_data->hook.t.app.alproto, - .progress = (uint16_t)state, + .progress = state, + .sub_state = s->init_data->hook.t.app.sub_state, .sm_list = (uint16_t)sm_list, .sm_list_base = (uint16_t)sm_list, .dir = dir, @@ -982,7 +1011,8 @@ int DetectEngineAppInspectionEngine2Signature(DetectEngineCtx *de_ctx, Signature DetectEngineAppInspectionEngine t = { .alproto = s->init_data->hook.t.app.alproto, - .progress = (uint16_t)s->init_data->hook.t.app.app_progress, + .progress = s->init_data->hook.t.app.app_progress, + .sub_state = s->init_data->hook.t.app.sub_state, .sm_list = (uint16_t)s->init_data->hook.sm_list, .sm_list_base = (uint16_t)s->init_data->hook.sm_list, .dir = dir, @@ -2211,10 +2241,21 @@ uint8_t DetectEngineInspectBufferGeneric(DetectEngineCtx *de_ctx, DetectEngineTh // wrapper for both DetectAppLayerInspectEngineRegister and DetectAppLayerMpmRegister // with cast of callback function -void DetectAppLayerMultiRegister(const char *name, AppProto alproto, uint32_t dir, int progress, +void DetectAppLayerMultiRegisterSubState(const char *name, AppProto alproto, uint32_t dir, + uint8_t sub_state, uint8_t progress, InspectionMultiBufferGetDataPtr GetData, int priority) +{ + AppLayerInspectEngineRegisterInternal(name, alproto, dir, sub_state, progress, + DetectEngineInspectMultiBufferGeneric, NULL, NULL, GetData); + DetectAppLayerMpmMultiRegisterSubState(name, dir, priority, PrefilterMultiGenericMpmRegister, + GetData, alproto, sub_state, progress); +} + +// wrapper for both DetectAppLayerInspectEngineRegister and DetectAppLayerMpmRegister +// with cast of callback function +void DetectAppLayerMultiRegister(const char *name, AppProto alproto, uint32_t dir, uint8_t progress, InspectionMultiBufferGetDataPtr GetData, int priority) { - AppLayerInspectEngineRegisterInternal(name, alproto, dir, progress, + AppLayerInspectEngineRegisterInternal(name, alproto, dir, 0, (uint8_t)progress, DetectEngineInspectMultiBufferGeneric, NULL, NULL, GetData); DetectAppLayerMpmMultiRegister( name, dir, priority, PrefilterMultiGenericMpmRegister, GetData, alproto, progress); diff --git a/src/detect-engine.h b/src/detect-engine.h index 83e9c9e725fe..0fc327b65cc9 100644 --- a/src/detect-engine.h +++ b/src/detect-engine.h @@ -167,10 +167,21 @@ int DetectEngineInspectPktBufferGeneric( void DetectAppLayerInspectEngineRegister(const char *name, AppProto alproto, uint32_t dir, int progress, InspectEngineFuncPtr Callback2, InspectionBufferGetDataPtr GetData); +/** + * \brief register an app inspection engine for a tx type + * \param type the tx type + */ +void DetectAppLayerInspectEngineRegisterSubState(const char *name, AppProto alproto, uint32_t dir, + uint8_t type, uint8_t progress, InspectEngineFuncPtr Callback2, + InspectionBufferGetDataPtr GetData); + void DetectAppLayerInspectEngineRegisterSingle(const char *name, AppProto alproto, uint32_t dir, int progress, InspectEngineFuncPtr Callback2, InspectionSingleBufferGetDataPtr GetData); -void DetectAppLayerMultiRegister(const char *name, AppProto alproto, uint32_t dir, int progress, +void DetectAppLayerMultiRegisterSubState(const char *name, AppProto alproto, uint32_t dir, + uint8_t sub_state, uint8_t progress, InspectionMultiBufferGetDataPtr GetData, int priority); + +void DetectAppLayerMultiRegister(const char *name, AppProto alproto, uint32_t dir, uint8_t progress, InspectionMultiBufferGetDataPtr GetData, int priority); void DetectPktInspectEngineRegister(const char *name, diff --git a/src/detect.c b/src/detect.c index be673d0713cb..47c287151dde 100644 --- a/src/detect.c +++ b/src/detect.c @@ -1309,14 +1309,33 @@ static int DetectRunTxInspectRule(ThreadVars *tv, DetectEngineCtx *de_ctx, 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 transactional signature, and we are toclient if (!(inspect_flags & BIT_U32(engine->id)) && (direction == engine->dir || ((s->flags & SIG_FLAG_TXBOTHDIR) && direction == 1))) { + if (engine->alproto != ALPROTO_UNKNOWN && // app-layer-events is registered for each + // proto this way + tx->tx_type != engine->sub_state) { + TRACE_SID_TXS(s->id, tx, + "skip because engine alproto %s sub_state %u != tx_type %u (engine " + "progress %u)", + AppProtoToString(engine->alproto), engine->sub_state, tx->tx_type, + engine->progress); + engine = engine->next; + continue; + } + TRACE_SID_TXS(s->id, tx, + "inspecting engine alproto %s sub_state %u == tx_type %u (engine progress %u)", + AppProtoToString(engine->alproto), engine->sub_state, tx->tx_type, + engine->progress); + void *tx_ptr = DetectGetInnerTx(tx->tx_ptr, f->alproto, engine->alproto, flow_flags); if (tx_ptr == NULL) { + TRACE_SID_TXS(s->id, tx, "no tx_ptr after DetectGetInnerTx"); if (engine->alproto != ALPROTO_UNKNOWN) { + TRACE_SID_TXS(s->id, tx, "no tx_ptr skip engine"); /* special case: file_data on 'alert tcp' will have engines * in the list that are not for us. */ engine = engine->next; @@ -1325,6 +1344,7 @@ static int DetectRunTxInspectRule(ThreadVars *tv, DetectEngineCtx *de_ctx, tx_ptr = tx->tx_ptr; } } + TRACE_SID_TXS(s->id, tx, "tx_ptr %p", tx_ptr); /* engines are sorted per progress, except that the one with * mpm/prefilter enabled is first */ @@ -1415,6 +1435,8 @@ static int DetectRunTxInspectRule(ThreadVars *tv, DetectEngineCtx *de_ctx, break; } else if (!(inspect_flags & BIT_U32(engine->id)) && s->flags & SIG_FLAG_TXBOTHDIR && direction != engine->dir) { + TRACE_SID_TXS(s->id, tx, "handle bidir engine"); + // for transactional 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 @@ -1498,7 +1520,7 @@ static int DetectRunTxInspectRule(ThreadVars *tv, DetectEngineCtx *de_ctx, #define NO_TX \ { \ - NULL, 0, NULL, NULL, 0, 0, 0, 0, false, \ + NULL, 0, NULL, NULL, 0, 0, 0, 0, false, 0, \ } /** \internal @@ -1511,11 +1533,16 @@ static DetectTransaction GetDetectTx(const uint8_t ipproto, const AppProto alpro DEBUG_VALIDATE_BUG_ON(tx_end_state >= 48); AppLayerTxData *txd = AppLayerParserGetTxData(ipproto, alproto, tx_ptr); - const int tx_progress = AppLayerParserGetStateProgress(ipproto, alproto, tx_ptr, flow_flags); + const uint8_t tx_progress = + (uint8_t)AppLayerParserGetStateProgress(ipproto, alproto, tx_ptr, flow_flags); DEBUG_VALIDATE_BUG_ON(tx_progress >= 48); + const uint8_t e_tx_end_state = txd->tx_type == 0 ? (uint8_t)tx_end_state + : (flow_flags & STREAM_TOSERVER) ? txd->tx_type_eop_ts + : txd->tx_type_eop_tc; + bool updated = (flow_flags & STREAM_TOSERVER) ? txd->updated_ts : txd->updated_tc; - if (!updated && tx_progress < tx_end_state && ((flow_flags & STREAM_EOF) == 0)) { + if (!updated && tx_progress < e_tx_end_state && ((flow_flags & STREAM_EOF) == 0)) { DetectTransaction no_tx = NO_TX; return no_tx; } @@ -1536,6 +1563,10 @@ static DetectTransaction GetDetectTx(const uint8_t ipproto, const AppProto alpro return no_tx; } + if (txd->tx_type != 0) { + SCLogDebug("using tx_type %u", txd->tx_type); + } + const uint8_t detect_progress = (flow_flags & STREAM_TOSERVER) ? txd->detect_progress_ts : txd->detect_progress_tc; @@ -1551,8 +1582,9 @@ static DetectTransaction GetDetectTx(const uint8_t ipproto, const AppProto alpro .detect_progress = detect_progress, .detect_progress_orig = detect_progress, .tx_progress = (uint8_t)tx_progress, - .tx_end_state = (uint8_t)tx_end_state, + .tx_end_state = e_tx_end_state, .is_last = false, + .tx_type = txd->tx_type, }; return tx; } diff --git a/src/detect.h b/src/detect.h index aec4758796f0..2cd4025526d4 100644 --- a/src/detect.h +++ b/src/detect.h @@ -424,7 +424,8 @@ typedef struct DetectEngineAppInspectionEngine_ { bool match_on_null; uint16_t sm_list; uint16_t sm_list_base; /**< base buffer being transformed */ - int16_t progress; + uint8_t progress; + uint8_t sub_state; /**< matches tx type */ struct { union { @@ -578,6 +579,8 @@ typedef struct SignatureHook_ { union { struct { AppProto alproto; + /** sub state for a specific transaction type or 0 if not used */ + uint8_t sub_state; /** progress value of the app-layer hook specified in the rule. Sets the app_proto * specific progress value. */ int app_progress; @@ -787,7 +790,8 @@ typedef struct DetectBufferMpmRegistry_ { InspectionMultiBufferGetDataPtr GetMultiData; }; AppProto alproto; - int tx_min_progress; + uint8_t tx_min_progress; + uint8_t sub_state; } app_v2; /* pkt matching: use if type == DETECT_BUFFER_MPM_TYPE_PKT */ @@ -1579,6 +1583,8 @@ typedef struct PrefilterEngineList_ { SignatureMask pkt_mask; /**< mask for pkt engines */ + uint8_t sub_state; + enum SignatureHookPkt pkt_hook; /** Context for matching. Might be MpmCtx for MPM engines, other ctx' @@ -1612,9 +1618,12 @@ typedef struct PrefilterEngine_ { SignatureMask mask; /**< mask for pkt engines */ uint8_t hook; /**< enum SignatureHookPkt */ } pkt; - /** Minimal Tx progress we need before running the engine. Only used - * with Tx Engine. Set to -1 for all states. */ - int8_t tx_min_progress; + struct { + /** Minimal Tx progress we need before running the engine. Only used + * with Tx Engine. Set to -1 for all states. */ + int8_t tx_min_progress; + uint8_t sub_state; + } app; uint8_t frame_type; } ctx; From 99f3b8f6d5dc1339a7d6245b0d6145fc14968db9 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Wed, 17 Jun 2026 09:41:43 +0200 Subject: [PATCH 22/69] detect/parse: initial substate support (cherry picked from commit 59e2c5a40c655a667690fc2df231300aef02e586) --- src/detect-parse.c | 49 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/src/detect-parse.c b/src/detect-parse.c index 6ea964275314..ca01c72da96f 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -1300,11 +1300,12 @@ static int SigParseProtoHookPkt(Signature *s, const char *proto_hook, const char return 0; } -static SignatureHook SetAppHook(const AppProto alproto, int progress) +static SignatureHook SetAppHook(const AppProto alproto, uint8_t sub_state, uint8_t progress) { SignatureHook h = { .type = SIGNATURE_HOOK_TYPE_APP, .t.app.alproto = alproto, + .t.app.sub_state = sub_state, .t.app.app_progress = progress, }; return h; @@ -1313,31 +1314,59 @@ static SignatureHook SetAppHook(const AppProto alproto, int progress) /** * \param proto_hook string of protocol and hook, e.g. dns:request_complete */ -static int SigParseProtoHookApp(Signature *s, const char *proto_hook, const char *p, const char *h) +static int SigParseProtoHookApp( + Signature *s, const char *proto_hook, const char *p, const char *in_h) { + char hook[33]; + strlcpy(hook, in_h, 33); + const char *h = hook; + const char *t = NULL; + uint8_t sub_state = 0; + + bool has_type = strchr(hook, ':') != NULL; + if (has_type) { + char *rem = NULL; + t = strtok_r(hook, ":", &rem); + h = rem; + SCLogDebug("h: '%s' t: '%s'", h, t); + } + if (h == NULL || strlen(h) == 0) { + SCLogError("invalid hook specification '%s'", hook); + return -1; + } + + if (t != NULL) { + if (strlen(t) == 0) { + SCLogError("invalid tx type specification '%s'", hook); + return -1; + } + + /* TODO handle substate here */ + } + SCLogDebug("h:'%s'", h); if (strcmp(h, "request_started") == 0) { s->flags |= SIG_FLAG_TOSERVER; - s->init_data->hook = - SetAppHook(s->alproto, 0); // state 0 should be the starting state in each protocol. + s->init_data->hook = SetAppHook( + s->alproto, sub_state, 0); // state 0 should be the starting state in each protocol. } else if (strcmp(h, "response_started") == 0) { s->flags |= SIG_FLAG_TOCLIENT; - s->init_data->hook = - SetAppHook(s->alproto, 0); // state 0 should be the starting state in each protocol. + s->init_data->hook = SetAppHook( + s->alproto, sub_state, 0); // state 0 should be the starting state in each protocol. } else if (strcmp(h, "request_complete") == 0) { s->flags |= SIG_FLAG_TOSERVER; - s->init_data->hook = SetAppHook(s->alproto, + s->init_data->hook = SetAppHook(s->alproto, sub_state, AppLayerParserGetStateProgressCompletionStatus(s->alproto, STREAM_TOSERVER)); } else if (strcmp(h, "response_complete") == 0) { s->flags |= SIG_FLAG_TOCLIENT; - s->init_data->hook = SetAppHook(s->alproto, + s->init_data->hook = SetAppHook(s->alproto, sub_state, AppLayerParserGetStateProgressCompletionStatus(s->alproto, STREAM_TOCLIENT)); } else { 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); + s->init_data->hook = SetAppHook(s->alproto, sub_state, progress_ts); } else { const int progress_tc = AppLayerParserGetStateIdByName( IPPROTO_TCP /* TODO */, s->alproto, h, STREAM_TOCLIENT); @@ -1345,7 +1374,7 @@ static int SigParseProtoHookApp(Signature *s, const char *proto_hook, const char return -1; } s->flags |= SIG_FLAG_TOCLIENT; - s->init_data->hook = SetAppHook(s->alproto, progress_tc); + s->init_data->hook = SetAppHook(s->alproto, sub_state, progress_tc); } } From 2a9675809e2ebbd75bfbbc3dbeac842349cb2afc Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Thu, 11 Jun 2026 13:16:27 +0200 Subject: [PATCH 23/69] detect/app-layer-event: support sub-state progress handling (cherry picked from commit 67e2cb538ab17d1655730c7b30b8bbc54b5ec203) --- src/detect-app-layer-event.c | 14 +++++++++++--- src/detect-app-layer-state.c | 13 +++++++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/detect-app-layer-event.c b/src/detect-app-layer-event.c index e7886da5d7d6..33eea1d53ac2 100644 --- a/src/detect-app-layer-event.c +++ b/src/detect-app-layer-event.c @@ -122,9 +122,17 @@ static uint8_t DetectEngineAptEventInspect(DetectEngineCtx *de_ctx, DetectEngine if (r == 1) { return DETECT_ENGINE_INSPECT_SIG_MATCH; } else { - if (AppLayerParserGetStateProgress(f->proto, alproto, tx, flags) == - AppLayerParserGetStateProgressCompletionStatus(alproto, flags)) - { + AppLayerTxData *txd = AppLayerParserGetTxData(f->proto, alproto, tx); + uint8_t tx_end_state; + if (txd->tx_type == 0) { + tx_end_state = (uint8_t)AppLayerParserGetStateProgressCompletionStatus(alproto, flags); + } else { + if (flags & STREAM_TOSERVER) + tx_end_state = txd->tx_type_eop_ts; + else + tx_end_state = txd->tx_type_eop_tc; + } + if (AppLayerParserGetStateProgress(f->proto, alproto, tx, flags) == tx_end_state) { return DETECT_ENGINE_INSPECT_SIG_CANT_MATCH; } else { return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; diff --git a/src/detect-app-layer-state.c b/src/detect-app-layer-state.c index 6ec8d013cdb8..58e35ce8aa32 100644 --- a/src/detect-app-layer-state.c +++ b/src/detect-app-layer-state.c @@ -128,8 +128,17 @@ static uint8_t DetectEngineAptStateInspect(DetectEngineCtx *de_ctx, DetectEngine SCLogDebug("DETECT_ENGINE_INSPECT_SIG_MATCH"); return DETECT_ENGINE_INSPECT_SIG_MATCH; } else { - if (AppLayerParserGetStateProgress(f->proto, alproto, tx, flags) == - AppLayerParserGetStateProgressCompletionStatus(alproto, flags)) { + AppLayerTxData *txd = AppLayerParserGetTxData(f->proto, alproto, tx); + uint8_t tx_end_state; + if (txd->tx_type == 0) { + tx_end_state = (uint8_t)AppLayerParserGetStateProgressCompletionStatus(alproto, flags); + } else { + if (flags & STREAM_TOSERVER) + tx_end_state = txd->tx_type_eop_ts; + else + tx_end_state = txd->tx_type_eop_tc; + } + if (AppLayerParserGetStateProgress(f->proto, alproto, tx, flags) == tx_end_state) { SCLogDebug("DETECT_ENGINE_INSPECT_SIG_CANT_MATCH"); return DETECT_ENGINE_INSPECT_SIG_CANT_MATCH; } else { From e8abfa95df87e1dec2844079869383bfdc675e75 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Thu, 11 Jun 2026 18:23:12 +0200 Subject: [PATCH 24/69] output/tx: support substate completion flags (cherry picked from commit 2f41e0ef2d9da45f2c3c47da93fb0a2522d96377) --- src/output-tx.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/output-tx.c b/src/output-tx.c index 2874b40f6ef2..a77e39c26716 100644 --- a/src/output-tx.c +++ b/src/output-tx.c @@ -405,9 +405,9 @@ static TmEcode OutputTxLog(ThreadVars *tv, Packet *p, void *thread_data) AppLayerGetTxIterState state; memset(&state, 0, sizeof(state)); - const int complete_ts = + const int default_complete_ts = AppLayerParserGetStateProgressCompletionStatus(alproto, STREAM_TOSERVER); - const int complete_tc = + const int default_complete_tc = AppLayerParserGetStateProgressCompletionStatus(alproto, STREAM_TOCLIENT); while (1) { AppLayerGetTxIterTuple ires = IterFunc(ipproto, alproto, alstate, tx_id, total_txs, &state); @@ -418,6 +418,14 @@ static TmEcode OutputTxLog(ThreadVars *tv, Packet *p, void *thread_data) SCLogDebug("STARTING tx_id %" PRIu64 ", tx %p", tx_id, tx); AppLayerTxData *txd = AppLayerParserGetTxData(ipproto, alproto, tx); + int complete_ts, complete_tc; + if (txd->tx_type == 0) { + complete_ts = default_complete_ts; + complete_tc = default_complete_tc; + } else { + complete_ts = txd->tx_type_eop_ts; + complete_tc = txd->tx_type_eop_tc; + } const int tx_progress_ts = AppLayerParserGetStateProgress(ipproto, alproto, tx, ts_disrupt_flags); From f6bc5918eb9d0fc16c9045a54165799e6ac232c2 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Sat, 6 Jun 2026 21:37:02 +0200 Subject: [PATCH 25/69] http2: split transaction state machines Split into 2 sub-states: - stream, which has the "HTTP" requests and responses, including DOH2 - global, which has the settings and other global or control handling Introduce a simpler progress tracking for the global sub state: - HTTP2ProgGlobalStart and HTTP2ProgGlobalComplete. The stream sub state uses the same state machine as before. Ticket: #8386. (cherry picked from commit 7ebc699df09cb3ab1d8c18e7c51f14314a55f5d6) --- rust/cbindgen.toml | 2 + rust/src/http2/detect.rs | 7 +- rust/src/http2/http2.rs | 226 +++++++++++++++++++++++++++++++-------- src/detect-http-header.c | 20 ++-- src/detect-http2.c | 57 ++++++---- 5 files changed, 229 insertions(+), 83 deletions(-) diff --git a/rust/cbindgen.toml b/rust/cbindgen.toml index 1a7b5ce9c3ef..a4c631079f76 100644 --- a/rust/cbindgen.toml +++ b/rust/cbindgen.toml @@ -89,6 +89,8 @@ include = [ "FtpStateValues", "FtpDataStateValues", "HTTP2TxProgress", + "HTTP2TxGlobalProgress", + "HTTP2TxType", "DataRepType", ] diff --git a/rust/src/http2/detect.rs b/rust/src/http2/detect.rs index e82cec1367fe..fc08b7dee63c 100644 --- a/rust/src/http2/detect.rs +++ b/rust/src/http2/detect.rs @@ -16,7 +16,8 @@ */ use super::http2::{ - HTTP2Event, HTTP2Frame, HTTP2FrameTypeData, HTTP2State, HTTP2Transaction, HTTP2TxProgress, + HTTP2Event, HTTP2Frame, HTTP2FrameTypeData, HTTP2Progress, HTTP2State, HTTP2Transaction, + HTTP2TxProgress, }; use super::parser; use crate::detect::uint::{detect_match_uint, DetectUintData}; @@ -1040,7 +1041,9 @@ fn http2_tx_set_header(state: &mut HTTP2State, name: &[u8], input: &[u8]) { data: txdata, }); //we do not expect more data from client - tx.progress_ts = HTTP2TxProgress::HTTP2ProgClosed; + if let HTTP2Progress::STREAM(ref mut stream_tx) = tx.progress { + stream_tx.progress_ts = HTTP2TxProgress::HTTP2ProgClosed; + } } #[no_mangle] diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs index f8ee0c54748c..12bd32b4e275 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -81,6 +81,13 @@ static mut HTTP2_MAX_STREAMS: usize = 4096; // 0x1000 static mut HTTP2_MAX_FRAMES: usize = 65536; pub(super) static mut HTTP2_COMPRESSION_BOMB_LIMIT: u64 = 1_048_576; +#[repr(u8)] +#[derive(Copy, Clone, PartialOrd, PartialEq, Eq, Debug)] +pub enum HTTP2TxType { + HTTP2TxTypeStream = 1, + HTTP2TxTypeGlobal = 2, +} + #[derive(AppLayerFrameType)] pub enum Http2FrameType { Hdr, @@ -131,9 +138,14 @@ pub enum HTTP2TxProgress { HTTP2ProgHeaders = 1, HTTP2ProgData = 2, HTTP2ProgClosed = 3, - HTTP2ProgComplete = 4, - //not a RFC-defined state, used for stream 0 frames applying to the global connection - HTTP2ProgGlobal = 5, + HTTP2ProgComplete = 4, // complete is a pseudo state set only when both sides are closed +} + +#[repr(u8)] +#[derive(Copy, Clone, PartialOrd, PartialEq, Eq, Debug)] +pub enum HTTP2TxGlobalProgress { + HTTP2ProgGlobalStart = 0, + HTTP2ProgGlobalComplete = 1, } #[derive(Debug)] @@ -155,12 +167,122 @@ pub struct DohHttp2Tx { pub dns_response_tx: Option, } +#[derive(Debug)] +pub struct HTTP2StreamProgress { + pub progress_ts: HTTP2TxProgress, + pub progress_tc: HTTP2TxProgress, +} + +impl HTTP2StreamProgress { + fn init() -> Self { + Self { + progress_ts: HTTP2TxProgress::HTTP2ProgStart, + progress_tc: HTTP2TxProgress::HTTP2ProgStart, + } + } + fn complete() -> Self { + Self { + progress_ts: HTTP2TxProgress::HTTP2ProgComplete, + progress_tc: HTTP2TxProgress::HTTP2ProgComplete, + } + } + fn is_complete(&self) -> bool { + self.progress_ts >= HTTP2TxProgress::HTTP2ProgComplete + && self.progress_tc >= HTTP2TxProgress::HTTP2ProgComplete + } + fn is_complete_for_direction(&self, direction: u8) -> bool { + if direction & Direction::ToServer as u8 != 0 { + self.progress_ts >= HTTP2TxProgress::HTTP2ProgComplete + } else { + self.progress_tc >= HTTP2TxProgress::HTTP2ProgComplete + } + } +} + +#[derive(Debug)] +pub struct HTTP2GlobalProgress { + pub progress_ts: HTTP2TxGlobalProgress, + pub progress_tc: HTTP2TxGlobalProgress, +} + +impl HTTP2GlobalProgress { + fn _init() -> Self { + Self { + progress_ts: HTTP2TxGlobalProgress::HTTP2ProgGlobalStart, + progress_tc: HTTP2TxGlobalProgress::HTTP2ProgGlobalStart, + } + } + fn complete() -> Self { + Self { + progress_ts: HTTP2TxGlobalProgress::HTTP2ProgGlobalComplete, + progress_tc: HTTP2TxGlobalProgress::HTTP2ProgGlobalComplete, + } + } + fn is_complete(&self) -> bool { + self.progress_ts >= HTTP2TxGlobalProgress::HTTP2ProgGlobalComplete + && self.progress_tc >= HTTP2TxGlobalProgress::HTTP2ProgGlobalComplete + } + fn is_complete_for_direction(&self, direction: u8) -> bool { + if direction & Direction::ToServer as u8 != 0 { + self.progress_ts >= HTTP2TxGlobalProgress::HTTP2ProgGlobalComplete + } else { + self.progress_tc >= HTTP2TxGlobalProgress::HTTP2ProgGlobalComplete + } + } +} + +#[derive(Debug)] +pub enum HTTP2Progress { + STREAM(HTTP2StreamProgress), + GLOBAL(HTTP2GlobalProgress), +} + +impl HTTP2Progress { + fn get(&self, direction: u8) -> i32 { + if let HTTP2Progress::STREAM(ref s) = self { + if direction == STREAM_TOSERVER { + return s.progress_ts as i32; + } else { + return s.progress_tc as i32; + } + } else if let HTTP2Progress::GLOBAL(ref g) = self { + if direction == STREAM_TOSERVER { + return g.progress_ts as i32; + } else { + return g.progress_tc as i32; + } + } + 0 + } + + fn is_complete(&self) -> bool { + let complete = if let HTTP2Progress::STREAM(ref s) = self { + s.is_complete() + } else if let HTTP2Progress::GLOBAL(ref g) = self { + g.is_complete() + } else { + false + }; + complete + } + + pub fn is_complete_for_direction(&self, direction: u8) -> bool { + let complete = if let HTTP2Progress::STREAM(ref s) = self { + s.is_complete_for_direction(direction) + } else if let HTTP2Progress::GLOBAL(ref g) = self { + g.is_complete_for_direction(direction) + } else { + false + }; + complete + } +} + #[derive(Debug)] pub struct HTTP2Transaction { tx_id: u64, pub stream_id: u32, - pub progress_tc: HTTP2TxProgress, - pub progress_ts: HTTP2TxProgress, + pub progress: HTTP2Progress, to_drop: bool, child_stream_id: u32, @@ -198,8 +320,7 @@ impl HTTP2Transaction { tx_id: 0, stream_id: 0, child_stream_id: 0, - progress_tc: HTTP2TxProgress::HTTP2ProgStart, - progress_ts: HTTP2TxProgress::HTTP2ProgStart, + progress: HTTP2Progress::STREAM(HTTP2StreamProgress::init()), to_drop: false, frames_tc: Vec::new(), frames_ts: Vec::new(), @@ -215,6 +336,7 @@ impl HTTP2Transaction { } pub fn free(&mut self) { + SCLogDebug!("free: tx_id {} stream_id {}", self.tx_id, self.stream_id); if !self.file_range.is_null() { if let Some(c) = unsafe { SC } { if let Some(sfcm) = unsafe { SURICATA_HTTP2_FILE_CONFIG } { @@ -405,8 +527,12 @@ impl HTTP2Transaction { if header.flags & parser::HTTP2_FLAG_HEADER_END_HEADERS == 0 { self.child_stream_id = hs.stream_id; } - if self.progress_tc < HTTP2TxProgress::HTTP2ProgHeaders { - self.progress_tc = HTTP2TxProgress::HTTP2ProgHeaders; + if let HTTP2Progress::STREAM(ref mut stream_tx) = self.progress { + if stream_tx.progress_tc < HTTP2TxProgress::HTTP2ProgHeaders { + stream_tx.progress_tc = HTTP2TxProgress::HTTP2ProgHeaders; + } + } else { + panic!("global"); } } r = self.handle_headers(&hs.blocks, dir); @@ -430,34 +556,36 @@ impl HTTP2Transaction { } _ => {} } - //handle closing state changes - let state = if dir == Direction::ToServer { - &mut self.progress_ts - } else { - &mut self.progress_tc - }; - match data { - HTTP2FrameTypeData::HEADERS(_) | HTTP2FrameTypeData::DATA => { - if header.flags & parser::HTTP2_FLAG_HEADER_EOS != 0 { - if *state < HTTP2TxProgress::HTTP2ProgClosed { - *state = HTTP2TxProgress::HTTP2ProgClosed; - if self.progress_ts == HTTP2TxProgress::HTTP2ProgClosed - && self.progress_tc == HTTP2TxProgress::HTTP2ProgClosed - { - self.progress_ts = HTTP2TxProgress::HTTP2ProgComplete; - self.progress_tc = HTTP2TxProgress::HTTP2ProgComplete; + if let HTTP2Progress::STREAM(ref mut stream_tx) = self.progress { + //handle closing state changes + let state = if dir == Direction::ToServer { + &mut stream_tx.progress_ts + } else { + &mut stream_tx.progress_tc + }; + match data { + HTTP2FrameTypeData::HEADERS(_) | HTTP2FrameTypeData::DATA => { + if header.flags & parser::HTTP2_FLAG_HEADER_EOS != 0 { + if *state < HTTP2TxProgress::HTTP2ProgClosed { + *state = HTTP2TxProgress::HTTP2ProgClosed; + if stream_tx.progress_ts == HTTP2TxProgress::HTTP2ProgClosed + && stream_tx.progress_tc == HTTP2TxProgress::HTTP2ProgClosed + { + stream_tx.progress_ts = HTTP2TxProgress::HTTP2ProgComplete; + stream_tx.progress_tc = HTTP2TxProgress::HTTP2ProgComplete; + } } + } else if header.ftype == parser::HTTP2FrameType::Data as u8 { + //not end of stream + if *state < HTTP2TxProgress::HTTP2ProgData { + *state = HTTP2TxProgress::HTTP2ProgData; + } + } else if *state < HTTP2TxProgress::HTTP2ProgHeaders { + *state = HTTP2TxProgress::HTTP2ProgHeaders; } - } else if header.ftype == parser::HTTP2FrameType::Data as u8 { - //not end of stream - if *state < HTTP2TxProgress::HTTP2ProgData { - *state = HTTP2TxProgress::HTTP2ProgData; - } - } else if *state < HTTP2TxProgress::HTTP2ProgHeaders { - *state = HTTP2TxProgress::HTTP2ProgHeaders; } + _ => {} } - _ => {} } return r; } @@ -751,10 +879,13 @@ impl HTTP2State { //as it affects the global connection, there is no end to it let mut tx = HTTP2Transaction::new(); tx.tx_data = AppLayerTxData::for_direction(dir); + tx.tx_data.tx_type = HTTP2TxType::HTTP2TxTypeGlobal as u8; + tx.tx_data.tx_type_eop_ts = HTTP2TxGlobalProgress::HTTP2ProgGlobalComplete as u8; + tx.tx_data.tx_type_eop_tc = HTTP2TxGlobalProgress::HTTP2ProgGlobalComplete as u8; self.tx_id += 1; tx.tx_id = self.tx_id; - tx.progress_tc = HTTP2TxProgress::HTTP2ProgGlobal; - tx.progress_ts = HTTP2TxProgress::HTTP2ProgGlobal; + tx.progress = HTTP2Progress::GLOBAL(HTTP2GlobalProgress::complete()); + SCLogDebug!("global tx created {:?}", tx); // a global tx (stream id 0) does not hold files cf RFC 9113 section 5.1.1 self.transactions.push_back(tx); return self.transactions.back_mut().unwrap(); @@ -772,8 +903,7 @@ impl HTTP2State { } tx_old.set_event(HTTP2Event::TooManyStreams); // use a distinct state, even if we do not log it - tx_old.progress_ts = HTTP2TxProgress::HTTP2ProgComplete; - tx_old.progress_tc = HTTP2TxProgress::HTTP2ProgComplete; + tx_old.progress = HTTP2Progress::STREAM(HTTP2StreamProgress::complete()); tx_old.to_drop = true; tx_old.tx_data.updated_tc = true; tx_old.tx_data.updated_ts = true; @@ -797,9 +927,7 @@ impl HTTP2State { }; let index = self.find_tx_index(sid); if index > 0 { - if self.transactions[index - 1].progress_tc >= HTTP2TxProgress::HTTP2ProgClosed - && self.transactions[index - 1].progress_ts >= HTTP2TxProgress::HTTP2ProgClosed - { + if self.transactions[index - 1].progress.is_complete() { //these frames can be received in this state for a short period if header.ftype != parser::HTTP2FrameType::RstStream as u8 && header.ftype != parser::HTTP2FrameType::WindowUpdate as u8 @@ -825,8 +953,7 @@ impl HTTP2State { } tx_old.set_event(HTTP2Event::TooManyStreams); // use a distinct state, even if we do not log it - tx_old.progress_ts = HTTP2TxProgress::HTTP2ProgComplete; - tx_old.progress_tc = HTTP2TxProgress::HTTP2ProgComplete; + tx_old.progress = HTTP2Progress::STREAM(HTTP2StreamProgress::complete()); tx_old.to_drop = true; tx_old.tx_data.updated_tc = true; tx_old.tx_data.updated_ts = true; @@ -840,6 +967,9 @@ impl HTTP2State { tx.tx_data.update_file_flags(self.state_data.file_flags); tx.update_file_flags(tx.tx_data.file_flags); tx.tx_data.file_tx = STREAM_TOSERVER | STREAM_TOCLIENT; // might hold files in both directions + tx.tx_data.tx_type = HTTP2TxType::HTTP2TxTypeStream as u8; + tx.tx_data.tx_type_eop_ts = HTTP2TxProgress::HTTP2ProgComplete as u8; + tx.tx_data.tx_type_eop_tc = HTTP2TxProgress::HTTP2ProgComplete as u8; self.transactions.push_back(tx); return Some(self.transactions.back_mut().unwrap()); } @@ -1247,6 +1377,12 @@ impl HTTP2State { return AppLayerResult::err(); } let tx = tx.unwrap(); + SCLogDebug!( + "tx stream_id {} tx id {} progress {:?}", + tx.stream_id, + tx.tx_id, + tx.progress + ); if let Some(frame) = frame_hdr { frame.set_tx(flow, tx.tx_id); } @@ -1534,11 +1670,7 @@ unsafe extern "C" fn http2_tx_get_alstate_progress( tx: *mut std::os::raw::c_void, direction: u8, ) -> std::os::raw::c_int { let tx = cast_pointer!(tx, HTTP2Transaction); - if direction == STREAM_TOSERVER { - return tx.progress_ts as i32; - } else { - return tx.progress_tc as i32; - } + return tx.progress.get(direction); } unsafe extern "C" fn http2_getfiles( diff --git a/src/detect-http-header.c b/src/detect-http-header.c index bb3e4ee37ff2..1ecef2bbd360 100644 --- a/src/detect-http-header.c +++ b/src/detect-http-header.c @@ -440,15 +440,12 @@ void DetectHttpHeaderRegister(void) PrefilterMpmHttpHeaderResponseRegister, NULL, ALPROTO_HTTP1, 0); /* not used, registered twice: HEADERS/TRAILER */ - DetectAppLayerInspectEngineRegister("http_header", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetBuffer2ForTX); - DetectAppLayerMpmRegister("http_header", SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, - GetBuffer2ForTX, ALPROTO_HTTP2, HTTP2ProgHeaders); - - DetectAppLayerInspectEngineRegister("http_header", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, - HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetBuffer2ForTX); - DetectAppLayerMpmRegister("http_header", SIG_FLAG_TOCLIENT, 2, PrefilterGenericMpmRegister, - GetBuffer2ForTX, ALPROTO_HTTP2, HTTP2ProgHeaders); + /* header is for stream TX type */ + DetectAppLayerInspectEngineRegisterSubState("http_header", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, + HTTP2TxTypeStream, HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetBuffer2ForTX); + DetectAppLayerMpmRegisterSubState("http_header", SIG_FLAG_TOCLIENT, 2, + PrefilterGenericMpmRegister, GetBuffer2ForTX, ALPROTO_HTTP2, HTTP2TxTypeStream, + HTTP2ProgHeaders); DetectBufferTypeSetDescriptionByName("http_header", "http headers"); @@ -615,8 +612,9 @@ void DetectHttpRequestHeaderRegister(void) sigmatch_table[DETECT_HTTP_REQUEST_HEADER].flags |= SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER; - DetectAppLayerMultiRegister("http_request_header", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2ProgHeaders, GetHttp2HeaderData, 2); + DetectAppLayerMultiRegisterSubState("http_request_header", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, + HTTP2TxTypeStream, HTTP2ProgHeaders, GetHttp2HeaderData, 2); + DetectAppLayerMultiRegister("http_request_header", ALPROTO_HTTP1, SIG_FLAG_TOSERVER, HTP_REQUEST_PROGRESS_HEADERS, GetHttp1HeaderData, 2); diff --git a/src/detect-http2.c b/src/detect-http2.c index c1bd7f53faf4..09cc486b3e28 100644 --- a/src/detect-http2.c +++ b/src/detect-http2.c @@ -175,29 +175,40 @@ void DetectHttp2Register(void) sigmatch_table[DETECT_HTTP2_HEADERNAME].Setup = DetectHTTP2headerNameSetup; sigmatch_table[DETECT_HTTP2_HEADERNAME].flags |= SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER; - DetectAppLayerMultiRegister("http2_header_name", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, - HTTP2ProgHeaders, SCHttp2TxGetHeaderName, 2); - DetectAppLayerMultiRegister("http2_header_name", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2ProgHeaders, SCHttp2TxGetHeaderName, 2); - - DetectBufferTypeSupportsMultiInstance("http2_header_name"); - DetectBufferTypeSetDescriptionByName("http2_header_name", - "HTTP2 header name"); - g_http2_header_buffer_id = DetectBufferTypeGetByName("http2_header_name"); - - DetectAppLayerInspectEngineRegister( - "http2", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, 0, DetectEngineInspectGenericList, NULL); - DetectAppLayerInspectEngineRegister( - "http2", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, 0, DetectEngineInspectGenericList, NULL); - - g_http2_match_buffer_id = DetectBufferTypeRegister("http2"); - - DetectAppLayerInspectEngineRegister("http2_complete", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2ProgComplete, DetectEngineInspectGenericList, NULL); - DetectAppLayerInspectEngineRegister("http2_complete", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, - HTTP2ProgComplete, DetectEngineInspectGenericList, NULL); - - g_http2_complete_buffer_id = DetectBufferTypeRegister("http2_complete"); + /* registration for for Stream Tx Sub State */ + DetectAppLayerMultiRegisterSubState("http2:header_name", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, + HTTP2TxTypeStream, HTTP2ProgHeaders, SCHttp2TxGetHeaderName, 2); + DetectAppLayerMultiRegisterSubState("http2:header_name", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, + HTTP2TxTypeStream, HTTP2ProgHeaders, SCHttp2TxGetHeaderName, 2); + + DetectBufferTypeSupportsMultiInstance("http2:header_name"); + DetectBufferTypeSetDescriptionByName("http2:header_name", "HTTP2 header name"); + g_http2_header_buffer_id = DetectBufferTypeGetByName("http2:header_name"); + + g_http2_match_buffer_id = DetectBufferTypeRegister("http2:start"); + /* registration for for Stream Tx Sub State */ + DetectAppLayerInspectEngineRegisterSubState("http2:start", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, + HTTP2TxTypeStream, 0, DetectEngineInspectGenericList, NULL); + DetectAppLayerInspectEngineRegisterSubState("http2:start", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, + HTTP2TxTypeStream, 0, DetectEngineInspectGenericList, NULL); + /* registration for for Global Tx Sub State */ + DetectAppLayerInspectEngineRegisterSubState("http2:start", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, + HTTP2TxTypeGlobal, 0, DetectEngineInspectGenericList, NULL); + DetectAppLayerInspectEngineRegisterSubState("http2:start", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, + HTTP2TxTypeGlobal, 0, DetectEngineInspectGenericList, NULL); + + g_http2_complete_buffer_id = DetectBufferTypeRegister("http2:complete"); + + /* registration for for Stream Tx Sub State */ + DetectAppLayerInspectEngineRegisterSubState("http2:complete", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, + HTTP2TxTypeStream, HTTP2ProgComplete, DetectEngineInspectGenericList, NULL); + DetectAppLayerInspectEngineRegisterSubState("http2:complete", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, + HTTP2TxTypeStream, HTTP2ProgComplete, DetectEngineInspectGenericList, NULL); + /* registration for for Global Tx Sub State */ + DetectAppLayerInspectEngineRegisterSubState("http2:complete", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, + HTTP2TxTypeGlobal, HTTP2ProgGlobalComplete, DetectEngineInspectGenericList, NULL); + DetectAppLayerInspectEngineRegisterSubState("http2:complete", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, + HTTP2TxTypeGlobal, HTTP2ProgGlobalComplete, DetectEngineInspectGenericList, NULL); } /** From a822738bd8af8fe10df8f4a04f3480251548cc5e Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Wed, 17 Jun 2026 09:41:43 +0200 Subject: [PATCH 26/69] detect/parse: initial http2 substate support Hard coded for now. (cherry picked from commit c943de4facf7ba3d702e478c051dcb44b1ef1643) --- src/detect-parse.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/detect-parse.c b/src/detect-parse.c index ca01c72da96f..b8d16a9fcc87 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -1340,8 +1340,18 @@ static int SigParseProtoHookApp( SCLogError("invalid tx type specification '%s'", hook); return -1; } - - /* TODO handle substate here */ + if (strcmp(p, "http2") == 0) { + if (strcmp(t, "stream") == 0) { + sub_state = HTTP2TxTypeStream; + } else if (strcmp(t, "global") == 0) { + sub_state = HTTP2TxTypeGlobal; + } else { + SCLogError("unknown http/2 tx type specification '%s': valid values are 'stream' " + "and 'global'", + hook); + return -1; + } + } } SCLogDebug("h:'%s'", h); From 079782c13ab86e936812db6b103604a8e04c9d3b Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Mon, 8 Jun 2026 21:12:09 +0200 Subject: [PATCH 27/69] detect/file: register http/2 with sub-state (cherry picked from commit 416b9c12aeba930a5880a755866e5dc066aaae90) --- src/detect-file-data.c | 38 ++++++++++++++++++++++---------------- src/detect-filemagic.c | 4 ++-- src/detect-filename.c | 4 ++-- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/detect-file-data.c b/src/detect-file-data.c index 48d5b0d249dc..3984b1958ea2 100644 --- a/src/detect-file-data.c +++ b/src/detect-file-data.c @@ -72,6 +72,8 @@ typedef struct { int direction; int to_client_progress; int to_server_progress; + uint8_t sub_state_ts; + uint8_t sub_state_tc; } DetectFileHandlerProtocol_t; /* Table with all filehandler registrations */ @@ -89,10 +91,14 @@ DetectFileHandlerProtocol_t al_protocols[ALPROTO_WITHFILES_MAX] = { .direction = SIG_FLAG_TOSERVER | SIG_FLAG_TOCLIENT, .to_client_progress = HTP_RESPONSE_PROGRESS_BODY, .to_server_progress = HTP_REQUEST_PROGRESS_BODY }, - { .alproto = ALPROTO_HTTP2, + { + .alproto = ALPROTO_HTTP2, .direction = SIG_FLAG_TOSERVER | SIG_FLAG_TOCLIENT, .to_client_progress = HTTP2ProgData, - .to_server_progress = HTTP2ProgData }, + .to_server_progress = HTTP2ProgData, + .sub_state_tc = HTTP2TxTypeStream, + .sub_state_ts = HTTP2TxTypeStream, + }, { .alproto = ALPROTO_SMTP, .direction = SIG_FLAG_TOSERVER, .to_server_progress = SMTP_REQUEST_DATA }, @@ -121,24 +127,24 @@ void DetectFileRegisterProto( void DetectFileRegisterFileProtocols(DetectFileHandlerTableElmt *reg) { for (size_t i = 0; i < g_alproto_max; i++) { - if (al_protocols[i].alproto == ALPROTO_UNKNOWN) { + DetectFileHandlerProtocol_t *p = &al_protocols[i]; + if (p->alproto == ALPROTO_UNKNOWN) { break; } - int direction = al_protocols[i].direction == 0 - ? (int)(SIG_FLAG_TOSERVER | SIG_FLAG_TOCLIENT) - : al_protocols[i].direction; + int direction = + p->direction == 0 ? (int)(SIG_FLAG_TOSERVER | SIG_FLAG_TOCLIENT) : p->direction; if (direction & SIG_FLAG_TOCLIENT) { - DetectAppLayerMpmRegister(reg->name, SIG_FLAG_TOCLIENT, reg->priority, reg->PrefilterFn, - NULL, al_protocols[i].alproto, al_protocols[i].to_client_progress); - DetectAppLayerInspectEngineRegister(reg->name, al_protocols[i].alproto, - SIG_FLAG_TOCLIENT, al_protocols[i].to_client_progress, reg->Callback, NULL); + DetectAppLayerMpmRegisterSubState(reg->name, SIG_FLAG_TOCLIENT, reg->priority, + reg->PrefilterFn, NULL, p->alproto, p->sub_state_tc, p->to_client_progress); + DetectAppLayerInspectEngineRegisterSubState(reg->name, p->alproto, SIG_FLAG_TOCLIENT, + p->sub_state_tc, p->to_client_progress, reg->Callback, NULL); } if (direction & SIG_FLAG_TOSERVER) { - DetectAppLayerMpmRegister(reg->name, SIG_FLAG_TOSERVER, reg->priority, reg->PrefilterFn, - NULL, al_protocols[i].alproto, al_protocols[i].to_server_progress); - DetectAppLayerInspectEngineRegister(reg->name, al_protocols[i].alproto, - SIG_FLAG_TOSERVER, al_protocols[i].to_server_progress, reg->Callback, NULL); + DetectAppLayerMpmRegisterSubState(reg->name, SIG_FLAG_TOSERVER, reg->priority, + reg->PrefilterFn, NULL, p->alproto, p->sub_state_ts, p->to_server_progress); + DetectAppLayerInspectEngineRegisterSubState(reg->name, p->alproto, SIG_FLAG_TOSERVER, + p->sub_state_ts, p->to_server_progress, reg->Callback, NULL); } } } @@ -594,8 +600,8 @@ int PrefilterMpmFiledataRegister(DetectEngineCtx *de_ctx, SigGroupHead *sgh, Mpm pectx->mpm_ctx = mpm_ctx; pectx->transforms = &mpm_reg->transforms; - return PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxFiledata, - mpm_reg->app_v2.alproto, mpm_reg->app_v2.tx_min_progress, + return PrefilterAppendTxEngineSubState(de_ctx, sgh, PrefilterTxFiledata, + mpm_reg->app_v2.alproto, mpm_reg->app_v2.sub_state, mpm_reg->app_v2.tx_min_progress, pectx, PrefilterMpmFiledataFree, mpm_reg->pname); } diff --git a/src/detect-filemagic.c b/src/detect-filemagic.c index 1d756bb4d7fe..d29ff53ebcac 100644 --- a/src/detect-filemagic.c +++ b/src/detect-filemagic.c @@ -405,8 +405,8 @@ static int PrefilterMpmFilemagicRegister(DetectEngineCtx *de_ctx, SigGroupHead * pectx->mpm_ctx = mpm_ctx; pectx->transforms = &mpm_reg->transforms; - return PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxFilemagic, - mpm_reg->app_v2.alproto, mpm_reg->app_v2.tx_min_progress, + return PrefilterAppendTxEngineSubState(de_ctx, sgh, PrefilterTxFilemagic, + mpm_reg->app_v2.alproto, mpm_reg->app_v2.sub_state, mpm_reg->app_v2.tx_min_progress, pectx, PrefilterMpmFilemagicFree, mpm_reg->pname); } diff --git a/src/detect-filename.c b/src/detect-filename.c index df67166c3224..c5cf7432ac69 100644 --- a/src/detect-filename.c +++ b/src/detect-filename.c @@ -342,8 +342,8 @@ static int PrefilterMpmFilenameRegister(DetectEngineCtx *de_ctx, SigGroupHead *s pectx->mpm_ctx = mpm_ctx; pectx->transforms = &mpm_reg->transforms; - return PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxFilename, - mpm_reg->app_v2.alproto, mpm_reg->app_v2.tx_min_progress, + return PrefilterAppendTxEngineSubState(de_ctx, sgh, PrefilterTxFilename, + mpm_reg->app_v2.alproto, mpm_reg->app_v2.sub_state, mpm_reg->app_v2.tx_min_progress, pectx, PrefilterMpmFilenameFree, mpm_reg->pname); } From c486a4cc42819b3c7ef0c3290935627117110e30 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Mon, 8 Jun 2026 22:07:38 +0200 Subject: [PATCH 28/69] detect/http2: don't double register engines Each keyword supporting DOH2 must register explicitly (cherry picked from commit 1bb843d83f64abefff6460ef6a9acb4bbd9563c3) --- src/detect-engine-mpm.c | 5 ----- src/detect-engine.c | 6 ------ 2 files changed, 11 deletions(-) diff --git a/src/detect-engine-mpm.c b/src/detect-engine-mpm.c index 03c40c260272..b70a292a031c 100644 --- a/src/detect-engine-mpm.c +++ b/src/detect-engine-mpm.c @@ -107,11 +107,6 @@ static void RegisterInternal(const char *name, int direction, int priority, FatalError("MPM engine registration for %s failed", name); } - // every HTTP2 can be accessed from DOH2 - if (alproto == ALPROTO_HTTP2 || alproto == ALPROTO_DNS) { - RegisterInternal(name, direction, priority, PrefilterRegister, GetData, GetDataSingle, - GetMultiData, ALPROTO_DOH2, sub_state, tx_min_progress); - } DetectBufferMpmRegistry *am = SCCalloc(1, sizeof(*am)); BUG_ON(am == NULL); am->name = name; diff --git a/src/detect-engine.c b/src/detect-engine.c index 051a8fc04ff0..0c5ccbd85c70 100644 --- a/src/detect-engine.c +++ b/src/detect-engine.c @@ -233,12 +233,6 @@ static void AppLayerInspectEngineRegisterInternal(const char *name, AppProto alp } else { direction = 1; } - // every DNS or HTTP2 can be accessed from DOH2 - if (alproto == ALPROTO_HTTP2 || alproto == ALPROTO_DNS) { - AppLayerInspectEngineRegisterInternal(name, ALPROTO_DOH2, dir, sub_state, progress, - Callback, GetData, GetDataSingle, GetMultiData); - } - DetectEngineAppInspectionEngine *new_engine = SCCalloc(1, sizeof(DetectEngineAppInspectionEngine)); if (unlikely(new_engine == NULL)) { From 91c76f7f27651a7c70cd7c5f006203e853dc0323 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Mon, 8 Jun 2026 19:50:17 +0200 Subject: [PATCH 29/69] detect/dns: register keywords for DOH2 as well Now that DNS keywords are no longer registered for DOH2, the keywords need to manually registered for DOH2. (cherry picked from commit b36ec146aa48c2a35d1976d3471e516a40206a6d) --- rust/src/dns/detect.rs | 69 ++++++++++++++++++++++++++++++++++++--- src/detect-dns-name.c | 18 ++++++++++ src/detect-dns-response.c | 13 ++++++-- 3 files changed, 92 insertions(+), 8 deletions(-) diff --git a/rust/src/dns/detect.rs b/rust/src/dns/detect.rs index db373e90366b..4be142f4be1b 100644 --- a/rust/src/dns/detect.rs +++ b/rust/src/dns/detect.rs @@ -23,14 +23,16 @@ use crate::detect::uint::{ }; use crate::detect::{helper_keyword_register_sticky_buffer, SigTableElmtStickyBuffer}; use crate::direction::Direction; +use crate::http2::http2::{HTTP2TxProgress, HTTP2TxType}; use std::ffi::CStr; use std::os::raw::{c_int, c_void}; use suricata_sys::sys::{ - DetectEngineCtx, DetectEngineThreadCtx, Flow, SCDetectBufferSetActiveList, - SCDetectHelperBufferRegister, SCDetectHelperKeywordAliasRegister, - SCDetectHelperKeywordRegister, SCDetectHelperMultiBufferProgressMpmRegister, - SCDetectSignatureSetAppProto, SCSigMatchAppendSMToList, SCSigTableAppLiteElmt, SigMatchCtx, - Signature, + AppProtoEnum, DetectEngineCtx, DetectEngineThreadCtx, Flow, SCDetectBufferSetActiveList, + SCDetectHelperBufferRegister, SCDetectHelperBufferProgressRegisterSubState, + SCDetectHelperKeywordAliasRegister, SCDetectHelperKeywordRegister, + SCDetectHelperMultiBufferProgressMpmRegister, + SCDetectHelperMultiBufferProgressMpmRegisterSubState, SCDetectSignatureSetAppProto, + SCSigMatchAppendSMToList, SCSigTableAppLiteElmt, SigMatchCtx, Signature, }; /// Perform the DNS opcode match. @@ -365,6 +367,18 @@ pub unsafe extern "C" fn SCDetectDNSRegister() { Some(dns_tx_get_answer_name), 1, // response complete ); + _ = SCDetectHelperMultiBufferProgressMpmRegisterSubState( + b"dns.answer.name\0".as_ptr() as *const libc::c_char, + b"dns answer name\0".as_ptr() as *const libc::c_char, + AppProtoEnum::ALPROTO_DOH2 as u16, + STREAM_TOSERVER | STREAM_TOCLIENT, + /* Register also in the TO_SERVER direction, even though this is not + normal, it could be provided as part of a request. */ + Some(dns_tx_get_answer_name), + HTTP2TxType::HTTP2TxTypeStream as u8, + HTTP2TxProgress::HTTP2ProgClosed as u8, + ); + let kw = SCSigTableAppLiteElmt { name: b"dns.opcode\0".as_ptr() as *const libc::c_char, desc: b"Match the DNS header opcode flag.\0".as_ptr() as *const libc::c_char, @@ -380,6 +394,14 @@ pub unsafe extern "C" fn SCDetectDNSRegister() { ALPROTO_DNS, STREAM_TOSERVER | STREAM_TOCLIENT, ); + _ = SCDetectHelperBufferProgressRegisterSubState( + b"dns.opcode\0".as_ptr() as *const libc::c_char, + AppProtoEnum::ALPROTO_DOH2 as u16, + STREAM_TOSERVER | STREAM_TOCLIENT, + HTTP2TxType::HTTP2TxTypeStream as u8, + HTTP2TxProgress::HTTP2ProgClosed as u8, + ); + let kw = SigTableElmtStickyBuffer { name: String::from("dns.query.name"), desc: String::from("DNS query name sticky buffer"), @@ -397,6 +419,18 @@ pub unsafe extern "C" fn SCDetectDNSRegister() { Some(dns_tx_get_query_name), 1, // request or response complete ); + _ = SCDetectHelperMultiBufferProgressMpmRegisterSubState( + b"dns.query.name\0".as_ptr() as *const libc::c_char, + b"dns query name\0".as_ptr() as *const libc::c_char, + AppProtoEnum::ALPROTO_DOH2 as u16, + STREAM_TOSERVER | STREAM_TOCLIENT, + /* Register in both directions as the query is usually echoed back + in the response. */ + Some(dns_tx_get_query_name), + HTTP2TxType::HTTP2TxTypeStream as u8, + HTTP2TxProgress::HTTP2ProgClosed as u8, + ); + let kw = SCSigTableAppLiteElmt { name: b"dns.rcode\0".as_ptr() as *const libc::c_char, desc: b"Match the DNS header rcode flag.\0".as_ptr() as *const libc::c_char, @@ -412,6 +446,14 @@ pub unsafe extern "C" fn SCDetectDNSRegister() { ALPROTO_DNS, STREAM_TOSERVER | STREAM_TOCLIENT, ); + _ = SCDetectHelperBufferProgressRegisterSubState( + b"dns.rcode\0".as_ptr() as *const libc::c_char, + AppProtoEnum::ALPROTO_DOH2 as u16, + STREAM_TOSERVER | STREAM_TOCLIENT, + HTTP2TxType::HTTP2TxTypeStream as u8, + HTTP2TxProgress::HTTP2ProgClosed as u8, + ); + let kw = SCSigTableAppLiteElmt { name: b"dns.rrtype\0".as_ptr() as *const libc::c_char, desc: b"Match the DNS rrtype in message body.\0".as_ptr() as *const libc::c_char, @@ -427,6 +469,14 @@ pub unsafe extern "C" fn SCDetectDNSRegister() { ALPROTO_DNS, STREAM_TOSERVER | STREAM_TOCLIENT, ); + _ = SCDetectHelperBufferProgressRegisterSubState( + b"dns.rrtype\0".as_ptr() as *const libc::c_char, + AppProtoEnum::ALPROTO_DOH2 as u16, + STREAM_TOSERVER | STREAM_TOCLIENT, + HTTP2TxType::HTTP2TxTypeStream as u8, + HTTP2TxProgress::HTTP2ProgClosed as u8, + ); + let kw = SigTableElmtStickyBuffer { name: String::from("dns.query"), desc: String::from("sticky buffer to match DNS query-buffer"), @@ -446,6 +496,15 @@ pub unsafe extern "C" fn SCDetectDNSRegister() { Some(dns_tx_get_query), // reuse, will be called only toserver 1, // request complete ); + _ = SCDetectHelperMultiBufferProgressMpmRegisterSubState( + b"dns_query\0".as_ptr() as *const libc::c_char, + b"dns request query\0".as_ptr() as *const libc::c_char, + AppProtoEnum::ALPROTO_DOH2 as u16, + STREAM_TOSERVER, + Some(dns_tx_get_query), + HTTP2TxType::HTTP2TxTypeStream as u8, + HTTP2TxProgress::HTTP2ProgClosed as u8, + ); } #[cfg(test)] diff --git a/src/detect-dns-name.c b/src/detect-dns-name.c index 246965b65557..9bf9999b166d 100644 --- a/src/detect-dns-name.c +++ b/src/detect-dns-name.c @@ -136,21 +136,39 @@ static int Register(const char *keyword, const char *desc, const char *doc, return DetectBufferTypeGetByName(keyword); } +/* helper to register the same buffer for DOH2 as we had for DNS, with their + * own alproto, substate and progress. But don't reregister the keyword itself. */ +static void RegisterDoh2(const char *keyword, InspectionMultiBufferGetDataPtr GetBufferFn) +{ + const AppProto alproto = ALPROTO_DOH2; + DetectAppLayerMultiRegisterSubState(keyword, alproto, SIG_FLAG_TOSERVER, HTTP2TxTypeStream, + HTTP2ProgClosed, GetBufferFn, 2); + DetectAppLayerMultiRegisterSubState(keyword, alproto, SIG_FLAG_TOCLIENT, HTTP2TxTypeStream, + HTTP2ProgClosed, GetBufferFn, 2); +} + void DetectDnsNameRegister(void) { query_buffer_id = Register("dns.queries.rrname", "DNS query rrname sticky buffer", "/rules/dns-keywords.html#dns.queries.rrname", SetupQueryBuffer, SCDnsTxGetQueryName, ALPROTO_DNS); + RegisterDoh2("dns.queries.rrname", SCDnsTxGetQueryName); + answer_buffer_id = Register("dns.answers.rrname", "DNS answer rrname sticky buffer", "/rules/dns-keywords.html#dns.answers.rrname", SetupAnswerBuffer, SCDnsTxGetAnswerName, ALPROTO_DNS); + RegisterDoh2("dns.answers.rrname", SCDnsTxGetAnswerName); + additional_buffer_id = Register("dns.additionals.rrname", "DNS additionals rrname sticky buffer", "/rules/dns-keywords.html#dns-additionals-rrname", SetupAdditionalsBuffer, SCDnsTxGetAdditionalName, ALPROTO_DNS); + RegisterDoh2("dns.additionals.rrname", SCDnsTxGetAdditionalName); + authority_buffer_id = Register("dns.authorities.rrname", "DNS authorities rrname sticky buffer", "/rules/dns-keywords.html#dns-authorities-rrname", SetupAuthoritiesBuffer, SCDnsTxGetAuthorityName, ALPROTO_DNS); + RegisterDoh2("dns.authorities.rrname", SCDnsTxGetAuthorityName); mdns_query_buffer_id = Register("mdns.queries.rrname", "mDNS query rrname sticky buffer", "/rules/mdns-keywords.html#mdns.queries.rrname", SetupQueryBufferMdns, diff --git a/src/detect-dns-response.c b/src/detect-dns-response.c index 81ac31304de1..f33819bf84d8 100644 --- a/src/detect-dns-response.c +++ b/src/detect-dns-response.c @@ -321,9 +321,9 @@ static int DetectDnsResponsePrefilterMpmRegister(DetectEngineCtx *de_ctx, SigGro pectx->mpm_ctx = mpm_ctx; pectx->transforms = &mpm_reg->transforms; - return PrefilterAppendTxEngine(de_ctx, sgh, DetectDnsResponsePrefilterTx, - mpm_reg->app_v2.alproto, mpm_reg->app_v2.tx_min_progress, pectx, - DetectDnsResponsePrefilterMpmFree, mpm_reg->pname); + return PrefilterAppendTxEngineSubState(de_ctx, sgh, DetectDnsResponsePrefilterTx, + mpm_reg->app_v2.alproto, mpm_reg->app_v2.sub_state, mpm_reg->app_v2.tx_min_progress, + pectx, DetectDnsResponsePrefilterMpmFree, mpm_reg->pname); } static void SCDetectMdnsResponseRrnameRegister(void) @@ -365,6 +365,13 @@ void DetectDnsResponseRegister(void) DetectAppLayerMpmRegister(keyword, SIG_FLAG_TOCLIENT, 2, DetectDnsResponsePrefilterMpmRegister, NULL, ALPROTO_DNS, 1); + /* DOH2: Register in the TO_CLIENT direction. */ + DetectAppLayerInspectEngineRegisterSubState(keyword, ALPROTO_DOH2, SIG_FLAG_TOCLIENT, + HTTP2TxTypeStream, HTTP2ProgClosed, DetectEngineInspectCb, NULL); + DetectAppLayerMpmRegisterSubState(keyword, SIG_FLAG_TOCLIENT, 2, + DetectDnsResponsePrefilterMpmRegister, NULL, ALPROTO_DOH2, HTTP2TxTypeStream, + HTTP2ProgClosed); + DetectBufferTypeSetDescriptionByName(keyword, "dns response rrname"); DetectBufferTypeSupportsMultiInstance(keyword); From 3e5d5f7bd9d0e60f4119ac59c6c9f6ee73a53505 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Mon, 8 Jun 2026 22:09:05 +0200 Subject: [PATCH 30/69] detect/http: register keywords for HTTP/2 Now that HTTP keywords are no longer automatically registered for HTTP/2, register them manually. (cherry picked from commit 17953d2355a475cc382ca86a11ab54633ee68d01) --- src/detect-http-client-body.c | 8 ++++---- src/detect-http-cookie.c | 21 ++++++++++++--------- src/detect-http-header-names.c | 20 +++++++++++--------- src/detect-http-header.c | 10 ++++++++-- src/detect-http-headers-stub.h | 19 +++++++++++-------- src/detect-http-host.c | 18 ++++++++++-------- src/detect-http-method.c | 9 +++++---- src/detect-http-protocol.c | 18 ++++++++++-------- src/detect-http-raw-header.c | 20 +++++++++++--------- src/detect-http-request-line.c | 9 +++++---- src/detect-http-response-line.c | 9 +++++---- src/detect-http-stat-code.c | 9 +++++---- src/detect-http-stat-msg.c | 9 +++++---- src/detect-http-ua.c | 9 +++++---- src/detect-http-uri.c | 17 +++++++++-------- 15 files changed, 116 insertions(+), 89 deletions(-) diff --git a/src/detect-http-client-body.c b/src/detect-http-client-body.c index 074a892ecdf0..2de5dd422257 100644 --- a/src/detect-http-client-body.c +++ b/src/detect-http-client-body.c @@ -111,10 +111,10 @@ void DetectHttpClientBodyRegister(void) DetectAppLayerMpmRegister("http_client_body", SIG_FLAG_TOSERVER, 2, PrefilterMpmHttpRequestBodyRegister, NULL, ALPROTO_HTTP1, HTP_REQUEST_PROGRESS_BODY); - DetectAppLayerInspectEngineRegister("http_client_body", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2ProgData, DetectEngineInspectFiledata, NULL); - DetectAppLayerMpmRegister("http_client_body", SIG_FLAG_TOSERVER, 2, - PrefilterMpmFiledataRegister, NULL, ALPROTO_HTTP2, HTTP2ProgData); + DetectAppLayerInspectEngineRegisterSubState("http_client_body", ALPROTO_HTTP2, + SIG_FLAG_TOSERVER, HTTP2TxTypeStream, HTTP2ProgData, DetectEngineInspectFiledata, NULL); + DetectAppLayerMpmRegisterSubState("http_client_body", SIG_FLAG_TOSERVER, 2, + PrefilterMpmFiledataRegister, NULL, ALPROTO_HTTP2, HTTP2TxTypeStream, HTTP2ProgData); DetectBufferTypeSetDescriptionByName("http_client_body", "http request body"); diff --git a/src/detect-http-cookie.c b/src/detect-http-cookie.c index 857d85ce7fd7..5596d02b101d 100644 --- a/src/detect-http-cookie.c +++ b/src/detect-http-cookie.c @@ -119,15 +119,18 @@ void DetectHttpCookieRegister(void) DetectAppLayerMpmRegister("http_cookie", SIG_FLAG_TOCLIENT, 2, PrefilterGenericMpmRegister, GetResponseData, ALPROTO_HTTP1, HTP_REQUEST_PROGRESS_HEADERS); - DetectAppLayerInspectEngineRegister("http_cookie", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetRequestData2); - DetectAppLayerInspectEngineRegister("http_cookie", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, - HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetResponseData2); - - DetectAppLayerMpmRegister("http_cookie", SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, - GetRequestData2, ALPROTO_HTTP2, HTTP2ProgHeaders); - DetectAppLayerMpmRegister("http_cookie", SIG_FLAG_TOCLIENT, 2, PrefilterGenericMpmRegister, - GetResponseData2, ALPROTO_HTTP2, HTTP2ProgHeaders); + DetectAppLayerInspectEngineRegisterSubState("http_cookie", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, + HTTP2TxTypeStream, HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetRequestData2); + DetectAppLayerInspectEngineRegisterSubState("http_cookie", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, + HTTP2TxTypeStream, HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, + GetResponseData2); + + DetectAppLayerMpmRegisterSubState("http_cookie", SIG_FLAG_TOSERVER, 2, + PrefilterGenericMpmRegister, GetRequestData2, ALPROTO_HTTP2, HTTP2TxTypeStream, + HTTP2ProgHeaders); + DetectAppLayerMpmRegisterSubState("http_cookie", SIG_FLAG_TOCLIENT, 2, + PrefilterGenericMpmRegister, GetResponseData2, ALPROTO_HTTP2, HTTP2TxTypeStream, + HTTP2ProgHeaders); DetectBufferTypeSetDescriptionByName("http_cookie", "http cookie header"); diff --git a/src/detect-http-header-names.c b/src/detect-http-header-names.c index 5f905f4c3aa2..d20497e0d958 100644 --- a/src/detect-http-header-names.c +++ b/src/detect-http-header-names.c @@ -235,15 +235,17 @@ void DetectHttpHeaderNamesRegister(void) HTP_RESPONSE_PROGRESS_HEADERS, DetectEngineInspectBufferGeneric, GetBuffer1ForTX); /* http2 */ - DetectAppLayerMpmRegister(BUFFER_NAME, SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, - GetBuffer2ForTX, ALPROTO_HTTP2, HTTP2ProgHeaders); - DetectAppLayerMpmRegister(BUFFER_NAME, SIG_FLAG_TOCLIENT, 2, PrefilterGenericMpmRegister, - GetBuffer2ForTX, ALPROTO_HTTP2, HTTP2ProgHeaders); - - DetectAppLayerInspectEngineRegister(BUFFER_NAME, ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetBuffer2ForTX); - DetectAppLayerInspectEngineRegister(BUFFER_NAME, ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, - HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetBuffer2ForTX); + DetectAppLayerMpmRegisterSubState(BUFFER_NAME, SIG_FLAG_TOSERVER, 2, + PrefilterGenericMpmRegister, GetBuffer2ForTX, ALPROTO_HTTP2, HTTP2TxTypeStream, + HTTP2ProgHeaders); + DetectAppLayerMpmRegisterSubState(BUFFER_NAME, SIG_FLAG_TOCLIENT, 2, + PrefilterGenericMpmRegister, GetBuffer2ForTX, ALPROTO_HTTP2, HTTP2TxTypeStream, + HTTP2ProgHeaders); + + DetectAppLayerInspectEngineRegisterSubState(BUFFER_NAME, ALPROTO_HTTP2, SIG_FLAG_TOSERVER, + HTTP2TxTypeStream, HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetBuffer2ForTX); + DetectAppLayerInspectEngineRegisterSubState(BUFFER_NAME, ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, + HTTP2TxTypeStream, HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetBuffer2ForTX); DetectBufferTypeSetDescriptionByName(BUFFER_NAME, BUFFER_DESC); diff --git a/src/detect-http-header.c b/src/detect-http-header.c index 1ecef2bbd360..6f155071407d 100644 --- a/src/detect-http-header.c +++ b/src/detect-http-header.c @@ -441,6 +441,12 @@ void DetectHttpHeaderRegister(void) 0); /* not used, registered twice: HEADERS/TRAILER */ /* header is for stream TX type */ + DetectAppLayerInspectEngineRegisterSubState("http_header", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, + HTTP2TxTypeStream, HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetBuffer2ForTX); + DetectAppLayerMpmRegisterSubState("http_header", SIG_FLAG_TOSERVER, 2, + PrefilterGenericMpmRegister, GetBuffer2ForTX, ALPROTO_HTTP2, HTTP2TxTypeStream, + HTTP2ProgHeaders); + DetectAppLayerInspectEngineRegisterSubState("http_header", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, HTTP2TxTypeStream, HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetBuffer2ForTX); DetectAppLayerMpmRegisterSubState("http_header", SIG_FLAG_TOCLIENT, 2, @@ -648,8 +654,8 @@ void DetectHttpResponseHeaderRegister(void) sigmatch_table[DETECT_HTTP_RESPONSE_HEADER].flags |= SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER; - DetectAppLayerMultiRegister("http_response_header", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, - HTTP2ProgHeaders, GetHttp2HeaderData, 2); + DetectAppLayerMultiRegisterSubState("http_response_header", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, + HTTP2TxTypeStream, HTTP2ProgHeaders, GetHttp2HeaderData, 2); DetectAppLayerMultiRegister("http_response_header", ALPROTO_HTTP1, SIG_FLAG_TOCLIENT, HTP_RESPONSE_PROGRESS_HEADERS, GetHttp1HeaderData, 2); diff --git a/src/detect-http-headers-stub.h b/src/detect-http-headers-stub.h index 10eff07cc8d0..4567d011e9af 100644 --- a/src/detect-http-headers-stub.h +++ b/src/detect-http-headers-stub.h @@ -197,26 +197,29 @@ static void DetectHttpHeadersRegisterStub(void) #ifdef KEYWORD_TOSERVER DetectAppLayerMpmRegister(BUFFER_NAME, SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, GetRequestData, ALPROTO_HTTP1, HTP_REQUEST_PROGRESS_HEADERS); - DetectAppLayerMpmRegister(BUFFER_NAME, SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, - GetRequestData2, ALPROTO_HTTP2, HTTP2ProgHeaders); + DetectAppLayerMpmRegisterSubState(BUFFER_NAME, SIG_FLAG_TOSERVER, 2, + PrefilterGenericMpmRegister, GetRequestData2, ALPROTO_HTTP2, HTTP2TxTypeStream, + HTTP2ProgHeaders); #endif #ifdef KEYWORD_TOCLIENT DetectAppLayerMpmRegister(BUFFER_NAME, SIG_FLAG_TOCLIENT, 2, PrefilterGenericMpmRegister, GetResponseData, ALPROTO_HTTP1, HTP_RESPONSE_PROGRESS_HEADERS); - DetectAppLayerMpmRegister(BUFFER_NAME, SIG_FLAG_TOCLIENT, 2, PrefilterGenericMpmRegister, - GetResponseData2, ALPROTO_HTTP2, HTTP2ProgHeaders); + DetectAppLayerMpmRegisterSubState(BUFFER_NAME, SIG_FLAG_TOCLIENT, 2, + PrefilterGenericMpmRegister, GetResponseData2, ALPROTO_HTTP2, HTTP2TxTypeStream, + HTTP2ProgHeaders); #endif #ifdef KEYWORD_TOSERVER DetectAppLayerInspectEngineRegister(BUFFER_NAME, ALPROTO_HTTP1, SIG_FLAG_TOSERVER, HTP_REQUEST_PROGRESS_HEADERS, DetectEngineInspectBufferGeneric, GetRequestData); - DetectAppLayerInspectEngineRegister(BUFFER_NAME, ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetRequestData2); + DetectAppLayerInspectEngineRegisterSubState(BUFFER_NAME, ALPROTO_HTTP2, SIG_FLAG_TOSERVER, + HTTP2TxTypeStream, HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetRequestData2); #endif #ifdef KEYWORD_TOCLIENT DetectAppLayerInspectEngineRegister(BUFFER_NAME, ALPROTO_HTTP1, SIG_FLAG_TOCLIENT, HTP_RESPONSE_PROGRESS_HEADERS, DetectEngineInspectBufferGeneric, GetResponseData); - DetectAppLayerInspectEngineRegister(BUFFER_NAME, ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, - HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetResponseData2); + DetectAppLayerInspectEngineRegisterSubState(BUFFER_NAME, ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, + HTTP2TxTypeStream, HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, + GetResponseData2); #endif DetectBufferTypeSetDescriptionByName(BUFFER_NAME, BUFFER_DESC); diff --git a/src/detect-http-host.c b/src/detect-http-host.c index 1b8777bf169f..9f2b59e31778 100644 --- a/src/detect-http-host.c +++ b/src/detect-http-host.c @@ -116,11 +116,12 @@ void DetectHttpHHRegister(void) DetectAppLayerMpmRegister("http_host", SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, GetData, ALPROTO_HTTP1, HTP_REQUEST_PROGRESS_HEADERS); - DetectAppLayerInspectEngineRegister("http_host", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetData2); + DetectAppLayerInspectEngineRegisterSubState("http_host", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, + HTTP2TxTypeStream, HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetData2); - DetectAppLayerMpmRegister("http_host", SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, - GetData2, ALPROTO_HTTP2, HTTP2ProgHeaders); + DetectAppLayerMpmRegisterSubState("http_host", SIG_FLAG_TOSERVER, 2, + PrefilterGenericMpmRegister, GetData2, ALPROTO_HTTP2, HTTP2TxTypeStream, + HTTP2ProgHeaders); DetectBufferTypeRegisterValidateCallback("http_host", DetectHttpHostValidateCallback); @@ -156,11 +157,12 @@ void DetectHttpHHRegister(void) DetectAppLayerMpmRegister("http_raw_host", SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, GetRawData, ALPROTO_HTTP1, HTP_REQUEST_PROGRESS_HEADERS); - DetectAppLayerInspectEngineRegister("http_raw_host", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetRawData2); + DetectAppLayerInspectEngineRegisterSubState("http_raw_host", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, + HTTP2TxTypeStream, HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetRawData2); - DetectAppLayerMpmRegister("http_raw_host", SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, - GetRawData2, ALPROTO_HTTP2, HTTP2ProgHeaders); + DetectAppLayerMpmRegisterSubState("http_raw_host", SIG_FLAG_TOSERVER, 2, + PrefilterGenericMpmRegister, GetRawData2, ALPROTO_HTTP2, HTTP2TxTypeStream, + HTTP2ProgHeaders); DetectBufferTypeSetDescriptionByName("http_raw_host", "http raw host header"); diff --git a/src/detect-http-method.c b/src/detect-http-method.c index 99061a49a426..e9161d2d91e3 100644 --- a/src/detect-http-method.c +++ b/src/detect-http-method.c @@ -106,11 +106,12 @@ void DetectHttpMethodRegister(void) DetectAppLayerMpmRegister("http_method", SIG_FLAG_TOSERVER, 4, PrefilterGenericMpmRegister, GetData, ALPROTO_HTTP1, HTP_REQUEST_PROGRESS_LINE); - DetectAppLayerInspectEngineRegister("http_method", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetData2); + DetectAppLayerInspectEngineRegisterSubState("http_method", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, + HTTP2TxTypeStream, HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetData2); - DetectAppLayerMpmRegister("http_method", SIG_FLAG_TOSERVER, 4, PrefilterGenericMpmRegister, - GetData2, ALPROTO_HTTP2, HTTP2ProgHeaders); + DetectAppLayerMpmRegisterSubState("http_method", SIG_FLAG_TOSERVER, 4, + PrefilterGenericMpmRegister, GetData2, ALPROTO_HTTP2, HTTP2TxTypeStream, + HTTP2ProgHeaders); DetectBufferTypeSetDescriptionByName("http_method", "http request method"); diff --git a/src/detect-http-protocol.c b/src/detect-http-protocol.c index c2423b552a7e..3bf8d97c03d7 100644 --- a/src/detect-http-protocol.c +++ b/src/detect-http-protocol.c @@ -172,14 +172,16 @@ void DetectHttpProtocolRegister(void) DetectAppLayerInspectEngineRegister(BUFFER_NAME, ALPROTO_HTTP1, SIG_FLAG_TOCLIENT, HTP_RESPONSE_PROGRESS_LINE, DetectEngineInspectBufferGeneric, GetData); - DetectAppLayerInspectEngineRegister(BUFFER_NAME, ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2ProgStart, DetectEngineInspectBufferGeneric, GetData2); - DetectAppLayerMpmRegister(BUFFER_NAME, SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, - GetData2, ALPROTO_HTTP2, HTTP2ProgStart); - DetectAppLayerInspectEngineRegister(BUFFER_NAME, ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, - HTTP2ProgStart, DetectEngineInspectBufferGeneric, GetData2); - DetectAppLayerMpmRegister(BUFFER_NAME, SIG_FLAG_TOCLIENT, 2, PrefilterGenericMpmRegister, - GetData2, ALPROTO_HTTP2, HTTP2ProgStart); + DetectAppLayerInspectEngineRegisterSubState(BUFFER_NAME, ALPROTO_HTTP2, SIG_FLAG_TOSERVER, + HTTP2TxTypeStream, HTTP2ProgStart, DetectEngineInspectBufferGeneric, GetData2); + DetectAppLayerMpmRegisterSubState(BUFFER_NAME, SIG_FLAG_TOSERVER, 2, + PrefilterGenericMpmRegister, GetData2, ALPROTO_HTTP2, HTTP2TxTypeStream, + HTTP2ProgStart); + DetectAppLayerInspectEngineRegisterSubState(BUFFER_NAME, ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, + HTTP2TxTypeStream, HTTP2ProgStart, DetectEngineInspectBufferGeneric, GetData2); + DetectAppLayerMpmRegisterSubState(BUFFER_NAME, SIG_FLAG_TOCLIENT, 2, + PrefilterGenericMpmRegister, GetData2, ALPROTO_HTTP2, HTTP2TxTypeStream, + HTTP2ProgStart); DetectBufferTypeSetDescriptionByName(BUFFER_NAME, BUFFER_DESC); diff --git a/src/detect-http-raw-header.c b/src/detect-http-raw-header.c index 9f79178ba86c..141f5c49a933 100644 --- a/src/detect-http-raw-header.c +++ b/src/detect-http-raw-header.c @@ -114,15 +114,17 @@ void DetectHttpRawHeaderRegister(void) PrefilterMpmHttpHeaderRawResponseRegister, NULL, ALPROTO_HTTP1, 0); /* progress handled in register */ - DetectAppLayerInspectEngineRegister("http_raw_header", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetData2); - DetectAppLayerInspectEngineRegister("http_raw_header", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, - HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetData2); - - DetectAppLayerMpmRegister("http_raw_header", SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, - GetData2, ALPROTO_HTTP2, HTTP2ProgHeaders); - DetectAppLayerMpmRegister("http_raw_header", SIG_FLAG_TOCLIENT, 2, PrefilterGenericMpmRegister, - GetData2, ALPROTO_HTTP2, HTTP2ProgHeaders); + DetectAppLayerInspectEngineRegisterSubState("http_raw_header", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, + HTTP2TxTypeStream, HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetData2); + DetectAppLayerInspectEngineRegisterSubState("http_raw_header", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, + HTTP2TxTypeStream, HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetData2); + + DetectAppLayerMpmRegisterSubState("http_raw_header", SIG_FLAG_TOSERVER, 2, + PrefilterGenericMpmRegister, GetData2, ALPROTO_HTTP2, HTTP2TxTypeStream, + HTTP2ProgHeaders); + DetectAppLayerMpmRegisterSubState("http_raw_header", SIG_FLAG_TOCLIENT, 2, + PrefilterGenericMpmRegister, GetData2, ALPROTO_HTTP2, HTTP2TxTypeStream, + HTTP2ProgHeaders); DetectBufferTypeSetDescriptionByName("http_raw_header", "raw http headers"); diff --git a/src/detect-http-request-line.c b/src/detect-http-request-line.c index 0403350076a7..976b14dff08f 100644 --- a/src/detect-http-request-line.c +++ b/src/detect-http-request-line.c @@ -116,10 +116,11 @@ void DetectHttpRequestLineRegister(void) DetectAppLayerMpmRegister("http_request_line", SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, GetData, ALPROTO_HTTP1, HTP_REQUEST_PROGRESS_LINE); - DetectAppLayerInspectEngineRegister("http_request_line", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2ProgData, DetectEngineInspectBufferGeneric, GetData2); - DetectAppLayerMpmRegister("http_request_line", SIG_FLAG_TOSERVER, 2, - PrefilterGenericMpmRegister, GetData2, ALPROTO_HTTP2, HTTP2ProgData); + DetectAppLayerInspectEngineRegisterSubState("http_request_line", ALPROTO_HTTP2, + SIG_FLAG_TOSERVER, HTTP2TxTypeStream, HTTP2ProgData, DetectEngineInspectBufferGeneric, + GetData2); + DetectAppLayerMpmRegisterSubState("http_request_line", SIG_FLAG_TOSERVER, 2, + PrefilterGenericMpmRegister, GetData2, ALPROTO_HTTP2, HTTP2TxTypeStream, HTTP2ProgData); DetectBufferTypeSetDescriptionByName("http_request_line", "http request line"); diff --git a/src/detect-http-response-line.c b/src/detect-http-response-line.c index 307dd55bdc21..9695a087d6dc 100644 --- a/src/detect-http-response-line.c +++ b/src/detect-http-response-line.c @@ -115,10 +115,11 @@ void DetectHttpResponseLineRegister(void) DetectAppLayerMpmRegister("http_response_line", SIG_FLAG_TOCLIENT, 2, PrefilterGenericMpmRegister, GetData, ALPROTO_HTTP1, HTP_RESPONSE_PROGRESS_LINE); - DetectAppLayerInspectEngineRegister("http_response_line", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, - HTTP2ProgData, DetectEngineInspectBufferGeneric, GetData2); - DetectAppLayerMpmRegister("http_response_line", SIG_FLAG_TOCLIENT, 2, - PrefilterGenericMpmRegister, GetData2, ALPROTO_HTTP2, HTTP2ProgData); + DetectAppLayerInspectEngineRegisterSubState("http_response_line", ALPROTO_HTTP2, + SIG_FLAG_TOCLIENT, HTTP2TxTypeStream, HTTP2ProgData, DetectEngineInspectBufferGeneric, + GetData2); + DetectAppLayerMpmRegisterSubState("http_response_line", SIG_FLAG_TOCLIENT, 2, + PrefilterGenericMpmRegister, GetData2, ALPROTO_HTTP2, HTTP2TxTypeStream, HTTP2ProgData); DetectBufferTypeSetDescriptionByName("http_response_line", "http response line"); diff --git a/src/detect-http-stat-code.c b/src/detect-http-stat-code.c index 92e51f3281cf..ca3e0f985726 100644 --- a/src/detect-http-stat-code.c +++ b/src/detect-http-stat-code.c @@ -107,11 +107,12 @@ void DetectHttpStatCodeRegister (void) DetectAppLayerMpmRegister("http_stat_code", SIG_FLAG_TOCLIENT, 4, PrefilterGenericMpmRegister, GetData, ALPROTO_HTTP1, HTP_RESPONSE_PROGRESS_LINE); - DetectAppLayerInspectEngineRegister("http_stat_code", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, - HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetData2); + DetectAppLayerInspectEngineRegisterSubState("http_stat_code", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, + HTTP2TxTypeStream, HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetData2); - DetectAppLayerMpmRegister("http_stat_code", SIG_FLAG_TOCLIENT, 4, PrefilterGenericMpmRegister, - GetData2, ALPROTO_HTTP2, HTTP2ProgHeaders); + DetectAppLayerMpmRegisterSubState("http_stat_code", SIG_FLAG_TOCLIENT, 4, + PrefilterGenericMpmRegister, GetData2, ALPROTO_HTTP2, HTTP2TxTypeStream, + HTTP2ProgHeaders); DetectBufferTypeSetDescriptionByName("http_stat_code", "http response status code"); diff --git a/src/detect-http-stat-msg.c b/src/detect-http-stat-msg.c index 70cd3edd3677..ba24528e1126 100644 --- a/src/detect-http-stat-msg.c +++ b/src/detect-http-stat-msg.c @@ -117,10 +117,11 @@ void DetectHttpStatMsgRegister (void) DetectAppLayerMpmRegister("http_stat_msg", SIG_FLAG_TOCLIENT, 3, PrefilterGenericMpmRegister, GetData, ALPROTO_HTTP1, HTP_RESPONSE_PROGRESS_LINE); - DetectAppLayerInspectEngineRegister("http_stat_msg", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, - HTTP2ProgStart, DetectEngineInspectBufferGeneric, GetData2); - DetectAppLayerMpmRegister("http_stat_msg", SIG_FLAG_TOCLIENT, 2, PrefilterGenericMpmRegister, - GetData2, ALPROTO_HTTP2, HTTP2ProgStart); + DetectAppLayerInspectEngineRegisterSubState("http_stat_msg", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, + HTTP2TxTypeStream, HTTP2ProgStart, DetectEngineInspectBufferGeneric, GetData2); + DetectAppLayerMpmRegisterSubState("http_stat_msg", SIG_FLAG_TOCLIENT, 2, + PrefilterGenericMpmRegister, GetData2, ALPROTO_HTTP2, HTTP2TxTypeStream, + HTTP2ProgStart); DetectBufferTypeSetDescriptionByName("http_stat_msg", "http response status message"); diff --git a/src/detect-http-ua.c b/src/detect-http-ua.c index f5ce3075b64e..b5e1654b0a2f 100644 --- a/src/detect-http-ua.c +++ b/src/detect-http-ua.c @@ -107,11 +107,12 @@ void DetectHttpUARegister(void) DetectAppLayerMpmRegister("http_user_agent", SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, GetData, ALPROTO_HTTP1, HTP_REQUEST_PROGRESS_HEADERS); - DetectAppLayerInspectEngineRegister("http_user_agent", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetData2); + DetectAppLayerInspectEngineRegisterSubState("http_user_agent", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, + HTTP2TxTypeStream, HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetData2); - DetectAppLayerMpmRegister("http_user_agent", SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, - GetData2, ALPROTO_HTTP2, HTTP2ProgHeaders); + DetectAppLayerMpmRegisterSubState("http_user_agent", SIG_FLAG_TOSERVER, 2, + PrefilterGenericMpmRegister, GetData2, ALPROTO_HTTP2, HTTP2TxTypeStream, + HTTP2ProgHeaders); DetectBufferTypeSetDescriptionByName("http_user_agent", "http user agent"); diff --git a/src/detect-http-uri.c b/src/detect-http-uri.c index 5638ae5d8884..30f81c6fd21d 100644 --- a/src/detect-http-uri.c +++ b/src/detect-http-uri.c @@ -111,11 +111,11 @@ void DetectHttpUriRegister (void) DetectAppLayerMpmRegister("http_uri", SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, GetData, ALPROTO_HTTP1, HTP_REQUEST_PROGRESS_LINE); - DetectAppLayerInspectEngineRegister("http_uri", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetData2); + DetectAppLayerInspectEngineRegisterSubState("http_uri", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, + HTTP2TxTypeStream, HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetData2); - DetectAppLayerMpmRegister("http_uri", SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, - GetData2, ALPROTO_HTTP2, HTTP2ProgHeaders); + DetectAppLayerMpmRegisterSubState("http_uri", SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, + GetData2, ALPROTO_HTTP2, HTTP2TxTypeStream, HTTP2ProgHeaders); DetectBufferTypeSetDescriptionByName("http_uri", "http request uri"); @@ -149,11 +149,12 @@ void DetectHttpUriRegister (void) GetRawData, ALPROTO_HTTP1, HTP_REQUEST_PROGRESS_LINE); // no difference between raw and decoded uri for HTTP2 - DetectAppLayerInspectEngineRegister("http_raw_uri", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetData2); + DetectAppLayerInspectEngineRegisterSubState("http_raw_uri", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, + HTTP2TxTypeStream, HTTP2ProgHeaders, DetectEngineInspectBufferGeneric, GetData2); - DetectAppLayerMpmRegister("http_raw_uri", SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, - GetData2, ALPROTO_HTTP2, HTTP2ProgHeaders); + DetectAppLayerMpmRegisterSubState("http_raw_uri", SIG_FLAG_TOSERVER, 2, + PrefilterGenericMpmRegister, GetData2, ALPROTO_HTTP2, HTTP2TxTypeStream, + HTTP2ProgHeaders); DetectBufferTypeSetDescriptionByName("http_raw_uri", "raw http uri"); From e7b5240a155b5ab3d652c5c049a9d34ef6c14994 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Mon, 8 Jun 2026 22:45:05 +0200 Subject: [PATCH 31/69] detect: change how DOH2 inspection works DNS/HTTP2 no longer automatically registers all keywords also for DOH2. Instead, the DNS keywords and HTTP/2 stream keywords are registered for DOH2 explicitly as well. - flow alproto DOH2 + engine DOH2 -> inspect inner DNS - flow alproto DOH2 + engine HTTP2 -> inspect outer HTTP/2 - flow alproto DOH2 + engine UNKNOWN -> inspect outer HTTP/2 (cherry picked from commit 5735b87c3a8eaeb0c74ac1225fa1a698808461fc) --- src/detect.c | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/detect.c b/src/detect.c index 47c287151dde..6dc7706abcf7 100644 --- a/src/detect.c +++ b/src/detect.c @@ -1238,19 +1238,34 @@ DetectRunTxSortHelper(const void *a, const void *b) #define TRACE_SID_TXS(sid,txs,...) #endif -// Get inner transaction for engine +/** \internal + * \brief get correct transaction pointer + * + * Gets an encapsulated DNS transaction in the DOH2 case. + * + * Returns NULL is the TX is not to be inspected by this engine. + */ void *DetectGetInnerTx(void *tx_ptr, AppProto alproto, AppProto engine_alproto, uint8_t flow_flags) { + SCLogDebug("pre: tx_ptr %p flow::alproto %s engine::alproto %s", tx_ptr, + AppProtoToString(alproto), AppProtoToString(engine_alproto)); if (unlikely(alproto == ALPROTO_DOH2)) { - if (engine_alproto == ALPROTO_DNS) { - // need to get the dns tx pointer - tx_ptr = SCDoH2GetDnsTx(tx_ptr, flow_flags); - } else if (engine_alproto != ALPROTO_HTTP2 && engine_alproto != ALPROTO_UNKNOWN) { - // incompatible engine->alproto with flow alproto - tx_ptr = NULL; + switch (engine_alproto) { + case ALPROTO_DOH2: + /* need to get the dns tx pointer */ + tx_ptr = SCDoH2GetDnsTx(tx_ptr, flow_flags); + break; + case ALPROTO_HTTP2: + case ALPROTO_UNKNOWN: + /* tx_ptr is untouched, so use outer (HTTP/2) layer */ + break; + default: + /* any other protocol is a mismatch with DOH2 */ + tx_ptr = NULL; + break; } } else if (engine_alproto != alproto && engine_alproto != ALPROTO_UNKNOWN) { - // incompatible engine->alproto with flow alproto + /* incompatible engine->alproto with flow alproto */ tx_ptr = NULL; } return tx_ptr; From c9ef3bfca1988e6f1628d8d3db15ce3366d96e75 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Mon, 8 Jun 2026 22:46:48 +0200 Subject: [PATCH 32/69] output/dns: update for DOH2 change (cherry picked from commit cedb836d012db47be9984af68512872b2d9d826e) --- src/output-json-dns.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/output-json-dns.c b/src/output-json-dns.c index 03b6aa5ffdd0..0a99f3eae37e 100644 --- a/src/output-json-dns.c +++ b/src/output-json-dns.c @@ -266,9 +266,9 @@ bool AlertJsonDoh2(void *txptr, SCJsonBuilder *js) SCJbRestoreMark(js, &mark); } // then log one DNS tx if any, preferring the answer - void *tx_dns = DetectGetInnerTx(txptr, ALPROTO_DOH2, ALPROTO_DNS, STREAM_TOCLIENT); + void *tx_dns = DetectGetInnerTx(txptr, ALPROTO_DOH2, ALPROTO_DOH2, STREAM_TOCLIENT); if (tx_dns == NULL) { - tx_dns = DetectGetInnerTx(txptr, ALPROTO_DOH2, ALPROTO_DNS, STREAM_TOSERVER); + tx_dns = DetectGetInnerTx(txptr, ALPROTO_DOH2, ALPROTO_DOH2, STREAM_TOSERVER); } bool r2 = false; if (tx_dns) { @@ -287,9 +287,9 @@ static int JsonDoh2Logger(ThreadVars *tv, void *thread_data, const Packet *p, Fl LogDnsLogThread *td = (LogDnsLogThread *)thread_data; LogDnsFileCtx *dnslog_ctx = td->dnslog_ctx; - void *tx_dns = DetectGetInnerTx(txptr, ALPROTO_DOH2, ALPROTO_DNS, STREAM_TOCLIENT); + void *tx_dns = DetectGetInnerTx(txptr, ALPROTO_DOH2, ALPROTO_DOH2, STREAM_TOCLIENT); if (tx_dns == NULL) { - tx_dns = DetectGetInnerTx(txptr, ALPROTO_DOH2, ALPROTO_DNS, STREAM_TOSERVER); + tx_dns = DetectGetInnerTx(txptr, ALPROTO_DOH2, ALPROTO_DOH2, STREAM_TOSERVER); } /* DOH2 is always logged in flow direction, as its driven by the scope of an From fe2f9d098f28bcd3dbb7d4d44b6c17f36db00f59 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Sun, 14 Jun 2026 15:03:47 +0200 Subject: [PATCH 33/69] http2: mark HTTP2TxProgress and HTTP2TxGlobalProgress as AppLayerState (cherry picked from commit 14100d795c1a2aebe9c62f412e5f64a505fd6c64) --- rust/src/http2/http2.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs index 12bd32b4e275..f8016c41fd9c 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -132,7 +132,8 @@ pub enum HTTP2FrameTypeData { } #[repr(u8)] -#[derive(Copy, Clone, PartialOrd, PartialEq, Eq, Debug)] +#[derive(AppLayerState, Copy, Clone, PartialOrd, PartialEq, Eq, Debug)] +#[suricata(alstate_strip_prefix = "HTTP2Prog")] pub enum HTTP2TxProgress { HTTP2ProgStart = 0, HTTP2ProgHeaders = 1, @@ -142,7 +143,8 @@ pub enum HTTP2TxProgress { } #[repr(u8)] -#[derive(Copy, Clone, PartialOrd, PartialEq, Eq, Debug)] +#[derive(AppLayerState, Copy, Clone, PartialOrd, PartialEq, Eq, Debug)] +#[suricata(alstate_strip_prefix = "HTTP2ProgGlobal")] pub enum HTTP2TxGlobalProgress { HTTP2ProgGlobalStart = 0, HTTP2ProgGlobalComplete = 1, From 05bcc64cb47f18bbff652fbd975d6a227ef6f6ae Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Mon, 15 Jun 2026 07:27:35 +0200 Subject: [PATCH 34/69] http2: register names for states per sub-state (cherry picked from commit 63b814357794acc49574a4aaecad125f4059cd27) --- rust/src/http2/http2.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs index f8016c41fd9c..533a6abb0660 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -40,8 +40,8 @@ use std::fmt; use std::io; use suricata_sys::sys::{ AppLayerParserState, AppProto, SCAppLayerForceProtocolChange, - SCAppLayerParserConfParserEnabled, SCAppLayerParserRegisterLogger, - SCAppLayerProtoDetectConfProtoDetectionEnabled, + SCAppLayerParserConfParserEnabled, SCAppLayerParserRegisterGetTxSubStateFuncs, + SCAppLayerParserRegisterLogger, SCAppLayerProtoDetectConfProtoDetectionEnabled, }; use suricata_sys::sys::AppProtoEnum::ALPROTO_HTTP1; @@ -1779,6 +1779,20 @@ pub unsafe extern "C" fn SCRegisterHttp2Parser() { } } SCAppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_HTTP2); + + SCAppLayerParserRegisterGetTxSubStateFuncs( + ALPROTO_HTTP2, + HTTP2TxType::HTTP2TxTypeStream as u8, + Some(HTTP2TxProgress::ffi_id_from_name), + Some(HTTP2TxProgress::ffi_name_from_id), + ); + SCAppLayerParserRegisterGetTxSubStateFuncs( + ALPROTO_HTTP2, + HTTP2TxType::HTTP2TxTypeGlobal as u8, + Some(HTTP2TxGlobalProgress::ffi_id_from_name), + Some(HTTP2TxGlobalProgress::ffi_name_from_id), + ); + SCLogDebug!("Rust http2 parser registered."); } else { SCLogNotice!("Protocol detector and parser disabled for HTTP2."); From 00dadae5048c17db98dac8aec24d05cd9b300383 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Sat, 13 Jun 2026 21:27:33 +0200 Subject: [PATCH 35/69] detect/firewall: policy for substates For the most part hard coded for HTTP/2 for now. (cherry picked from commit 116a06009236a31a85121e40fd9ddb59f215bc73) --- src/detect-parse.c | 132 ++++++++++++++++++++++++++++++++++++++------- src/detect.c | 57 +++++++++++++------- src/detect.h | 3 ++ 3 files changed, 154 insertions(+), 38 deletions(-) diff --git a/src/detect-parse.c b/src/detect-parse.c index b8d16a9fcc87..149e029f65e5 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -3946,6 +3946,49 @@ static int DoParsePolicy(const char *policy_name, struct DetectFirewallPolicy *p return 1; } +static int DoParseAppSubStatePolicy(const char *prefix, const AppProto app_proto, + const uint8_t sub_state, const char *sub_state_name, const uint8_t state, + const char *hookname, const uint8_t complete_state, const int direction, + struct DetectFirewallPolicies *fw_policies, struct DetectFirewallAppPolicy *app_fw_policies) +{ + char policy_name[256]; + BUG_ON(sub_state_name == NULL); + BUG_ON(hookname == NULL); + + char *nname = SCStrdup(hookname); + if (nname == NULL) + return -1; + for (int i = 0; nname[i] != '\0'; i++) { + if (nname[i] == '_') + nname[i] = '-'; + } + + const char *app_name = AppProtoToString(app_proto); + int r = snprintf(policy_name, sizeof(policy_name), "%s.%s.%s.%s", prefix, app_name, + sub_state_name, nname); + SCLogDebug("policy_name %s", policy_name); + SCFree(nname); + if (r < 0 || (size_t)r >= sizeof(policy_name)) { + FatalError("internal error: failed to assemble firewall policy config string"); + } + + struct DetectFirewallPolicy *pol; + if (direction == STREAM_TOSERVER) + pol = &fw_policies->http2_substates[sub_state - 1].ts[state]; + else + pol = &fw_policies->http2_substates[sub_state - 1].tc[state]; + + r = DoParsePolicy(policy_name, pol); + /* for policies with an alert action, create a policy sig */ + if (r == 1 && pol->action & ACTION_ALERT) { + SCLogDebug("adding policy signature"); + return AddAppPolicySignature(fw_policies->policy_signatures, direction, app_proto, app_name, + state, hookname, pol); + } + SCLogDebug("r %d", r); + return r; +} + static int DoParseAppPolicy(const char *prefix, const AppProto app_proto, const char *hookname, const uint8_t state, const uint8_t complete_state, const int direction, struct DetectFirewallPolicies *fw_policies, struct DetectFirewallAppPolicy *app_fw_policies) @@ -4051,6 +4094,17 @@ int DetectFirewallInitDefaultPolicies(DetectEngineCtx *de_ctx) app_fw_policies[a].tc[i].action_scope = ACTION_SCOPE_FLOW; } } + + /* TODO hard coded for HTTP/2 for now */ + for (int s = 0; s < 2; s++) { + for (int i = 0; i < 48; i++) { + fw_policies->http2_substates[s].ts[i].action = ACTION_DROP; + fw_policies->http2_substates[s].ts[i].action_scope = ACTION_SCOPE_FLOW; + fw_policies->http2_substates[s].tc[i].action = ACTION_DROP; + fw_policies->http2_substates[s].tc[i].action_scope = ACTION_SCOPE_FLOW; + } + } + de_ctx->fw_policies = fw_policies; return 0; @@ -4116,27 +4170,69 @@ int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *de_ctx) if (!AppProtoIsValid(a)) continue; - const uint8_t complete_state_ts = - (const uint8_t)AppLayerParserGetStateProgressCompletionStatus(a, STREAM_TOSERVER); - for (uint8_t state = 0; state <= complete_state_ts; state++) { - const char *name = - AppLayerParserGetStateNameById(IPPROTO_TCP, a, state, STREAM_TOSERVER); - if (DoParseAppPolicy(prefix, a, name, state, complete_state_ts, STREAM_TOSERVER, - fw_policies, app_fw_policies) < 0) - return -1; - } + if (AppLayerParserSupportsSubStates(a)) { + uint8_t max_sub_state = AppLayerParserGetMaxSubState(a); + SCLogDebug("%s: max sub state for %u is %u", AppProtoToString(a), a, max_sub_state); + for (uint8_t s = 1; s <= max_sub_state; s++) { + SCLogDebug("%s: checking sub state %u", AppProtoToString(a), s); - const uint8_t complete_state_tc = - (const uint8_t)AppLayerParserGetStateProgressCompletionStatus(a, STREAM_TOCLIENT); - for (uint8_t state = 0; state <= complete_state_tc; state++) { - const char *name = - AppLayerParserGetStateNameById(IPPROTO_TCP, a, state, STREAM_TOCLIENT); - if (DoParseAppPolicy(prefix, a, name, state, complete_state_tc, STREAM_TOCLIENT, - fw_policies, app_fw_policies) < 0) - return -1; + const char *sub_state_name = AppLayerParserGetSubStateName(a, s); + if (sub_state_name == NULL) + continue; + + // iterate the states belonging to the sub state + const uint8_t max_state = AppLayerParserGetSubStateCompletion( + a, s); // TODO allow different completion per direction? + /* to_server */ + for (uint8_t state = 0; state <= max_state; state++) { + SCLogDebug("protocol %s: sub state:%s state:%u", AppProtoToString(a), + sub_state_name, state); + const char *state_name = + AppLayerParserGetSubStateProgressName(a, s, state, STREAM_TOSERVER); + BUG_ON(state_name == NULL); + SCLogDebug("protocol %s: sub state:%s state:%s", AppProtoToString(a), + sub_state_name, state_name); + if (DoParseAppSubStatePolicy(prefix, a, s, sub_state_name, state, state_name, + max_state, STREAM_TOSERVER, fw_policies, app_fw_policies) < 0) + return -1; + } + /* to_client */ + for (uint8_t state = 0; state <= max_state; state++) { + SCLogDebug("protocol %s: to_client: sub state:%s state:%u", AppProtoToString(a), + sub_state_name, state); + const char *state_name = + AppLayerParserGetSubStateProgressName(a, s, state, STREAM_TOCLIENT); + BUG_ON(state_name == NULL); + SCLogDebug("protocol %s: to_client: sub state:%s state:%s", AppProtoToString(a), + sub_state_name, state_name); + if (DoParseAppSubStatePolicy(prefix, a, s, sub_state_name, state, state_name, + max_state, STREAM_TOCLIENT, fw_policies, app_fw_policies) < 0) + return -1; + } + } + } else { + const uint8_t complete_state_ts = + (const uint8_t)AppLayerParserGetStateProgressCompletionStatus( + a, STREAM_TOSERVER); + for (uint8_t state = 0; state <= complete_state_ts; state++) { + const char *name = + AppLayerParserGetStateNameById(IPPROTO_TCP, a, state, STREAM_TOSERVER); + if (DoParseAppPolicy(prefix, a, name, state, complete_state_ts, STREAM_TOSERVER, + fw_policies, app_fw_policies) < 0) + return -1; + } + const uint8_t complete_state_tc = + (const uint8_t)AppLayerParserGetStateProgressCompletionStatus( + a, STREAM_TOCLIENT); + for (uint8_t state = 0; state <= complete_state_tc; state++) { + const char *name = + AppLayerParserGetStateNameById(IPPROTO_TCP, a, state, STREAM_TOCLIENT); + if (DoParseAppPolicy(prefix, a, name, state, complete_state_tc, STREAM_TOCLIENT, + fw_policies, app_fw_policies) < 0) + return -1; + } } } - return 0; } diff --git a/src/detect.c b/src/detect.c index 6dc7706abcf7..da3503ebd39d 100644 --- a/src/detect.c +++ b/src/detect.c @@ -1722,19 +1722,37 @@ static inline void DetectRunAppendDefaultAppPolicyAlert(DetectEngineThreadCtx *d * to look up configurable default policies later */ static const struct DetectFirewallPolicy *DetectFirewallApplyDefaultAppPolicy( - DetectEngineThreadCtx *det_ctx, const struct DetectFirewallAppPolicy *policies, + DetectEngineThreadCtx *det_ctx, const struct DetectFirewallPolicies *policies, const DetectTransaction *tx, Packet *p, const AppProto alproto, const uint8_t direction, const uint8_t progress) { const struct DetectFirewallPolicy *policy; - if (direction & STREAM_TOSERVER) { - policy = &policies[alproto].ts[progress]; - SCLogDebug("packet %" PRIu64 ", hook:%u, toserver, policy: action %02x scope %u", - p->pcap_cnt, progress, policy->action, policy->action_scope); + SCLogDebug("packet %" PRIu64 ": tx type %u", p->pcap_cnt, tx->tx_type); + if (tx->tx_type != 0) { + // TODO hard coded to HTTP/2 for now + BUG_ON(alproto != ALPROTO_HTTP2); + if (direction & STREAM_TOSERVER) { + policy = &policies->http2_substates[tx->tx_type - 1].ts[progress]; + SCLogDebug("packet %" PRIu64 + ", sub_state:%u, hook:%u, toserver, policy: action %02x scope %u", + p->pcap_cnt, tx->tx_type, progress, policy->action, policy->action_scope); + } else { + policy = &policies->http2_substates[tx->tx_type - 1].tc[progress]; + SCLogDebug("packet %" PRIu64 + ", sub_state:%u, hook:%u, toclient, policy: action %02x scope %u", + p->pcap_cnt, tx->tx_type, progress, policy->action, policy->action_scope); + } + } else { - policy = &policies[alproto].tc[progress]; - SCLogDebug("packet %" PRIu64 ", hook:%u, toclient, policy: action %02x scope %u", - p->pcap_cnt, progress, policy->action, policy->action_scope); + if (direction & STREAM_TOSERVER) { + policy = &policies->app[alproto].ts[progress]; + SCLogDebug("packet %" PRIu64 ", hook:%u, toserver, policy: action %02x scope %u", + p->pcap_cnt, progress, policy->action, policy->action_scope); + } else { + policy = &policies->app[alproto].tc[progress]; + SCLogDebug("packet %" PRIu64 ", hook:%u, toclient, policy: action %02x scope %u", + p->pcap_cnt, progress, policy->action, policy->action_scope); + } } if (policy->action & ACTION_DROP) { @@ -1806,7 +1824,7 @@ static const struct DetectFirewallPolicy *DetectFirewallApplyDefaultAppPolicy( * \retval DETECT_TX_FW_FC_OK no action needed */ static enum DetectTxFirewallFlowControl DetectFirewallApplyDefaultPolicies( - DetectEngineThreadCtx *det_ctx, const struct DetectFirewallAppPolicy *policies, + DetectEngineThreadCtx *det_ctx, const struct DetectFirewallPolicies *policies, DetectTransaction *tx, Packet *p, const AppProto alproto, const uint8_t direction, const uint8_t start_hook, const uint8_t end_hook) { @@ -1827,7 +1845,7 @@ static enum DetectTxFirewallFlowControl DetectFirewallApplyDefaultPolicies( BOOL2STR(apply_to_packet)); const struct DetectFirewallPolicy *policy = DetectFirewallApplyDefaultAppPolicy( - det_ctx, det_ctx->de_ctx->fw_policies->app, tx, p, alproto, direction, hook); + det_ctx, policies, tx, p, alproto, direction, hook); SCLogDebug("fw: hook:%u policy:%02x apply_to_packet:%s", hook, policy->action, BOOL2STR(apply_to_packet)); if (policy->action & ACTION_DROP) { @@ -1942,9 +1960,9 @@ static enum DetectTxFirewallFlowControl DetectRunTxPreCheckFirewallPolicy( s->app_progress_hook, tx->detect_progress, tx->detect_progress_orig); /* if this rule was after the state we expected meaning that there are * no rules for that state. Invoke the default policies. */ - enum DetectTxFirewallFlowControl r = DetectFirewallApplyDefaultPolicies(det_ctx, - det_ctx->de_ctx->fw_policies->app, tx, p, s->alproto, direction, - tx->detect_progress_orig, s->app_progress_hook - 1); + enum DetectTxFirewallFlowControl r = + DetectFirewallApplyDefaultPolicies(det_ctx, det_ctx->de_ctx->fw_policies, tx, p, + s->alproto, direction, tx->detect_progress_orig, s->app_progress_hook - 1); if (r != DETECT_TX_FW_FC_OK) { /* both SKIP and BREAK mean: no more fw rules to inspect. * SKIP applies to just this TX. @@ -2105,8 +2123,8 @@ static void DetectRunTxFirewallApplyAccept(DetectEngineThreadCtx *det_ctx, Packe ? tx->tx_end_state : MIN(tx->tx_end_state, s->app_progress_hook + 1); enum DetectTxFirewallFlowControl r = - DetectFirewallApplyDefaultPolicies(det_ctx, det_ctx->de_ctx->fw_policies->app, - tx, p, s->alproto, direction, s->app_progress_hook + 1, last_hook); + DetectFirewallApplyDefaultPolicies(det_ctx, det_ctx->de_ctx->fw_policies, tx, p, + s->alproto, direction, s->app_progress_hook + 1, last_hook); if (r == DETECT_TX_FW_FC_BREAK) { fw_state->fw_skip_app_filter = true; return; @@ -2168,8 +2186,8 @@ static int DetectTxFirewallNoRulesApplyPolicies(DetectEngineThreadCtx *det_ctx, SCLogDebug("tx.detect_progress_orig %u tx.tx_progress %u", tx->detect_progress_orig, tx->tx_progress); enum DetectTxFirewallFlowControl r = - DetectFirewallApplyDefaultPolicies(det_ctx, det_ctx->de_ctx->fw_policies->app, - tx, p, alproto, flow_flags & (STREAM_TOSERVER | STREAM_TOCLIENT), + DetectFirewallApplyDefaultPolicies(det_ctx, det_ctx->de_ctx->fw_policies, tx, p, + alproto, flow_flags & (STREAM_TOSERVER | STREAM_TOCLIENT), tx->detect_progress_orig, tx->tx_progress); SCLogDebug("r %u", r); if (r == DETECT_TX_FW_FC_BREAK) @@ -2280,9 +2298,8 @@ static int DetectRunTxFirewallRuleNoMatch(DetectEngineThreadCtx *det_ctx, const * we have to invoke the default policy. We only check the current rule hook. * DROP is immediate, flow control for various accept options is handled by * the DetectRunTxPreCheckFirewallPolicy function for the next rule. */ - const struct DetectFirewallPolicy *policy = - DetectFirewallApplyDefaultAppPolicy(det_ctx, det_ctx->de_ctx->fw_policies->app, tx, - p, s->alproto, flow_flags, s->app_progress_hook); + const struct DetectFirewallPolicy *policy = DetectFirewallApplyDefaultAppPolicy(det_ctx, + det_ctx->de_ctx->fw_policies, tx, p, s->alproto, flow_flags, s->app_progress_hook); SCLogDebug("fw_last_for_progress policy %02x", policy->action); if (policy->action & ACTION_DROP) { return 1; diff --git a/src/detect.h b/src/detect.h index 2cd4025526d4..a7e238eec160 100644 --- a/src/detect.h +++ b/src/detect.h @@ -942,6 +942,9 @@ struct DetectFirewallPolicies { /* hash table with a Signature object per default policy that has `alert` enabled. */ HashTable *policy_signatures; + /* hard coded for now: http2 substates. Index at substate - 1. */ + struct DetectFirewallAppPolicy http2_substates[2]; + /** app layer policies, one per alproto */ struct DetectFirewallAppPolicy app[]; }; From a44fb56b30f6077a1f98c80e4139624cd09cd8ab Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Tue, 16 Jun 2026 21:52:36 +0200 Subject: [PATCH 36/69] detect/parse: parse sub state hooks Register a generic list for each sub state / progress combo. (cherry picked from commit 3ef0d2ce1685a732159e9873f62385f859c7010b) --- src/detect-parse.c | 278 +++++++++++++++++++++++++++++++-------------- 1 file changed, 190 insertions(+), 88 deletions(-) diff --git a/src/detect-parse.c b/src/detect-parse.c index 149e029f65e5..d4116d217455 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -1157,65 +1157,131 @@ void DetectRegisterAppLayerHookLists(void) alproto_name = "http1"; SCLogDebug("alproto %u/%s", a, alproto_name); - const int max_progress_ts = - AppLayerParserGetStateProgressCompletionStatus(a, STREAM_TOSERVER); - const int max_progress_tc = - AppLayerParserGetStateProgressCompletionStatus(a, STREAM_TOCLIENT); - - char ts_tx_started[64]; - snprintf(ts_tx_started, sizeof(ts_tx_started), "%s:request_started:generic", alproto_name); - DetectAppLayerInspectEngineRegister( - ts_tx_started, a, SIG_FLAG_TOSERVER, 0, DetectEngineInspectGenericList, NULL); - SCLogDebug("- hook %s:%s list %s (%u)", alproto_name, "request_name", ts_tx_started, - (uint32_t)strlen(ts_tx_started)); - - char tc_tx_started[64]; - snprintf(tc_tx_started, sizeof(tc_tx_started), "%s:response_started:generic", alproto_name); - DetectAppLayerInspectEngineRegister( - tc_tx_started, a, SIG_FLAG_TOCLIENT, 0, DetectEngineInspectGenericList, NULL); - SCLogDebug("- hook %s:%s list %s (%u)", alproto_name, "response_name", tc_tx_started, - (uint32_t)strlen(tc_tx_started)); - - char ts_tx_complete[64]; - snprintf(ts_tx_complete, sizeof(ts_tx_complete), "%s:request_complete:generic", - alproto_name); - DetectAppLayerInspectEngineRegister(ts_tx_complete, a, SIG_FLAG_TOSERVER, max_progress_ts, - DetectEngineInspectGenericList, NULL); - SCLogDebug("- hook %s:%s list %s (%u)", alproto_name, "request_name", ts_tx_complete, - (uint32_t)strlen(ts_tx_complete)); - - char tc_tx_complete[64]; - snprintf(tc_tx_complete, sizeof(tc_tx_complete), "%s:response_complete:generic", - alproto_name); - DetectAppLayerInspectEngineRegister(tc_tx_complete, a, SIG_FLAG_TOCLIENT, max_progress_tc, - DetectEngineInspectGenericList, NULL); - SCLogDebug("- hook %s:%s list %s (%u)", alproto_name, "response_name", tc_tx_complete, - (uint32_t)strlen(tc_tx_complete)); - - for (int p = 0; p <= max_progress_ts; p++) { - const char *name = AppLayerParserGetStateNameById( - IPPROTO_TCP /* TODO no ipproto */, a, p, STREAM_TOSERVER); - if (name != NULL && !IsBuiltIn(name)) { - char list_name[64]; - snprintf(list_name, sizeof(list_name), "%s:%s:generic", alproto_name, name); - SCLogDebug("- hook %s:%s list %s (%u)", alproto_name, name, list_name, - (uint32_t)strlen(list_name)); - - DetectAppLayerInspectEngineRegister( - list_name, a, SIG_FLAG_TOSERVER, p, DetectEngineInspectGenericList, NULL); + if (AppLayerParserSupportsSubStates(a)) { + uint8_t max_sub_state = AppLayerParserGetMaxSubState(a); + SCLogDebug("%s: max sub state for %u is %u", AppProtoToString(a), a, max_sub_state); + for (uint8_t s = 1; s <= max_sub_state; s++) { + const uint8_t max_state = AppLayerParserGetSubStateCompletion( + a, s); // TODO allow different completion per direction? + const char *sub_state_name = AppLayerParserGetSubStateName(a, s); + if (sub_state_name == NULL) + continue; + + char ts_tx_started[64]; + snprintf(ts_tx_started, sizeof(ts_tx_started), "%s:%s:request_started:generic", + alproto_name, sub_state_name); + DetectAppLayerInspectEngineRegisterSubState(ts_tx_started, a, SIG_FLAG_TOSERVER, s, + 0, DetectEngineInspectGenericList, NULL); + + char tc_tx_started[64]; + snprintf(tc_tx_started, sizeof(tc_tx_started), "%s:%s:response_started:generic", + alproto_name, sub_state_name); + DetectAppLayerInspectEngineRegisterSubState(tc_tx_started, a, SIG_FLAG_TOCLIENT, s, + 0, DetectEngineInspectGenericList, NULL); + + char ts_tx_complete[64]; + snprintf(ts_tx_complete, sizeof(ts_tx_complete), "%s:%s:request_complete:generic", + alproto_name, sub_state_name); + DetectAppLayerInspectEngineRegisterSubState(ts_tx_complete, a, SIG_FLAG_TOSERVER, s, + max_state, DetectEngineInspectGenericList, NULL); + + char tc_tx_complete[64]; + snprintf(tc_tx_complete, sizeof(tc_tx_complete), "%s:%s:response_complete:generic", + alproto_name, sub_state_name); + DetectAppLayerInspectEngineRegisterSubState(tc_tx_complete, a, SIG_FLAG_TOCLIENT, s, + max_state, DetectEngineInspectGenericList, NULL); + + /* to_server */ + for (uint8_t state = 0; state <= max_state; state++) { + const char *state_name = + AppLayerParserGetSubStateProgressName(a, s, state, STREAM_TOSERVER); + BUG_ON(state_name == NULL); + + if (state_name != NULL && !IsBuiltIn(state_name)) { + char list_name[64]; + snprintf(list_name, sizeof(list_name), "%s:%s:%s:generic", alproto_name, + sub_state_name, state_name); + DetectAppLayerInspectEngineRegisterSubState(list_name, a, SIG_FLAG_TOSERVER, + s, state, DetectEngineInspectGenericList, NULL); + } + } + /* to_client */ + for (uint8_t state = 0; state <= max_state; state++) { + const char *state_name = + AppLayerParserGetSubStateProgressName(a, s, state, STREAM_TOCLIENT); + BUG_ON(state_name == NULL); + if (state_name != NULL && !IsBuiltIn(state_name)) { + char list_name[64]; + snprintf(list_name, sizeof(list_name), "%s:%s:%s:generic", alproto_name, + sub_state_name, state_name); + DetectAppLayerInspectEngineRegisterSubState(list_name, a, SIG_FLAG_TOCLIENT, + s, state, DetectEngineInspectGenericList, NULL); + } + } } - } - for (int p = 0; p <= max_progress_tc; p++) { - const char *name = AppLayerParserGetStateNameById( - IPPROTO_TCP /* TODO no ipproto */, a, p, STREAM_TOCLIENT); - if (name != NULL && !IsBuiltIn(name)) { - char list_name[64]; - snprintf(list_name, sizeof(list_name), "%s:%s:generic", alproto_name, name); - SCLogDebug("- hook %s:%s list %s (%u)", alproto_name, name, list_name, - (uint32_t)strlen(list_name)); - - DetectAppLayerInspectEngineRegister( - list_name, a, SIG_FLAG_TOCLIENT, p, DetectEngineInspectGenericList, NULL); + } else { + const uint8_t max_progress_ts = + AppLayerParserGetStateProgressCompletionStatus(a, STREAM_TOSERVER); + const uint8_t max_progress_tc = + AppLayerParserGetStateProgressCompletionStatus(a, STREAM_TOCLIENT); + + char ts_tx_started[64]; + snprintf(ts_tx_started, sizeof(ts_tx_started), "%s:request_started:generic", + alproto_name); + DetectAppLayerInspectEngineRegister( + ts_tx_started, a, SIG_FLAG_TOSERVER, 0, DetectEngineInspectGenericList, NULL); + SCLogDebug("- hook %s:%s list %s (%u)", alproto_name, "request_name", ts_tx_started, + (uint32_t)strlen(ts_tx_started)); + + char tc_tx_started[64]; + snprintf(tc_tx_started, sizeof(tc_tx_started), "%s:response_started:generic", + alproto_name); + DetectAppLayerInspectEngineRegister( + tc_tx_started, a, SIG_FLAG_TOCLIENT, 0, DetectEngineInspectGenericList, NULL); + SCLogDebug("- hook %s:%s list %s (%u)", alproto_name, "response_name", tc_tx_started, + (uint32_t)strlen(tc_tx_started)); + + char ts_tx_complete[64]; + snprintf(ts_tx_complete, sizeof(ts_tx_complete), "%s:request_complete:generic", + alproto_name); + DetectAppLayerInspectEngineRegister(ts_tx_complete, a, SIG_FLAG_TOSERVER, + max_progress_ts, DetectEngineInspectGenericList, NULL); + SCLogDebug("- hook %s:%s list %s (%u)", alproto_name, "request_name", ts_tx_complete, + (uint32_t)strlen(ts_tx_complete)); + + char tc_tx_complete[64]; + snprintf(tc_tx_complete, sizeof(tc_tx_complete), "%s:response_complete:generic", + alproto_name); + DetectAppLayerInspectEngineRegister(tc_tx_complete, a, SIG_FLAG_TOCLIENT, + max_progress_tc, DetectEngineInspectGenericList, NULL); + SCLogDebug("- hook %s:%s list %s (%u)", alproto_name, "response_name", tc_tx_complete, + (uint32_t)strlen(tc_tx_complete)); + + for (uint8_t p = 0; p <= max_progress_ts; p++) { + const char *name = AppLayerParserGetStateNameById( + IPPROTO_TCP /* TODO no ipproto */, a, p, STREAM_TOSERVER); + if (name != NULL && !IsBuiltIn(name)) { + char list_name[64]; + snprintf(list_name, sizeof(list_name), "%s:%s:generic", alproto_name, name); + SCLogDebug("- hook %s:%s list %s (%u)", alproto_name, name, list_name, + (uint32_t)strlen(list_name)); + + DetectAppLayerInspectEngineRegister(list_name, a, SIG_FLAG_TOSERVER, p, + DetectEngineInspectGenericList, NULL); + } + } + for (uint8_t p = 0; p <= max_progress_tc; p++) { + const char *name = AppLayerParserGetStateNameById( + IPPROTO_TCP /* TODO no ipproto */, a, p, STREAM_TOCLIENT); + if (name != NULL && !IsBuiltIn(name)) { + char list_name[64]; + snprintf(list_name, sizeof(list_name), "%s:%s:generic", alproto_name, name); + SCLogDebug("- hook %s:%s list %s (%u)", alproto_name, name, list_name, + (uint32_t)strlen(list_name)); + + DetectAppLayerInspectEngineRegister(list_name, a, SIG_FLAG_TOCLIENT, p, + DetectEngineInspectGenericList, NULL); + } } } } @@ -1352,44 +1418,80 @@ static int SigParseProtoHookApp( return -1; } } - } - - SCLogDebug("h:'%s'", h); - if (strcmp(h, "request_started") == 0) { - s->flags |= SIG_FLAG_TOSERVER; - s->init_data->hook = SetAppHook( - s->alproto, sub_state, 0); // state 0 should be the starting state in each protocol. - } else if (strcmp(h, "response_started") == 0) { - s->flags |= SIG_FLAG_TOCLIENT; - s->init_data->hook = SetAppHook( - s->alproto, sub_state, 0); // state 0 should be the starting state in each protocol. - } else if (strcmp(h, "request_complete") == 0) { - s->flags |= SIG_FLAG_TOSERVER; - s->init_data->hook = SetAppHook(s->alproto, sub_state, - AppLayerParserGetStateProgressCompletionStatus(s->alproto, STREAM_TOSERVER)); - } else if (strcmp(h, "response_complete") == 0) { - s->flags |= SIG_FLAG_TOCLIENT; - s->init_data->hook = SetAppHook(s->alproto, sub_state, - AppLayerParserGetStateProgressCompletionStatus(s->alproto, STREAM_TOCLIENT)); - } else { - const int progress_ts = AppLayerParserGetStateIdByName( - IPPROTO_TCP /* TODO */, s->alproto, h, STREAM_TOSERVER); - if (progress_ts >= 0) { + const uint8_t max_state = AppLayerParserGetSubStateCompletion( + s->alproto, sub_state); // TODO allow different completion per direction? + if (strcmp(h, "request_started") == 0) { s->flags |= SIG_FLAG_TOSERVER; - s->init_data->hook = SetAppHook(s->alproto, sub_state, progress_ts); + s->init_data->hook = SetAppHook(s->alproto, sub_state, + 0); // state 0 should be the starting state in each protocol. + } else if (strcmp(h, "response_started") == 0) { + s->flags |= SIG_FLAG_TOCLIENT; + s->init_data->hook = SetAppHook(s->alproto, sub_state, + 0); // state 0 should be the starting state in each protocol. + } else if (strcmp(h, "request_complete") == 0) { + s->flags |= SIG_FLAG_TOSERVER; + s->init_data->hook = SetAppHook(s->alproto, sub_state, max_state); + } else if (strcmp(h, "response_complete") == 0) { + s->flags |= SIG_FLAG_TOCLIENT; + s->init_data->hook = SetAppHook(s->alproto, sub_state, max_state); } else { - const int progress_tc = AppLayerParserGetStateIdByName( - IPPROTO_TCP /* TODO */, s->alproto, h, STREAM_TOCLIENT); - if (progress_tc < 0) { - return -1; + const int8_t progress_ts = + AppLayerParserGetSubStateProgressId(s->alproto, sub_state, h, STREAM_TOSERVER); + if (progress_ts >= 0) { + s->flags |= SIG_FLAG_TOSERVER; + s->init_data->hook = SetAppHook(s->alproto, sub_state, progress_ts); + } else { + const int8_t progress_tc = AppLayerParserGetSubStateProgressId( + s->alproto, sub_state, h, STREAM_TOCLIENT); + if (progress_tc < 0) { + return -1; + } + s->flags |= SIG_FLAG_TOCLIENT; + s->init_data->hook = SetAppHook(s->alproto, sub_state, progress_tc); } + } + } else { + SCLogDebug("h:'%s'", h); + if (strcmp(h, "request_started") == 0) { + s->flags |= SIG_FLAG_TOSERVER; + s->init_data->hook = SetAppHook(s->alproto, sub_state, + 0); // state 0 should be the starting state in each protocol. + } else if (strcmp(h, "response_started") == 0) { s->flags |= SIG_FLAG_TOCLIENT; - s->init_data->hook = SetAppHook(s->alproto, sub_state, progress_tc); + s->init_data->hook = SetAppHook(s->alproto, sub_state, + 0); // state 0 should be the starting state in each protocol. + } else if (strcmp(h, "request_complete") == 0) { + s->flags |= SIG_FLAG_TOSERVER; + s->init_data->hook = SetAppHook(s->alproto, sub_state, + AppLayerParserGetStateProgressCompletionStatus(s->alproto, STREAM_TOSERVER)); + } else if (strcmp(h, "response_complete") == 0) { + s->flags |= SIG_FLAG_TOCLIENT; + s->init_data->hook = SetAppHook(s->alproto, sub_state, + AppLayerParserGetStateProgressCompletionStatus(s->alproto, STREAM_TOCLIENT)); + } else { + const int progress_ts = AppLayerParserGetStateIdByName( + IPPROTO_TCP /* TODO */, s->alproto, h, STREAM_TOSERVER); + if (progress_ts >= 0) { + if (progress_ts >= 48) { + return -1; + } + s->flags |= SIG_FLAG_TOSERVER; + s->init_data->hook = SetAppHook(s->alproto, sub_state, (uint8_t)progress_ts); + } else { + const int progress_tc = AppLayerParserGetStateIdByName( + IPPROTO_TCP /* TODO */, s->alproto, h, STREAM_TOCLIENT); + if (progress_tc < 0 || progress_tc >= 48) { + return -1; + } + s->flags |= SIG_FLAG_TOCLIENT; + s->init_data->hook = SetAppHook(s->alproto, sub_state, (uint8_t)progress_tc); + } } } + /* use in_h to include sub state */ char generic_hook_name[64]; - snprintf(generic_hook_name, sizeof(generic_hook_name), "%s:%s:generic", p, h); + snprintf(generic_hook_name, sizeof(generic_hook_name), "%s:%s:generic", p, in_h); int list = DetectBufferTypeGetByName(generic_hook_name); if (list < 0) { SCLogError("no list registered as %s for hook %s", generic_hook_name, proto_hook); From 07b4674eacfb25cc90080dd30bea76be6595e25f Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Thu, 18 Jun 2026 07:13:08 +0200 Subject: [PATCH 37/69] http2: set event on frame types not allowed on stream id (cherry picked from commit c17ab6a65e28e69a1da3b45d7a1b403d65fb9451) --- rust/src/http2/http2.rs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs index 533a6abb0660..5f9cfeae8aaf 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -1394,6 +1394,22 @@ impl HTTP2State { if let Some(frame) = frame_pdu { frame.set_tx(flow, tx.tx_id); } + // Validate: per RFC 9113 only SETTINGS, WINDOW_UPDATE, PING, and GOAWAY are allowed on stream 0 + if head.stream_id == 0 + && head.ftype != parser::HTTP2FrameType::Settings as u8 + && head.ftype != parser::HTTP2FrameType::WindowUpdate as u8 + && head.ftype != parser::HTTP2FrameType::Ping as u8 + && head.ftype != parser::HTTP2FrameType::GoAway as u8 + { + if head.ftype == parser::HTTP2FrameType::Data as u8 && head.stream_id == 0 { + tx.tx_data.set_event(HTTP2Event::DataStreamZero as u8); + } else { + tx.tx_data.set_event(HTTP2Event::InvalidFrameHeader as u8); + } + input = &rem[hlsafe..]; + continue; // skip this frame, continue parsing the next + } + if let Some(doh_req_buf) = tx.handle_frame(&head, &txdata, dir) { if let Ok(mut dtx) = dns_parse_request(&doh_req_buf, &DnsVariant::Dns) { dtx.id = 1; @@ -1432,9 +1448,7 @@ impl HTTP2State { } else { tx.tx_data.set_event(HTTP2Event::TooManyFrames as u8); } - if ftype == parser::HTTP2FrameType::Data as u8 && sid == 0 { - tx.tx_data.set_event(HTTP2Event::DataStreamZero as u8); - } else if ftype == parser::HTTP2FrameType::Data as u8 && sid > 0 { + if ftype == parser::HTTP2FrameType::Data as u8 && sid > 0 { tx.handle_data_frame(rem, hlsafe, dir, flow, padded, over); let (il, ol) = if dir == Direction::ToClient { ( From 0cd2e8d1b124f4848ed947f6a36665bd86b1c999 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Thu, 18 Jun 2026 07:21:38 +0200 Subject: [PATCH 38/69] http2: update push promise to account for stream id 0 filtering (cherry picked from commit bb41ef9caf0677ccfb1607340c97bdfa71bd106d) --- rust/src/http2/http2.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs index 5f9cfeae8aaf..410cb4eb6ecb 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -533,8 +533,6 @@ impl HTTP2Transaction { if stream_tx.progress_tc < HTTP2TxProgress::HTTP2ProgHeaders { stream_tx.progress_tc = HTTP2TxProgress::HTTP2ProgHeaders; } - } else { - panic!("global"); } } r = self.handle_headers(&hs.blocks, dir); From 57aec267a0174476e30e0e0571d6e5ec4e661ae9 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Thu, 18 Jun 2026 15:41:13 +0200 Subject: [PATCH 39/69] detect/parse: don't setup sub states if protocol doesn't support it (cherry picked from commit 956030452fb1be38f0f32767e91a21f35d3ee674) --- src/detect-parse.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/detect-parse.c b/src/detect-parse.c index d4116d217455..6ec9a6546fff 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -1417,6 +1417,9 @@ static int SigParseProtoHookApp( hook); return -1; } + } else { + SCLogError("sub states currently only supported for http2"); + return -1; } const uint8_t max_state = AppLayerParserGetSubStateCompletion( s->alproto, sub_state); // TODO allow different completion per direction? From 35369aa3441d309024e197ef0c96f5dc07da1edf Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Sat, 20 Jun 2026 08:45:08 +0200 Subject: [PATCH 40/69] app-layer: for sub state API treat DOH2 as HTTP/2 Otherwise we'd have to re-register the relevant callbacks. (cherry picked from commit 6a3f9bc0b055b09b8c4c249df899ff9f80b1e7ca) --- src/app-layer-parser.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/app-layer-parser.c b/src/app-layer-parser.c index 1b807d73622b..7a7a550c49cc 100644 --- a/src/app-layer-parser.c +++ b/src/app-layer-parser.c @@ -1240,6 +1240,9 @@ int AppLayerParserGetStateProgressCompletionStatus(AppProto alproto, int8_t AppLayerParserGetSubStateProgressId( const AppProto alproto, const uint8_t sub_state, const char *state, const uint8_t dir_flag) { + if (alproto == ALPROTO_DOH2) + return AppLayerParserGetSubStateProgressId(ALPROTO_HTTP2, sub_state, state, dir_flag); + BUG_ON(alp_ctx.ctxs[alproto][FLOW_PROTO_DEFAULT].max_sub_state == 0); BUG_ON(dir_flag != STREAM_TOSERVER && dir_flag != STREAM_TOCLIENT); @@ -1267,6 +1270,9 @@ int8_t AppLayerParserGetSubStateProgressId( const char *AppLayerParserGetSubStateProgressName(const AppProto alproto, const uint8_t sub_state, const uint8_t state, const uint8_t dir_flag) { + if (alproto == ALPROTO_DOH2) + return AppLayerParserGetSubStateProgressName(ALPROTO_HTTP2, sub_state, state, dir_flag); + BUG_ON(alp_ctx.ctxs[alproto][FLOW_PROTO_DEFAULT].max_sub_state == 0); BUG_ON(dir_flag != STREAM_TOSERVER && dir_flag != STREAM_TOCLIENT); @@ -1286,6 +1292,9 @@ const char *AppLayerParserGetSubStateProgressName(const AppProto alproto, const uint8_t AppLayerParserGetSubStateCompletion(const AppProto alproto, const uint8_t sub_state) { + if (alproto == ALPROTO_DOH2) + return AppLayerParserGetSubStateCompletion(ALPROTO_HTTP2, sub_state); + BUG_ON(alp_ctx.ctxs[alproto][FLOW_PROTO_DEFAULT].max_sub_state == 0); /* TODO hard coded for now */ @@ -1303,6 +1312,9 @@ uint8_t AppLayerParserGetSubStateCompletion(const AppProto alproto, const uint8_ const char *AppLayerParserGetSubStateName(const AppProto alproto, const uint8_t sub_state) { + if (alproto == ALPROTO_DOH2) + return AppLayerParserGetSubStateName(ALPROTO_HTTP2, sub_state); + BUG_ON(alp_ctx.ctxs[alproto][FLOW_PROTO_DEFAULT].max_sub_state == 0); /* TODO hard coded for now */ @@ -1320,6 +1332,8 @@ const char *AppLayerParserGetSubStateName(const AppProto alproto, const uint8_t uint8_t AppLayerParserGetMaxSubState(const AppProto alproto) { + if (alproto == ALPROTO_DOH2) + return AppLayerParserGetMaxSubState(ALPROTO_HTTP2); return alp_ctx.ctxs[alproto][FLOW_PROTO_DEFAULT].max_sub_state; } From 415664110e986e527a6219c9a35cb84dcaaf691c Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Sat, 20 Jun 2026 23:27:09 +0200 Subject: [PATCH 41/69] detect/parse: allow bigger protocol/hook specifications (cherry picked from commit eb87137471718ada1c2dc3e8c9745b19812603e3) --- src/detect-parse.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/detect-parse.c b/src/detect-parse.c index 6ec9a6546fff..97c4c58973c1 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -1383,8 +1383,8 @@ static SignatureHook SetAppHook(const AppProto alproto, uint8_t sub_state, uint8 static int SigParseProtoHookApp( Signature *s, const char *proto_hook, const char *p, const char *in_h) { - char hook[33]; - strlcpy(hook, in_h, 33); + char hook[64]; + strlcpy(hook, in_h, sizeof(hook)); const char *h = hook; const char *t = NULL; uint8_t sub_state = 0; @@ -1493,7 +1493,7 @@ static int SigParseProtoHookApp( } /* use in_h to include sub state */ - char generic_hook_name[64]; + char generic_hook_name[128]; snprintf(generic_hook_name, sizeof(generic_hook_name), "%s:%s:generic", p, in_h); int list = DetectBufferTypeGetByName(generic_hook_name); if (list < 0) { @@ -1525,11 +1525,11 @@ static int SigParseProtoHookApp( static int SigParseProto(Signature *s, const char *protostr) { SCEnter(); - if (strlen(protostr) > 32) + if (strlen(protostr) >= 64) return -1; - char proto[33]; - strlcpy(proto, protostr, 33); + char proto[64]; + strlcpy(proto, protostr, sizeof(proto)); const char *p = proto; const char *h = NULL; From 7f8fb20f3ce8d26b791768a9568ad39641489404 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Sun, 21 Jun 2026 22:25:21 +0200 Subject: [PATCH 42/69] detect/firewall: implement app policy as hash table With substate support the fixed table approach for policies was no longer a good fit, so convert to a hash table. The policies are stored per alproto, sub_state, progress and direction. (cherry picked from commit bffea309db779bcd2948293bc9ad1f3c840bb1e7) --- src/detect-engine-analyzer.c | 15 ++-- src/detect-engine.c | 1 + src/detect-parse.c | 150 ++++++++++++++++++++++------------- src/detect.c | 34 ++------ src/detect.h | 17 ++-- 5 files changed, 119 insertions(+), 98 deletions(-) diff --git a/src/detect-engine-analyzer.c b/src/detect-engine-analyzer.c index fe398e3bd41f..46dddce57411 100644 --- a/src/detect-engine-analyzer.c +++ b/src/detect-engine-analyzer.c @@ -2004,13 +2004,16 @@ static void AddPolicy(const DetectEngineCtx *de_ctx, RuleAnalyzer *ctx, const Ap const uint8_t state, const uint8_t direction) { char policy_string[64] = ""; - const struct DetectFirewallPolicy *p = NULL; const struct DetectFirewallPolicies *fw_policies = de_ctx->fw_policies; - if (direction == STREAM_TOSERVER) { - p = &fw_policies->app[a].ts[state]; - } else { - p = &fw_policies->app[a].tc[state]; - } + const struct DetectFirewallAppPolicy lookup = { + .alproto = a, .sub_state = 0, .progress = state, .direction = direction + }; + const struct DetectFirewallAppPolicy *ap = + HashTableLookup(fw_policies->app_policies, (void *)&lookup, 0); + if (ap == NULL) + return; + const struct DetectFirewallPolicy *p = &ap->policy; + const char *as = ActionScopeToString(p->action_scope); DEBUG_VALIDATE_BUG_ON(as == NULL); if (as == NULL) diff --git a/src/detect-engine.c b/src/detect-engine.c index 0c5ccbd85c70..29193c0b0bda 100644 --- a/src/detect-engine.c +++ b/src/detect-engine.c @@ -2895,6 +2895,7 @@ void DetectEngineCtxFree(DetectEngineCtx *de_ctx) } } HashTableFree(de_ctx->fw_policies->policy_signatures); + HashTableFree(de_ctx->fw_policies->app_policies); } SCFree(de_ctx->fw_policies); SCFree(de_ctx); diff --git a/src/detect-parse.c b/src/detect-parse.c index 97c4c58973c1..47e2b720821c 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -3874,6 +3874,33 @@ void DetectSetupParseRegexes(const char *parse_str, DetectParseRegex *detect_par } } +static uint32_t AppPolicyHashFunc(HashTable *ht, void *data, uint16_t datalen) +{ + const struct DetectFirewallAppPolicy *p = data; + /* use a prime-mix hash */ + uint32_t hash = p->alproto * 65537 + p->sub_state * 257 + p->progress * 5 + + (p->direction == STREAM_TOSERVER); + hash ^= (hash >> 10) ^ (hash >> 20); + return hash % ht->array_size; +} + +static char AppPolicyCompareFunc(void *data1, uint16_t datalen1, void *data2, uint16_t datalen2) +{ + const struct DetectFirewallAppPolicy *p1 = data1; + const struct DetectFirewallAppPolicy *p2 = data2; + + if (p1 == NULL || p2 == NULL) + return 0; + + return p1->direction == p2->direction && p1->alproto == p2->alproto && + p1->sub_state == p2->sub_state && p1->progress == p2->progress; +} + +static void AppPolicyHashFree(void *data) +{ + struct DetectFirewallAppPolicy *p = data; + SCFree(p); +} static uint32_t PolicySignatureHashFunc(HashTable *ht, void *data, uint16_t datalen) { const Signature *s = data; @@ -4054,7 +4081,7 @@ static int DoParsePolicy(const char *policy_name, struct DetectFirewallPolicy *p static int DoParseAppSubStatePolicy(const char *prefix, const AppProto app_proto, const uint8_t sub_state, const char *sub_state_name, const uint8_t state, const char *hookname, const uint8_t complete_state, const int direction, - struct DetectFirewallPolicies *fw_policies, struct DetectFirewallAppPolicy *app_fw_policies) + struct DetectFirewallPolicies *fw_policies) { char policy_name[256]; BUG_ON(sub_state_name == NULL); @@ -4077,18 +4104,33 @@ static int DoParseAppSubStatePolicy(const char *prefix, const AppProto app_proto FatalError("internal error: failed to assemble firewall policy config string"); } - struct DetectFirewallPolicy *pol; - if (direction == STREAM_TOSERVER) - pol = &fw_policies->http2_substates[sub_state - 1].ts[state]; - else - pol = &fw_policies->http2_substates[sub_state - 1].tc[state]; + struct DetectFirewallAppPolicy *app_pol = SCCalloc(1, sizeof(*app_pol)); + if (app_pol == NULL) + return -1; + + app_pol->alproto = app_proto; + app_pol->sub_state = sub_state; + app_pol->progress = state; + app_pol->direction = (uint8_t)direction; + /* init to drop:flow by default, will be overwritten by DoParsePolicy if there + * is a config for this hook. */ + app_pol->policy.action = ACTION_DROP; + app_pol->policy.action_scope = ACTION_SCOPE_FLOW; + + r = DoParsePolicy(policy_name, &app_pol->policy); + if (r < 0) { + SCFree(app_pol); + return -1; + } - r = DoParsePolicy(policy_name, pol); + if (HashTableAdd(fw_policies->app_policies, app_pol, 0) != 0) { + FatalError("internal error: insert policy into hash table"); + } /* for policies with an alert action, create a policy sig */ - if (r == 1 && pol->action & ACTION_ALERT) { + if (r == 1 && app_pol->policy.action & ACTION_ALERT) { SCLogDebug("adding policy signature"); return AddAppPolicySignature(fw_policies->policy_signatures, direction, app_proto, app_name, - state, hookname, pol); + state, hookname, &app_pol->policy); } SCLogDebug("r %d", r); return r; @@ -4096,7 +4138,7 @@ static int DoParseAppSubStatePolicy(const char *prefix, const AppProto app_proto static int DoParseAppPolicy(const char *prefix, const AppProto app_proto, const char *hookname, const uint8_t state, const uint8_t complete_state, const int direction, - struct DetectFirewallPolicies *fw_policies, struct DetectFirewallAppPolicy *app_fw_policies) + struct DetectFirewallPolicies *fw_policies) { char policy_name[256]; const char *in_name = hookname; @@ -4130,12 +4172,20 @@ static int DoParseAppPolicy(const char *prefix, const AppProto app_proto, const FatalError("internal error: failed to assemble firewall policy config string"); } - struct DetectFirewallPolicy *pol; - if (direction == STREAM_TOSERVER) - pol = &app_fw_policies[app_proto].ts[state]; - else - pol = &app_fw_policies[app_proto].tc[state]; - r = DoParsePolicy(policy_name, pol); + struct DetectFirewallAppPolicy *app_pol = SCCalloc(1, sizeof(*app_pol)); + if (app_pol == NULL) + return -1; + + app_pol->alproto = app_proto; + app_pol->sub_state = 0; + app_pol->progress = state; + app_pol->direction = (uint8_t)direction; + /* init to drop:flow by default, will be overwritten by DoParsePolicy if there + * is a config for this hook. */ + app_pol->policy.action = ACTION_DROP; + app_pol->policy.action_scope = ACTION_SCOPE_FLOW; + + r = DoParsePolicy(policy_name, &app_pol->policy); if (r == 0 && in_name != NULL) { if (state == 0) { if (direction == STREAM_TOSERVER) @@ -4155,32 +4205,46 @@ static int DoParseAppPolicy(const char *prefix, const AppProto app_proto, const FatalError("internal error: failed to assemble firewall policy config string"); } - r = DoParsePolicy(policy_name, pol); + r = DoParsePolicy(policy_name, &app_pol->policy); + } + if (r < 0) { + SCFree(app_pol); + return -1; + } + + if (HashTableAdd(fw_policies->app_policies, app_pol, 0) != 0) { + FatalError("internal error: insert policy into hash table"); } /* for policies with an alert action, create a policy sig */ - if (r == 1 && pol->action & ACTION_ALERT) { + if (r == 1 && app_pol->policy.action & ACTION_ALERT) { SCLogDebug("adding policy signature"); return AddAppPolicySignature(fw_policies->policy_signatures, direction, app_proto, app_name, - state, hookname, pol); + state, hookname, &app_pol->policy); } + return r; } /** \brief allocate and initialize to default values the policies table */ int DetectFirewallInitDefaultPolicies(DetectEngineCtx *de_ctx) { - struct DetectFirewallPolicies *fw_policies = SCCalloc( - 1, sizeof(*fw_policies) + g_alproto_max * sizeof(struct DetectFirewallAppPolicy)); + struct DetectFirewallPolicies *fw_policies = SCCalloc(1, sizeof(*fw_policies)); if (fw_policies == NULL) return -1; - struct DetectFirewallAppPolicy *app_fw_policies = fw_policies->app; - if (app_fw_policies == NULL) - goto error; fw_policies->policy_signatures = HashTableInit( 512, PolicySignatureHashFunc, PolicySignatureCompareFunc, PolicySignatureHashFree); - if (fw_policies->policy_signatures == NULL) - goto error; + if (fw_policies->policy_signatures == NULL) { + SCFree(fw_policies); + return -1; + } + fw_policies->app_policies = + HashTableInit(512, AppPolicyHashFunc, AppPolicyCompareFunc, AppPolicyHashFree); + if (fw_policies->app_policies == NULL) { + HashTableFree(fw_policies->policy_signatures); + SCFree(fw_policies); + return -1; + } fw_policies->pkt[DETECT_FIREWALL_POLICY_PACKET_FILTER].action = ACTION_DROP; fw_policies->pkt[DETECT_FIREWALL_POLICY_PACKET_FILTER].action_scope = ACTION_SCOPE_PACKET; @@ -4191,31 +4255,8 @@ int DetectFirewallInitDefaultPolicies(DetectEngineCtx *de_ctx) fw_policies->pkt[DETECT_FIREWALL_POLICY_PRE_STREAM].action = ACTION_ACCEPT; fw_policies->pkt[DETECT_FIREWALL_POLICY_PRE_STREAM].action_scope = ACTION_SCOPE_HOOK; - for (AppProto a = 0; a < g_alproto_max; a++) { - for (int i = 0; i < 48; i++) { - app_fw_policies[a].ts[i].action = ACTION_DROP; - app_fw_policies[a].ts[i].action_scope = ACTION_SCOPE_FLOW; - app_fw_policies[a].tc[i].action = ACTION_DROP; - app_fw_policies[a].tc[i].action_scope = ACTION_SCOPE_FLOW; - } - } - - /* TODO hard coded for HTTP/2 for now */ - for (int s = 0; s < 2; s++) { - for (int i = 0; i < 48; i++) { - fw_policies->http2_substates[s].ts[i].action = ACTION_DROP; - fw_policies->http2_substates[s].ts[i].action_scope = ACTION_SCOPE_FLOW; - fw_policies->http2_substates[s].tc[i].action = ACTION_DROP; - fw_policies->http2_substates[s].tc[i].action_scope = ACTION_SCOPE_FLOW; - } - } - de_ctx->fw_policies = fw_policies; return 0; - -error: - SCFree(fw_policies); - return -1; } int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *de_ctx) @@ -4230,9 +4271,6 @@ int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *de_ctx) struct DetectFirewallPolicies *fw_policies = de_ctx->fw_policies; if (fw_policies == NULL) return -1; - struct DetectFirewallAppPolicy *app_fw_policies = fw_policies->app; - if (app_fw_policies == NULL) - return -1; r = snprintf(policy_name, sizeof(policy_name), "%s.packet-filter", prefix); if (r < 0 || (size_t)r >= sizeof(policy_name)) { @@ -4298,7 +4336,7 @@ int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *de_ctx) SCLogDebug("protocol %s: sub state:%s state:%s", AppProtoToString(a), sub_state_name, state_name); if (DoParseAppSubStatePolicy(prefix, a, s, sub_state_name, state, state_name, - max_state, STREAM_TOSERVER, fw_policies, app_fw_policies) < 0) + max_state, STREAM_TOSERVER, fw_policies) < 0) return -1; } /* to_client */ @@ -4311,7 +4349,7 @@ int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *de_ctx) SCLogDebug("protocol %s: to_client: sub state:%s state:%s", AppProtoToString(a), sub_state_name, state_name); if (DoParseAppSubStatePolicy(prefix, a, s, sub_state_name, state, state_name, - max_state, STREAM_TOCLIENT, fw_policies, app_fw_policies) < 0) + max_state, STREAM_TOCLIENT, fw_policies) < 0) return -1; } } @@ -4323,7 +4361,7 @@ int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *de_ctx) const char *name = AppLayerParserGetStateNameById(IPPROTO_TCP, a, state, STREAM_TOSERVER); if (DoParseAppPolicy(prefix, a, name, state, complete_state_ts, STREAM_TOSERVER, - fw_policies, app_fw_policies) < 0) + fw_policies) < 0) return -1; } const uint8_t complete_state_tc = @@ -4333,7 +4371,7 @@ int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *de_ctx) const char *name = AppLayerParserGetStateNameById(IPPROTO_TCP, a, state, STREAM_TOCLIENT); if (DoParseAppPolicy(prefix, a, name, state, complete_state_tc, STREAM_TOCLIENT, - fw_policies, app_fw_policies) < 0) + fw_policies) < 0) return -1; } } diff --git a/src/detect.c b/src/detect.c index da3503ebd39d..d52ed5510186 100644 --- a/src/detect.c +++ b/src/detect.c @@ -1726,34 +1726,16 @@ static const struct DetectFirewallPolicy *DetectFirewallApplyDefaultAppPolicy( const DetectTransaction *tx, Packet *p, const AppProto alproto, const uint8_t direction, const uint8_t progress) { - const struct DetectFirewallPolicy *policy; SCLogDebug("packet %" PRIu64 ": tx type %u", p->pcap_cnt, tx->tx_type); - if (tx->tx_type != 0) { - // TODO hard coded to HTTP/2 for now - BUG_ON(alproto != ALPROTO_HTTP2); - if (direction & STREAM_TOSERVER) { - policy = &policies->http2_substates[tx->tx_type - 1].ts[progress]; - SCLogDebug("packet %" PRIu64 - ", sub_state:%u, hook:%u, toserver, policy: action %02x scope %u", - p->pcap_cnt, tx->tx_type, progress, policy->action, policy->action_scope); - } else { - policy = &policies->http2_substates[tx->tx_type - 1].tc[progress]; - SCLogDebug("packet %" PRIu64 - ", sub_state:%u, hook:%u, toclient, policy: action %02x scope %u", - p->pcap_cnt, tx->tx_type, progress, policy->action, policy->action_scope); - } - } else { - if (direction & STREAM_TOSERVER) { - policy = &policies->app[alproto].ts[progress]; - SCLogDebug("packet %" PRIu64 ", hook:%u, toserver, policy: action %02x scope %u", - p->pcap_cnt, progress, policy->action, policy->action_scope); - } else { - policy = &policies->app[alproto].tc[progress]; - SCLogDebug("packet %" PRIu64 ", hook:%u, toclient, policy: action %02x scope %u", - p->pcap_cnt, progress, policy->action, policy->action_scope); - } - } + const struct DetectFirewallAppPolicy lookup = { + .alproto = alproto, .sub_state = tx->tx_type, .progress = progress, .direction = direction + }; + const struct DetectFirewallAppPolicy *ap = + HashTableLookup(policies->app_policies, (void *)&lookup, 0); + /* table should be fully populated, so this should not be able to fail */ + DEBUG_VALIDATE_BUG_ON(ap == NULL); + const struct DetectFirewallPolicy *policy = &ap->policy; if (policy->action & ACTION_DROP) { SCLogDebug("dropping packet PKT_DROP_REASON_FW_DEFAULT_APP_POLICY"); diff --git a/src/detect.h b/src/detect.h index a7e238eec160..7ca8a112c6a4 100644 --- a/src/detect.h +++ b/src/detect.h @@ -926,12 +926,12 @@ struct DetectFirewallPolicy { uint8_t action_scope; /**< same as Signature::action_scope. Scope argument for the action. */ }; -/** Application layer firewall policies per hook. */ struct DetectFirewallAppPolicy { - /** policy per hook/progress value (max 48) for toserver direction. */ - struct DetectFirewallPolicy ts[48]; - /** policy per hook/progress value (max 48) for toclient direction. */ - struct DetectFirewallPolicy tc[48]; + AppProto alproto; + uint8_t sub_state; + uint8_t progress; + uint8_t direction; + struct DetectFirewallPolicy policy; }; struct DetectFirewallPolicies { @@ -942,11 +942,8 @@ struct DetectFirewallPolicies { /* hash table with a Signature object per default policy that has `alert` enabled. */ HashTable *policy_signatures; - /* hard coded for now: http2 substates. Index at substate - 1. */ - struct DetectFirewallAppPolicy http2_substates[2]; - - /** app layer policies, one per alproto */ - struct DetectFirewallAppPolicy app[]; + /* hash table with policies, hashed by alproto, sub_state, progress and direction */ + HashTable *app_policies; }; /* Flow states: From ab7767bff3c3f136f194cbcfbb98dc166adeab63 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Mon, 22 Jun 2026 14:53:23 +0200 Subject: [PATCH 43/69] detect/firewall: add default alert signature to policy object Now that there is a policy hash, make the alert signature object a member of that to avoid another hash table lookup. (cherry picked from commit 3b6b381f9069f0315b29770f6504d7485e1aa443) --- src/detect-engine.c | 1 - src/detect-parse.c | 82 ++++++++------------------------------------- src/detect-parse.h | 2 -- src/detect.c | 12 +++---- src/detect.h | 6 ++-- 5 files changed, 21 insertions(+), 82 deletions(-) diff --git a/src/detect-engine.c b/src/detect-engine.c index 29193c0b0bda..8c558999a556 100644 --- a/src/detect-engine.c +++ b/src/detect-engine.c @@ -2894,7 +2894,6 @@ void DetectEngineCtxFree(DetectEngineCtx *de_ctx) SCFree(de_ctx->fw_policies->pkt_policy_signatures[i]); } } - HashTableFree(de_ctx->fw_policies->policy_signatures); HashTableFree(de_ctx->fw_policies->app_policies); } SCFree(de_ctx->fw_policies); diff --git a/src/detect-parse.c b/src/detect-parse.c index 47e2b720821c..fc393038c591 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -3899,36 +3899,13 @@ static char AppPolicyCompareFunc(void *data1, uint16_t datalen1, void *data2, ui static void AppPolicyHashFree(void *data) { struct DetectFirewallAppPolicy *p = data; + Signature *s = p->alert_signature; + if (s != NULL) { + SCFree(s->msg); + SCFree(s); + } SCFree(p); } -static uint32_t PolicySignatureHashFunc(HashTable *ht, void *data, uint16_t datalen) -{ - const Signature *s = data; - const int dir = 1 + (s->flags & SIG_FLAG_TOSERVER) != 0; // 2 for ts, 1 for tc - uint32_t hash = s->alproto * s->app_progress_hook * dir; - hash = hash % ht->array_size; - return hash; -} - -static char PolicySignatureCompareFunc( - void *data1, uint16_t datalen1, void *data2, uint16_t datalen2) -{ - const Signature *s1 = data1; - const Signature *s2 = data2; - - if (s1 == NULL || s2 == NULL) - return 0; - - return s1->flags == s2->flags && s1->alproto == s2->alproto && - s1->app_progress_hook == s2->app_progress_hook; -} - -static void PolicySignatureHashFree(void *data) -{ - Signature *s = data; - SCFree(s->msg); - SCFree(s); -} const char *ActionScopeToString(enum ActionScope s) { @@ -4019,9 +3996,7 @@ static int AddPktPolicySignature(struct DetectFirewallPolicies *fw_policies, return 0; } -static int AddAppPolicySignature(HashTable *ht, const int direction, const AppProto alproto, - const char *app_name, const uint8_t hook, const char *hookname, - struct DetectFirewallPolicy *pol) +static int AddAppPolicySignature(struct DetectFirewallAppPolicy *pol) { Signature *s = SCCalloc(1, sizeof(*s)); // SigAlloc does way more than we need if (s == NULL) @@ -4033,11 +4008,11 @@ static int AddAppPolicySignature(HashTable *ht, const int direction, const AppPr SCFree(s); return -1; } - s->app_progress_hook = hook; - s->action = pol->action; - s->action_scope = pol->action_scope; - s->alproto = alproto; - s->flags = (direction == STREAM_TOSERVER) ? SIG_FLAG_TOSERVER : SIG_FLAG_TOCLIENT; + s->app_progress_hook = pol->progress; + s->action = pol->policy.action; + s->action_scope = pol->policy.action_scope; + s->alproto = pol->alproto; + s->flags = (pol->direction == STREAM_TOSERVER) ? SIG_FLAG_TOSERVER : SIG_FLAG_TOCLIENT; s->flags |= SIG_FLAG_FIREWALL; s->type = SIG_TYPE_APP_TX; s->detect_table = DETECT_TABLE_APP_FILTER; @@ -4046,11 +4021,7 @@ static int AddAppPolicySignature(HashTable *ht, const int direction, const AppPr s->gid = 1; s->prio = 3; - if (HashTableAdd(ht, s, 0) != 0) { - SCFree(s->msg); - SCFree(s); - return -1; - } + pol->alert_signature = s; SCLogDebug("added to hash"); return 0; } @@ -4129,8 +4100,7 @@ static int DoParseAppSubStatePolicy(const char *prefix, const AppProto app_proto /* for policies with an alert action, create a policy sig */ if (r == 1 && app_pol->policy.action & ACTION_ALERT) { SCLogDebug("adding policy signature"); - return AddAppPolicySignature(fw_policies->policy_signatures, direction, app_proto, app_name, - state, hookname, &app_pol->policy); + return AddAppPolicySignature(app_pol); } SCLogDebug("r %d", r); return r; @@ -4219,8 +4189,7 @@ static int DoParseAppPolicy(const char *prefix, const AppProto app_proto, const /* for policies with an alert action, create a policy sig */ if (r == 1 && app_pol->policy.action & ACTION_ALERT) { SCLogDebug("adding policy signature"); - return AddAppPolicySignature(fw_policies->policy_signatures, direction, app_proto, app_name, - state, hookname, &app_pol->policy); + return AddAppPolicySignature(app_pol); } return r; @@ -4232,16 +4201,9 @@ int DetectFirewallInitDefaultPolicies(DetectEngineCtx *de_ctx) struct DetectFirewallPolicies *fw_policies = SCCalloc(1, sizeof(*fw_policies)); if (fw_policies == NULL) return -1; - fw_policies->policy_signatures = HashTableInit( - 512, PolicySignatureHashFunc, PolicySignatureCompareFunc, PolicySignatureHashFree); - if (fw_policies->policy_signatures == NULL) { - SCFree(fw_policies); - return -1; - } fw_policies->app_policies = HashTableInit(512, AppPolicyHashFunc, AppPolicyCompareFunc, AppPolicyHashFree); if (fw_policies->app_policies == NULL) { - HashTableFree(fw_policies->policy_signatures); SCFree(fw_policies); return -1; } @@ -4379,22 +4341,6 @@ int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *de_ctx) return 0; } -Signature *DetectFirewallGetPolicySignature(struct DetectFirewallPolicies *fw_policies, - const AppProto alproto, const int direction, const uint8_t hook) -{ - if (fw_policies != NULL && fw_policies->policy_signatures != NULL) { - Signature lookup; - lookup.alproto = alproto; - lookup.flags = SIG_FLAG_FIREWALL | - (direction == STREAM_TOSERVER ? SIG_FLAG_TOSERVER : SIG_FLAG_TOCLIENT); - lookup.app_progress_hook = hook; - - Signature *s = HashTableLookup(fw_policies->policy_signatures, &lookup, 0); - return s; - } - return NULL; -} - /* * TESTS */ diff --git a/src/detect-parse.h b/src/detect-parse.h index 53aff021d2d1..47cc52a54d52 100644 --- a/src/detect-parse.h +++ b/src/detect-parse.h @@ -122,7 +122,5 @@ struct DetectFirewallPolicy; void DetectFirewallPolicyToString(const struct DetectFirewallPolicy *p, char *out, size_t out_size); int DetectFirewallInitDefaultPolicies(DetectEngineCtx *); int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *); -Signature *DetectFirewallGetPolicySignature(struct DetectFirewallPolicies *fw_policies, - const AppProto alproto, const int direction, const uint8_t hook); #endif /* SURICATA_DETECT_PARSE_H */ diff --git a/src/detect.c b/src/detect.c index d52ed5510186..b519ef836bfe 100644 --- a/src/detect.c +++ b/src/detect.c @@ -1700,12 +1700,10 @@ struct DetectFirewallAppTxState { }; static inline void DetectRunAppendDefaultAppPolicyAlert(DetectEngineThreadCtx *det_ctx, Packet *p, - const bool apply_to_packet, const int direction, const uint64_t tx_id, - const AppProto alproto, const uint8_t hook) + const bool apply_to_packet, const uint64_t tx_id, const struct DetectFirewallAppPolicy *ap) { if (EngineModeIsFirewall()) { - Signature *s = DetectFirewallGetPolicySignature( - det_ctx->de_ctx->fw_policies, alproto, direction, hook); + const Signature *s = ap->alert_signature; BUG_ON(s == NULL); uint8_t alert_flags = apply_to_packet ? PACKET_ALERT_FLAG_APPLY_ACTION_TO_PACKET : 0; AlertQueueAppendAppTx(det_ctx, s, p, tx_id, alert_flags); @@ -1746,8 +1744,7 @@ static const struct DetectFirewallPolicy *DetectFirewallApplyDefaultAppPolicy( p->flow->aux_flags |= FLOW_AUX_ACTION_BY_FIREWALL; } if (policy->action & ACTION_ALERT) { - DetectRunAppendDefaultAppPolicyAlert( - det_ctx, p, true, direction, tx->tx_id, alproto, progress); + DetectRunAppendDefaultAppPolicyAlert(det_ctx, p, true, tx->tx_id, ap); } } else if (policy->action & ACTION_ACCEPT) { /* should the accept be applied to the packet? @@ -1780,8 +1777,7 @@ static const struct DetectFirewallPolicy *DetectFirewallApplyDefaultAppPolicy( if (policy->action & ACTION_ALERT) { SCLogDebug("policy alert, do the append"); - DetectRunAppendDefaultAppPolicyAlert( - det_ctx, p, apply_to_packet, direction, tx->tx_id, alproto, progress); + DetectRunAppendDefaultAppPolicyAlert(det_ctx, p, apply_to_packet, tx->tx_id, ap); } else if (apply_to_packet) { SCLogDebug("default accept: last_tx"); DetectRunAppendDefaultAccept(det_ctx, p); diff --git a/src/detect.h b/src/detect.h index 7ca8a112c6a4..3225f49fd9c0 100644 --- a/src/detect.h +++ b/src/detect.h @@ -932,6 +932,9 @@ struct DetectFirewallAppPolicy { uint8_t progress; uint8_t direction; struct DetectFirewallPolicy policy; + /* signature that will be logged if the policy includes "alert". Will + * be set to NULL if alert is not part of the policy. */ + Signature *alert_signature; }; struct DetectFirewallPolicies { @@ -939,9 +942,6 @@ struct DetectFirewallPolicies { struct DetectFirewallPolicy pkt[DETECT_FIREWALL_POLICY_SIZE]; Signature *pkt_policy_signatures[DETECT_FIREWALL_POLICY_SIZE]; - /* hash table with a Signature object per default policy that has `alert` enabled. */ - HashTable *policy_signatures; - /* hash table with policies, hashed by alproto, sub_state, progress and direction */ HashTable *app_policies; }; From 164754d6fbaf9e8ba3476dfe2e08da937f14ba05 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Thu, 25 Jun 2026 13:51:47 +0200 Subject: [PATCH 44/69] detect/mpm: don't register engines for disabled protocols (cherry picked from commit af1c72c37ed6033b603cf9521fa1fa9783ba62d1) --- src/detect-engine-mpm.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/detect-engine-mpm.c b/src/detect-engine-mpm.c index b70a292a031c..c969f1cace0d 100644 --- a/src/detect-engine-mpm.c +++ b/src/detect-engine-mpm.c @@ -28,6 +28,7 @@ #include "suricata-common.h" #include "app-layer-protos.h" +#include "app-layer-parser.h" #include "decode.h" #include "detect.h" @@ -95,6 +96,12 @@ static void RegisterInternal(const char *name, int direction, int priority, SCLogDebug("registering %s/%d/%d/%p/%p/%u/%d", name, direction, priority, PrefilterRegister, GetData, alproto, tx_min_progress); + if (!AppLayerParserIsEnabled(alproto)) { + SCLogDebug("%s is disabled", AppProtoToString(alproto)); + return; + } + SCLogDebug("%s is enabled", AppProtoToString(alproto)); + BUG_ON(tx_min_progress >= 48); // must register GetData with PrefilterGenericMpmRegister From 22f3d1e1432a4b452a3a5fe7695cff4112de0753 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Wed, 24 Jun 2026 20:50:46 +0200 Subject: [PATCH 45/69] detect: add debug validation checks Check that sub state handling is correct. (cherry picked from commit 2e1003a0268e4250ffe0cba550d08ed973ed13ed) --- src/detect-engine-helper.c | 9 +++++++++ src/detect-engine-mpm.c | 2 ++ src/detect-engine-prefilter.c | 3 +++ src/detect-engine.c | 7 +++++++ 4 files changed, 21 insertions(+) diff --git a/src/detect-engine-helper.c b/src/detect-engine-helper.c index ae83dc067f4f..c5a1f1fa1abf 100644 --- a/src/detect-engine-helper.c +++ b/src/detect-engine-helper.c @@ -30,6 +30,8 @@ #include "detect-parse.h" #include "detect-engine-content-inspection.h" #include "rust.h" +#include "app-layer-parser.h" +#include "util-validate.h" int SCDetectHelperBufferRegister(const char *name, AppProto alproto, uint8_t direction) { @@ -47,6 +49,7 @@ int SCDetectHelperBufferRegister(const char *name, AppProto alproto, uint8_t dir int SCDetectHelperBufferProgressRegister( const char *name, AppProto alproto, uint8_t direction, int progress) { + DEBUG_VALIDATE_BUG_ON(AppLayerParserSupportsSubStates(alproto)); if (direction & STREAM_TOSERVER) { DetectAppLayerInspectEngineRegister( name, alproto, SIG_FLAG_TOSERVER, progress, DetectEngineInspectGenericList, NULL); @@ -61,6 +64,7 @@ int SCDetectHelperBufferProgressRegister( int SCDetectHelperBufferProgressRegisterSubState( const char *name, AppProto alproto, uint8_t direction, uint8_t sub_state, uint8_t progress) { + DEBUG_VALIDATE_BUG_ON(AppLayerParserSupportsSubStates(alproto) && sub_state == 0); if (direction & STREAM_TOSERVER) { DetectAppLayerInspectEngineRegisterSubState(name, alproto, SIG_FLAG_TOSERVER, sub_state, (uint8_t)progress, DetectEngineInspectGenericList, NULL); @@ -75,6 +79,7 @@ int SCDetectHelperBufferProgressRegisterSubState( int SCDetectHelperBufferMpmRegister(const char *name, const char *desc, AppProto alproto, uint8_t direction, InspectionSingleBufferGetDataPtr GetData) { + DEBUG_VALIDATE_BUG_ON(AppLayerParserSupportsSubStates(alproto)); if (direction & STREAM_TOSERVER) { DetectAppLayerInspectEngineRegisterSingle( name, alproto, SIG_FLAG_TOSERVER, 0, DetectEngineInspectBufferSingle, GetData); @@ -94,6 +99,7 @@ int SCDetectHelperBufferMpmRegister(const char *name, const char *desc, AppProto int SCDetectHelperBufferProgressMpmRegister(const char *name, const char *desc, AppProto alproto, uint8_t direction, InspectionSingleBufferGetDataPtr GetData, int progress) { + DEBUG_VALIDATE_BUG_ON(AppLayerParserSupportsSubStates(alproto)); if (direction & STREAM_TOSERVER) { DetectAppLayerInspectEngineRegisterSingle(name, alproto, SIG_FLAG_TOSERVER, progress, DetectEngineInspectBufferSingle, GetData); @@ -114,6 +120,7 @@ int SCDetectHelperMultiBufferProgressMpmRegister(const char *name, const char *d AppProto alproto, uint8_t direction, InspectionMultiBufferGetDataPtr GetData, uint8_t progress) { + DEBUG_VALIDATE_BUG_ON(AppLayerParserSupportsSubStates(alproto)); if (direction & STREAM_TOSERVER) { DetectAppLayerMultiRegister(name, alproto, SIG_FLAG_TOSERVER, progress, GetData, 2); } @@ -129,6 +136,7 @@ int SCDetectHelperMultiBufferProgressMpmRegisterSubState(const char *name, const AppProto alproto, uint8_t direction, InspectionMultiBufferGetDataPtr GetData, uint8_t sub_state, uint8_t progress) { + DEBUG_VALIDATE_BUG_ON(AppLayerParserSupportsSubStates(alproto) && sub_state == 0); if (direction & STREAM_TOSERVER) { DetectAppLayerMultiRegisterSubState( name, alproto, SIG_FLAG_TOSERVER, sub_state, progress, GetData, 2); @@ -145,6 +153,7 @@ int SCDetectHelperMultiBufferProgressMpmRegisterSubState(const char *name, const int SCDetectHelperMultiBufferMpmRegister(const char *name, const char *desc, AppProto alproto, uint8_t direction, InspectionMultiBufferGetDataPtr GetData) { + DEBUG_VALIDATE_BUG_ON(AppLayerParserSupportsSubStates(alproto)); return SCDetectHelperMultiBufferProgressMpmRegister(name, desc, alproto, direction, GetData, 0); } diff --git a/src/detect-engine-mpm.c b/src/detect-engine-mpm.c index c969f1cace0d..53ac69fad4ab 100644 --- a/src/detect-engine-mpm.c +++ b/src/detect-engine-mpm.c @@ -101,6 +101,8 @@ static void RegisterInternal(const char *name, int direction, int priority, return; } SCLogDebug("%s is enabled", AppProtoToString(alproto)); + DEBUG_VALIDATE_BUG_ON(AppLayerParserSupportsSubStates(alproto) && sub_state == 0); + DEBUG_VALIDATE_BUG_ON(!AppLayerParserSupportsSubStates(alproto) && sub_state != 0); BUG_ON(tx_min_progress >= 48); diff --git a/src/detect-engine-prefilter.c b/src/detect-engine-prefilter.c index 9c330f36359d..6b9ca7476364 100644 --- a/src/detect-engine-prefilter.c +++ b/src/detect-engine-prefilter.c @@ -127,6 +127,7 @@ void DetectRunPrefilterTx(DetectEngineThreadCtx *det_ctx, } if (engine->ctx.app.tx_min_progress != -1) { + DEBUG_VALIDATE_BUG_ON(engine->alproto == ALPROTO_UNKNOWN); #ifdef DEBUG const char *pname = AppLayerParserGetStateNameById(ipproto, engine->alproto, engine->ctx.app.tx_min_progress, @@ -178,6 +179,7 @@ void DetectRunPrefilterTx(DetectEngineThreadCtx *det_ctx, engine->is_last_for_progress, tx->detect_progress); } } else { + DEBUG_VALIDATE_BUG_ON(engine->alproto != ALPROTO_UNKNOWN); PREFILTER_PROFILING_START(det_ctx); engine->cb.PrefilterTx(det_ctx, engine->pectx, p, p->flow, tx_ptr, tx->tx_id, tx->tx_data_ptr, flow_flags); @@ -367,6 +369,7 @@ int PrefilterAppendTxEngineSubState(DetectEngineCtx *de_ctx, SigGroupHead *sgh, PrefilterTxFn PrefilterTxFunc, AppProto alproto, uint8_t sub_state, const int8_t tx_min_progress, void *pectx, void (*FreeFunc)(void *pectx), const char *name) { + BUG_ON(AppLayerParserSupportsSubStates(alproto) && sub_state == 0); if (sgh == NULL || PrefilterTxFunc == NULL || pectx == NULL) return -1; diff --git a/src/detect-engine.c b/src/detect-engine.c index 8c558999a556..f5f75fa9ae7e 100644 --- a/src/detect-engine.c +++ b/src/detect-engine.c @@ -199,6 +199,11 @@ static void AppLayerInspectEngineRegisterInternal(const char *name, AppProto alp InspectionBufferGetDataPtr GetData, InspectionSingleBufferGetDataPtr GetDataSingle, InspectionMultiBufferGetDataPtr GetMultiData) { + /* ignore special case unknown */ + if (alproto != ALPROTO_UNKNOWN && AppLayerParserIsEnabled(alproto)) { + DEBUG_VALIDATE_BUG_ON(AppLayerParserSupportsSubStates(alproto) && sub_state == 0); + DEBUG_VALIDATE_BUG_ON(!AppLayerParserSupportsSubStates(alproto) && sub_state != 0); + } BUG_ON(progress >= 48); DetectBufferTypeRegister(name); @@ -2238,6 +2243,7 @@ uint8_t DetectEngineInspectBufferGeneric(DetectEngineCtx *de_ctx, DetectEngineTh void DetectAppLayerMultiRegisterSubState(const char *name, AppProto alproto, uint32_t dir, uint8_t sub_state, uint8_t progress, InspectionMultiBufferGetDataPtr GetData, int priority) { + BUG_ON(AppLayerParserSupportsSubStates(alproto) && sub_state == 0); AppLayerInspectEngineRegisterInternal(name, alproto, dir, sub_state, progress, DetectEngineInspectMultiBufferGeneric, NULL, NULL, GetData); DetectAppLayerMpmMultiRegisterSubState(name, dir, priority, PrefilterMultiGenericMpmRegister, @@ -2249,6 +2255,7 @@ void DetectAppLayerMultiRegisterSubState(const char *name, AppProto alproto, uin void DetectAppLayerMultiRegister(const char *name, AppProto alproto, uint32_t dir, uint8_t progress, InspectionMultiBufferGetDataPtr GetData, int priority) { + BUG_ON(AppLayerParserSupportsSubStates(alproto)); AppLayerInspectEngineRegisterInternal(name, alproto, dir, 0, (uint8_t)progress, DetectEngineInspectMultiBufferGeneric, NULL, NULL, GetData); DetectAppLayerMpmMultiRegister( From 957bcb9870a526f2ac3686685a6af3360f5836fd Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Thu, 25 Jun 2026 17:49:02 +0200 Subject: [PATCH 46/69] app-layer: use macro for progress ceiling (cherry picked from commit 17a3e0d44fde3d03ec250566ef46dec176b0a65b) --- src/app-layer-parser.c | 2 +- src/app-layer-parser.h | 3 +++ src/detect-engine-mpm.c | 2 +- src/detect-engine.c | 2 +- src/detect-parse.c | 4 ++-- src/detect.c | 4 ++-- 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/app-layer-parser.c b/src/app-layer-parser.c index 7a7a550c49cc..4a2aee67c293 100644 --- a/src/app-layer-parser.c +++ b/src/app-layer-parser.c @@ -1259,7 +1259,7 @@ int8_t AppLayerParserGetSubStateProgressId( return -1; } SCLogDebug("state:%s v:%u", state, v); - BUG_ON(v > 48); + BUG_ON(v >= APP_LAYER_MAX_PROGRESS); return (int8_t)v; } } diff --git a/src/app-layer-parser.h b/src/app-layer-parser.h index 7aa79b1cb567..696318321158 100644 --- a/src/app-layer-parser.h +++ b/src/app-layer-parser.h @@ -90,6 +90,9 @@ typedef struct AppLayerTxConfig AppLayerTxConfig; int AppLayerParserProtoIsRegistered(uint8_t ipproto, AppProto alproto); +/** progress values need to stay under this. */ +#define APP_LAYER_MAX_PROGRESS 48 + /***** transaction handling *****/ int AppLayerParserSetup(void); diff --git a/src/detect-engine-mpm.c b/src/detect-engine-mpm.c index 53ac69fad4ab..8339c75c4ebb 100644 --- a/src/detect-engine-mpm.c +++ b/src/detect-engine-mpm.c @@ -104,7 +104,7 @@ static void RegisterInternal(const char *name, int direction, int priority, DEBUG_VALIDATE_BUG_ON(AppLayerParserSupportsSubStates(alproto) && sub_state == 0); DEBUG_VALIDATE_BUG_ON(!AppLayerParserSupportsSubStates(alproto) && sub_state != 0); - BUG_ON(tx_min_progress >= 48); + BUG_ON(tx_min_progress >= APP_LAYER_MAX_PROGRESS); // must register GetData with PrefilterGenericMpmRegister BUG_ON(PrefilterRegister == PrefilterGenericMpmRegister && GetData == NULL); diff --git a/src/detect-engine.c b/src/detect-engine.c index f5f75fa9ae7e..ecff778cf637 100644 --- a/src/detect-engine.c +++ b/src/detect-engine.c @@ -204,7 +204,7 @@ static void AppLayerInspectEngineRegisterInternal(const char *name, AppProto alp DEBUG_VALIDATE_BUG_ON(AppLayerParserSupportsSubStates(alproto) && sub_state == 0); DEBUG_VALIDATE_BUG_ON(!AppLayerParserSupportsSubStates(alproto) && sub_state != 0); } - BUG_ON(progress >= 48); + BUG_ON(progress >= APP_LAYER_MAX_PROGRESS); DetectBufferTypeRegister(name); const int sm_list = DetectBufferTypeGetByName(name); diff --git a/src/detect-parse.c b/src/detect-parse.c index fc393038c591..87be3465f493 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -1475,7 +1475,7 @@ static int SigParseProtoHookApp( const int progress_ts = AppLayerParserGetStateIdByName( IPPROTO_TCP /* TODO */, s->alproto, h, STREAM_TOSERVER); if (progress_ts >= 0) { - if (progress_ts >= 48) { + if (progress_ts >= APP_LAYER_MAX_PROGRESS) { return -1; } s->flags |= SIG_FLAG_TOSERVER; @@ -1483,7 +1483,7 @@ static int SigParseProtoHookApp( } else { const int progress_tc = AppLayerParserGetStateIdByName( IPPROTO_TCP /* TODO */, s->alproto, h, STREAM_TOCLIENT); - if (progress_tc < 0 || progress_tc >= 48) { + if (progress_tc < 0 || progress_tc >= APP_LAYER_MAX_PROGRESS) { return -1; } s->flags |= SIG_FLAG_TOCLIENT; diff --git a/src/detect.c b/src/detect.c index b519ef836bfe..98d442dcfc81 100644 --- a/src/detect.c +++ b/src/detect.c @@ -1545,12 +1545,12 @@ static int DetectRunTxInspectRule(ThreadVars *tv, DetectEngineCtx *de_ctx, static DetectTransaction GetDetectTx(const uint8_t ipproto, const AppProto alproto, const uint64_t tx_id, void *tx_ptr, const int tx_end_state, const uint8_t flow_flags) { - DEBUG_VALIDATE_BUG_ON(tx_end_state >= 48); + DEBUG_VALIDATE_BUG_ON(tx_end_state >= APP_LAYER_MAX_PROGRESS); AppLayerTxData *txd = AppLayerParserGetTxData(ipproto, alproto, tx_ptr); const uint8_t tx_progress = (uint8_t)AppLayerParserGetStateProgress(ipproto, alproto, tx_ptr, flow_flags); - DEBUG_VALIDATE_BUG_ON(tx_progress >= 48); + DEBUG_VALIDATE_BUG_ON(tx_progress >= APP_LAYER_MAX_PROGRESS); const uint8_t e_tx_end_state = txd->tx_type == 0 ? (uint8_t)tx_end_state : (flow_flags & STREAM_TOSERVER) ? txd->tx_type_eop_ts From feb9d86b59719cef234236f7a7717f42bc890485 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Fri, 26 Jun 2026 12:21:02 +0200 Subject: [PATCH 47/69] detect/alert: store sub-state in PacketAlert In preparation of being able to log it. (cherry picked from commit 7e60c3205ea331eeb0fbf289dd49a1d5411a92fe) --- src/decode.h | 1 + src/detect-engine-alert.c | 21 +++++++++++---------- src/detect-engine-alert.h | 4 ++-- src/detect.c | 33 +++++++++++++++++++++------------ 4 files changed, 35 insertions(+), 24 deletions(-) diff --git a/src/decode.h b/src/decode.h index 2ae81c23a968..96e72c906aa8 100644 --- a/src/decode.h +++ b/src/decode.h @@ -249,6 +249,7 @@ typedef struct PacketAlert_ { SigIntId iid; /* Internal ID, used for sorting */ uint8_t action; /* Rule or threshold action to be applied to packet */ uint8_t flags; + uint8_t sub_state; /**< tx sub state. 0 if not used. */ const struct Signature_ *s; uint64_t tx_id; /* Used for sorting */ int64_t frame_id; diff --git a/src/detect-engine-alert.c b/src/detect-engine-alert.c index 29fb424eca97..c1593d261197 100644 --- a/src/detect-engine-alert.c +++ b/src/detect-engine-alert.c @@ -372,8 +372,8 @@ static inline int PacketAlertSetContext( /** \internal */ -static inline PacketAlert PacketAlertSet( - DetectEngineThreadCtx *det_ctx, const Signature *s, uint64_t tx_id, uint8_t alert_flags) +static inline PacketAlert PacketAlertSet(DetectEngineThreadCtx *det_ctx, const Signature *s, + uint64_t tx_id, const uint8_t sub_state, uint8_t alert_flags) { PacketAlert pa; pa.iid = s->iid; @@ -382,6 +382,7 @@ static inline PacketAlert PacketAlertSet( pa.flags = alert_flags; /* Set tx_id if the frame has it */ pa.tx_id = tx_id; + pa.sub_state = sub_state; pa.frame_id = (alert_flags & PACKET_ALERT_FLAG_FRAME) ? det_ctx->frame_id : 0; PacketAlertSetContext(det_ctx, &pa, s); return pa; @@ -391,12 +392,12 @@ static inline PacketAlert PacketAlertSet( * \brief Append signature to local packet alert queue for later preprocessing */ static void AlertQueueAppend(DetectEngineThreadCtx *det_ctx, const Signature *s, Packet *p, - uint64_t tx_id, uint8_t alert_flags) + const uint64_t tx_id, const uint8_t sub_state, uint8_t alert_flags) { /* first time we see a drop action signature, set that in the packet */ /* we do that even before inserting into the queue, so we save it even if appending fails */ if (p->alerts.drop.action == 0 && s->action & ACTION_DROP) { - p->alerts.drop = PacketAlertSet(det_ctx, s, tx_id, alert_flags); + p->alerts.drop = PacketAlertSet(det_ctx, s, tx_id, sub_state, alert_flags); SCLogDebug("sid %u: set PacketAlert drop action. s->iid %" PRIu32 "", s->id, s->iid); } @@ -409,7 +410,7 @@ static void AlertQueueAppend(DetectEngineThreadCtx *det_ctx, const Signature *s, return; } } - det_ctx->alert_queue[pos] = PacketAlertSet(det_ctx, s, tx_id, alert_flags); + det_ctx->alert_queue[pos] = PacketAlertSet(det_ctx, s, tx_id, sub_state, alert_flags); SCLogDebug("packet %" PRIu64 ": appending sid %" PRIu32 ", s->iid %" PRIu32 " to alert queue", p->pcap_cnt, s->id, s->iid); @@ -420,10 +421,10 @@ static void AlertQueueAppend(DetectEngineThreadCtx *det_ctx, const Signature *s, * \brief Append signature to local packet alert queue for later preprocessing */ void AlertQueueAppendAppTx(DetectEngineThreadCtx *det_ctx, const Signature *s, Packet *p, - uint64_t tx_id, uint8_t alert_flags) + uint64_t tx_id, uint8_t sub_state, uint8_t alert_flags) { alert_flags |= (PACKET_ALERT_FLAG_TX | PACKET_ALERT_FLAG_STATE_MATCH); - return AlertQueueAppend(det_ctx, s, p, tx_id, alert_flags); + return AlertQueueAppend(det_ctx, s, p, tx_id, sub_state, alert_flags); } /** @@ -432,10 +433,10 @@ void AlertQueueAppendAppTx(DetectEngineThreadCtx *det_ctx, const Signature *s, P * comes from the packet alert path. */ void AlertQueueAppendAppTxFromPacket(DetectEngineThreadCtx *det_ctx, const Signature *s, Packet *p, - uint64_t tx_id, uint8_t alert_flags) + uint64_t tx_id, const uint8_t sub_state, uint8_t alert_flags) { alert_flags |= PACKET_ALERT_FLAG_TX; - return AlertQueueAppend(det_ctx, s, p, tx_id, alert_flags); + return AlertQueueAppend(det_ctx, s, p, tx_id, sub_state, alert_flags); } /** @@ -444,7 +445,7 @@ void AlertQueueAppendAppTxFromPacket(DetectEngineThreadCtx *det_ctx, const Signa void AlertQueueAppendPacket( DetectEngineThreadCtx *det_ctx, const Signature *s, Packet *p, uint8_t alert_flags) { - return AlertQueueAppend(det_ctx, s, p, PACKET_ALERT_NOTX, alert_flags); + return AlertQueueAppend(det_ctx, s, p, PACKET_ALERT_NOTX, 0, alert_flags); } /** \internal diff --git a/src/detect-engine-alert.h b/src/detect-engine-alert.h index 259444d5e9f7..63bed72057eb 100644 --- a/src/detect-engine-alert.h +++ b/src/detect-engine-alert.h @@ -33,9 +33,9 @@ void AlertQueueFree(DetectEngineThreadCtx *det_ctx); void AlertQueueAppendPacket( DetectEngineThreadCtx *det_ctx, const Signature *s, Packet *p, uint8_t alert_flags); void AlertQueueAppendAppTxFromPacket(DetectEngineThreadCtx *det_ctx, const Signature *s, Packet *p, - uint64_t tx_id, uint8_t alert_flags); + uint64_t tx_id, const uint8_t sub_state, uint8_t alert_flags); void AlertQueueAppendAppTx(DetectEngineThreadCtx *det_ctx, const Signature *s, Packet *p, - uint64_t tx_id, uint8_t alert_flags); + uint64_t tx_id, const uint8_t sub_state, uint8_t alert_flags); void PacketAlertFinalize(const DetectEngineCtx *, DetectEngineThreadCtx *, Packet *); #ifdef UNITTESTS int PacketAlertCheck(Packet *, uint32_t); diff --git a/src/detect.c b/src/detect.c index 98d442dcfc81..3030b9e0e389 100644 --- a/src/detect.c +++ b/src/detect.c @@ -749,7 +749,7 @@ static void DetectRulePacketAppendAlert(const DetectEngineCtx *de_ctx, alert_flags |= PACKET_ALERT_FLAG_TX_GUESSED; } txd->guessed_applayer_logged++; - AlertQueueAppendAppTxFromPacket(det_ctx, s, p, tx_id, alert_flags); + AlertQueueAppendAppTxFromPacket(det_ctx, s, p, tx_id, txd->tx_type, alert_flags); return; } } @@ -1700,13 +1700,14 @@ struct DetectFirewallAppTxState { }; static inline void DetectRunAppendDefaultAppPolicyAlert(DetectEngineThreadCtx *det_ctx, Packet *p, - const bool apply_to_packet, const uint64_t tx_id, const struct DetectFirewallAppPolicy *ap) + const bool apply_to_packet, const DetectTransaction *tx, + const struct DetectFirewallAppPolicy *ap) { if (EngineModeIsFirewall()) { const Signature *s = ap->alert_signature; BUG_ON(s == NULL); uint8_t alert_flags = apply_to_packet ? PACKET_ALERT_FLAG_APPLY_ACTION_TO_PACKET : 0; - AlertQueueAppendAppTx(det_ctx, s, p, tx_id, alert_flags); + AlertQueueAppendAppTx(det_ctx, s, p, tx->tx_id, tx->tx_type, alert_flags); } } @@ -1744,7 +1745,7 @@ static const struct DetectFirewallPolicy *DetectFirewallApplyDefaultAppPolicy( p->flow->aux_flags |= FLOW_AUX_ACTION_BY_FIREWALL; } if (policy->action & ACTION_ALERT) { - DetectRunAppendDefaultAppPolicyAlert(det_ctx, p, true, tx->tx_id, ap); + DetectRunAppendDefaultAppPolicyAlert(det_ctx, p, true, tx, ap); } } else if (policy->action & ACTION_ACCEPT) { /* should the accept be applied to the packet? @@ -1777,7 +1778,7 @@ static const struct DetectFirewallPolicy *DetectFirewallApplyDefaultAppPolicy( if (policy->action & ACTION_ALERT) { SCLogDebug("policy alert, do the append"); - DetectRunAppendDefaultAppPolicyAlert(det_ctx, p, apply_to_packet, tx->tx_id, ap); + DetectRunAppendDefaultAppPolicyAlert(det_ctx, p, apply_to_packet, tx, ap); } else if (apply_to_packet) { SCLogDebug("default accept: last_tx"); DetectRunAppendDefaultAccept(det_ctx, p); @@ -2202,10 +2203,10 @@ static void DetectRunTxFirewallRuleFullMatch(DetectEngineThreadCtx *det_ctx, con if (fw_accept_to_packet) { SCLogDebug("packet %" PRIu64 ": apply accept to packet", p->pcap_cnt); SCLogDebug("accept:(tx|hook): should be applied to the packet"); - AlertQueueAppendAppTx( - det_ctx, s, p, tx->tx_id, PACKET_ALERT_FLAG_APPLY_ACTION_TO_PACKET); + AlertQueueAppendAppTx(det_ctx, s, p, tx->tx_id, tx->tx_type, + PACKET_ALERT_FLAG_APPLY_ACTION_TO_PACKET); } else { - AlertQueueAppendAppTx(det_ctx, s, p, tx->tx_id, 0); + AlertQueueAppendAppTx(det_ctx, s, p, tx->tx_id, tx->tx_type, 0); } DetectRunTxFirewallApplyAccept(det_ctx, p, flow_flags, s, tx, fw_state); } else if (s->action & ACTION_DROP) { @@ -2217,10 +2218,10 @@ static void DetectRunTxFirewallRuleFullMatch(DetectEngineThreadCtx *det_ctx, con f->aux_flags |= FLOW_AUX_ACTION_BY_FIREWALL; } SCLogDebug("append alert"); - AlertQueueAppendAppTx(det_ctx, s, p, tx->tx_id, 0); + AlertQueueAppendAppTx(det_ctx, s, p, tx->tx_id, tx->tx_type, 0); } else { SCLogDebug("append alert"); - AlertQueueAppendAppTx(det_ctx, s, p, tx->tx_id, 0); + AlertQueueAppendAppTx(det_ctx, s, p, tx->tx_id, tx->tx_type, 0); } } @@ -2596,7 +2597,7 @@ static void DetectRunTx(ThreadVars *tv, "%p/%" PRIu64 " sig %u (%u) matched", tx.tx_ptr, tx.tx_id, s->id, s->iid); if ((s->flags & SIG_FLAG_FIREWALL) == 0) { - AlertQueueAppendAppTx(det_ctx, s, p, tx.tx_id, 0); + AlertQueueAppendAppTx(det_ctx, s, p, tx.tx_id, tx.tx_type, 0); } else { DetectRunTxFirewallRuleFullMatch(det_ctx, s, &tx, &fw_state, f, p, flow_flags); } @@ -2813,7 +2814,15 @@ static void DetectRunFrames(ThreadVars *tv, DetectEngineCtx *de_ctx, DetectEngin const uint8_t alert_flags = (PACKET_ALERT_FLAG_STATE_MATCH | PACKET_ALERT_FLAG_FRAME); if (frame->flags & FRAME_FLAG_TX_ID_SET) { - AlertQueueAppendAppTx(det_ctx, s, p, frame->tx_id, alert_flags); + const uint8_t ipproto = p->proto; + uint8_t sub_state = 0; + void *tx = AppLayerParserGetTx( + ipproto, alproto, p->flow->alstate, frame->tx_id); + if (tx) { + AppLayerTxData *txd = AppLayerParserGetTxData(ipproto, alproto, tx); + sub_state = txd->tx_type; + } + AlertQueueAppendAppTx(det_ctx, s, p, frame->tx_id, sub_state, alert_flags); } else { AlertQueueAppendPacket(det_ctx, s, p, alert_flags); } From e11bb03ba2d72b894f6d525a26bf7111105d88ef Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Fri, 26 Jun 2026 14:22:49 +0200 Subject: [PATCH 48/69] eve/alert: add sub state output (cherry picked from commit e1e24d6cb422d927df9e9f28ffedba21750d4f1d) --- etc/schema.json | 4 ++++ src/output-json-alert.c | 19 ++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/etc/schema.json b/etc/schema.json index 321d39d4ff6b..5a2d47492cfd 100644 --- a/etc/schema.json +++ b/etc/schema.json @@ -8276,6 +8276,10 @@ "type": "object", "additionalProperties": true }, + "sub_state": { + "type": "string", + "description": "Transaction type or sub state." + }, "suricata_version": { "type": "string" }, diff --git a/src/output-json-alert.c b/src/output-json-alert.c index e9b4588473a1..cda308663dff 100644 --- a/src/output-json-alert.c +++ b/src/output-json-alert.c @@ -322,8 +322,8 @@ static void AlertAddPayload(AlertJsonOutputCtx *json_output_ctx, SCJsonBuilder * } } -static void AlertAddAppLayer( - const Packet *p, SCJsonBuilder *jb, const uint64_t tx_id, const uint16_t option_flags) +static void AlertAddAppLayer(const Packet *p, SCJsonBuilder *jb, const uint64_t tx_id, + const uint8_t sub_state, const uint16_t option_flags) { const AppProto proto = FlowGetAppProtocol(p->flow); EveJsonSimpleAppLayerLogger *al = SCEveJsonSimpleGetLogger(proto); @@ -341,6 +341,13 @@ static void AlertAddAppLayer( AppLayerParserGetStateNameById(p->flow->proto, proto, ts, STREAM_TOSERVER)); SCJbSetString(jb, "tc_progress", AppLayerParserGetStateNameById(p->flow->proto, proto, tc, STREAM_TOCLIENT)); + if (sub_state) { + const char *sname = AppLayerParserGetSubStateName(proto, sub_state); + if (sname != NULL) { + SCJbSetString(jb, "sub_state", sname); + } + } + SCJbGetMark(jb, &mark); switch (proto) { // first check some protocols need special options for alerts logging @@ -375,6 +382,12 @@ static void AlertAddAppLayer( AppLayerParserGetStateNameById(p->flow->proto, proto, ts, STREAM_TOSERVER)); SCJbSetString(jb, "tc_progress", AppLayerParserGetStateNameById(p->flow->proto, proto, tc, STREAM_TOCLIENT)); + if (sub_state) { + const char *sname = AppLayerParserGetSubStateName(proto, sub_state); + if (sname != NULL) { + SCJbSetString(jb, "sub_state", sname); + } + } } } switch (proto) { @@ -761,7 +774,7 @@ static int AlertJson(ThreadVars *tv, JsonAlertLogThread *aft, const Packet *p) if (p->flow != NULL) { if (pa->flags & PACKET_ALERT_FLAG_TX) { if (json_output_ctx->flags & LOG_JSON_APP_LAYER) { - AlertAddAppLayer(p, jb, pa->tx_id, json_output_ctx->flags); + AlertAddAppLayer(p, jb, pa->tx_id, pa->sub_state, json_output_ctx->flags); } /* including fileinfo data is configured by the metadata setting */ if (json_output_ctx->flags & LOG_JSON_RULE_METADATA) { From 512a1867843d918c6296ea241883a0d8cf23eba2 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Mon, 29 Jun 2026 12:01:26 +0200 Subject: [PATCH 49/69] detect/firewall: support lte mode for substate (cherry picked from commit 2511070a584da3d3643810655cfe8dfa7185d05b) --- src/detect-engine-prefilter.c | 9 +-- src/detect-engine.c | 100 +++++++++++++++++++++++----------- src/detect-engine.h | 5 +- src/detect-parse.c | 27 +++++---- 4 files changed, 91 insertions(+), 50 deletions(-) diff --git a/src/detect-engine-prefilter.c b/src/detect-engine-prefilter.c index 6b9ca7476364..cd74336bdebf 100644 --- a/src/detect-engine-prefilter.c +++ b/src/detect-engine-prefilter.c @@ -973,14 +973,15 @@ static int SetupNonPrefilter(DetectEngineCtx *de_ctx, SigGroupHead *sgh) for (uint8_t state = 0; state < s->app_progress_hook; state++) { SCLogDebug("handle HOOK %u LTE", state); const int dir = (s->flags & SIG_FLAG_TOSERVER) ? 0 : 1; + const uint8_t sub_state = s->init_data->hook.t.app.sub_state; const char *pname = DetectEngineAppHookToName( - s->alproto, state, dir == 0 ? STREAM_TOSERVER : STREAM_TOCLIENT); + s->alproto, sub_state, state, dir == 0 ? STREAM_TOSERVER : STREAM_TOCLIENT); if (pname == NULL) { goto error; } - const int sm_list = DetectEngineAppHookToSmlist( - s->alproto, state, dir == 0 ? STREAM_TOSERVER : STREAM_TOCLIENT); - uint8_t sub_state = s->init_data->hook.t.app.sub_state; + const uint8_t direction = dir == 0 ? STREAM_TOSERVER : STREAM_TOCLIENT; + const int sm_list = + DetectEngineAppHookToSmlist(s->alproto, sub_state, state, direction); if (TxNonPFAddSig(de_ctx, tx_engines_hash, s->alproto, sub_state, dir, state, sm_list, pname, s) != 0) { goto error; diff --git a/src/detect-engine.c b/src/detect-engine.c index ecff778cf637..556e2a246b38 100644 --- a/src/detect-engine.c +++ b/src/detect-engine.c @@ -836,37 +836,46 @@ static void AppendAppInspectEngine(DetectEngineCtx *de_ctx, * \param direction STREAM_TOSERVER or STREAM_TOCLIENT */ const char *DetectEngineAppHookToName( - const AppProto p, const uint8_t state, const uint8_t direction) + const AppProto p, const uint8_t sub_state, const uint8_t state, const uint8_t direction) { if (!((direction & (STREAM_TOSERVER | STREAM_TOCLIENT)) == STREAM_TOSERVER) && !((direction & (STREAM_TOSERVER | STREAM_TOCLIENT)) == STREAM_TOCLIENT)) return NULL; - const char *pname = AppLayerParserGetStateNameById(IPPROTO_TCP, // TODO - p, state, direction); - if (pname == NULL) { - if (state == 0) { - if (direction == STREAM_TOSERVER) { - pname = "request_started"; - } else { - pname = "response_started"; - } - } else { - const int complete = AppLayerParserGetStateProgressCompletionStatus(p, direction); - if (state == complete) { + if (sub_state == 0) { + const char *pname = AppLayerParserGetStateNameById(IPPROTO_TCP, // TODO + p, state, direction); + if (pname == NULL) { + if (state == 0) { if (direction == STREAM_TOSERVER) { - pname = "request_complete"; + pname = "request_started"; } else { - pname = "response_complete"; + pname = "response_started"; + } + } else { + const int complete = AppLayerParserGetStateProgressCompletionStatus(p, direction); + if (state == complete) { + if (direction == STREAM_TOSERVER) { + pname = "request_complete"; + } else { + pname = "response_complete"; + } } } } + return pname; + } else { + BUG_ON(!AppLayerParserSupportsSubStates(p)); + const char *name = AppLayerParserGetSubStateProgressName(p, sub_state, state, direction); + return name; } - return pname; } -/** \brief get the sm_list for a app hook */ -int DetectEngineAppHookToSmlist(const AppProto p, const uint8_t state, const int direction) +/** \brief get the sm_list for a app hook + * \param sub_state sub_state to use or 0 if not in use + * */ +int DetectEngineAppHookToSmlist( + const AppProto p, const uint8_t sub_state, const uint8_t state, const uint8_t direction) { const char *app_proto = AppProtoToString(p); if (app_proto == NULL) { @@ -876,20 +885,45 @@ int DetectEngineAppHookToSmlist(const AppProto p, const uint8_t state, const int if (strcmp(app_proto, "http") == 0) app_proto = "http1"; - const char *name = - DetectEngineAppHookToName(p, state, direction & (STREAM_TOSERVER | STREAM_TOCLIENT)); - if (name == NULL) { - return -1; - } - char generic_hook_name[256]; - snprintf(generic_hook_name, sizeof(generic_hook_name), "%s:%s:generic", app_proto, name); - int list = DetectBufferTypeGetByName(generic_hook_name); - if (list < 0) { - SCLogError("no list registered as %s for %s hook %s", generic_hook_name, app_proto, name); - return -1; + if (sub_state == 0) { + const char *name = DetectEngineAppHookToName( + p, 0, state, direction & (STREAM_TOSERVER | STREAM_TOCLIENT)); + if (name == NULL) { + return -1; + } + + snprintf(generic_hook_name, sizeof(generic_hook_name), "%s:%s:generic", app_proto, name); + + int list = DetectBufferTypeGetByName(generic_hook_name); + if (list < 0) { + SCLogError( + "no list registered as %s for %s hook %s", generic_hook_name, app_proto, name); + return -1; + } + return list; + } else { + BUG_ON(!AppLayerParserSupportsSubStates(p)); + + const char *sname = AppLayerParserGetSubStateName(p, sub_state); + if (sname == NULL) + return -1; + + const char *name = AppLayerParserGetSubStateProgressName(p, sub_state, state, direction); + if (name == NULL) + return -1; + + snprintf(generic_hook_name, sizeof(generic_hook_name), "%s:%s:%s:generic", app_proto, sname, + name); + + int list = DetectBufferTypeGetByName(generic_hook_name); + if (list < 0) { + SCLogError("no list registered as %s for %s sub_state %s hook %s", generic_hook_name, + app_proto, sname, name); + return -1; + } + return list; } - return list; } /** @@ -908,7 +942,7 @@ int DetectEngineAppInspectionEngine2Signature(DetectEngineCtx *de_ctx, Signature SCLogDebug("need an inspect engine per state, range 0-%u", s->app_progress_hook); for (uint8_t state = 0; state < s->app_progress_hook; state++) { uint8_t dir = 0; - int direction = 0; + uint8_t direction = 0; BUG_ON((s->flags & (SIG_FLAG_TOSERVER | SIG_FLAG_TOCLIENT)) == (SIG_FLAG_TOSERVER | SIG_FLAG_TOCLIENT)); BUG_ON((s->flags & (SIG_FLAG_TOSERVER | SIG_FLAG_TOCLIENT)) == 0); @@ -920,8 +954,8 @@ int DetectEngineAppInspectionEngine2Signature(DetectEngineCtx *de_ctx, Signature dir = 1; } - int sm_list = - DetectEngineAppHookToSmlist(s->init_data->hook.t.app.alproto, 0, direction); + int sm_list = DetectEngineAppHookToSmlist(s->init_data->hook.t.app.alproto, + s->init_data->hook.t.app.sub_state, 0, direction); if (sm_list < 0) return -1; diff --git a/src/detect-engine.h b/src/detect-engine.h index 0fc327b65cc9..45f3bcbe3163 100644 --- a/src/detect-engine.h +++ b/src/detect-engine.h @@ -221,7 +221,8 @@ bool DetectMd5ValidateCallback( void DeStateRegisterTests(void); const char *DetectEngineAppHookToName( - const AppProto p, const uint8_t state, const uint8_t direction); -int DetectEngineAppHookToSmlist(const AppProto p, const uint8_t state, const int direction); + const AppProto p, const uint8_t sub_state, const uint8_t state, const uint8_t direction); +int DetectEngineAppHookToSmlist( + const AppProto p, const uint8_t sub_state, const uint8_t state, const uint8_t direction); #endif /* SURICATA_DETECT_ENGINE_H */ diff --git a/src/detect-parse.c b/src/detect-parse.c index 87be3465f493..95385837679a 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -1384,6 +1384,7 @@ static int SigParseProtoHookApp( Signature *s, const char *proto_hook, const char *p, const char *in_h) { char hook[64]; + char generic_hook_name[256]; strlcpy(hook, in_h, sizeof(hook)); const char *h = hook; const char *t = NULL; @@ -1421,6 +1422,12 @@ static int SigParseProtoHookApp( SCLogError("sub states currently only supported for http2"); return -1; } + /* FW hook LTE mode */ + if (*h == '<') { + h++; + SCLogDebug("hook and prior hooks: '%s'", h); + s->flags |= SIG_FLAG_FW_HOOK_LTE; + } const uint8_t max_state = AppLayerParserGetSubStateCompletion( s->alproto, sub_state); // TODO allow different completion per direction? if (strcmp(h, "request_started") == 0) { @@ -1453,7 +1460,14 @@ static int SigParseProtoHookApp( s->init_data->hook = SetAppHook(s->alproto, sub_state, progress_tc); } } + snprintf(generic_hook_name, sizeof(generic_hook_name), "%s:%s:%s:generic", p, t, h); } else { + /* FW hook LTE mode */ + if (*h == '<') { + h++; + SCLogDebug("hook and prior hooks: '%s'", h); + s->flags |= SIG_FLAG_FW_HOOK_LTE; + } SCLogDebug("h:'%s'", h); if (strcmp(h, "request_started") == 0) { s->flags |= SIG_FLAG_TOSERVER; @@ -1490,11 +1504,10 @@ static int SigParseProtoHookApp( s->init_data->hook = SetAppHook(s->alproto, sub_state, (uint8_t)progress_tc); } } + snprintf(generic_hook_name, sizeof(generic_hook_name), "%s:%s:generic", p, h); } + SCLogDebug("generic_hook_name %s", generic_hook_name); - /* use in_h to include sub state */ - char generic_hook_name[128]; - snprintf(generic_hook_name, sizeof(generic_hook_name), "%s:%s:generic", p, in_h); int list = DetectBufferTypeGetByName(generic_hook_name); if (list < 0) { SCLogError("no list registered as %s for hook %s", generic_hook_name, proto_hook); @@ -1559,14 +1572,6 @@ static int SigParseProto(Signature *s, const char *protostr) SCLogError("invalid protocol specification '%s'", proto); return -1; } - - /* FW hook LTE mode */ - SCLogDebug("hook '%s'", h); - if (*h == '<') { - h++; - SCLogDebug("hook and prior hooks: '%s'", h); - s->flags |= SIG_FLAG_FW_HOOK_LTE; - } if (SigParseProtoHookApp(s, protostr, p, h) < 0) { SCLogError("protocol \"%s\" does not support hook \"%s\"", p, h); SCReturnInt(-1); From 3208779b0fc9c6a631913669700327ef08b5a6e3 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Sun, 21 Jun 2026 22:25:21 +0200 Subject: [PATCH 50/69] detect/firewall: support substate in analyzer (cherry picked from commit e4176d2b9a83956ce5e774daaa7132437bae8a07) --- src/detect-engine-analyzer.c | 84 +++++++++++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 7 deletions(-) diff --git a/src/detect-engine-analyzer.c b/src/detect-engine-analyzer.c index 46dddce57411..7e8493489e50 100644 --- a/src/detect-engine-analyzer.c +++ b/src/detect-engine-analyzer.c @@ -2001,12 +2001,12 @@ void EngineAnalysisRules(const DetectEngineCtx *de_ctx, #include "app-layer-parser.h" static void AddPolicy(const DetectEngineCtx *de_ctx, RuleAnalyzer *ctx, const AppProto a, - const uint8_t state, const uint8_t direction) + const uint8_t sub_state, const uint8_t state, const uint8_t direction) { char policy_string[64] = ""; const struct DetectFirewallPolicies *fw_policies = de_ctx->fw_policies; const struct DetectFirewallAppPolicy lookup = { - .alproto = a, .sub_state = 0, .progress = state, .direction = direction + .alproto = a, .sub_state = sub_state, .progress = state, .direction = direction }; const struct DetectFirewallAppPolicy *ap = HashTableLookup(fw_policies->app_policies, (void *)&lookup, 0); @@ -2044,12 +2044,12 @@ static void AddPolicy(const DetectEngineCtx *de_ctx, RuleAnalyzer *ctx, const Ap } static void FirewallAddRulesForState(const DetectEngineCtx *de_ctx, const AppProto a, - const uint8_t state, const uint8_t direction, RuleAnalyzer *ctx) + const uint8_t sub_state, const uint8_t state, const uint8_t direction, RuleAnalyzer *ctx) { uint32_t accept_rules = 0; - AddPolicy(de_ctx, ctx, a, state, direction); + AddPolicy(de_ctx, ctx, a, sub_state, state, direction); SCJbOpenArray(ctx->js, "rules"); - for (Signature *s = de_ctx->sig_list; s != NULL; s = s->next) { + for (const Signature *s = de_ctx->sig_list; s != NULL; s = s->next) { if ((s->flags & SIG_FLAG_FIREWALL) == 0) break; if (s->type != SIG_TYPE_APP_TX) @@ -2067,6 +2067,25 @@ static void FirewallAddRulesForState(const DetectEngineCtx *de_ctx, const AppPro } } + /* sig has no sub_state field, so check the app inspect engines (if any). + * We assume that the only engines we have either: + * - are unknown/substate 0 + * - matching the rule's substate */ + if (s->app_inspect != NULL) { + bool skip_rule = false; + for (const DetectEngineAppInspectionEngine *engine = s->app_inspect; engine != NULL; + engine = engine->next) { + if (engine->alproto == ALPROTO_UNKNOWN) { + // skip engines targeting unknown, like stream or app-layer-event + } else if (engine->sub_state != sub_state) { + skip_rule = true; + break; + } + } + if (skip_rule) { + continue; + } + } if ((s->flags & SIG_FLAG_FW_HOOK_LTE) && state < s->app_progress_hook) { SCJbAppendString(ctx->js, s->sig_str); accept_rules += ((s->action & ACTION_ACCEPT) != 0); @@ -2126,6 +2145,57 @@ int FirewallAnalyzer(const DetectEngineCtx *de_ctx) if (!AppProtoIsValid(a)) continue; + if (AppLayerParserSupportsSubStates(a)) { + SCJbOpenObject(ctx.js, AppProtoToString(a)); + const uint8_t max_sub_state = AppLayerParserGetMaxSubState(a); + for (uint8_t sub_state = 1; sub_state <= max_sub_state; sub_state++) { + const char *sub_state_name = AppLayerParserGetSubStateName(a, sub_state); + const uint8_t max_progress = AppLayerParserGetSubStateCompletion(a, sub_state); + for (uint8_t state = 0; state <= max_progress; state++) { + const char *name = AppLayerParserGetSubStateProgressName( + a, sub_state, state, STREAM_TOSERVER); + if (name == NULL) + continue; + + char table_name[256]; + snprintf(table_name, sizeof(table_name), "app:%s:%s:%s", AppProtoToString(a), + sub_state_name, name); + SCJbOpenObject(ctx.js, table_name); + FirewallAddRulesForState(de_ctx, a, sub_state, state, STREAM_TOSERVER, &ctx); + if (ctx.js_warnings) { + SCJbClose(ctx.js_warnings); + SCJbSetObject(ctx.js, "warnings", ctx.js_warnings); + SCJbFree(ctx.js_warnings); + ctx.js_warnings = NULL; + } + SCJbClose(ctx.js); + } + for (uint8_t state = 0; state <= max_progress; state++) { + const char *name = AppLayerParserGetSubStateProgressName( + a, sub_state, state, STREAM_TOCLIENT); + if (name == NULL) + continue; + + char table_name[256]; + snprintf(table_name, sizeof(table_name), "app:%s:%s:%s", AppProtoToString(a), + sub_state_name, name); + SCJbOpenObject(ctx.js, table_name); + FirewallAddRulesForState(de_ctx, a, sub_state, state, STREAM_TOCLIENT, &ctx); + if (ctx.js_warnings) { + SCJbClose(ctx.js_warnings); + SCJbSetObject(ctx.js, "warnings", ctx.js_warnings); + SCJbFree(ctx.js_warnings); + ctx.js_warnings = NULL; + } + SCJbClose(ctx.js); + } + } + SCJbClose(ctx.js); // app layer + continue; + } + + /* no sub state follows */ + const uint8_t complete_state_ts = (const uint8_t)AppLayerParserGetStateProgressCompletionStatus(a, STREAM_TOSERVER); SCJbOpenObject(ctx.js, AppProtoToString(a)); @@ -2144,7 +2214,7 @@ int FirewallAnalyzer(const DetectEngineCtx *de_ctx) char table_name[128]; snprintf(table_name, sizeof(table_name), "app:%s:%s", AppProtoToString(a), name); SCJbOpenObject(ctx.js, table_name); - FirewallAddRulesForState(de_ctx, a, state, STREAM_TOSERVER, &ctx); + FirewallAddRulesForState(de_ctx, a, 0, state, STREAM_TOSERVER, &ctx); if (ctx.js_warnings) { SCJbClose(ctx.js_warnings); SCJbSetObject(ctx.js, "warnings", ctx.js_warnings); @@ -2169,7 +2239,7 @@ int FirewallAnalyzer(const DetectEngineCtx *de_ctx) char table_name[128]; snprintf(table_name, sizeof(table_name), "app:%s:%s", AppProtoToString(a), name); SCJbOpenObject(ctx.js, table_name); - FirewallAddRulesForState(de_ctx, a, state, STREAM_TOCLIENT, &ctx); + FirewallAddRulesForState(de_ctx, a, 0, state, STREAM_TOCLIENT, &ctx); if (ctx.js_warnings) { SCJbClose(ctx.js_warnings); SCJbSetObject(ctx.js, "warnings", ctx.js_warnings); From 6bdbd836a97e8e6b7918721601a95a403fd9d610 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Tue, 30 Jun 2026 14:50:15 +0200 Subject: [PATCH 51/69] detect: give clear errors for http2 w/o substate http2 states were not yet supported even if the built-in states could already work. Clearly error out on a hook w/o substate. (cherry picked from commit 7517ba9f9864c8e37884816e22e2c8cc999ad8ad) --- src/detect-parse.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/detect-parse.c b/src/detect-parse.c index 95385837679a..5790ef46446d 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -1407,19 +1407,19 @@ static int SigParseProtoHookApp( SCLogError("invalid tx type specification '%s'", hook); return -1; } - if (strcmp(p, "http2") == 0) { + if (strcmp(p, "http2") == 0 || strcmp(p, "doh2") == 0) { if (strcmp(t, "stream") == 0) { sub_state = HTTP2TxTypeStream; } else if (strcmp(t, "global") == 0) { sub_state = HTTP2TxTypeGlobal; } else { - SCLogError("unknown http/2 tx type specification '%s': valid values are 'stream' " + SCLogError("unknown %s tx type specification '%s': valid values are 'stream' " "and 'global'", - hook); + p, hook); return -1; } } else { - SCLogError("sub states currently only supported for http2"); + SCLogError("sub states currently only supported for http2 and doh2"); return -1; } /* FW hook LTE mode */ @@ -1462,6 +1462,12 @@ static int SigParseProtoHookApp( } snprintf(generic_hook_name, sizeof(generic_hook_name), "%s:%s:%s:generic", p, t, h); } else { + if (AppLayerParserSupportsSubStates(s->alproto)) { + SCLogError( + "protocol %s requires a substate specification: %s::%s", p, p, hook); + return -1; + } + /* FW hook LTE mode */ if (*h == '<') { h++; From 41b60bce743e97d6191d1f8f15ee12c43d7f00de Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Tue, 30 Jun 2026 20:08:45 +0200 Subject: [PATCH 52/69] detect: add debug validation checks to assert assumptions (cherry picked from commit cfc98aeeaf3b12b07796e78f94e57064e46485da) --- src/detect-engine-prefilter.c | 5 +++++ src/detect.c | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/detect-engine-prefilter.c b/src/detect-engine-prefilter.c index cd74336bdebf..fc78c62f3c57 100644 --- a/src/detect-engine-prefilter.c +++ b/src/detect-engine-prefilter.c @@ -113,6 +113,11 @@ void DetectRunPrefilterTx(DetectEngineThreadCtx *det_ctx, engine, AppProtoToString(engine->alproto), engine->ctx.app.tx_min_progress, engine->ctx.app.sub_state, tx->tx_type); + DEBUG_VALIDATE_BUG_ON( + AppLayerParserSupportsSubStates(engine->alproto) && engine->ctx.app.sub_state == 0); + DEBUG_VALIDATE_BUG_ON(!AppLayerParserSupportsSubStates(engine->alproto) && + engine->ctx.app.sub_state != 0); + if (engine->alproto != ALPROTO_UNKNOWN && engine->ctx.app.sub_state != tx->tx_type) { SCLogDebug("%" PRIu64 ": engine %p sub_state %u mismatch with tx %u", p->pcap_cnt, engine, engine->ctx.app.sub_state, tx->tx_type); diff --git a/src/detect.c b/src/detect.c index 3030b9e0e389..b84b0b1e9490 100644 --- a/src/detect.c +++ b/src/detect.c @@ -1330,6 +1330,11 @@ static int DetectRunTxInspectRule(ThreadVars *tv, DetectEngineCtx *de_ctx, if (!(inspect_flags & BIT_U32(engine->id)) && (direction == engine->dir || ((s->flags & SIG_FLAG_TXBOTHDIR) && direction == 1))) { + DEBUG_VALIDATE_BUG_ON( + AppLayerParserSupportsSubStates(engine->alproto) && engine->sub_state == 0); + DEBUG_VALIDATE_BUG_ON( + !AppLayerParserSupportsSubStates(engine->alproto) && engine->sub_state != 0); + if (engine->alproto != ALPROTO_UNKNOWN && // app-layer-events is registered for each // proto this way tx->tx_type != engine->sub_state) { From 532770a489e541d4a7849c4e45b70e7f5c3ee11d Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Tue, 30 Jun 2026 20:19:22 +0200 Subject: [PATCH 53/69] detect/firewall: harden policy lookup logic (cherry picked from commit 110e74642e86c1fcbc2ea17713a5dccb060a8e66) --- src/detect.c | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/src/detect.c b/src/detect.c index b84b0b1e9490..6364c3dee675 100644 --- a/src/detect.c +++ b/src/detect.c @@ -1725,22 +1725,31 @@ static inline void DetectRunAppendDefaultAppPolicyAlert(DetectEngineThreadCtx *d * \note alproto and progress are unused right now, will be used * to look up configurable default policies later */ -static const struct DetectFirewallPolicy *DetectFirewallApplyDefaultAppPolicy( +static struct DetectFirewallPolicy DetectFirewallApplyDefaultAppPolicy( DetectEngineThreadCtx *det_ctx, const struct DetectFirewallPolicies *policies, const DetectTransaction *tx, Packet *p, const AppProto alproto, const uint8_t direction, const uint8_t progress) { + const uint8_t dir_flags = direction & (STREAM_TOSERVER | STREAM_TOCLIENT); + SCLogDebug("packet %" PRIu64 ": tx type %u", p->pcap_cnt, tx->tx_type); + const struct DetectFirewallPolicy drop_policy = { .action = ACTION_DROP, + .action_scope = ACTION_SCOPE_FLOW }; const struct DetectFirewallAppPolicy lookup = { - .alproto = alproto, .sub_state = tx->tx_type, .progress = progress, .direction = direction + .alproto = alproto, .sub_state = tx->tx_type, .progress = progress, .direction = dir_flags }; + const struct DetectFirewallPolicy *policy = NULL; const struct DetectFirewallAppPolicy *ap = HashTableLookup(policies->app_policies, (void *)&lookup, 0); - /* table should be fully populated, so this should not be able to fail */ + /* table should be fully populated, so this should not be able to fail. + * However as it continues to confuse tooling, at a fallback. */ DEBUG_VALIDATE_BUG_ON(ap == NULL); - const struct DetectFirewallPolicy *policy = &ap->policy; - + if (likely(ap != NULL)) { + policy = &ap->policy; + } else { + policy = &drop_policy; + } if (policy->action & ACTION_DROP) { SCLogDebug("dropping packet PKT_DROP_REASON_FW_DEFAULT_APP_POLICY"); PacketDrop(p, policy->action, PKT_DROP_REASON_FW_DEFAULT_APP_POLICY); @@ -1792,7 +1801,7 @@ static const struct DetectFirewallPolicy *DetectFirewallApplyDefaultAppPolicy( /* should be unreachable */ DEBUG_VALIDATE_BUG_ON(1); } - return policy; + return *policy; } /** \internal @@ -1828,27 +1837,27 @@ static enum DetectTxFirewallFlowControl DetectFirewallApplyDefaultPolicies( direction & STREAM_TOSERVER ? "toserver" : "toclient", hook, BOOL2STR(apply_to_packet)); - const struct DetectFirewallPolicy *policy = DetectFirewallApplyDefaultAppPolicy( + const struct DetectFirewallPolicy policy = DetectFirewallApplyDefaultAppPolicy( det_ctx, policies, tx, p, alproto, direction, hook); - SCLogDebug("fw: hook:%u policy:%02x apply_to_packet:%s", hook, policy->action, + SCLogDebug("fw: hook:%u policy:%02x apply_to_packet:%s", hook, policy.action, BOOL2STR(apply_to_packet)); - if (policy->action & ACTION_DROP) { - SCLogDebug("fw: action %02x", policy->action); + if (policy.action & ACTION_DROP) { + SCLogDebug("fw: action %02x", policy.action); return DETECT_TX_FW_FC_BREAK; - } else if (policy->action & ACTION_ACCEPT) { - SCLogDebug("fw: accept hook %u action %02x", hook, policy->action); + } else if (policy.action & ACTION_ACCEPT) { + SCLogDebug("fw: accept hook %u action %02x", hook, policy.action); /* accepting flow, so skip rest of the fw rules */ - if (policy->action_scope == ACTION_SCOPE_FLOW) { + if (policy.action_scope == ACTION_SCOPE_FLOW) { SCLogDebug("fw: accept flow"); return DETECT_TX_FW_FC_SKIP; /* accepting flow, so skip rest of the fw rules for this tx */ - } else if (policy->action_scope == ACTION_SCOPE_TX) { + } else if (policy.action_scope == ACTION_SCOPE_TX) { return DETECT_TX_FW_FC_SKIP; - } else if (policy->action_scope == ACTION_SCOPE_HOOK) { + } else if (policy.action_scope == ACTION_SCOPE_HOOK) { /* we're done */ if (apply_to_packet) { return DETECT_TX_FW_FC_SKIP; @@ -2282,10 +2291,10 @@ static int DetectRunTxFirewallRuleNoMatch(DetectEngineThreadCtx *det_ctx, const * we have to invoke the default policy. We only check the current rule hook. * DROP is immediate, flow control for various accept options is handled by * the DetectRunTxPreCheckFirewallPolicy function for the next rule. */ - const struct DetectFirewallPolicy *policy = DetectFirewallApplyDefaultAppPolicy(det_ctx, + const struct DetectFirewallPolicy policy = DetectFirewallApplyDefaultAppPolicy(det_ctx, det_ctx->de_ctx->fw_policies, tx, p, s->alproto, flow_flags, s->app_progress_hook); - SCLogDebug("fw_last_for_progress policy %02x", policy->action); - if (policy->action & ACTION_DROP) { + SCLogDebug("fw_last_for_progress policy %02x", policy.action); + if (policy.action & ACTION_DROP) { return 1; } } From 67339d038b2eb9fbb59bf950a6af9b6c150bf5b9 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Wed, 1 Jul 2026 10:08:51 +0200 Subject: [PATCH 54/69] eve/alert: clean up state logging (cherry picked from commit f02a427059dfb896fb7ecdabb2c38c7a3f61822c) --- src/output-json-alert.c | 127 +++++++++++++++++----------------------- 1 file changed, 54 insertions(+), 73 deletions(-) diff --git a/src/output-json-alert.c b/src/output-json-alert.c index cda308663dff..8c4576d2126d 100644 --- a/src/output-json-alert.c +++ b/src/output-json-alert.c @@ -322,74 +322,61 @@ static void AlertAddPayload(AlertJsonOutputCtx *json_output_ctx, SCJsonBuilder * } } +static void AlertAddAppLayerStates(const Packet *p, const AppProto alproto, const uint8_t sub_state, + void *tx, SCJsonBuilder *jb) +{ + const int ts = AppLayerParserGetStateProgress(p->flow->proto, alproto, tx, STREAM_TOSERVER); + const int tc = AppLayerParserGetStateProgress(p->flow->proto, alproto, tx, STREAM_TOCLIENT); + SCJbSetString(jb, "ts_progress", + AppLayerParserGetStateNameById(p->flow->proto, alproto, ts, STREAM_TOSERVER)); + SCJbSetString(jb, "tc_progress", + AppLayerParserGetStateNameById(p->flow->proto, alproto, tc, STREAM_TOCLIENT)); + if (sub_state) { + const char *sname = AppLayerParserGetSubStateName(alproto, sub_state); + if (sname != NULL) { + SCJbSetString(jb, "sub_state", sname); + } + } +} + static void AlertAddAppLayer(const Packet *p, SCJsonBuilder *jb, const uint64_t tx_id, const uint8_t sub_state, const uint16_t option_flags) { const AppProto proto = FlowGetAppProtocol(p->flow); EveJsonSimpleAppLayerLogger *al = SCEveJsonSimpleGetLogger(proto); + void *state = FlowGetAppState(p->flow); + void *tx = NULL; + if (state) { + tx = AppLayerParserGetTx(p->flow->proto, proto, state, tx_id); + } + if (tx == NULL) + return; + + AlertAddAppLayerStates(p, proto, sub_state, tx, jb); + SCJsonBuilderMark mark = { 0, 0, 0 }; if (al && al->LogTx) { - void *state = FlowGetAppState(p->flow); - if (state) { - void *tx = AppLayerParserGetTx(p->flow->proto, proto, state, tx_id); - if (tx) { - const int ts = - AppLayerParserGetStateProgress(p->flow->proto, proto, tx, STREAM_TOSERVER); - const int tc = - AppLayerParserGetStateProgress(p->flow->proto, proto, tx, STREAM_TOCLIENT); - SCJbSetString(jb, "ts_progress", - AppLayerParserGetStateNameById(p->flow->proto, proto, ts, STREAM_TOSERVER)); - SCJbSetString(jb, "tc_progress", - AppLayerParserGetStateNameById(p->flow->proto, proto, tc, STREAM_TOCLIENT)); - if (sub_state) { - const char *sname = AppLayerParserGetSubStateName(proto, sub_state); - if (sname != NULL) { - SCJbSetString(jb, "sub_state", sname); + SCJbGetMark(jb, &mark); + switch (proto) { + // first check some protocols need special options for alerts logging + case ALPROTO_WEBSOCKET: + if (option_flags & + (LOG_JSON_WEBSOCKET_PAYLOAD | LOG_JSON_WEBSOCKET_PAYLOAD_BASE64)) { + const bool pp = (option_flags & LOG_JSON_WEBSOCKET_PAYLOAD) != 0; + const bool pb64 = (option_flags & LOG_JSON_WEBSOCKET_PAYLOAD_BASE64) != 0; + if (!SCWebSocketLogDetails(tx, jb, pp, pb64)) { + SCJbRestoreMark(jb, &mark); } + // nothing more to log or do + return; } - - SCJbGetMark(jb, &mark); - switch (proto) { - // first check some protocols need special options for alerts logging - case ALPROTO_WEBSOCKET: - if (option_flags & - (LOG_JSON_WEBSOCKET_PAYLOAD | LOG_JSON_WEBSOCKET_PAYLOAD_BASE64)) { - bool pp = (option_flags & LOG_JSON_WEBSOCKET_PAYLOAD) != 0; - bool pb64 = (option_flags & LOG_JSON_WEBSOCKET_PAYLOAD_BASE64) != 0; - if (!SCWebSocketLogDetails(tx, jb, pp, pb64)) { - SCJbRestoreMark(jb, &mark); - } - // nothing more to log or do - return; - } - } - if (!al->LogTx(tx, jb)) { - SCJbRestoreMark(jb, &mark); - } - } } - return; - } - void *state = FlowGetAppState(p->flow); - if (state) { - void *tx = AppLayerParserGetTx(p->flow->proto, proto, state, tx_id); - if (tx) { - const int ts = - AppLayerParserGetStateProgress(p->flow->proto, proto, tx, STREAM_TOSERVER); - const int tc = - AppLayerParserGetStateProgress(p->flow->proto, proto, tx, STREAM_TOCLIENT); - SCJbSetString(jb, "ts_progress", - AppLayerParserGetStateNameById(p->flow->proto, proto, ts, STREAM_TOSERVER)); - SCJbSetString(jb, "tc_progress", - AppLayerParserGetStateNameById(p->flow->proto, proto, tc, STREAM_TOCLIENT)); - if (sub_state) { - const char *sname = AppLayerParserGetSubStateName(proto, sub_state); - if (sname != NULL) { - SCJbSetString(jb, "sub_state", sname); - } - } + if (!al->LogTx(tx, jb)) { + SCJbRestoreMark(jb, &mark); } + return; } + switch (proto) { case ALPROTO_HTTP1: // TODO: Could result in an empty http object being logged. @@ -453,26 +440,20 @@ static void AlertAddAppLayer(const Packet *p, SCJsonBuilder *jb, const uint64_t SCJbRestoreMark(jb, &mark); } break; - case ALPROTO_DCERPC: { - if (state) { - void *tx = AppLayerParserGetTx(p->flow->proto, proto, state, tx_id); - if (tx) { - SCJbGetMark(jb, &mark); - SCJbOpenObject(jb, "dcerpc"); - if (p->proto == IPPROTO_TCP) { - if (!SCDcerpcLogJsonRecordTcp(state, tx, jb)) { - SCJbRestoreMark(jb, &mark); - } - } else { - if (!SCDcerpcLogJsonRecordUdp(state, tx, jb)) { - SCJbRestoreMark(jb, &mark); - } - } - SCJbClose(jb); + case ALPROTO_DCERPC: + SCJbGetMark(jb, &mark); + SCJbOpenObject(jb, "dcerpc"); + if (p->proto == IPPROTO_TCP) { + if (!SCDcerpcLogJsonRecordTcp(state, tx, jb)) { + SCJbRestoreMark(jb, &mark); + } + } else { + if (!SCDcerpcLogJsonRecordUdp(state, tx, jb)) { + SCJbRestoreMark(jb, &mark); } } + SCJbClose(jb); break; - } default: break; } From 9af776fcec80ac3471b296d49f62b3fbb0bc24a0 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Wed, 1 Jul 2026 10:29:32 +0200 Subject: [PATCH 55/69] eve/alert: log sub state progress values (cherry picked from commit 56d9670af7c69747f5cf0b0a8a15e66380053bf1) --- src/output-json-alert.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/output-json-alert.c b/src/output-json-alert.c index 8c4576d2126d..023a3d4fe136 100644 --- a/src/output-json-alert.c +++ b/src/output-json-alert.c @@ -327,15 +327,22 @@ static void AlertAddAppLayerStates(const Packet *p, const AppProto alproto, cons { const int ts = AppLayerParserGetStateProgress(p->flow->proto, alproto, tx, STREAM_TOSERVER); const int tc = AppLayerParserGetStateProgress(p->flow->proto, alproto, tx, STREAM_TOCLIENT); - SCJbSetString(jb, "ts_progress", - AppLayerParserGetStateNameById(p->flow->proto, alproto, ts, STREAM_TOSERVER)); - SCJbSetString(jb, "tc_progress", - AppLayerParserGetStateNameById(p->flow->proto, alproto, tc, STREAM_TOCLIENT)); - if (sub_state) { + if (sub_state == 0) { + SCJbSetString(jb, "ts_progress", + AppLayerParserGetStateNameById(p->flow->proto, alproto, ts, STREAM_TOSERVER)); + SCJbSetString(jb, "tc_progress", + AppLayerParserGetStateNameById(p->flow->proto, alproto, tc, STREAM_TOCLIENT)); + } else { const char *sname = AppLayerParserGetSubStateName(alproto, sub_state); if (sname != NULL) { SCJbSetString(jb, "sub_state", sname); } + SCJbSetString(jb, "ts_progress", + AppLayerParserGetSubStateProgressName( + alproto, sub_state, (uint8_t)ts, STREAM_TOSERVER)); + SCJbSetString(jb, "tc_progress", + AppLayerParserGetSubStateProgressName( + alproto, sub_state, (uint8_t)tc, STREAM_TOCLIENT)); } } From f17981672994621cb6bd0ddfd2923c85df52d246 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Wed, 1 Jul 2026 20:40:39 +0200 Subject: [PATCH 56/69] detect: sync prefilter and app inspect alproto logic In firewall mode the alproto logic when building prefilter and rule engines is strict, but was out of sync between prefilter and rule app inspect engines. For SIGNATURE_HOOK_TYPE_APP rules the logic is strict, with an exception for HTTP/2 and DOH2. DOH2 is not a full protocol implementation, but rather HTTP/2 with a different alproto. Fixes: d64954a873be ("detect: don't register unrelated inspect engines") (cherry picked from commit f8f28c6f6ff58e2325bd51611092f5199ce7ac82) --- src/app-layer-protos.h | 18 ++++++++++++++++++ src/detect-engine-mpm.c | 12 ++++++++++-- src/detect-engine-prefilter.c | 7 +++++++ src/detect-engine.c | 2 +- src/detect-parse.c | 2 +- 5 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/app-layer-protos.h b/src/app-layer-protos.h index 2498c744d2f4..c6267a586bde 100644 --- a/src/app-layer-protos.h +++ b/src/app-layer-protos.h @@ -116,6 +116,24 @@ static inline bool AppProtoEquals(AppProto sigproto, AppProto alproto) return false; } +// whether a signature AppProto matches a flow (or signature) AppProto +// only see DOH2/HTTP2 as the same +static inline bool AppProtoEqualsStrict(AppProto sigproto, AppProto alproto) +{ + if (sigproto == alproto) { + return true; + } + switch (sigproto) { + case ALPROTO_HTTP2: + // a HTTP2 signature matches on either HTTP2 or DOH2 flows + return (alproto == ALPROTO_DOH2); + case ALPROTO_DOH2: + // a DOH2 signature accepts dns, http2 or http generic keywords + return (alproto == ALPROTO_HTTP2); + } + return false; +} + // whether a signature AppProto matches a flow (or signature) AppProto static inline AppProto AppProtoCommon(AppProto sigproto, AppProto alproto) { diff --git a/src/detect-engine-mpm.c b/src/detect-engine-mpm.c index 8339c75c4ebb..10c2c70fc765 100644 --- a/src/detect-engine-mpm.c +++ b/src/detect-engine-mpm.c @@ -2172,8 +2172,16 @@ static void PrepareMpms(DetectEngineCtx *de_ctx, SigGroupHead *sh) case DETECT_BUFFER_MPM_TYPE_APP: { for (int e = 0; e < engines_idx[list]; e++) { const AppProto alproto = engines[list][e]; - if (!(AppProtoEquals(s->alproto, alproto) || s->alproto == 0)) - continue; + if (s->init_data->hook.type == SIGNATURE_HOOK_TYPE_APP) { + /* SIGNATURE_HOOK_TYPE_APP rules are exact about their protocol */ + if (!(AppProtoEqualsStrict(s->alproto, alproto))) { + continue; + } + } else { + /* other rules use the more relax AppProtoEquals logic */ + if (!(AppProtoEquals(s->alproto, alproto) || s->alproto == 0)) + continue; + } DetectBufferInstance lookup = { .list = list, .alproto = alproto }; DetectBufferInstance *instance = HashListTableLookup(bufs, &lookup, 0); diff --git a/src/detect-engine-prefilter.c b/src/detect-engine-prefilter.c index fc78c62f3c57..40bc9042b3a0 100644 --- a/src/detect-engine-prefilter.c +++ b/src/detect-engine-prefilter.c @@ -1037,6 +1037,13 @@ static int SetupNonPrefilter(DetectEngineCtx *de_ctx, SigGroupHead *sgh) AppProtoToString(app->alproto), app->sm_list, s->flags & (SIG_FLAG_TOSERVER | SIG_FLAG_TOCLIENT)); + /* extra strict alproto check for SIGNATURE_HOOK_TYPE_APP, + * just like with mpm and per rule app engine. */ + if (s->init_data->hook.type == SIGNATURE_HOOK_TYPE_APP) { + if (!AppProtoEqualsStrict(s->alproto, app->alproto)) + continue; + } + /* skip if: * - not in our dir * - not our list diff --git a/src/detect-engine.c b/src/detect-engine.c index 556e2a246b38..e0774e7f8baf 100644 --- a/src/detect-engine.c +++ b/src/detect-engine.c @@ -736,7 +736,7 @@ static void AppendAppInspectEngine(DetectEngineCtx *de_ctx, } else if (s->alproto != ALPROTO_UNKNOWN) { if (s->init_data->hook.type == SIGNATURE_HOOK_TYPE_APP) { /* SIGNATURE_HOOK_TYPE_APP rules are exact about their protocol */ - if (t->alproto != s->alproto) { + if (!(AppProtoEqualsStrict(s->alproto, t->alproto))) { return; } } else { diff --git a/src/detect-parse.c b/src/detect-parse.c index 5790ef46446d..1e2f7319b0b6 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -2850,7 +2850,7 @@ static int SigValidateCheckBuffers( if (s->init_data->hook.type == SIGNATURE_HOOK_TYPE_APP) { /* only allow rules to use the hook for engines at that * exact progress for now. */ - if (app->alproto != s->alproto) { + if (!(AppProtoEqualsStrict(s->alproto, app->alproto))) { continue; } } else { From 9bd5a757b84ee91abd53d4a2a4b7b0d4ce76ef1b Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Wed, 1 Jul 2026 22:52:53 +0200 Subject: [PATCH 57/69] http2: rename state *start to *started This is to make the hook use names consistent with the default hooks and other protocols. (cherry picked from commit cf216b63097b3c2cba018b06426661881b7878e6) --- rust/src/http2/http2.rs | 12 ++++++------ src/detect-http-protocol.c | 8 ++++---- src/detect-http-stat-msg.c | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs index 410cb4eb6ecb..504434ed4aa2 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -135,7 +135,7 @@ pub enum HTTP2FrameTypeData { #[derive(AppLayerState, Copy, Clone, PartialOrd, PartialEq, Eq, Debug)] #[suricata(alstate_strip_prefix = "HTTP2Prog")] pub enum HTTP2TxProgress { - HTTP2ProgStart = 0, + HTTP2ProgStarted = 0, HTTP2ProgHeaders = 1, HTTP2ProgData = 2, HTTP2ProgClosed = 3, @@ -146,7 +146,7 @@ pub enum HTTP2TxProgress { #[derive(AppLayerState, Copy, Clone, PartialOrd, PartialEq, Eq, Debug)] #[suricata(alstate_strip_prefix = "HTTP2ProgGlobal")] pub enum HTTP2TxGlobalProgress { - HTTP2ProgGlobalStart = 0, + HTTP2ProgGlobalStarted = 0, HTTP2ProgGlobalComplete = 1, } @@ -178,8 +178,8 @@ pub struct HTTP2StreamProgress { impl HTTP2StreamProgress { fn init() -> Self { Self { - progress_ts: HTTP2TxProgress::HTTP2ProgStart, - progress_tc: HTTP2TxProgress::HTTP2ProgStart, + progress_ts: HTTP2TxProgress::HTTP2ProgStarted, + progress_tc: HTTP2TxProgress::HTTP2ProgStarted, } } fn complete() -> Self { @@ -210,8 +210,8 @@ pub struct HTTP2GlobalProgress { impl HTTP2GlobalProgress { fn _init() -> Self { Self { - progress_ts: HTTP2TxGlobalProgress::HTTP2ProgGlobalStart, - progress_tc: HTTP2TxGlobalProgress::HTTP2ProgGlobalStart, + progress_ts: HTTP2TxGlobalProgress::HTTP2ProgGlobalStarted, + progress_tc: HTTP2TxGlobalProgress::HTTP2ProgGlobalStarted, } } fn complete() -> Self { diff --git a/src/detect-http-protocol.c b/src/detect-http-protocol.c index 3bf8d97c03d7..3e1a62ff5419 100644 --- a/src/detect-http-protocol.c +++ b/src/detect-http-protocol.c @@ -173,15 +173,15 @@ void DetectHttpProtocolRegister(void) HTP_RESPONSE_PROGRESS_LINE, DetectEngineInspectBufferGeneric, GetData); DetectAppLayerInspectEngineRegisterSubState(BUFFER_NAME, ALPROTO_HTTP2, SIG_FLAG_TOSERVER, - HTTP2TxTypeStream, HTTP2ProgStart, DetectEngineInspectBufferGeneric, GetData2); + HTTP2TxTypeStream, HTTP2ProgStarted, DetectEngineInspectBufferGeneric, GetData2); DetectAppLayerMpmRegisterSubState(BUFFER_NAME, SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister, GetData2, ALPROTO_HTTP2, HTTP2TxTypeStream, - HTTP2ProgStart); + HTTP2ProgStarted); DetectAppLayerInspectEngineRegisterSubState(BUFFER_NAME, ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, - HTTP2TxTypeStream, HTTP2ProgStart, DetectEngineInspectBufferGeneric, GetData2); + HTTP2TxTypeStream, HTTP2ProgStarted, DetectEngineInspectBufferGeneric, GetData2); DetectAppLayerMpmRegisterSubState(BUFFER_NAME, SIG_FLAG_TOCLIENT, 2, PrefilterGenericMpmRegister, GetData2, ALPROTO_HTTP2, HTTP2TxTypeStream, - HTTP2ProgStart); + HTTP2ProgStarted); DetectBufferTypeSetDescriptionByName(BUFFER_NAME, BUFFER_DESC); diff --git a/src/detect-http-stat-msg.c b/src/detect-http-stat-msg.c index ba24528e1126..6ebfbb758660 100644 --- a/src/detect-http-stat-msg.c +++ b/src/detect-http-stat-msg.c @@ -118,10 +118,10 @@ void DetectHttpStatMsgRegister (void) GetData, ALPROTO_HTTP1, HTP_RESPONSE_PROGRESS_LINE); DetectAppLayerInspectEngineRegisterSubState("http_stat_msg", ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, - HTTP2TxTypeStream, HTTP2ProgStart, DetectEngineInspectBufferGeneric, GetData2); + HTTP2TxTypeStream, HTTP2ProgStarted, DetectEngineInspectBufferGeneric, GetData2); DetectAppLayerMpmRegisterSubState("http_stat_msg", SIG_FLAG_TOCLIENT, 2, PrefilterGenericMpmRegister, GetData2, ALPROTO_HTTP2, HTTP2TxTypeStream, - HTTP2ProgStart); + HTTP2ProgStarted); DetectBufferTypeSetDescriptionByName("http_stat_msg", "http response status message"); From fc99592a4248ee0f6b60e041993be146707eeab8 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Thu, 2 Jul 2026 08:04:23 +0200 Subject: [PATCH 58/69] doh2: do not enable parser if http2 is disabled DOH2 depends on HTTP/2, so it makes no sense to enable it separately. It would also put the sub state handling in a weird state, as the DOH2 side reuses the registered HTTP/2 callbacks. (cherry picked from commit f545f06cf2b0d40a70f79384595a908e4a1f0043) --- rust/src/http2/http2.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs index 504434ed4aa2..b8af209e305c 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -1712,6 +1712,7 @@ const PARSER_NAME: &[u8] = b"http2\0"; #[no_mangle] pub unsafe extern "C" fn SCRegisterHttp2Parser() { + let mut http2_enabled = false; let default_port = CString::new("[80]").unwrap(); let mut parser = RustParser { name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, @@ -1754,6 +1755,7 @@ pub unsafe extern "C" fn SCRegisterHttp2Parser() { ALPROTO_HTTP2 = alproto; if SCAppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { let _ = AppLayerRegisterParser(&parser, alproto); + http2_enabled = true; } if let Some(val) = conf_get("app-layer.protocols.http2.max-streams") { if let Ok(v) = val.parse::() { @@ -1818,7 +1820,11 @@ pub unsafe extern "C" fn SCRegisterHttp2Parser() { let alproto = AppLayerRegisterProtocolDetection(&parser, 1); ALPROTO_DOH2 = alproto; if SCAppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { - let _ = AppLayerRegisterParser(&parser, alproto); + if http2_enabled { + let _ = AppLayerRegisterParser(&parser, alproto); + } else { + SCLogWarning!("DOH2 cannot be enabled if http2 is disabled"); + } } else { SCLogWarning!("DOH2 is not meant to be detection-only."); } From ee79d0d7c8bb080101e1b303122d518e5e69f45b Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Thu, 2 Jul 2026 10:13:00 +0200 Subject: [PATCH 59/69] app-layer: check if protocol is enabled for sub state callbacks (cherry picked from commit 9a294bf73b9fcc4b5dd089dc0b351fbcda187549) --- src/app-layer-parser.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/app-layer-parser.c b/src/app-layer-parser.c index 4a2aee67c293..eca1abbed875 100644 --- a/src/app-layer-parser.c +++ b/src/app-layer-parser.c @@ -1235,11 +1235,15 @@ int AppLayerParserGetStateProgressCompletionStatus(AppProto alproto, * registered callbacks. * * \retval -1 not found + * \retval -2 parser is not enabled * \retval id value belonging to the state name */ int8_t AppLayerParserGetSubStateProgressId( const AppProto alproto, const uint8_t sub_state, const char *state, const uint8_t dir_flag) { + if (!AppLayerParserIsEnabled(alproto)) + return -2; + if (alproto == ALPROTO_DOH2) return AppLayerParserGetSubStateProgressId(ALPROTO_HTTP2, sub_state, state, dir_flag); @@ -1270,6 +1274,9 @@ int8_t AppLayerParserGetSubStateProgressId( const char *AppLayerParserGetSubStateProgressName(const AppProto alproto, const uint8_t sub_state, const uint8_t state, const uint8_t dir_flag) { + if (!AppLayerParserIsEnabled(alproto)) + return NULL; + if (alproto == ALPROTO_DOH2) return AppLayerParserGetSubStateProgressName(ALPROTO_HTTP2, sub_state, state, dir_flag); @@ -1292,6 +1299,9 @@ const char *AppLayerParserGetSubStateProgressName(const AppProto alproto, const uint8_t AppLayerParserGetSubStateCompletion(const AppProto alproto, const uint8_t sub_state) { + if (!AppLayerParserIsEnabled(alproto)) + return 0; + if (alproto == ALPROTO_DOH2) return AppLayerParserGetSubStateCompletion(ALPROTO_HTTP2, sub_state); @@ -1312,6 +1322,9 @@ uint8_t AppLayerParserGetSubStateCompletion(const AppProto alproto, const uint8_ const char *AppLayerParserGetSubStateName(const AppProto alproto, const uint8_t sub_state) { + if (!AppLayerParserIsEnabled(alproto)) + return NULL; + if (alproto == ALPROTO_DOH2) return AppLayerParserGetSubStateName(ALPROTO_HTTP2, sub_state); From 0350bc4450a3e7314c7f5b15cc0c562e35a2389b Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Thu, 2 Jul 2026 14:15:28 +0200 Subject: [PATCH 60/69] detect/firewall: avoid passing state match for policy (cherry picked from commit c5d4a250b555d5e063c9a6f539c308608bdc6c8c) --- src/detect.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/detect.c b/src/detect.c index 6364c3dee675..cd5a51b0f200 100644 --- a/src/detect.c +++ b/src/detect.c @@ -1712,7 +1712,7 @@ static inline void DetectRunAppendDefaultAppPolicyAlert(DetectEngineThreadCtx *d const Signature *s = ap->alert_signature; BUG_ON(s == NULL); uint8_t alert_flags = apply_to_packet ? PACKET_ALERT_FLAG_APPLY_ACTION_TO_PACKET : 0; - AlertQueueAppendAppTx(det_ctx, s, p, tx->tx_id, tx->tx_type, alert_flags); + AlertQueueAppendAppTxFromPacket(det_ctx, s, p, tx->tx_id, tx->tx_type, alert_flags); } } From aaa49d992310d4cf3a7b21e3022e353668178afa Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Thu, 2 Jul 2026 14:47:26 +0200 Subject: [PATCH 61/69] detect: use sub state to select inspect engines When building the per signature app_inspect list, only add engines that match the sub state specified in the hook. (cherry picked from commit a824d97d71feca651d1ecb6cb4729361a583f80f) --- src/detect-engine.c | 5 +++++ src/detect-parse.c | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/detect-engine.c b/src/detect-engine.c index e0774e7f8baf..5d383f3f1698 100644 --- a/src/detect-engine.c +++ b/src/detect-engine.c @@ -739,6 +739,11 @@ static void AppendAppInspectEngine(DetectEngineCtx *de_ctx, if (!(AppProtoEqualsStrict(s->alproto, t->alproto))) { return; } + + /* skip engines not for us */ + if (s->init_data->hook.t.app.sub_state != t->sub_state) { + return; + } } else { /* other rules use the more relax AppProtoEquals logic */ if (!AppProtoEquals(s->alproto, t->alproto)) { diff --git a/src/detect-parse.c b/src/detect-parse.c index 1e2f7319b0b6..66b911636075 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -2853,6 +2853,8 @@ static int SigValidateCheckBuffers( if (!(AppProtoEqualsStrict(s->alproto, app->alproto))) { continue; } + if (app->sub_state != s->init_data->hook.t.app.sub_state) + continue; } else { if (!(AppProtoEquals(s->alproto, app->alproto) || s->alproto == 0)) { continue; From 33e0be0b4a8898ecd13682b0525939984ba3d93b Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Wed, 8 Jul 2026 11:35:28 +0200 Subject: [PATCH 62/69] output/tx: allow registering logging at substate Register HTTP/2 and DOH2 this way. (cherry picked from commit 89baff01c9d8efd161869f5f9cd6022f76cba082) --- src/output-json-dns.c | 8 ++++++-- src/output-tx.c | 43 ++++++++++++++++++++++++++++++++++++------- src/output-tx.h | 3 +++ src/output.c | 39 +++++++++++++++++++++++++++------------ src/output.h | 5 +++++ src/runmodes.c | 16 ++++++++++++---- 6 files changed, 89 insertions(+), 25 deletions(-) diff --git a/src/output-json-dns.c b/src/output-json-dns.c index 0a99f3eae37e..cc4e10f5db73 100644 --- a/src/output-json-dns.c +++ b/src/output-json-dns.c @@ -694,7 +694,11 @@ void JsonDnsLogRegister (void) void JsonDoh2LogRegister(void) { - OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonDoH2Log", "eve-log.doh2", - JsonDnsLogInitCtxSub, ALPROTO_DOH2, JsonDoh2Logger, LogDnsLogThreadInit, + OutputRegisterTxSubModuleWithProgressSubState(LOGGER_JSON_TX, "eve-log", "JsonDoH2Log::stream", + "eve-log.doh2", JsonDnsLogInitCtxSub, ALPROTO_DOH2, HTTP2TxTypeStream, JsonDoh2Logger, + HTTP2ProgData, HTTP2ProgData, LogDnsLogThreadInit, LogDnsLogThreadDeinit); + OutputRegisterTxSubModuleWithProgressSubState(LOGGER_JSON_TX, "eve-log", "LogDoh2Log::global", + "eve-log.doh2", JsonDnsLogInitCtxSub, ALPROTO_DOH2, HTTP2TxTypeGlobal, JsonDoh2Logger, + HTTP2ProgGlobalComplete, HTTP2ProgGlobalComplete, LogDnsLogThreadInit, LogDnsLogThreadDeinit); } diff --git a/src/output-tx.c b/src/output-tx.c index a77e39c26716..e9698a704e28 100644 --- a/src/output-tx.c +++ b/src/output-tx.c @@ -48,6 +48,7 @@ typedef struct OutputTxLoggerThreadData_ { * log module (e.g. http.log) with different output ctx'. */ typedef struct OutputTxLogger_ { AppProto alproto; + uint8_t sub_state; TxLogger LogFunc; TxLoggerCondition LogCondition; void *initdata; @@ -63,10 +64,13 @@ typedef struct OutputTxLogger_ { static OutputTxLogger **list = NULL; -int SCOutputRegisterTxLogger(LoggerId id, const char *name, AppProto alproto, TxLogger LogFunc, - void *initdata, int tc_log_progress, int ts_log_progress, TxLoggerCondition LogCondition, - ThreadInitFunc ThreadInit, ThreadDeinitFunc ThreadDeinit) +static int SCOutputRegisterTxLoggerInternal(LoggerId id, const char *name, AppProto alproto, + const uint8_t sub_state, TxLogger LogFunc, void *initdata, int tc_log_progress, + int ts_log_progress, TxLoggerCondition LogCondition, ThreadInitFunc ThreadInit, + ThreadDeinitFunc ThreadDeinit) { + BUG_ON(sub_state > 0 && (tc_log_progress < 0 || ts_log_progress < 0)); + if (list == NULL) { list = SCCalloc(g_alproto_max, sizeof(OutputTxLogger *)); if (unlikely(list == NULL)) { @@ -85,6 +89,7 @@ int SCOutputRegisterTxLogger(LoggerId id, const char *name, AppProto alproto, Tx return -1; op->alproto = alproto; + op->sub_state = sub_state; op->LogFunc = LogFunc; op->LogCondition = LogCondition; op->initdata = initdata; @@ -131,6 +136,23 @@ int SCOutputRegisterTxLogger(LoggerId id, const char *name, AppProto alproto, Tx return 0; } +int SCOutputRegisterTxLogger(LoggerId id, const char *name, AppProto alproto, TxLogger LogFunc, + void *initdata, int tc_log_progress, int ts_log_progress, TxLoggerCondition LogCondition, + ThreadInitFunc ThreadInit, ThreadDeinitFunc ThreadDeinit) +{ + return SCOutputRegisterTxLoggerInternal(id, name, alproto, 0, LogFunc, initdata, + tc_log_progress, ts_log_progress, LogCondition, ThreadInit, ThreadDeinit); +} + +int SCOutputRegisterTxLoggerForSubState(LoggerId id, const char *name, AppProto alproto, + const uint8_t sub_state, TxLogger LogFunc, void *initdata, int tc_log_progress, + int ts_log_progress, TxLoggerCondition LogCondition, ThreadInitFunc ThreadInit, + ThreadDeinitFunc ThreadDeinit) +{ + return SCOutputRegisterTxLoggerInternal(id, name, alproto, sub_state, LogFunc, initdata, + tc_log_progress, ts_log_progress, LogCondition, ThreadInit, ThreadDeinit); +} + extern bool g_file_logger_enabled; extern bool g_filedata_logger_enabled; @@ -274,8 +296,8 @@ struct Ctx { static void OutputTxLogCallLoggers(ThreadVars *tv, OutputTxLoggerThreadData *op_thread_data, const OutputTxLogger *logger, const OutputLoggerThreadStore *store, Packet *p, Flow *f, - void *alstate, void *tx, const uint64_t tx_id, const AppProto alproto, const bool eof, - const int tx_progress_ts, const int tx_progress_tc, struct Ctx *ctx) + void *alstate, void *tx, const uint64_t tx_id, AppLayerTxData *txd, const AppProto alproto, + const bool eof, const int tx_progress_ts, const int tx_progress_tc, struct Ctx *ctx) { DEBUG_VALIDATE_BUG_ON(logger == NULL && store != NULL); DEBUG_VALIDATE_BUG_ON(logger != NULL && store == NULL); @@ -295,6 +317,13 @@ static void OutputTxLogCallLoggers(ThreadVars *tv, OutputTxLoggerThreadData *op_ SCLogDebug("pcap_cnt %" PRIu64 ", tx_id %" PRIu64 " logger %d. EOF %s", p->pcap_cnt, tx_id, logger->logger_id, eof ? "true" : "false"); + if (logger->sub_state != txd->tx_type) { + SCLogDebug("logger:%s flow:%s: skip logger for wrong sub state: logger %u tx %u", + AppProtoToString(logger->alproto), AppProtoToString(alproto), + logger->sub_state, txd->tx_type); + goto next_logger; + } + if (eof) { SCLogDebug("EOF, so log now"); } else { @@ -503,8 +532,8 @@ static TmEcode OutputTxLog(ThreadVars *tv, Packet *p, void *thread_data) struct Ctx ctx = { .tx_logged = txd->logged.flags, .tx_logged_old = txd->logged.flags }; SCLogDebug("logger: expect %08x, have %08x", logger_expectation, ctx.tx_logged); - OutputTxLogCallLoggers(tv, op_thread_data, logger, store, p, f, alstate, tx, tx_id, alproto, - eof, tx_progress_ts, tx_progress_tc, &ctx); + OutputTxLogCallLoggers(tv, op_thread_data, logger, store, p, f, alstate, tx, tx_id, txd, + alproto, eof, tx_progress_ts, tx_progress_tc, &ctx); SCLogDebug("logger: expect %08x, have %08x", logger_expectation, ctx.tx_logged); if (ctx.tx_logged != ctx.tx_logged_old) { diff --git a/src/output-tx.h b/src/output-tx.h index 8bf52d4c5780..556e02b8322c 100644 --- a/src/output-tx.h +++ b/src/output-tx.h @@ -77,6 +77,9 @@ typedef bool (*TxLoggerCondition)( int SCOutputRegisterTxLogger(LoggerId id, const char *name, AppProto alproto, TxLogger LogFunc, void *, int tc_log_progress, int ts_log_progress, TxLoggerCondition LogCondition, ThreadInitFunc, ThreadDeinitFunc); +int SCOutputRegisterTxLoggerForSubState(LoggerId id, const char *name, AppProto alproto, + const uint8_t sub_state, TxLogger LogFunc, void *, int tc_log_progress, int ts_log_progress, + TxLoggerCondition LogCondition, ThreadInitFunc, ThreadDeinitFunc); /** Internal function: private API. */ void OutputTxLoggerRegister (void); diff --git a/src/output.c b/src/output.c index a608ff58e4ac..a3357be9c511 100644 --- a/src/output.c +++ b/src/output.c @@ -302,9 +302,9 @@ static void OutputRegisterTxModuleWrapper(LoggerId id, const char *name, const c } static void OutputRegisterTxSubModuleWrapper(LoggerId id, const char *parent_name, const char *name, - const char *conf_name, OutputInitSubFunc InitFunc, AppProto alproto, TxLogger TxLogFunc, - int tc_log_progress, int ts_log_progress, TxLoggerCondition TxLogCondition, - ThreadInitFunc ThreadInit, ThreadDeinitFunc ThreadDeinit) + const char *conf_name, OutputInitSubFunc InitFunc, AppProto alproto, + const uint8_t sub_state, TxLogger TxLogFunc, int tc_log_progress, int ts_log_progress, + TxLoggerCondition TxLogCondition, ThreadInitFunc ThreadInit, ThreadDeinitFunc ThreadDeinit) { if (unlikely(TxLogFunc == NULL)) { goto error; @@ -323,6 +323,7 @@ static void OutputRegisterTxSubModuleWrapper(LoggerId id, const char *parent_nam module->TxLogFunc = TxLogFunc; module->TxLogCondition = TxLogCondition; module->alproto = alproto; + module->sub_state = sub_state; module->tc_log_progress = tc_log_progress; module->ts_log_progress = ts_log_progress; module->ThreadInit = ThreadInit; @@ -355,8 +356,8 @@ void OutputRegisterTxSubModuleWithCondition(LoggerId id, const char *parent_name const char *conf_name, OutputInitSubFunc InitFunc, AppProto alproto, TxLogger TxLogFunc, TxLoggerCondition TxLogCondition, ThreadInitFunc ThreadInit, ThreadDeinitFunc ThreadDeinit) { - OutputRegisterTxSubModuleWrapper(id, parent_name, name, conf_name, InitFunc, alproto, TxLogFunc, - -1, -1, TxLogCondition, ThreadInit, ThreadDeinit); + OutputRegisterTxSubModuleWrapper(id, parent_name, name, conf_name, InitFunc, alproto, 0, + TxLogFunc, -1, -1, TxLogCondition, ThreadInit, ThreadDeinit); } /** @@ -380,8 +381,17 @@ void OutputRegisterTxSubModuleWithProgress(LoggerId id, const char *parent_name, int tc_log_progress, int ts_log_progress, ThreadInitFunc ThreadInit, ThreadDeinitFunc ThreadDeinit) { - OutputRegisterTxSubModuleWrapper(id, parent_name, name, conf_name, InitFunc, alproto, TxLogFunc, - tc_log_progress, ts_log_progress, NULL, ThreadInit, ThreadDeinit); + OutputRegisterTxSubModuleWrapper(id, parent_name, name, conf_name, InitFunc, alproto, 0, + TxLogFunc, tc_log_progress, ts_log_progress, NULL, ThreadInit, ThreadDeinit); +} + +void OutputRegisterTxSubModuleWithProgressSubState(LoggerId id, const char *parent_name, + const char *name, const char *conf_name, OutputInitSubFunc InitFunc, AppProto alproto, + const uint8_t sub_state, TxLogger TxLogFunc, uint8_t tc_log_progress, + uint8_t ts_log_progress, ThreadInitFunc ThreadInit, ThreadDeinitFunc ThreadDeinit) +{ + OutputRegisterTxSubModuleWrapper(id, parent_name, name, conf_name, InitFunc, alproto, sub_state, + TxLogFunc, tc_log_progress, ts_log_progress, NULL, ThreadInit, ThreadDeinit); } /** @@ -404,8 +414,8 @@ void OutputRegisterTxSubModule(LoggerId id, const char *parent_name, const char const char *conf_name, OutputInitSubFunc InitFunc, AppProto alproto, TxLogger TxLogFunc, ThreadInitFunc ThreadInit, ThreadDeinitFunc ThreadDeinit) { - OutputRegisterTxSubModuleWrapper(id, parent_name, name, conf_name, InitFunc, alproto, TxLogFunc, - -1, -1, NULL, ThreadInit, ThreadDeinit); + OutputRegisterTxSubModuleWrapper(id, parent_name, name, conf_name, InitFunc, alproto, 0, + TxLogFunc, -1, -1, NULL, ThreadInit, ThreadDeinit); } /** @@ -1090,9 +1100,14 @@ void OutputRegisterLoggers(void) /* http log */ LogHttpLogRegister(); JsonHttpLogRegister(); - OutputRegisterTxSubModuleWithProgress(LOGGER_JSON_TX, "eve-log", "LogHttp2Log", "eve-log.http2", - OutputJsonLogInitSub, ALPROTO_HTTP2, JsonGenericDirFlowLogger, HTTP2ProgClosed, - HTTP2ProgClosed, JsonLogThreadInit, JsonLogThreadDeinit); + OutputRegisterTxSubModuleWithProgressSubState(LOGGER_JSON_TX, "eve-log", "LogHttp2Log::stream", + "eve-log.http2", OutputJsonLogInitSub, ALPROTO_HTTP2, HTTP2TxTypeStream, + JsonGenericDirFlowLogger, HTTP2ProgClosed, HTTP2ProgClosed, JsonLogThreadInit, + JsonLogThreadDeinit); + OutputRegisterTxSubModuleWithProgressSubState(LOGGER_JSON_TX, "eve-log", "LogHttp2Log::global", + "eve-log.http2", OutputJsonLogInitSub, ALPROTO_HTTP2, HTTP2TxTypeGlobal, + JsonGenericDirFlowLogger, HTTP2ProgGlobalComplete, HTTP2ProgGlobalComplete, + JsonLogThreadInit, JsonLogThreadDeinit); /* tls log */ LogTlsLogRegister(); JsonTlsLogRegister(); diff --git a/src/output.h b/src/output.h index c09e13a48d7b..79ac966f9394 100644 --- a/src/output.h +++ b/src/output.h @@ -74,6 +74,7 @@ typedef struct OutputModule_ { SCStreamingLogger StreamingLogFunc; StatsLogger StatsLogFunc; AppProto alproto; + uint8_t sub_state; enum SCOutputStreamingType stream_type; int tc_log_progress; int ts_log_progress; @@ -121,6 +122,10 @@ void OutputRegisterTxSubModuleWithProgress(LoggerId id, const char *parent_name, const char *conf_name, OutputInitSubFunc InitFunc, AppProto alproto, TxLogger TxLogFunc, int tc_log_progress, int ts_log_progress, ThreadInitFunc ThreadInit, ThreadDeinitFunc ThreadDeinit); +void OutputRegisterTxSubModuleWithProgressSubState(LoggerId id, const char *parent_name, + const char *name, const char *conf_name, OutputInitSubFunc InitFunc, AppProto alproto, + const uint8_t sub_state, TxLogger TxLogFunc, uint8_t tc_log_progress, + uint8_t ts_log_progress, ThreadInitFunc ThreadInit, ThreadDeinitFunc ThreadDeinit); void OutputRegisterFileSubModule(LoggerId id, const char *parent_name, const char *name, const char *conf_name, OutputInitSubFunc InitFunc, SCFileLogger FileLogFunc, diff --git a/src/runmodes.c b/src/runmodes.c index 05c5e925ef7a..d171dca750c6 100644 --- a/src/runmodes.c +++ b/src/runmodes.c @@ -634,10 +634,18 @@ static void SetupOutput( SCOutputRegisterPacketLogger(module->logger_id, module->name, module->PacketLogFunc, module->PacketConditionFunc, output_ctx, module->ThreadInit, module->ThreadDeinit); } else if (module->TxLogFunc) { - SCLogDebug("%s is a tx logger", module->name); - SCOutputRegisterTxLogger(module->logger_id, module->name, module->alproto, - module->TxLogFunc, output_ctx, module->tc_log_progress, module->ts_log_progress, - module->TxLogCondition, module->ThreadInit, module->ThreadDeinit); + if (module->sub_state == 0) { + SCLogDebug("%s is a tx logger", module->name); + SCOutputRegisterTxLogger(module->logger_id, module->name, module->alproto, + module->TxLogFunc, output_ctx, module->tc_log_progress, module->ts_log_progress, + module->TxLogCondition, module->ThreadInit, module->ThreadDeinit); + } else { + SCLogDebug("%s is a tx logger for sub state %u", module->name, module->sub_state); + SCOutputRegisterTxLoggerForSubState(module->logger_id, module->name, module->alproto, + module->sub_state, module->TxLogFunc, output_ctx, module->tc_log_progress, + module->ts_log_progress, module->TxLogCondition, module->ThreadInit, + module->ThreadDeinit); + } /* Not used with wild card loggers */ if (module->alproto != ALPROTO_UNKNOWN) { logger_bits[module->alproto] |= BIT_U32(module->logger_id); From 50536346e405c957911347276a6d8e4cb6a96ca5 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Thu, 9 Jul 2026 10:19:35 +0200 Subject: [PATCH 63/69] detect: strict validation of buffers Make sure that every buffer in a signature is actually used in the setup of the engines. Reject sigs that are locked to a certain substate and use buffers that require another. (cherry picked from commit 8a5e8c63b286822e8fae7c9a1df7112a22ace9c7) --- src/detect-parse.c | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/src/detect-parse.c b/src/detect-parse.c index 66b911636075..48e1ee9a5c4d 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -2842,21 +2842,42 @@ static int SigValidateCheckBuffers( SCReturnInt(0); } + uint32_t app_buffers_evaluated = 0; + bool buffer_consumed = false; + uint32_t buffer_skip_alproto = 0; + uint32_t buffer_skip_substate = 0; const DetectEngineAppInspectionEngine *app = de_ctx->app_inspect_engines; for (; app != NULL; app = app->next) { if (app->sm_list != b->id) continue; + app_buffers_evaluated++; if (s->init_data->hook.type == SIGNATURE_HOOK_TYPE_APP) { /* only allow rules to use the hook for engines at that - * exact progress for now. */ - if (!(AppProtoEqualsStrict(s->alproto, app->alproto))) { + * exact progress for now. We make an exception for generic + * engines like app-layer-event. */ + if (!(AppProtoEqualsStrict(s->alproto, app->alproto) || + app->alproto == ALPROTO_UNKNOWN)) { + SCLogDebug("%u:%s: for buffer %s skip engine %s alproto %s", s->id, + AppProtoToString(s->alproto), bt->name, + DetectEngineBufferTypeGetNameById(de_ctx, app->sm_list), + AppProtoToString(app->alproto)); + buffer_skip_alproto++; continue; } - if (app->sub_state != s->init_data->hook.t.app.sub_state) + if (app->alproto != ALPROTO_UNKNOWN && + app->sub_state != s->init_data->hook.t.app.sub_state) { + buffer_skip_substate++; continue; + } } else { - if (!(AppProtoEquals(s->alproto, app->alproto) || s->alproto == 0)) { + if (!(AppProtoEquals(s->alproto, app->alproto) || s->alproto == ALPROTO_UNKNOWN || + app->alproto == ALPROTO_UNKNOWN)) { + SCLogDebug("%u:%s: for buffer %s skip engine %s alproto %s", s->id, + AppProtoToString(s->alproto), bt->name, + DetectEngineBufferTypeGetNameById(de_ctx, app->sm_list), + AppProtoToString(app->alproto)); + buffer_skip_alproto++; continue; } } @@ -2890,8 +2911,15 @@ static int SigValidateCheckBuffers( SCReturnInt(0); } } - } + buffer_consumed = true; + } + if (app_buffers_evaluated && !buffer_consumed) { + SCLogError("incompatible rule conditions, skipped buffer %s, reasons: app proto %u sub " + "state %u", + bt->name, buffer_skip_alproto, buffer_skip_substate); + SCReturnInt(0); + } if (!DetectEngineBufferRunValidateCallback(de_ctx, b->id, s, &de_ctx->sigerror)) { SCReturnInt(0); } From 94296e87859b4caa651c545e17f4b1463772405c Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Thu, 9 Jul 2026 14:03:12 +0200 Subject: [PATCH 64/69] app-layer: add substate support to list hooks Add substates to the --list-app-layer-hooks option. (cherry picked from commit 8455efd9ac9be052f5f5424805559611e8531b16) --- src/util-running-modes.c | 74 ++++++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 26 deletions(-) diff --git a/src/util-running-modes.c b/src/util-running-modes.c index 50b4a3791b0c..29bfd5c263d8 100644 --- a/src/util-running-modes.c +++ b/src/util-running-modes.c @@ -83,35 +83,57 @@ int ListAppLayerHooks(const char *conf_filename) if (alprotos[a] != 1) continue; - const char *alproto_name = AppProtoToString(a); - if (strcmp(alproto_name, "http") == 0) - alproto_name = "http1"; - SCLogDebug("alproto %u/%s", a, alproto_name); - - const int max_progress_ts = - AppLayerParserGetStateProgressCompletionStatus(a, STREAM_TOSERVER); - const int max_progress_tc = - AppLayerParserGetStateProgressCompletionStatus(a, STREAM_TOCLIENT); - - printf("%s:%s\n", alproto_name, "request_started"); - for (int p = 0; p <= max_progress_ts; p++) { - const char *name = AppLayerParserGetStateNameById( - IPPROTO_TCP /* TODO no ipproto */, a, p, STREAM_TOSERVER); - if (name != NULL && !IsBuiltIn(name)) { - printf("%s:%s\n", alproto_name, name); + if (AppLayerParserSupportsSubStates(a)) { + const uint8_t max_sub_state = AppLayerParserGetMaxSubState(a); + for (uint8_t sub_state = 1; sub_state <= max_sub_state; sub_state++) { + const char *sub_state_name = AppLayerParserGetSubStateName(a, sub_state); + const uint8_t max_progress = AppLayerParserGetSubStateCompletion(a, sub_state); + for (uint8_t state = 0; state <= max_progress; state++) { + const char *name = AppLayerParserGetSubStateProgressName( + a, sub_state, state, STREAM_TOSERVER); + if (name != NULL) { + printf("%s:%s:%s\n", AppProtoToString(a), sub_state_name, name); + } + } + for (uint8_t state = 0; state <= max_progress; state++) { + const char *name = AppLayerParserGetSubStateProgressName( + a, sub_state, state, STREAM_TOCLIENT); + if (name != NULL) { + printf("%s:%s:%s\n", AppProtoToString(a), sub_state_name, name); + } + } } - } - printf("%s:%s\n", alproto_name, "request_complete"); - - printf("%s:%s\n", alproto_name, "response_started"); - for (int p = 0; p <= max_progress_tc; p++) { - const char *name = AppLayerParserGetStateNameById( - IPPROTO_TCP /* TODO no ipproto */, a, p, STREAM_TOCLIENT); - if (name != NULL && !IsBuiltIn(name)) { - printf("%s:%s\n", alproto_name, name); + } else { + const char *alproto_name = AppProtoToString(a); + if (strcmp(alproto_name, "http") == 0) + alproto_name = "http1"; + SCLogDebug("alproto %u/%s", a, alproto_name); + + const int max_progress_ts = + AppLayerParserGetStateProgressCompletionStatus(a, STREAM_TOSERVER); + const int max_progress_tc = + AppLayerParserGetStateProgressCompletionStatus(a, STREAM_TOCLIENT); + + printf("%s:%s\n", alproto_name, "request_started"); + for (int p = 0; p <= max_progress_ts; p++) { + const char *name = AppLayerParserGetStateNameById( + IPPROTO_TCP /* TODO no ipproto */, a, p, STREAM_TOSERVER); + if (name != NULL && !IsBuiltIn(name)) { + printf("%s:%s\n", alproto_name, name); + } + } + printf("%s:%s\n", alproto_name, "request_complete"); + + printf("%s:%s\n", alproto_name, "response_started"); + for (int p = 0; p <= max_progress_tc; p++) { + const char *name = AppLayerParserGetStateNameById( + IPPROTO_TCP /* TODO no ipproto */, a, p, STREAM_TOCLIENT); + if (name != NULL && !IsBuiltIn(name)) { + printf("%s:%s\n", alproto_name, name); + } } + printf("%s:%s\n", alproto_name, "response_complete"); } - printf("%s:%s\n", alproto_name, "response_complete"); } return TM_ECODE_DONE; } From f1775f1e90cf46bc37bcfb5dc9cee2a41f879e1d Mon Sep 17 00:00:00 2001 From: Juliana Fajardini Date: Fri, 29 May 2026 12:25:03 -0300 Subject: [PATCH 65/69] yaml/firewall: expand firewall options explanation (cherry picked from commit f8945e7a1a8138acd36326cdad1327e54876a496) --- suricata.yaml.in | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/suricata.yaml.in b/suricata.yaml.in index 63c00cf69776..2ba4d6f089fd 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -2353,6 +2353,20 @@ firewall: # in order and rules are applied in that order (per state, see docs) #rule-files: # - firewall.rules + # + + # Default policies + # + # Choose a default policy for each firewall hook. + # It is also possible to specify policies by app-layer protocol. + # DNS example: Drop and alert on all DNS requests that are not allowed in firewall.rules, accept all responses. + # + #policies: + # packet-filter: ["drop:packet"] + # dns: + # request-started: ["accept:hook"] + # request-complete: ["drop:flow", "alert"] + # response-started: ["accept:tx"] ## ## Include other configs From 3c04da1e399c6b50d24d16ff584809cc038d9339 Mon Sep 17 00:00:00 2001 From: Lukas Sismis Date: Tue, 28 Jul 2026 18:08:32 +0200 Subject: [PATCH 66/69] detect/firewall: address HTTP/1 policies as http1 AppProtoToString(ALPROTO_HTTP1) returns "http", so an HTTP/1 policy had to be written as `http:` while its rule hooks were already spelled `http1:`. Use the same name in both places. Ticket: 8770 (cherry picked from commit 5391c4427ba7e34b574425c4cf6222b2e0a0feb0) --- doc/userguide/firewall/firewall-design.rst | 2 +- doc/userguide/firewall/firewall-example.rst | 2 +- src/detect-parse.c | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/userguide/firewall/firewall-design.rst b/doc/userguide/firewall/firewall-design.rst index d16c662fd03e..64f993d9cf36 100644 --- a/doc/userguide/firewall/firewall-design.rst +++ b/doc/userguide/firewall/firewall-design.rst @@ -62,7 +62,7 @@ Application layer tables ~~~~~~~~~~~~~~~~~~~~~~~~ If applayer is available, rules from the following tables apply. The tables for the -application layer are per app layer protocol and per protocol state. e.g. ``http:request_line``. +application layer are per app layer protocol and per protocol state. e.g. ``http1:request_line``. .. table:: diff --git a/doc/userguide/firewall/firewall-example.rst b/doc/userguide/firewall/firewall-example.rst index 7bff082d1d44..e4131e27a4f6 100644 --- a/doc/userguide/firewall/firewall-example.rst +++ b/doc/userguide/firewall/firewall-example.rst @@ -62,7 +62,7 @@ HTTP example with partially using default policies firewall: policies: - http: + http1: request-started: - "accept:hook" request-line: diff --git a/src/detect-parse.c b/src/detect-parse.c index 48e1ee9a5c4d..d1653e99a1d3 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -4107,7 +4107,7 @@ static int DoParseAppSubStatePolicy(const char *prefix, const AppProto app_proto nname[i] = '-'; } - const char *app_name = AppProtoToString(app_proto); + const char *app_name = (app_proto == ALPROTO_HTTP1) ? "http1" : AppProtoToString(app_proto); int r = snprintf(policy_name, sizeof(policy_name), "%s.%s.%s.%s", prefix, app_name, sub_state_name, nname); SCLogDebug("policy_name %s", policy_name); @@ -4176,7 +4176,7 @@ static int DoParseAppPolicy(const char *prefix, const AppProto app_proto, const nname[i] = '-'; } - const char *app_name = AppProtoToString(app_proto); + const char *app_name = (app_proto == ALPROTO_HTTP1) ? "http1" : AppProtoToString(app_proto); int r = snprintf(policy_name, sizeof(policy_name), "%s.%s.%s", prefix, app_name, nname); SCFree(nname); if (r < 0 || (size_t)r >= sizeof(policy_name)) { From 488a6dacb4f81d4c9d9bec78291f722411f561e2 Mon Sep 17 00:00:00 2001 From: Lukas Sismis Date: Tue, 28 Jul 2026 18:09:29 +0200 Subject: [PATCH 67/69] detect/firewall: group policies under packet and app nodes The policy config was a flat map mixing packet hooks and app-layer protocols: `packet-filter` next to `dns`. There was no node that meant "the packet hooks" or "the app-layer hooks", so a setting could not be scoped to one group. Move each group under its own node: packet-filter -> packet.filter packet-pre-flow -> packet.pre-flow packet-pre-stream -> packet.pre-stream . -> app.. Ticket: 8770 (cherry picked from commit 2b17b7ee838af69325a8298b4a5d15bb649041fb) --- doc/userguide/firewall/firewall-design.rst | 27 +++++----- doc/userguide/firewall/firewall-example.rst | 59 +++++++++++---------- src/detect-parse.c | 12 ++--- suricata.yaml.in | 12 +++-- 4 files changed, 58 insertions(+), 52 deletions(-) diff --git a/doc/userguide/firewall/firewall-design.rst b/doc/userguide/firewall/firewall-design.rst index 64f993d9cf36..fd4e19cc2783 100644 --- a/doc/userguide/firewall/firewall-design.rst +++ b/doc/userguide/firewall/firewall-design.rst @@ -327,28 +327,31 @@ of :ref:`engine analysis`. Default policies ================ -Each hook has a default policy. By default ``packet:filter`` enforces a ``drop:packet`` policy and the -``app:filter`` hooks applies ``drop:flow``. +Each hook has a default policy. By default ``packet.filter`` enforces a ``drop:packet`` policy and the +``app`` hooks apply ``drop:flow``. -The policies can be configured in ``firewall`` block in the config. +The policies can be configured in ``firewall`` block in the config. Packet hooks +live under ``packet`` and app-layer hooks under ``app``, keyed by protocol. -Example for ``packet:filter``, to use reject instead of drop:: +Example for ``packet.filter``, to use reject instead of drop:: firewall: policies: - packet-filter: [ "reject:packet" ] + packet: + filter: [ "reject:packet" ] Example for DNS:: firewall: policies: - dns: - request-started: ["accept:hook"] + app: + dns: + request-started: ["accept:hook"] - # Drop and alert on all DNS requests that are not allowed in - # firewall.rules. - request-complete: ["drop:flow", "alert"] + # Drop and alert on all DNS requests that are not allowed in + # firewall.rules. + request-complete: ["drop:flow", "alert"] - # Accept all responses. - response-started: ["accept:tx"] + # Accept all responses. + response-started: ["accept:tx"] diff --git a/doc/userguide/firewall/firewall-example.rst b/doc/userguide/firewall/firewall-example.rst index e4131e27a4f6..ebae32a8d857 100644 --- a/doc/userguide/firewall/firewall-example.rst +++ b/doc/userguide/firewall/firewall-example.rst @@ -62,35 +62,36 @@ HTTP example with partially using default policies firewall: policies: - http1: - request-started: - - "accept:hook" - request-line: - - "drop:flow" - - "alert" - request-headers: - - "drop:flow" - - "alert" - request-body: - - "accept:hook" - request-trailer: - - "accept:hook" - request-complete: - - "accept:hook" - - response-started: - - "accept:hook" - response-line: - - "drop:flow" - - "alert" - response-headers: - - "accept:hook" - response-body: - - "accept:hook" - response-trailer: - - "accept:hook" - response-complete: - - "accept:hook" + app: + http1: + request-started: + - "accept:hook" + request-line: + - "drop:flow" + - "alert" + request-headers: + - "drop:flow" + - "alert" + request-body: + - "accept:hook" + request-trailer: + - "accept:hook" + request-complete: + - "accept:hook" + + response-started: + - "accept:hook" + response-line: + - "drop:flow" + - "alert" + response-headers: + - "accept:hook" + response-body: + - "accept:hook" + response-trailer: + - "accept:hook" + response-complete: + - "accept:hook" :: diff --git a/src/detect-parse.c b/src/detect-parse.c index d1653e99a1d3..de5faefb11b9 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -4108,7 +4108,7 @@ static int DoParseAppSubStatePolicy(const char *prefix, const AppProto app_proto } const char *app_name = (app_proto == ALPROTO_HTTP1) ? "http1" : AppProtoToString(app_proto); - int r = snprintf(policy_name, sizeof(policy_name), "%s.%s.%s.%s", prefix, app_name, + int r = snprintf(policy_name, sizeof(policy_name), "%s.app.%s.%s.%s", prefix, app_name, sub_state_name, nname); SCLogDebug("policy_name %s", policy_name); SCFree(nname); @@ -4177,7 +4177,7 @@ static int DoParseAppPolicy(const char *prefix, const AppProto app_proto, const } const char *app_name = (app_proto == ALPROTO_HTTP1) ? "http1" : AppProtoToString(app_proto); - int r = snprintf(policy_name, sizeof(policy_name), "%s.%s.%s", prefix, app_name, nname); + int r = snprintf(policy_name, sizeof(policy_name), "%s.app.%s.%s", prefix, app_name, nname); SCFree(nname); if (r < 0 || (size_t)r >= sizeof(policy_name)) { FatalError("internal error: failed to assemble firewall policy config string"); @@ -4211,7 +4211,7 @@ static int DoParseAppPolicy(const char *prefix, const AppProto app_proto, const } if (hookname == NULL) return 0; - r = snprintf(policy_name, sizeof(policy_name), "%s.%s.%s", prefix, app_name, hookname); + r = snprintf(policy_name, sizeof(policy_name), "%s.app.%s.%s", prefix, app_name, hookname); if (r < 0 || (size_t)r >= sizeof(policy_name)) { FatalError("internal error: failed to assemble firewall policy config string"); } @@ -4275,7 +4275,7 @@ int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *de_ctx) if (fw_policies == NULL) return -1; - r = snprintf(policy_name, sizeof(policy_name), "%s.packet-filter", prefix); + r = snprintf(policy_name, sizeof(policy_name), "%s.packet.filter", prefix); if (r < 0 || (size_t)r >= sizeof(policy_name)) { FatalError("internal error: failed to assemble firewall policy config string"); } @@ -4288,7 +4288,7 @@ int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *de_ctx) DETECT_FIREWALL_POLICY_PACKET_FILTER) < 0) return -1; - r = snprintf(policy_name, sizeof(policy_name), "%s.packet-pre-flow", prefix); + r = snprintf(policy_name, sizeof(policy_name), "%s.packet.pre-flow", prefix); if (r < 0 || (size_t)r >= sizeof(policy_name)) { FatalError("internal error: failed to assemble firewall policy config string"); } @@ -4300,7 +4300,7 @@ int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *de_ctx) DETECT_FIREWALL_POLICY_PRE_FLOW) < 0) return -1; - r = snprintf(policy_name, sizeof(policy_name), "%s.packet-pre-stream", prefix); + r = snprintf(policy_name, sizeof(policy_name), "%s.packet.pre-stream", prefix); if (r < 0 || (size_t)r >= sizeof(policy_name)) { FatalError("internal error: failed to assemble firewall policy config string"); } diff --git a/suricata.yaml.in b/suricata.yaml.in index 2ba4d6f089fd..b4318e0c4dfa 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -2362,11 +2362,13 @@ firewall: # DNS example: Drop and alert on all DNS requests that are not allowed in firewall.rules, accept all responses. # #policies: - # packet-filter: ["drop:packet"] - # dns: - # request-started: ["accept:hook"] - # request-complete: ["drop:flow", "alert"] - # response-started: ["accept:tx"] + # packet: + # filter: ["drop:packet"] + # app: + # dns: + # request-started: ["accept:hook"] + # request-complete: ["drop:flow", "alert"] + # response-started: ["accept:tx"] ## ## Include other configs From 7da45e2fad30a55e36d7979a7f90286dc387d383 Mon Sep 17 00:00:00 2001 From: Lukas Sismis Date: Tue, 28 Jul 2026 18:09:52 +0200 Subject: [PATCH 68/69] detect/firewall: add default-policy to policy config Every hook has a built-in default policy, but expressing anything other than the built-in meant naming each hook explicitly. Add a `default-policy` setting that covers all hooks below it, so unlisted hooks still get a policy. For any hook the most specific setting present wins: app... app...default-policy app..default-policy app.default-policy default-policy built-in The packet hooks follow the same pattern under `packet`. Resolution moves into ResolveFirewallPolicy(), which walks the candidate paths most-specific-first and stops at the first one that is configured. A path that is present but empty is now a startup error rather than being treated as unset. DoParseAppSubStatePolicy() collapses into DoParseAppPolicy() as a sub state hook only differs by an extra path segment. Path assembly and hook-name normalisation move to helpers now that both are needed in more places. Ticket: 8770 (cherry picked from commit b5ced77f2628d9371234ea5837761093df0879f4) --- doc/userguide/firewall/firewall-design.rst | 56 ++-- src/detect-parse.c | 330 +++++++++++---------- suricata.yaml.in | 6 +- 3 files changed, 223 insertions(+), 169 deletions(-) diff --git a/doc/userguide/firewall/firewall-design.rst b/doc/userguide/firewall/firewall-design.rst index fd4e19cc2783..ecc33cd39397 100644 --- a/doc/userguide/firewall/firewall-design.rst +++ b/doc/userguide/firewall/firewall-design.rst @@ -327,31 +327,51 @@ of :ref:`engine analysis`. Default policies ================ -Each hook has a default policy. By default ``packet.filter`` enforces a ``drop:packet`` policy and the -``app`` hooks apply ``drop:flow``. +Each hook has a default policy applied to traffic that no firewall rule handled. +By default ``packet.filter`` enforces ``drop:packet``, ``packet.pre-flow`` and +``packet.pre-stream`` enforce ``accept:hook``, and every ``app`` hook enforces +``drop:flow``. -The policies can be configured in ``firewall`` block in the config. Packet hooks -live under ``packet`` and app-layer hooks under ``app``, keyed by protocol. - -Example for ``packet.filter``, to use reject instead of drop:: +Defaults are configured in the ``firewall.policies`` block. A ``default-policy`` +may be given at several levels; for any hook the most specific present setting +wins:: firewall: policies: + default-policy: ["accept:hook"] # global fallback (all hooks) packet: - filter: [ "reject:packet" ] - - -Example for DNS:: - - firewall: - policies: + default-policy: ["drop:packet"] # fallback for packet hooks + filter: ["drop:packet"] + pre-flow: ["accept:hook"] + pre-stream: ["accept:hook"] app: + default-policy: ["drop:flow"] # fallback for all app hooks dns: + default-policy: ["drop:flow"] # fallback for dns hooks request-started: ["accept:hook"] - - # Drop and alert on all DNS requests that are not allowed in - # firewall.rules. request-complete: ["drop:flow", "alert"] - - # Accept all responses. response-started: ["accept:tx"] + +Protocols whose hooks are grouped into sub states, such as HTTP/2, take an extra +level for the sub state name:: + + firewall: + policies: + app: + http2: + default-policy: ["drop:flow"] # fallback for all http2 hooks + stream: + default-policy: ["drop:flow"] # fallback for http2 stream hooks + request-started: ["accept:hook"] + global: + request-started: ["accept:hook"] + +Precedence: + +* packet hook: ``packet.`` > ``packet.default-policy`` > + ``policies.default-policy`` > built-in (``drop:packet`` or ``accept:hook``) +* app hook: ``app..`` > ``app..default-policy`` > + ``app.default-policy`` > ``policies.default-policy`` > built-in (``drop:flow``) +* app hook in a sub state: ``app...`` > + ``app...default-policy`` > ``app..default-policy`` > + ``app.default-policy`` > ``policies.default-policy`` > built-in (``drop:flow``) diff --git a/src/detect-parse.c b/src/detect-parse.c index de5faefb11b9..110cc98f18f3 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -4090,134 +4090,161 @@ static int DoParsePolicy(const char *policy_name, struct DetectFirewallPolicy *p return 1; } -static int DoParseAppSubStatePolicy(const char *prefix, const AppProto app_proto, - const uint8_t sub_state, const char *sub_state_name, const uint8_t state, - const char *hookname, const uint8_t complete_state, const int direction, - struct DetectFirewallPolicies *fw_policies) +/** + * \brief Assemble a firewall.policies config path, fatal on truncation. + */ +static void ATTR_FMT_PRINTF(3, 4) + FirewallPolicyPath(char *out_buf, size_t out_buf_sz, const char *fmt, ...) { - char policy_name[256]; - BUG_ON(sub_state_name == NULL); - BUG_ON(hookname == NULL); - - char *nname = SCStrdup(hookname); - if (nname == NULL) - return -1; - for (int i = 0; nname[i] != '\0'; i++) { - if (nname[i] == '_') - nname[i] = '-'; - } + va_list ap; + va_start(ap, fmt); + int r = vsnprintf(out_buf, out_buf_sz, fmt, ap); + va_end(ap); + if (r < 0 || (size_t)r >= out_buf_sz) + FatalError("Failed to assemble firewall policy config string"); +} - const char *app_name = (app_proto == ALPROTO_HTTP1) ? "http1" : AppProtoToString(app_proto); - int r = snprintf(policy_name, sizeof(policy_name), "%s.app.%s.%s.%s", prefix, app_name, - sub_state_name, nname); - SCLogDebug("policy_name %s", policy_name); - SCFree(nname); - if (r < 0 || (size_t)r >= sizeof(policy_name)) { - FatalError("internal error: failed to assemble firewall policy config string"); +/** + * \brief Resolve a firewall policy from the list of config paths. + * + * Paths are most-specific-first. The first path that has a policy configured + * wins. + * + * \retval 1 a config source was used and stored in \p out + * \retval 0 no source present, \p out is unmodified + * \retval -1 parse error, e.g. an empty policy + */ +static int ResolveFirewallPolicy( + struct DetectFirewallPolicy *out, const char *const *paths, const int npaths) +{ + for (int i = 0; i < npaths; i++) { + if (paths[i] == NULL) { + continue; + } + struct DetectFirewallPolicy tmp = { 0 }; + int r = DoParsePolicy(paths[i], &tmp); + if (r < 0) { + return -1; + } + if (r == 1) { + if (tmp.action == 0) { + SCLogError("%s: policy is set but empty", paths[i]); + return -1; + } + *out = tmp; + return 1; + } } + return 0; +} - struct DetectFirewallAppPolicy *app_pol = SCCalloc(1, sizeof(*app_pol)); - if (app_pol == NULL) - return -1; - - app_pol->alproto = app_proto; - app_pol->sub_state = sub_state; - app_pol->progress = state; - app_pol->direction = (uint8_t)direction; - /* init to drop:flow by default, will be overwritten by DoParsePolicy if there - * is a config for this hook. */ - app_pol->policy.action = ACTION_DROP; - app_pol->policy.action_scope = ACTION_SCOPE_FLOW; - - r = DoParsePolicy(policy_name, &app_pol->policy); - if (r < 0) { - SCFree(app_pol); - return -1; - } +/** + * \brief Generic start/complete hook alias for an app progress state, in config + * form (hyphens), or NULL for intermediate states. + */ +static const char *FirewallAppGenericHookName( + const uint8_t state, const uint8_t complete_state, const int direction) +{ + if (state == 0) + return (direction == STREAM_TOSERVER) ? "request-started" : "response-started"; + if (state == complete_state) + return (direction == STREAM_TOSERVER) ? "request-complete" : "response-complete"; + return NULL; +} - if (HashTableAdd(fw_policies->app_policies, app_pol, 0) != 0) { - FatalError("internal error: insert policy into hash table"); - } - /* for policies with an alert action, create a policy sig */ - if (r == 1 && app_pol->policy.action & ACTION_ALERT) { - SCLogDebug("adding policy signature"); - return AddAppPolicySignature(app_pol); +static void FirewallHookNameConvertUnderscoreToDash(const char *in, char *out, size_t out_size) +{ + strlcpy(out, in, out_size); + for (size_t i = 0; out[i] != '\0'; i++) { + if (out[i] == '_') + out[i] = '-'; } - SCLogDebug("r %d", r); - return r; } -static int DoParseAppPolicy(const char *prefix, const AppProto app_proto, const char *hookname, - const uint8_t state, const uint8_t complete_state, const int direction, +/** + * \brief Resolve and store one app-layer hook default policy. + * + * Handles both plain hooks (\p sub_state_name NULL) and sub state hooks, which + * only differ by an extra path segment. The policy is resolved most-specific + * first, e.g. for a sub state hook: + * + * .app... + * .app... + * .app...default-policy + * .app..default-policy + * .app.default-policy + * .default-policy + */ +static int DoParseAppPolicy(const char *prefix, const AppProto app_proto, const uint8_t sub_state, + const char *sub_state_name, const char *hookname, const uint8_t state, + const uint8_t complete_state, const int direction, struct DetectFirewallPolicies *fw_policies) { - char policy_name[256]; - const char *in_name = hookname; - if (hookname == NULL) { - if (state == 0) { - if (direction == STREAM_TOSERVER) - hookname = "request-started"; - else - hookname = "response-started"; - } else if (state == complete_state) { - if (direction == STREAM_TOSERVER) - hookname = "request-complete"; - else - hookname = "response-complete"; - } - if (hookname == NULL) - return 0; - } - char *nname = SCStrdup(hookname); - if (nname == NULL) - return -1; - for (int i = 0; nname[i] != '\0'; i++) { - if (nname[i] == '_') - nname[i] = '-'; + const char *app_name = (app_proto == ALPROTO_HTTP1) ? "http1" : AppProtoToString(app_proto); + // Generic serves for the first and the last state, NULL otherwise + const char *generic = FirewallAppGenericHookName(state, complete_state, direction); + + char primary[64]; + if (hookname != NULL) { + FirewallHookNameConvertUnderscoreToDash(hookname, primary, sizeof(primary)); + } else if (generic != NULL) { + strlcpy(primary, generic, sizeof(primary)); + } else { + return 0; } - const char *app_name = (app_proto == ALPROTO_HTTP1) ? "http1" : AppProtoToString(app_proto); - int r = snprintf(policy_name, sizeof(policy_name), "%s.app.%s.%s", prefix, app_name, nname); - SCFree(nname); - if (r < 0 || (size_t)r >= sizeof(policy_name)) { - FatalError("internal error: failed to assemble firewall policy config string"); + char scope[256]; + if (sub_state_name != NULL) { + char sub[64]; + FirewallHookNameConvertUnderscoreToDash(sub_state_name, sub, sizeof(sub)); + FirewallPolicyPath(scope, sizeof(scope), "%s.app.%s.%s", prefix, app_name, sub); + } else { + FirewallPolicyPath(scope, sizeof(scope), "%s.app.%s", prefix, app_name); } + char primary_key[320], generic_key[320], hook_dflt[320], proto_dflt[320], app_dflt[320], + global_dflt[320]; + FirewallPolicyPath(primary_key, sizeof(primary_key), "%s.%s", scope, primary); + if (generic != NULL && strcmp(primary, generic) != 0) { + FirewallPolicyPath(generic_key, sizeof(generic_key), "%s.%s", scope, generic); + } else { + generic_key[0] = '\0'; + } + FirewallPolicyPath(hook_dflt, sizeof(hook_dflt), "%s.default-policy", scope); + FirewallPolicyPath( + proto_dflt, sizeof(proto_dflt), "%s.app.%s.default-policy", prefix, app_name); + FirewallPolicyPath(app_dflt, sizeof(app_dflt), "%s.app.default-policy", prefix); + FirewallPolicyPath(global_dflt, sizeof(global_dflt), "%s.default-policy", prefix); + + const char *paths[] = { + // .app.[.]. + primary_key, + // .app.[.]. + generic_key[0] != '\0' ? generic_key : NULL, + // .app.[.].default-policy + hook_dflt, + // .app..default-policy + sub_state_name != NULL ? proto_dflt : NULL, + // .app.default-policy + app_dflt, + // .default-policy + global_dflt, + }; + struct DetectFirewallAppPolicy *app_pol = SCCalloc(1, sizeof(*app_pol)); if (app_pol == NULL) return -1; app_pol->alproto = app_proto; - app_pol->sub_state = 0; + app_pol->sub_state = sub_state; app_pol->progress = state; app_pol->direction = (uint8_t)direction; - /* init to drop:flow by default, will be overwritten by DoParsePolicy if there - * is a config for this hook. */ + /* built-in default, overwritten by ResolveFirewallPolicy if any of the + * config paths above has a policy. */ app_pol->policy.action = ACTION_DROP; app_pol->policy.action_scope = ACTION_SCOPE_FLOW; - r = DoParsePolicy(policy_name, &app_pol->policy); - if (r == 0 && in_name != NULL) { - if (state == 0) { - if (direction == STREAM_TOSERVER) - hookname = "request-started"; - else - hookname = "response-started"; - } else if (state == complete_state) { - if (direction == STREAM_TOSERVER) - hookname = "request-complete"; - else - hookname = "response-complete"; - } - if (hookname == NULL) - return 0; - r = snprintf(policy_name, sizeof(policy_name), "%s.app.%s.%s", prefix, app_name, hookname); - if (r < 0 || (size_t)r >= sizeof(policy_name)) { - FatalError("internal error: failed to assemble firewall policy config string"); - } - - r = DoParsePolicy(policy_name, &app_pol->policy); - } + int r = ResolveFirewallPolicy(&app_pol->policy, paths, (int)ARRAY_SIZE(paths)); if (r < 0) { SCFree(app_pol); return -1; @@ -4262,10 +4289,49 @@ int DetectFirewallInitDefaultPolicies(DetectEngineCtx *de_ctx) return 0; } +/** + * \brief Resolve and store one packet-hook default policy. + */ +static int DetectFirewallLoadPacketPolicy(struct DetectFirewallPolicies *fw_policies, + const char *prefix, enum DetectFirewallPacketPolicies id, const char *leaf) +{ + char specific[256], pkt_dflt[256], global_dflt[256]; + FirewallPolicyPath(specific, sizeof(specific), "%s.packet.%s", prefix, leaf); + FirewallPolicyPath(pkt_dflt, sizeof(pkt_dflt), "%s.packet.default-policy", prefix); + FirewallPolicyPath(global_dflt, sizeof(global_dflt), "%s.default-policy", prefix); + + struct DetectFirewallPolicy *pol = &fw_policies->pkt[id]; // built-in default + const char *paths[] = { specific, pkt_dflt, global_dflt }; + int r = ResolveFirewallPolicy(pol, paths, (int)ARRAY_SIZE(paths)); + if (r < 0) { + return -1; + } + if (r == 1 && (pol->action & ACTION_ALERT)) { + return AddPktPolicySignature(fw_policies, pol, id); + } + return 0; +} + +/** + * \brief Load the packet-hook default policies. + */ +static int DetectFirewallLoadPacketPolicies( + struct DetectFirewallPolicies *fw_policies, const char *prefix) +{ + if (DetectFirewallLoadPacketPolicy( + fw_policies, prefix, DETECT_FIREWALL_POLICY_PACKET_FILTER, "filter") < 0) + return -1; + if (DetectFirewallLoadPacketPolicy( + fw_policies, prefix, DETECT_FIREWALL_POLICY_PRE_FLOW, "pre-flow") < 0) + return -1; + if (DetectFirewallLoadPacketPolicy( + fw_policies, prefix, DETECT_FIREWALL_POLICY_PRE_STREAM, "pre-stream") < 0) + return -1; + return 0; +} + int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *de_ctx) { - int r; - char policy_name[256]; char prefix[96] = "firewall.policies"; if (strlen(de_ctx->config_prefix) > 0) { snprintf(prefix, sizeof(prefix), "%s.firewall.policies", de_ctx->config_prefix); @@ -4275,42 +4341,8 @@ int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *de_ctx) if (fw_policies == NULL) return -1; - r = snprintf(policy_name, sizeof(policy_name), "%s.packet.filter", prefix); - if (r < 0 || (size_t)r >= sizeof(policy_name)) { - FatalError("internal error: failed to assemble firewall policy config string"); - } - r = DoParsePolicy(policy_name, &fw_policies->pkt[DETECT_FIREWALL_POLICY_PACKET_FILTER]); - if (r < 0) - return -1; - if (fw_policies->pkt[DETECT_FIREWALL_POLICY_PACKET_FILTER].action & ACTION_ALERT) - if (AddPktPolicySignature(fw_policies, - &fw_policies->pkt[DETECT_FIREWALL_POLICY_PACKET_FILTER], - DETECT_FIREWALL_POLICY_PACKET_FILTER) < 0) - return -1; - - r = snprintf(policy_name, sizeof(policy_name), "%s.packet.pre-flow", prefix); - if (r < 0 || (size_t)r >= sizeof(policy_name)) { - FatalError("internal error: failed to assemble firewall policy config string"); - } - r = DoParsePolicy(policy_name, &fw_policies->pkt[DETECT_FIREWALL_POLICY_PRE_FLOW]); - if (r < 0) - return -1; - if (fw_policies->pkt[DETECT_FIREWALL_POLICY_PRE_FLOW].action & ACTION_ALERT) - if (AddPktPolicySignature(fw_policies, &fw_policies->pkt[DETECT_FIREWALL_POLICY_PRE_FLOW], - DETECT_FIREWALL_POLICY_PRE_FLOW) < 0) - return -1; - - r = snprintf(policy_name, sizeof(policy_name), "%s.packet.pre-stream", prefix); - if (r < 0 || (size_t)r >= sizeof(policy_name)) { - FatalError("internal error: failed to assemble firewall policy config string"); - } - r = DoParsePolicy(policy_name, &fw_policies->pkt[DETECT_FIREWALL_POLICY_PRE_STREAM]); - if (r < 0) + if (DetectFirewallLoadPacketPolicies(fw_policies, prefix) < 0) return -1; - if (fw_policies->pkt[DETECT_FIREWALL_POLICY_PRE_STREAM].action & ACTION_ALERT) - if (AddPktPolicySignature(fw_policies, &fw_policies->pkt[DETECT_FIREWALL_POLICY_PRE_STREAM], - DETECT_FIREWALL_POLICY_PRE_STREAM) < 0) - return -1; for (AppProto a = 0; a < g_alproto_max; a++) { if (!AppProtoIsValid(a)) @@ -4338,8 +4370,8 @@ int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *de_ctx) BUG_ON(state_name == NULL); SCLogDebug("protocol %s: sub state:%s state:%s", AppProtoToString(a), sub_state_name, state_name); - if (DoParseAppSubStatePolicy(prefix, a, s, sub_state_name, state, state_name, - max_state, STREAM_TOSERVER, fw_policies) < 0) + if (DoParseAppPolicy(prefix, a, s, sub_state_name, state_name, state, max_state, + STREAM_TOSERVER, fw_policies) < 0) return -1; } /* to_client */ @@ -4351,8 +4383,8 @@ int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *de_ctx) BUG_ON(state_name == NULL); SCLogDebug("protocol %s: to_client: sub state:%s state:%s", AppProtoToString(a), sub_state_name, state_name); - if (DoParseAppSubStatePolicy(prefix, a, s, sub_state_name, state, state_name, - max_state, STREAM_TOCLIENT, fw_policies) < 0) + if (DoParseAppPolicy(prefix, a, s, sub_state_name, state_name, state, max_state, + STREAM_TOCLIENT, fw_policies) < 0) return -1; } } @@ -4363,8 +4395,8 @@ int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *de_ctx) for (uint8_t state = 0; state <= complete_state_ts; state++) { const char *name = AppLayerParserGetStateNameById(IPPROTO_TCP, a, state, STREAM_TOSERVER); - if (DoParseAppPolicy(prefix, a, name, state, complete_state_ts, STREAM_TOSERVER, - fw_policies) < 0) + if (DoParseAppPolicy(prefix, a, 0, NULL, name, state, complete_state_ts, + STREAM_TOSERVER, fw_policies) < 0) return -1; } const uint8_t complete_state_tc = @@ -4373,8 +4405,8 @@ int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *de_ctx) for (uint8_t state = 0; state <= complete_state_tc; state++) { const char *name = AppLayerParserGetStateNameById(IPPROTO_TCP, a, state, STREAM_TOCLIENT); - if (DoParseAppPolicy(prefix, a, name, state, complete_state_tc, STREAM_TOCLIENT, - fw_policies) < 0) + if (DoParseAppPolicy(prefix, a, 0, NULL, name, state, complete_state_tc, + STREAM_TOCLIENT, fw_policies) < 0) return -1; } } diff --git a/suricata.yaml.in b/suricata.yaml.in index b4318e0c4dfa..bac0c55498c0 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -2357,11 +2357,13 @@ firewall: # Default policies # - # Choose a default policy for each firewall hook. - # It is also possible to specify policies by app-layer protocol. + # Choose a default policy for each firewall hook. A `default-policy` covers + # every hook below it, so hooks that are not listed still get a policy. + # The most specific setting wins. # DNS example: Drop and alert on all DNS requests that are not allowed in firewall.rules, accept all responses. # #policies: + # default-policy: ["drop:flow"] # packet: # filter: ["drop:packet"] # app: From 5368ee30f844dac31bc7418d4301f84556a922ff Mon Sep 17 00:00:00 2001 From: Lukas Sismis Date: Tue, 28 Jul 2026 18:09:52 +0200 Subject: [PATCH 69/69] detect/firewall: validate action scope against the hook class Validate the resolved scope against the class of hook it is being applied to and fail at startup if it does not fit, naming the config path and the scopes that would be accepted there. A global `accept:tx` is now a startup error. Ticket: 8770 (cherry picked from commit ba2606976bef913c1d0a4a8ff61c90823b76c0ca) --- doc/userguide/firewall/firewall-design.rst | 6 ++ src/detect-parse.c | 87 ++++++++++++++++++++-- src/detect.h | 5 ++ 3 files changed, 92 insertions(+), 6 deletions(-) diff --git a/doc/userguide/firewall/firewall-design.rst b/doc/userguide/firewall/firewall-design.rst index ecc33cd39397..bce1a55f2a42 100644 --- a/doc/userguide/firewall/firewall-design.rst +++ b/doc/userguide/firewall/firewall-design.rst @@ -375,3 +375,9 @@ Precedence: * app hook in a sub state: ``app...`` > ``app...default-policy`` > ``app..default-policy`` > ``app.default-policy`` > ``policies.default-policy`` > built-in (``drop:flow``) + +An action scope must be valid for the hook it is applied to. For example, +defining ``accept:tx`` as a global default policy will fail to start Suricata, +because ``packet`` policies do not accept ``tx``. +Cover such hooks with a more specific setting so the incompatible default never +reaches them. diff --git a/src/detect-parse.c b/src/detect-parse.c index 110cc98f18f3..615688639681 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -109,6 +109,19 @@ typedef struct SignatureParser_ { char opts[DETECT_MAX_RULE_SIZE]; } SignatureParser; +/** Valid action scopes per firewall hook class. Single source of truth for both + * scope validation and the human-readable "a/b/c" hint in error messages. */ +static const uint8_t fw_packet_hook_scopes[] = { + ACTION_SCOPE_PACKET, + ACTION_SCOPE_HOOK, + ACTION_SCOPE_FLOW, +}; +static const uint8_t fw_app_hook_scopes[] = { + ACTION_SCOPE_FLOW, + ACTION_SCOPE_TX, + ACTION_SCOPE_HOOK, +}; + const char *DetectListToHumanString(int list) { #define CASE_CODE_STRING(E, S) case E: return S; break @@ -4090,6 +4103,59 @@ static int DoParsePolicy(const char *policy_name, struct DetectFirewallPolicy *p return 1; } +static bool FirewallScopeValidForClass(uint8_t scope, enum DetectFirewallPolicyClass pol_class) +{ + const uint8_t *set = NULL; + size_t n = 0; + switch (pol_class) { + case DETECT_FIREWALL_POLICY_CLASS_PACKET: + set = fw_packet_hook_scopes; + n = ARRAY_SIZE(fw_packet_hook_scopes); + break; + case DETECT_FIREWALL_POLICY_CLASS_APP: + set = fw_app_hook_scopes; + n = ARRAY_SIZE(fw_app_hook_scopes); + break; + default: + FatalError("Invalid firewall policy class %u", (unsigned)pol_class); + } + for (size_t i = 0; i < n; i++) { + if (set[i] == scope) { + return true; + } + } + return false; +} + +/** + * \brief Render the valid scopes for a hook class to a string. + */ +static void FirewallScopeHintForClass( + enum DetectFirewallPolicyClass pol_class, char *out, size_t out_size) +{ + const uint8_t *set = NULL; + size_t n = 0; + switch (pol_class) { + case DETECT_FIREWALL_POLICY_CLASS_PACKET: + set = fw_packet_hook_scopes; + n = ARRAY_SIZE(fw_packet_hook_scopes); + break; + case DETECT_FIREWALL_POLICY_CLASS_APP: + set = fw_app_hook_scopes; + n = ARRAY_SIZE(fw_app_hook_scopes); + break; + default: + FatalError("Invalid firewall policy class %u", (unsigned)pol_class); + } + out[0] = '\0'; + for (size_t i = 0; i < n; i++) { + if (i > 0) { + strlcat(out, "/", out_size); + } + strlcat(out, ActionScopeToString((enum ActionScope)set[i]), out_size); + } +} + /** * \brief Assemble a firewall.policies config path, fatal on truncation. */ @@ -4108,14 +4174,14 @@ static void ATTR_FMT_PRINTF(3, 4) * \brief Resolve a firewall policy from the list of config paths. * * Paths are most-specific-first. The first path that has a policy configured - * wins. + * wins with its action scope validated against the target hook class. * * \retval 1 a config source was used and stored in \p out * \retval 0 no source present, \p out is unmodified - * \retval -1 parse error, e.g. an empty policy + * \retval -1 parse error, e.g. an empty policy, or invalid scope for the target class */ -static int ResolveFirewallPolicy( - struct DetectFirewallPolicy *out, const char *const *paths, const int npaths) +static int ResolveFirewallPolicy(struct DetectFirewallPolicy *out, + enum DetectFirewallPolicyClass pol_class, const char *const *paths, const int npaths) { for (int i = 0; i < npaths; i++) { if (paths[i] == NULL) { @@ -4131,6 +4197,13 @@ static int ResolveFirewallPolicy( SCLogError("%s: policy is set but empty", paths[i]); return -1; } + if (!FirewallScopeValidForClass(tmp.action_scope, pol_class)) { + char hint[32]; + FirewallScopeHintForClass(pol_class, hint, sizeof(hint)); + SCLogError("%s: action scope (\"%s\") is not valid. Valid scopes: %s", paths[i], + ActionScopeToString(tmp.action_scope), hint); + return -1; + } *out = tmp; return 1; } @@ -4244,7 +4317,8 @@ static int DoParseAppPolicy(const char *prefix, const AppProto app_proto, const app_pol->policy.action = ACTION_DROP; app_pol->policy.action_scope = ACTION_SCOPE_FLOW; - int r = ResolveFirewallPolicy(&app_pol->policy, paths, (int)ARRAY_SIZE(paths)); + int r = ResolveFirewallPolicy( + &app_pol->policy, DETECT_FIREWALL_POLICY_CLASS_APP, paths, (int)ARRAY_SIZE(paths)); if (r < 0) { SCFree(app_pol); return -1; @@ -4302,7 +4376,8 @@ static int DetectFirewallLoadPacketPolicy(struct DetectFirewallPolicies *fw_poli struct DetectFirewallPolicy *pol = &fw_policies->pkt[id]; // built-in default const char *paths[] = { specific, pkt_dflt, global_dflt }; - int r = ResolveFirewallPolicy(pol, paths, (int)ARRAY_SIZE(paths)); + int r = ResolveFirewallPolicy( + pol, DETECT_FIREWALL_POLICY_CLASS_PACKET, paths, (int)ARRAY_SIZE(paths)); if (r < 0) { return -1; } diff --git a/src/detect.h b/src/detect.h index 3225f49fd9c0..6aa98bd17ce5 100644 --- a/src/detect.h +++ b/src/detect.h @@ -913,6 +913,11 @@ enum DetectEngineType DETECT_ENGINE_TYPE_TENANT = 3, }; +enum DetectFirewallPolicyClass { + DETECT_FIREWALL_POLICY_CLASS_PACKET, + DETECT_FIREWALL_POLICY_CLASS_APP +}; + enum DetectFirewallPacketPolicies { DETECT_FIREWALL_POLICY_PACKET_FILTER, DETECT_FIREWALL_POLICY_PRE_FLOW,