From 2d4f1968b89128e4c0e39234ee75160e7dbd975d Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Sun, 17 May 2026 11:07:03 +0200 Subject: [PATCH] detect/firewall: support alert in default app policy Support `alert` as a secondary action in app-layer firewall policies. To implement this a Signature object is created per policy that uses alert, and this is stored in a hash table. When the policy is applied the signature is looked up and used in the PacketAlert. Ticket: #8566. --- etc/schema.json | 10 +++ rules/README.md | 1 + src/detect-engine-analyzer.c | 19 ----- src/detect-engine.c | 3 + src/detect-parse.c | 160 +++++++++++++++++++++++++++++++++-- src/detect-parse.h | 8 ++ src/detect.c | 63 +++++++++++--- src/detect.h | 3 + 8 files changed, 228 insertions(+), 39 deletions(-) diff --git a/etc/schema.json b/etc/schema.json index 00572e921f..3ef8209fdd 100644 --- a/etc/schema.json +++ b/etc/schema.json @@ -2014,6 +2014,16 @@ } } }, + "firewall": { + "type": "object", + "additionalProperties": false, + "properties": { + "hook": { + "type": "string", + "description": "Firewall hook for the match" + } + } + }, "flow": { "type": "object", "additionalProperties": false, diff --git a/rules/README.md b/rules/README.md index af383b2384..303802c223 100644 --- a/rules/README.md +++ b/rules/README.md @@ -10,6 +10,7 @@ signature IDs. | Component | Start | End | | ----------------- | ------- | ------- | | Decoder | 2200000 | 2200999 | +| Firewall | 2201000 | 2201999 | | Stream | 2210000 | 2210999 | | Generic App-Layer | 2260000 | 2260999 | diff --git a/src/detect-engine-analyzer.c b/src/detect-engine-analyzer.c index 760b931f7d..38d0d6f04c 100644 --- a/src/detect-engine-analyzer.c +++ b/src/detect-engine-analyzer.c @@ -2103,25 +2103,6 @@ void EngineAnalysisRules(const DetectEngineCtx *de_ctx, #include "app-layer-parser.h" -static const char *ActionScopeToString(enum ActionScope s) -{ - switch (s) { - case ACTION_SCOPE_PACKET: - return "packet"; - case ACTION_SCOPE_FLOW: - return "flow"; - break; - case ACTION_SCOPE_HOOK: - return "hook"; - case ACTION_SCOPE_TX: - return "tx"; - case ACTION_SCOPE_AUTO: - return "auto"; - } - DEBUG_VALIDATE_BUG_ON(1); - return "unknown"; -} - static void AddPolicy(const DetectEngineCtx *de_ctx, RuleAnalyzer *ctx, const AppProto a, const uint8_t state, const uint8_t direction) { diff --git a/src/detect-engine.c b/src/detect-engine.c index 213ee17053..78cf007176 100644 --- a/src/detect-engine.c +++ b/src/detect-engine.c @@ -2801,6 +2801,9 @@ void DetectEngineCtxFree(DetectEngineCtx *de_ctx) if (de_ctx->non_pf_engine_names) { HashTableFree(de_ctx->non_pf_engine_names); } + if (de_ctx->fw_policies) { + HashTableFree(de_ctx->fw_policies->policy_signatures); + } SCFree(de_ctx->fw_policies); SCFree(de_ctx); //DetectAddressGroupPrintMemory(); diff --git a/src/detect-parse.c b/src/detect-parse.c index 892879be34..f7b02b3822 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -3748,6 +3748,122 @@ void DetectSetupParseRegexes(const char *parse_str, DetectParseRegex *detect_par } } +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) +{ + switch (s) { + case ACTION_SCOPE_PACKET: + return "packet"; + case ACTION_SCOPE_FLOW: + return "flow"; + case ACTION_SCOPE_HOOK: + return "hook"; + case ACTION_SCOPE_TX: + return "tx"; + case ACTION_SCOPE_AUTO: + return "auto"; + } + DEBUG_VALIDATE_BUG_ON(1); + return "unknown"; +} + +void DetectFirewallPolicyToString(const struct DetectFirewallPolicy *p, char *out, size_t out_size) +{ + const char *as = ActionScopeToString(p->action_scope); + DEBUG_VALIDATE_BUG_ON(as == NULL); + if (as == NULL) + return; + if (p->action & ACTION_REJECT_ANY) { + if (p->action & ACTION_REJECT_DST) { + snprintf(out, out_size, "rejectdst:%s", as); + } else if (p->action & ACTION_REJECT_BOTH) { + snprintf(out, out_size, "rejectboth:%s", as); + } else { + snprintf(out, out_size, "rejectsrc:%s", as); + } + } else if (p->action & ACTION_DROP) { + snprintf(out, out_size, "drop:%s", as); + } else if (p->action & ACTION_ACCEPT) { + snprintf(out, out_size, "accept:%s", as); + } else { + DEBUG_VALIDATE_BUG_ON(1); + } + if (p->action & ACTION_PASS) { + if (p->action_scope == ACTION_SCOPE_FLOW) { + strlcat(out, ",pass:flow", out_size); + } else { + DEBUG_VALIDATE_BUG_ON(1); + } + } + if (p->action & ACTION_ALERT) { + strlcat(out, ",alert", out_size); + } +} + +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) +{ + Signature *s = SCCalloc(1, sizeof(*s)); // SigAlloc does way more than we need + if (s == NULL) + return -1; + char msg[256]; + snprintf(msg, sizeof(msg), "SURICATA FW default app policy"); + s->msg = SCStrdup(msg); + if (s->msg == NULL) { + 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->flags |= SIG_FLAG_FIREWALL; + s->type = SIG_TYPE_APP_TX; + s->detect_table = DETECT_TABLE_APP_FILTER; + s->id = 2201001; + s->rev = 1; + s->gid = 1; + s->prio = 3; + + if (HashTableAdd(ht, s, 0) != 0) { + SCFree(s->msg); + SCFree(s); + return -1; + } + SCLogDebug("added to hash"); + return 0; +} + static int DoParsePolicy(const char *policy_name, struct DetectFirewallPolicy *pol) { SCConfNode *policy_actions = SCConfGetNode(policy_name); @@ -3773,7 +3889,7 @@ static int DoParsePolicy(const char *policy_name, struct DetectFirewallPolicy *p 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 DetectFirewallAppPolicy *app_fw_policies) + struct DetectFirewallPolicies *fw_policies, struct DetectFirewallAppPolicy *app_fw_policies) { char policy_name[256]; const char *in_name = hookname; @@ -3807,10 +3923,12 @@ 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) - r = DoParsePolicy(policy_name, &app_fw_policies[app_proto].ts[state]); + pol = &app_fw_policies[app_proto].ts[state]; else - r = DoParsePolicy(policy_name, &app_fw_policies[app_proto].tc[state]); + pol = &app_fw_policies[app_proto].tc[state]; + r = DoParsePolicy(policy_name, pol); if (r == 0 && in_name != NULL) { if (state == 0) { if (direction == STREAM_TOSERVER) @@ -3830,10 +3948,14 @@ static int DoParseAppPolicy(const char *prefix, const AppProto app_proto, const FatalError("internal error: failed to assemble firewall policy config string"); } - if (direction == STREAM_TOSERVER) - return DoParsePolicy(policy_name, &app_fw_policies[app_proto].ts[state]); - else - return DoParsePolicy(policy_name, &app_fw_policies[app_proto].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); } return r; } @@ -3848,6 +3970,10 @@ int DetectFirewallInitDefaultPolicies(DetectEngineCtx *de_ctx) 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; fw_policies->pkt[DETECT_FIREWALL_POLICY_PACKET_FILTER].action = ACTION_DROP; fw_policies->pkt[DETECT_FIREWALL_POLICY_PACKET_FILTER].action_scope = ACTION_SCOPE_PACKET; @@ -3924,7 +4050,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, - app_fw_policies) < 0) + fw_policies, app_fw_policies) < 0) return -1; } @@ -3934,7 +4060,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, - app_fw_policies) < 0) + fw_policies, app_fw_policies) < 0) return -1; } } @@ -3942,6 +4068,22 @@ 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 72d72cf19e..1925ac9385 100644 --- a/src/detect-parse.h +++ b/src/detect-parse.h @@ -24,6 +24,7 @@ #ifndef SURICATA_DETECT_PARSE_H #define SURICATA_DETECT_PARSE_H +#include "action-globals.h" #include "app-layer-protos.h" #include "detect-engine-register.h" // types from detect.h with only forward declarations for bindgen @@ -32,6 +33,7 @@ typedef struct Signature_ Signature; typedef struct SigMatchCtx_ SigMatchCtx; typedef struct SigMatch_ SigMatch; typedef struct SigMatchData_ SigMatchData; +struct DetectFirewallPolicies; /** Flags to indicate if the Signature parsing must be done * switching the source and dest (for ip addresses and ports) @@ -114,7 +116,13 @@ int SC_Pcre2SubstringGet(pcre2_match_data *match_data, uint32_t number, PCRE2_UC void DetectRegisterAppLayerHookLists(void); void DetectListSupportedProtocols(void); +const char *ActionScopeToString(enum ActionScope s); + +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 138121103a..c0c1cf55f0 100644 --- a/src/detect.c +++ b/src/detect.c @@ -36,6 +36,7 @@ #include "app-layer-frames.h" #include "detect.h" +#include "detect-parse.h" #include "detect-dsize.h" #include "detect-engine.h" #include "detect-engine-build.h" @@ -1640,6 +1641,20 @@ struct DetectFirewallAppTxState { bool last_fw_rule; /**< processing the last fw rule, so we need to eval all hooks after it. */ }; +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) +{ + if (EngineModeIsFirewall()) { + Signature *s = DetectFirewallGetPolicySignature( + 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); + } +} + /** \internal * \brief apply default policy * \param p packet to apply policy to @@ -1650,8 +1665,8 @@ struct DetectFirewallAppTxState { * to look up configurable default policies later */ static const struct DetectFirewallPolicy *DetectFirewallApplyDefaultAppPolicy( - const struct DetectFirewallAppPolicy *policies, Packet *p, const AppProto alproto, - const uint8_t direction, const uint8_t progress) + DetectEngineThreadCtx *det_ctx, const struct DetectFirewallAppPolicy *policies, Packet *p, + const AppProto alproto, const uint8_t direction, const uint8_t progress) { const struct DetectFirewallPolicy *policy; if (direction & STREAM_TOSERVER) { @@ -1708,30 +1723,56 @@ static enum DetectTxFirewallFlowControl DetectFirewallApplyDefaultPolicies( direction & STREAM_TOSERVER ? "toserver" : "toclient", hook); const struct DetectFirewallPolicy *policy = DetectFirewallApplyDefaultAppPolicy( - det_ctx->de_ctx->fw_policies->app, p, alproto, direction, hook); - SCLogDebug("fw: hook:%u policy:%02x", hook, policy->action); + det_ctx, det_ctx->de_ctx->fw_policies->app, p, alproto, direction, hook); + SCLogDebug("fw: hook:%u policy:%02x apply_to_packet:%s", hook, policy->action, + BOOL2STR(apply_to_packet)); actions |= 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", hook); + + } 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) { - DetectRunAppendDefaultAccept(det_ctx, p); + if (policy->action & ACTION_ALERT) { + DetectRunAppendDefaultAppPolicyAlert( + det_ctx, p, is_last, direction, tx->tx_id, alproto, hook); + } else { + DetectRunAppendDefaultAccept(det_ctx, p); + } return DETECT_TX_FW_FC_SKIP; + } else if (policy->action_scope == ACTION_SCOPE_TX) { tx->tx_data_ptr->flags |= APP_LAYER_TX_ACCEPT; SCLogDebug("ACTION_SCOPE_TX, setting APP_LAYER_TX_ACCEPT"); - if (is_last) { + if (policy->action & ACTION_ALERT) { + DetectRunAppendDefaultAppPolicyAlert( + det_ctx, p, is_last, direction, tx->tx_id, alproto, hook); + } else if (is_last) { DetectRunAppendDefaultAccept(det_ctx, p); SCLogDebug("DetectRunAppendDefaultAccept for last tx"); } return DETECT_TX_FW_FC_SKIP; + + } else if (policy->action_scope == ACTION_SCOPE_HOOK) { + if (policy->action & ACTION_ALERT) { + DetectRunAppendDefaultAppPolicyAlert( + det_ctx, p, is_last, direction, tx->tx_id, alproto, hook); + } else if (is_last) { + DetectRunAppendDefaultAccept(det_ctx, p); + SCLogDebug("DetectRunAppendDefaultAccept for last tx"); + } + } + } else { + if (policy->action & ACTION_ALERT) { + DetectRunAppendDefaultAppPolicyAlert( + det_ctx, p, is_last, direction, tx->tx_id, alproto, hook); } } } + if ((is_last && (actions & (ACTION_ACCEPT | ACTION_DROP)) == ACTION_ACCEPT)) { DetectRunAppendDefaultAccept(det_ctx, p); } @@ -2384,9 +2425,9 @@ static void DetectRunTx(ThreadVars *tv, s->app_progress_hook); /* if this rule was the last for our progress state, and it didn't match, * we have to invoke the default policy. We only check the current rule hook. */ - const struct DetectFirewallPolicy *policy = - DetectFirewallApplyDefaultAppPolicy(det_ctx->de_ctx->fw_policies->app, p, - s->alproto, flow_flags, s->app_progress_hook); + const struct DetectFirewallPolicy *policy = DetectFirewallApplyDefaultAppPolicy( + det_ctx, det_ctx->de_ctx->fw_policies->app, p, s->alproto, flow_flags, + s->app_progress_hook); SCLogDebug("fw_last_for_progress policy %02x", policy->action); if (policy->action & ACTION_DROP) { fw_state.fw_skip_app_filter = true; diff --git a/src/detect.h b/src/detect.h index bb8b647ce8..9a349a095c 100644 --- a/src/detect.h +++ b/src/detect.h @@ -935,6 +935,9 @@ struct DetectFirewallPolicies { /** policy for packet_filter, pre_flow, pre_stream hooks */ struct DetectFirewallPolicy pkt[DETECT_FIREWALL_POLICY_SIZE]; + /* hash table with a Signature object per default policy that has `alert` enabled. */ + HashTable *policy_signatures; + /** app layer policies, one per alproto */ struct DetectFirewallAppPolicy app[]; };