From 31d048ed4bdc3b1d8ac8d9444bcefc17d8d18d71 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Wed, 26 Feb 2025 11:38:35 +0100 Subject: [PATCH] firewall: start of firewall rules support Config: Firewall rules are like normal rule, with some key differences. They are loaded separate, and first, from: ```yaml firewall-rule-path: /etc/suricata/firewall/ firewall-rule-files: - fw.rules ``` Can also be loaded with --firewall-rules-exclusive: Mostly for QA purposes. Allow -S with --firewall-rules-exclusive, so that firewall and threat detection rules can be tested together. Rules: Differences with regular "threat detection" rules: 1. these rules are evaluated before threat detection rules 2. these rules are evaluated in the order as they appear in the rule file 3. currently only rules specifying an explicit hook at supported a. as a consequence, no rules will be treated as (like) IP-only, PD-only or DE-only Require explicit action scope for firewall rules. Default policy is drop for the firewall tables. Actions: New action "accept" is added to allow traffic in the filter tables. New scope "accept:tx" is added to allow accepting a transaction. Tables: Rulesets are per table. Table processing order: `packet:filter` -> `packet:td` -> `app:*:*` -> `app:td`. Each of the tables has some unique properties: `packet:filter`: - default policy is `drop:packet` - rules are process in order - action scopes are explicit - `drop` or `accept` is immediate - `accept:hook` continues to `packet:td` `packet:td`: - default policy is `accept:hook` - rules are ordered by IDS/IPS ordering logic - action scopes are implicit - actions are queued - continues to `app:*:*` or `alert/action finalize` `app:*:*`: - default policy is `drop:flow` - rules are process in order - action scopes are explicit - `drop` is immediate - `accept` is conditional on possible `drop` from `packet:td` - `accept:hook` continues to `app:td`, `accept:packet` or `accept:flow` continues to `alert/action finalize` `app:td`: - default policy is `accept:hook` - rules are ordered by IDS/IPS ordering logic - action scopes are implicit - actions are queued - continues to `alert/action finalize` Implementation: During sigorder, split into packet:filter, app:*:* and general td. Allow fw rules to work when in pass:flow mode. When firewall mode is enabled, `pass:flow` will not skip the detection engine anymore, but instead process the firewall rules and then apply the pass before inspecting threat detect rules. --- etc/schema.json | 10 + rust/src/applayer.rs | 1 + src/action-globals.h | 3 + src/app-layer-parser.h | 2 + src/decode.c | 8 + src/decode.h | 10 +- src/detect-engine-alert.c | 56 ++++- src/detect-engine-analyzer.c | 15 ++ src/detect-engine-loader.c | 127 ++++++++-- src/detect-engine-prefilter.c | 35 ++- src/detect-engine-sigorder.c | 143 ++++++++--- src/detect-parse.c | 175 ++++++++++++-- src/detect-parse.h | 1 + src/detect.c | 430 ++++++++++++++++++++++++++++++++-- src/detect.h | 11 +- src/flow.h | 3 +- src/output-json-flow.c | 2 + src/suricata.c | 17 +- src/suricata.h | 2 + suricata.yaml.in | 14 ++ 20 files changed, 975 insertions(+), 90 deletions(-) diff --git a/etc/schema.json b/etc/schema.json index 3f7385c008..c0b4718d94 100644 --- a/etc/schema.json +++ b/etc/schema.json @@ -5341,6 +5341,16 @@ "description": "Number of packets dropped due to inner tunnel packet being dropped", "type": "integer" + }, + "default_packet_policy": { + "description": + "Number of packets dropped due to default packet policy", + "type": "integer" + }, + "default_app_policy": { + "description": + "Number of packets dropped due to default app policy", + "type": "integer" } }, "additionalProperties": false diff --git a/rust/src/applayer.rs b/rust/src/applayer.rs index debfb56cbb..4cd36339ad 100644 --- a/rust/src/applayer.rs +++ b/rust/src/applayer.rs @@ -551,6 +551,7 @@ pub const APP_LAYER_TX_SKIP_INSPECT_TS: u8 = BIT_U8!(0); pub const APP_LAYER_TX_SKIP_INSPECT_TC: u8 = BIT_U8!(1); pub const _APP_LAYER_TX_INSPECTED_TS: u8 = BIT_U8!(2); pub const _APP_LAYER_TX_INSPECTED_TC: u8 = BIT_U8!(3); +pub const APP_LAYER_TX_ACCEPT: u8 = BIT_U8!(4); /// cbindgen:ignore extern "C" { diff --git a/src/action-globals.h b/src/action-globals.h index d6509e98be..7aac5e17df 100644 --- a/src/action-globals.h +++ b/src/action-globals.h @@ -33,6 +33,7 @@ #define ACTION_REJECT_BOTH 0x10 #define ACTION_PASS 0x20 #define ACTION_CONFIG 0x40 +#define ACTION_ACCEPT 0x80 /**< firewall 'accept' rule */ #define ACTION_REJECT_ANY (ACTION_REJECT|ACTION_REJECT_DST|ACTION_REJECT_BOTH) @@ -42,6 +43,8 @@ enum ActionScope { ACTION_SCOPE_AUTO = 0, ACTION_SCOPE_PACKET, /**< apply action to packet */ ACTION_SCOPE_FLOW, /**< apply drop/pass/accept action to flow */ + ACTION_SCOPE_HOOK, /**< apply action to current hook */ + ACTION_SCOPE_TX, /**< apply action to current tx */ }; #endif /* SURICATA_ACTION_GLOBALS_H */ diff --git a/src/app-layer-parser.h b/src/app-layer-parser.h index 421a9778ca..3be6d6a99a 100644 --- a/src/app-layer-parser.h +++ b/src/app-layer-parser.h @@ -55,6 +55,8 @@ /** is tx fully inspected? */ #define APP_LAYER_TX_INSPECTED_TS BIT_U8(2) #define APP_LAYER_TX_INSPECTED_TC BIT_U8(3) +/** accept is applied to entire tx */ +#define APP_LAYER_TX_ACCEPT BIT_U8(4) /** parser has successfully processed in the input, and has consumed * all of it. */ diff --git a/src/decode.c b/src/decode.c index 3ba77b9229..446c29d51a 100644 --- a/src/decode.c +++ b/src/decode.c @@ -906,6 +906,10 @@ const char *PacketDropReasonToString(enum PacketDropReason r) return "nfq error"; case PKT_DROP_REASON_INNER_PACKET: return "tunnel packet drop"; + case PKT_DROP_REASON_DEFAULT_PACKET_POLICY: + return "default packet policy"; + case PKT_DROP_REASON_DEFAULT_APP_POLICY: + return "default app policy"; case PKT_DROP_REASON_NOT_SET: case PKT_DROP_REASON_MAX: return NULL; @@ -948,6 +952,10 @@ static const char *PacketDropReasonToJsonString(enum PacketDropReason r) return "ips.drop_reason.nfq_error"; case PKT_DROP_REASON_INNER_PACKET: return "ips.drop_reason.tunnel_packet_drop"; + case PKT_DROP_REASON_DEFAULT_PACKET_POLICY: + return "ips.drop_reason.default_packet_policy"; + case PKT_DROP_REASON_DEFAULT_APP_POLICY: + return "ips.drop_reason.default_app_policy"; case PKT_DROP_REASON_NOT_SET: case PKT_DROP_REASON_MAX: return NULL; diff --git a/src/decode.h b/src/decode.h index 4753497298..cef710c49b 100644 --- a/src/decode.h +++ b/src/decode.h @@ -262,7 +262,9 @@ typedef struct PacketAlert_ { /** alert is in a frame, frame_id set */ #define PACKET_ALERT_FLAG_FRAME 0x20 /** alert in a tx was forced */ -#define PACKET_ALERT_FLAG_TX_GUESSED 0x040 +#define PACKET_ALERT_FLAG_TX_GUESSED 0x40 +/** accept should be applied to packet */ +#define PACKET_ALERT_FLAG_APPLY_ACTION_TO_PACKET 0x80 extern uint16_t packet_alert_max; #define PACKET_ALERT_MAX 15 @@ -376,8 +378,10 @@ enum PacketDropReason { PKT_DROP_REASON_STREAM_MIDSTREAM, PKT_DROP_REASON_STREAM_REASSEMBLY, PKT_DROP_REASON_STREAM_URG, - PKT_DROP_REASON_NFQ_ERROR, /**< no nfq verdict, must be error */ - PKT_DROP_REASON_INNER_PACKET, /**< drop issued by inner (tunnel) packet */ + PKT_DROP_REASON_NFQ_ERROR, /**< no nfq verdict, must be error */ + PKT_DROP_REASON_INNER_PACKET, /**< drop issued by inner (tunnel) packet */ + PKT_DROP_REASON_DEFAULT_PACKET_POLICY, /**< drop issued by default packet policy */ + PKT_DROP_REASON_DEFAULT_APP_POLICY, /**< drop issued by default app policy */ PKT_DROP_REASON_MAX, }; diff --git a/src/detect-engine-alert.c b/src/detect-engine-alert.c index 1ec4790f52..0c5a84ebaa 100644 --- a/src/detect-engine-alert.c +++ b/src/detect-engine-alert.c @@ -154,8 +154,14 @@ int PacketAlertCheck(Packet *p, uint32_t sid) static inline void RuleActionToFlow(const uint8_t action, Flow *f) { + if (action & ACTION_ACCEPT) { + f->flags |= FLOW_ACTION_ACCEPT; + SCLogDebug("setting flow action pass"); + } + + // TODO pass and accept could be set at the same time? if (action & (ACTION_DROP | ACTION_REJECT_ANY | ACTION_PASS)) { - if (f->flags & (FLOW_ACTION_DROP | FLOW_ACTION_PASS)) { + if (f->flags & (FLOW_ACTION_DROP | FLOW_ACTION_PASS | FLOW_ACTION_ACCEPT)) { /* drop or pass already set. First to set wins. */ SCLogDebug("not setting %s flow already set to %s", (action & ACTION_PASS) ? "pass" : "drop", @@ -205,13 +211,22 @@ static void PacketApplySignatureActions(Packet *p, const Signature *s, const Pac if (pa->action & ACTION_PASS) { SCLogDebug("[packet %p][PASS sid %u]", p, s->id); // nothing to set in the packet + } else if (pa->action & ACTION_ACCEPT) { + const enum ActionScope as = pa->s->action_scope; + SCLogDebug("packet %" PRIu64 ": ACCEPT %u as:%u flags:%02x", p->pcap_cnt, s->id, as, + pa->flags); + if (as == ACTION_SCOPE_PACKET || as == ACTION_SCOPE_FLOW || + (pa->flags & PACKET_ALERT_FLAG_APPLY_ACTION_TO_PACKET)) { + SCLogDebug("packet %" PRIu64 ": sid:%u ACCEPT", p->pcap_cnt, s->id); + p->action |= ACTION_ACCEPT; + } } else if (pa->action & (ACTION_ALERT | ACTION_CONFIG)) { // nothing to set in the packet } else if (pa->action != 0) { DEBUG_VALIDATE_BUG_ON(1); // should be unreachable } - if ((pa->action & ACTION_PASS) && (p->flow != NULL) && + if ((pa->action & (ACTION_PASS | ACTION_ACCEPT)) && (p->flow != NULL) && (pa->flags & PACKET_ALERT_FLAG_APPLY_ACTION_TO_FLOW)) { RuleActionToFlow(pa->action, p->flow); } @@ -337,7 +352,7 @@ static inline void FlowApplySignatureActions( * - sig is IP or PD only * - match is in applayer * - match is in stream */ - if (pa->action & (ACTION_DROP | ACTION_PASS)) { + if (pa->action & (ACTION_DROP | ACTION_PASS | ACTION_ACCEPT)) { DEBUG_VALIDATE_BUG_ON(s->type == SIG_TYPE_NOT_SET); DEBUG_VALIDATE_BUG_ON(s->type == SIG_TYPE_MAX); @@ -366,15 +381,23 @@ static inline void FlowApplySignatureActions( static inline void PacketAlertFinalizeProcessQueue( const DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, Packet *p) { + const bool have_fw_rules = (de_ctx->flags & DE_HAS_FIREWALL) != 0; + if (det_ctx->alert_queue_size > 1) { /* sort the alert queue before thresholding and appending to Packet */ qsort(det_ctx->alert_queue, det_ctx->alert_queue_size, sizeof(PacketAlert), AlertQueueSortHelper); } + bool dropped = false; + bool skip_td = false; for (uint16_t i = 0; i < det_ctx->alert_queue_size; i++) { PacketAlert *pa = &det_ctx->alert_queue[i]; - const Signature *s = de_ctx->sig_array[pa->num]; + const Signature *s = pa->s; // de_ctx->sig_array[pa->num]; + if (have_fw_rules && skip_td && (s->flags & SIG_FLAG_FIREWALL) == 0) { + continue; + } + int res = PacketAlertHandle(de_ctx, det_ctx, s, p, pa); if (res > 0) { /* Now, if we have an alert, we have to check if we want @@ -403,8 +426,12 @@ static inline void PacketAlertFinalizeProcessQueue( PacketApplySignatureActions(p, s, pa); } - /* Thresholding removes this alert */ - if (res == 0 || res == 2 || (s->action & (ACTION_ALERT | ACTION_PASS)) == 0) { + /* skip firewall sigs following a drop: IDS mode still shows alerts after an alert. */ + if ((s->flags & SIG_FLAG_FIREWALL) && dropped) { + p->alerts.discarded++; + + /* Thresholding removes this alert */ + } else if (res == 0 || res == 2 || (s->action & (ACTION_ALERT | ACTION_PASS)) == 0) { SCLogDebug("sid:%u: skipping alert because of thresholding (res=%d) or NOALERT (%02x)", s->id, res, s->action); /* we will not copy this to the AlertQueue */ @@ -416,14 +443,27 @@ static inline void PacketAlertFinalizeProcessQueue( /* pass w/o alert found, we're done. Alert is not logged. */ if ((pa->action & (ACTION_PASS | ACTION_ALERT)) == ACTION_PASS) { SCLogDebug("sid:%u: is a pass rule, so break out of loop", s->id); - break; + if (!have_fw_rules) + break; + SCLogDebug("skipping td"); + skip_td = true; + continue; } p->alerts.cnt++; /* pass with alert, we're done. Alert is logged. */ if (pa->action & ACTION_PASS) { SCLogDebug("sid:%u: is a pass rule, so break out of loop", s->id); - break; + if (!have_fw_rules) + break; + SCLogDebug("skipping td"); + skip_td = true; + continue; + } + + // TODO we can also drop if alert is suppressed, right? + if (s->action & ACTION_DROP) { + dropped = true; } } else { p->alerts.discarded++; diff --git a/src/detect-engine-analyzer.c b/src/detect-engine-analyzer.c index e362a6ad8a..b5d12164ed 100644 --- a/src/detect-engine-analyzer.c +++ b/src/detect-engine-analyzer.c @@ -978,6 +978,12 @@ void EngineAnalysisRules2(const DetectEngineCtx *de_ctx, const Signature *s) if (ctx.js == NULL) SCReturn; + if (s->init_data->firewall_rule) { + JB_SET_STRING(ctx.js, "class", "firewall"); + } else { + JB_SET_STRING(ctx.js, "class", "threat detection"); + } + SCJbSetString(ctx.js, "raw", s->sig_str); SCJbSetUint(ctx.js, "id", s->id); SCJbSetUint(ctx.js, "gid", s->gid); @@ -1034,6 +1040,9 @@ void EngineAnalysisRules2(const DetectEngineCtx *de_ctx, const Signature *s) if (s->action & ACTION_PASS) { SCJbAppendString(ctx.js, "pass"); } + if (s->action & ACTION_ACCEPT) { + SCJbAppendString(ctx.js, "accept"); + } SCJbClose(ctx.js); if (s->action_scope == ACTION_SCOPE_AUTO) { @@ -1058,6 +1067,12 @@ void EngineAnalysisRules2(const DetectEngineCtx *de_ctx, const Signature *s) case ACTION_SCOPE_FLOW: SCJbSetString(ctx.js, "scope", "flow"); break; + case ACTION_SCOPE_HOOK: + SCJbSetString(ctx.js, "scope", "hook"); + break; + case ACTION_SCOPE_TX: + SCJbSetString(ctx.js, "scope", "tx"); + break; case ACTION_SCOPE_AUTO: /* should be unreachable */ break; } diff --git a/src/detect-engine-loader.c b/src/detect-engine-loader.c index 3dd6954d45..7b63b29b40 100644 --- a/src/detect-engine-loader.c +++ b/src/detect-engine-loader.c @@ -55,12 +55,8 @@ extern int engine_analysis; static bool fp_engine_analysis_set = false; bool rule_engine_analysis_set = false; -/** - * \brief Create the path if default-rule-path was specified - * \param sig_file The name of the file - * \retval str Pointer to the string path + sig_file - */ -char *DetectLoadCompleteSigPath(const DetectEngineCtx *de_ctx, const char *sig_file) +static char *DetectLoadCompleteSigPathWithKey( + const DetectEngineCtx *de_ctx, const char *default_key, const char *sig_file) { const char *defaultpath = NULL; char *path = NULL; @@ -74,10 +70,9 @@ char *DetectLoadCompleteSigPath(const DetectEngineCtx *de_ctx, const char *sig_f /* If we have a configuration prefix, only use it if the primary configuration node * is not marked as final, as that means it was provided on the command line with * a --set. */ - SCConfNode *default_rule_path = SCConfGetNode("default-rule-path"); + SCConfNode *default_rule_path = SCConfGetNode(default_key); if ((!default_rule_path || !default_rule_path->final) && strlen(de_ctx->config_prefix) > 0) { - snprintf(varname, sizeof(varname), "%s.default-rule-path", - de_ctx->config_prefix); + snprintf(varname, sizeof(varname), "%s.%s", de_ctx->config_prefix, default_key); default_rule_path = SCConfGetNode(varname); } if (default_rule_path) { @@ -103,6 +98,16 @@ char *DetectLoadCompleteSigPath(const DetectEngineCtx *de_ctx, const char *sig_f return path; } +/** + * \brief Create the path if default-rule-path was specified + * \param sig_file The name of the file + * \retval str Pointer to the string path + sig_file + */ +char *DetectLoadCompleteSigPath(const DetectEngineCtx *de_ctx, const char *sig_file) +{ + return DetectLoadCompleteSigPathWithKey(de_ctx, "default-rule-path", sig_file); +} + /** * \brief Load a file with signatures * \param de_ctx Pointer to the detection engine context @@ -112,7 +117,7 @@ char *DetectLoadCompleteSigPath(const DetectEngineCtx *de_ctx, const char *sig_f * \retval 0 on success, -1 on error */ static int DetectLoadSigFile(DetectEngineCtx *de_ctx, const char *sig_file, int *goodsigs, - int *badsigs, int *skippedsigs) + int *badsigs, int *skippedsigs, const bool firewall_rule) { int good = 0, bad = 0, skipped = 0; char line[DETECT_MAX_RULE_SIZE] = ""; @@ -164,7 +169,11 @@ static int DetectLoadSigFile(DetectEngineCtx *de_ctx, const char *sig_file, int de_ctx->rule_file = sig_file; de_ctx->rule_line = lineno - multiline; - Signature *sig = DetectEngineAppendSig(de_ctx, line); + Signature *sig = NULL; + if (firewall_rule) + sig = DetectFirewallRuleAppendNew(de_ctx, line); + else + sig = DetectEngineAppendSig(de_ctx, line); if (sig != NULL) { if (rule_engine_analysis_set || fp_engine_analysis_set) { if (fp_engine_analysis_set) { @@ -258,7 +267,7 @@ static int ProcessSigFiles(DetectEngineCtx *de_ctx, char *pattern, SigFileLoader } else { SCLogConfig("Loading rule file: %s", fname); } - r = DetectLoadSigFile(de_ctx, fname, good_sigs, bad_sigs, skipped_sigs); + r = DetectLoadSigFile(de_ctx, fname, good_sigs, bad_sigs, skipped_sigs, false); if (r < 0) { ++(st->bad_files); } @@ -276,6 +285,80 @@ static int ProcessSigFiles(DetectEngineCtx *de_ctx, char *pattern, SigFileLoader return r; } +static int LoadFirewallRuleFiles(DetectEngineCtx *de_ctx) +{ + if (de_ctx->firewall_rule_file_exclusive) { + int32_t good_sigs = 0; + int32_t bad_sigs = 0; + int32_t skipped_sigs = 0; + + SCLogNotice("fw: rule file full path \"%s\"", de_ctx->firewall_rule_file_exclusive); + + int ret = DetectLoadSigFile(de_ctx, de_ctx->firewall_rule_file_exclusive, &good_sigs, + &bad_sigs, &skipped_sigs, true); + + /* for now be as strict as possible */ + if (ret != 0 || bad_sigs != 0 || skipped_sigs != 0) { + /* Some rules failed to load, just exit as + * errors would have already been logged. */ + exit(EXIT_FAILURE); + } + + de_ctx->flags |= DE_HAS_FIREWALL; + + if (good_sigs == 0) { + SCLogNotice("fw: No rules loaded from %s.", de_ctx->firewall_rule_file_exclusive); + } else { + SCLogNotice("fw: %d rules loaded from %s.", good_sigs, + de_ctx->firewall_rule_file_exclusive); + de_ctx->sig_stat.good_sigs_total += good_sigs; + } + + return 0; + } + + SCConfNode *default_fw_rule_path = SCConfGetNode("firewall-rule-path"); + if (default_fw_rule_path == NULL) { + SCLogNotice("fw: firewall-rule-path not defined, skip loading firewall rules"); + return 0; + } + SCConfNode *rule_files = SCConfGetNode("firewall-rule-files"); + if (rule_files == NULL) { + SCLogNotice("fw: firewall-rule-files not defined, skip loading firewall rules"); + return 0; + } + + SCConfNode *file = NULL; + TAILQ_FOREACH (file, &rule_files->head, next) { + int32_t good_sigs = 0; + int32_t bad_sigs = 0; + int32_t skipped_sigs = 0; + + char *sfile = DetectLoadCompleteSigPathWithKey(de_ctx, "firewall-rule-path", file->val); + SCLogNotice("fw: rule file full path \"%s\"", sfile); + + int ret = DetectLoadSigFile(de_ctx, sfile, &good_sigs, &bad_sigs, &skipped_sigs, true); + SCFree(sfile); + + /* for now be as strict as possible */ + if (ret != 0 || bad_sigs != 0 || skipped_sigs != 0) { + /* Some rules failed to load, just exit as + * errors would have already been logged. */ + exit(EXIT_FAILURE); + } + + if (good_sigs == 0) { + SCLogNotice("fw: No rules loaded from %s.", file->val); + } else { + SCLogNotice("fw: %d rules loaded from %s.", good_sigs, file->val); + de_ctx->sig_stat.good_sigs_total += good_sigs; + } + } + de_ctx->flags |= DE_HAS_FIREWALL; + + return 0; +} + /** * \brief Load signatures * \param de_ctx Pointer to the detection engine context @@ -298,14 +381,29 @@ int SigLoadSignatures(DetectEngineCtx *de_ctx, char *sig_file, bool sig_file_exc int skipped_sigs = 0; if (strlen(de_ctx->config_prefix) > 0) { - snprintf(varname, sizeof(varname), "%s.rule-files", - de_ctx->config_prefix); + snprintf(varname, sizeof(varname), "%s.rule-files", de_ctx->config_prefix); } if (SCRunmodeGet() == RUNMODE_ENGINE_ANALYSIS) { SetupEngineAnalysis(de_ctx, &fp_engine_analysis_set, &rule_engine_analysis_set); } + if (de_ctx->firewall_rule_file_exclusive) { + if (LoadFirewallRuleFiles(de_ctx) < 0) { + if (de_ctx->failure_fatal) { + exit(EXIT_FAILURE); + } + ret = -1; + goto end; + } + + /* skip regular rules if we used a exclusive firewall rule file */ + if (!sig_file_exclusive && de_ctx->firewall_rule_file_exclusive) { + ret = 0; + goto skip_regular_rules; + } + } + /* ok, let's load signature files from the general config */ if (!(sig_file != NULL && sig_file_exclusive)) { rule_files = SCConfGetNode(varname); @@ -350,6 +448,7 @@ int SigLoadSignatures(DetectEngineCtx *de_ctx, char *sig_file, bool sig_file_exc } } +skip_regular_rules: /* now we should have signatures to work with */ if (sig_stat->good_sigs_total <= 0) { if (sig_stat->total_files > 0) { diff --git a/src/detect-engine-prefilter.c b/src/detect-engine-prefilter.c index 8bb5306054..bf0b9984fb 100644 --- a/src/detect-engine-prefilter.c +++ b/src/detect-engine-prefilter.c @@ -117,22 +117,41 @@ void DetectRunPrefilterTx(DetectEngineThreadCtx *det_ctx, } if (engine->ctx.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, + AppProtoToString(engine->alproto), pname); +#endif + /* if engine needs tx state to be higher, break out. */ if (engine->ctx.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 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) { SCLogDebug("tx already marked progress as beyond engine: %u > %u", tx->detect_progress, engine->ctx.tx_min_progress); goto next; + } else { + SCLogDebug("tx->tx_progress %u > engine->ctx.tx_min_progress %d: " + "tx->detect_progress %u", + tx->tx_progress, engine->ctx.tx_min_progress, tx->detect_progress); } } - +#ifdef DEBUG + uint32_t old = det_ctx->pmq.rule_id_array_cnt; +#endif 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); 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, + det_ctx->pmq.rule_id_array_cnt - old); if (tx->tx_progress > engine->ctx.tx_min_progress && engine->is_last_for_progress) { /* track with an offset of one, so that tx->progress 0 complete is tracked @@ -976,6 +995,20 @@ static int SetupNonPrefilter(DetectEngineCtx *de_ctx, SigGroupHead *sgh) } } } + /* handle hook only rules */ + if (!tx_non_pf && s->init_data->hook.type == SIGNATURE_HOOK_TYPE_APP) { + const int dir = (s->flags & SIG_FLAG_TOSERVER) ? 0 : 1; + const char *pname = AppLayerParserGetStateNameById(IPPROTO_TCP, // TODO + 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) { + goto error; + } + tx_non_pf = true; + } /* mark as prefiltered as the sig is now part of a engine */ // s->flags |= SIG_FLAG_PREFILTER; // TODO doesn't work for sigs that are in multiple sgh's diff --git a/src/detect-engine-sigorder.c b/src/detect-engine-sigorder.c index b6ce4a8ab6..aeb4d8db35 100644 --- a/src/detect-engine-sigorder.c +++ b/src/detect-engine-sigorder.c @@ -723,6 +723,50 @@ static int SCSigOrderByPriorityCompare(SCSigSignatureWrapper *sw1, return 0; } +static int SCSigOrderByIId(SCSigSignatureWrapper *sw1, SCSigSignatureWrapper *sw2) +{ + if (sw1->sig->num > sw2->sig->num) { + return -1; + } else if (sw1->sig->num < sw2->sig->num) { + return 1; + } + return 0; +} + +/* sort by: + * alproto, progress, iid + */ +static int SCSigOrderByAppFirewall(SCSigSignatureWrapper *sw1, SCSigSignatureWrapper *sw2) +{ + int sw1dir = (sw1->sig->flags & SIG_FLAG_TOSERVER) != 0 ? 0 : 1; + int sw2dir = (sw2->sig->flags & SIG_FLAG_TOSERVER) != 0 ? 0 : 1; + + if (sw1dir > sw2dir) { + return -1; + } else if (sw1dir < sw2dir) { + return 1; + } + + if (sw1->sig->alproto > sw2->sig->alproto) { + return -1; + } else if (sw1->sig->alproto < sw2->sig->alproto) { + return 1; + } + + if (sw1->sig->app_progress_hook > sw2->sig->app_progress_hook) { + return -1; + } else if (sw1->sig->app_progress_hook < sw2->sig->app_progress_hook) { + return 1; + } + + if (sw1->sig->num > sw2->sig->num) { + return -1; + } else if (sw1->sig->num < sw2->sig->num) { + return 1; + } + return 0; +} + /** * \brief Creates a Wrapper around the Signature * @@ -764,43 +808,54 @@ void SCSigOrderSignatures(DetectEngineCtx *de_ctx) return; } - Signature *sig = NULL; - SCSigSignatureWrapper *sigw = NULL; - SCSigSignatureWrapper *sigw_list = NULL; -#ifdef DEBUG - int i = 0; -#endif SCLogDebug("ordering signatures in memory"); + SCSigSignatureWrapper *sigw = NULL; + SCSigSignatureWrapper *td_sigw_list = NULL; /* unified td list */ - sig = de_ctx->sig_list; + SCSigSignatureWrapper *fw_pf_sigw_list = NULL; /* hook: packet_filter */ + SCSigSignatureWrapper *fw_af_sigw_list = NULL; /* hook: app_filter */ + + Signature *sig = de_ctx->sig_list; while (sig != NULL) { sigw = SCSigAllocSignatureWrapper(sig); /* Push signature wrapper onto a list, order doesn't matter here. */ - sigw->next = sigw_list; - sigw_list = sigw; - + if (sig->init_data->firewall_rule) { + if (sig->type == SIG_TYPE_PKT) { + sigw->next = fw_pf_sigw_list; + fw_pf_sigw_list = sigw; + } else { + // TODO review types. + sigw->next = fw_af_sigw_list; + fw_af_sigw_list = sigw; + } + } else { + sigw->next = td_sigw_list; + td_sigw_list = sigw; + } sig = sig->next; -#ifdef DEBUG - i++; -#endif } - /* Sort the list */ - sigw_list = SCSigOrder(sigw_list, de_ctx->sc_sig_order_funcs); - - SCLogDebug("Total Signatures to be processed by the" - "sigordering module: %d", i); - + /* despite having Append in the name, the new Sig/Rule funcs actually prepend with some special + * logic around bidir sigs. So to respect the firewall rule order, we sort this part of the list + * by the add order. */ + if (fw_pf_sigw_list) { + SCSigOrderFunc OrderFn = { .SWCompare = SCSigOrderByIId, .next = NULL }; + fw_pf_sigw_list = SCSigOrder(fw_pf_sigw_list, &OrderFn); + } + if (fw_af_sigw_list) { + SCSigOrderFunc OrderFn = { .SWCompare = SCSigOrderByAppFirewall, .next = NULL }; + fw_af_sigw_list = SCSigOrder(fw_af_sigw_list, &OrderFn); + } + if (td_sigw_list) { + /* Sort the list */ + td_sigw_list = SCSigOrder(td_sigw_list, de_ctx->sc_sig_order_funcs); + } /* Recreate the sig list in order */ de_ctx->sig_list = NULL; - sigw = sigw_list; -#ifdef DEBUG - i = 0; -#endif - while (sigw != NULL) { -#ifdef DEBUG - i++; -#endif + + /* firewall list for hook packet_filter */ + for (sigw = fw_pf_sigw_list; sigw != NULL;) { + SCLogDebug("post-sort packet_filter: sid %u", sigw->sig->id); sigw->sig->next = NULL; if (de_ctx->sig_list == NULL) { /* First entry on the list */ @@ -810,12 +865,44 @@ void SCSigOrderSignatures(DetectEngineCtx *de_ctx) sig->next = sigw->sig; sig = sig->next; } + SCSigSignatureWrapper *sigw_to_free = sigw; sigw = sigw->next; SCFree(sigw_to_free); } + /* firewall list for hook app_filter */ + for (sigw = fw_af_sigw_list; sigw != NULL;) { + SCLogDebug("post-sort app_filter: sid %u", sigw->sig->id); + sigw->sig->next = NULL; + if (de_ctx->sig_list == NULL) { + /* First entry on the list */ + de_ctx->sig_list = sigw->sig; + sig = de_ctx->sig_list; + } else { + sig->next = sigw->sig; + sig = sig->next; + } + + SCSigSignatureWrapper *sigw_to_free = sigw; + sigw = sigw->next; + SCFree(sigw_to_free); + } + /* threat detect list for hook app_td */ + for (sigw = td_sigw_list; sigw != NULL;) { + sigw->sig->next = NULL; + if (de_ctx->sig_list == NULL) { + /* First entry on the list */ + de_ctx->sig_list = sigw->sig; + sig = de_ctx->sig_list; + } else { + sig->next = sigw->sig; + sig = sig->next; + } - SCLogDebug("total signatures reordered by the sigordering module: %d", i); + SCSigSignatureWrapper *sigw_to_free = sigw; + sigw = sigw->next; + SCFree(sigw_to_free); + } } /** diff --git a/src/detect-parse.c b/src/detect-parse.c index 4647aa4f68..009106f13e 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -1329,6 +1329,8 @@ static int SigParseProtoHookApp(Signature *s, const char *proto_hook, const char SCLogDebug("protocol:%s hook:%s: type:%s alproto:%u hook:%d", p, h, SignatureHookTypeToString(s->init_data->hook.type), s->init_data->hook.t.app.alproto, s->init_data->hook.t.app.app_progress); + + s->app_progress_hook = (uint8_t)s->init_data->hook.t.app.app_progress; return 0; } @@ -1501,6 +1503,8 @@ static uint8_t ActionStringToFlags(const char *action) return ACTION_REJECT_BOTH | ACTION_DROP | ACTION_ALERT; } else if (strcasecmp(action, "config") == 0) { return ACTION_CONFIG; + } else if (strcasecmp(action, "accept") == 0) { + return ACTION_ACCEPT; } else { SCLogError("An invalid action \"%s\" was given", action); return 0; @@ -1556,6 +1560,23 @@ static int SigParseAction(Signature *s, const char *action_in) return -1; } s->action_scope = scope_flags; + } else if (flags & (ACTION_ACCEPT)) { + if (strcmp(o, "packet") == 0) { + scope_flags = (uint8_t)ACTION_SCOPE_PACKET; + } else if (strcmp(o, "hook") == 0) { + scope_flags = (uint8_t)ACTION_SCOPE_HOOK; + } else if (strcmp(o, "tx") == 0) { + scope_flags = (uint8_t)ACTION_SCOPE_TX; + } else if (strcmp(o, "flow") == 0) { + scope_flags = (uint8_t)ACTION_SCOPE_FLOW; + } else { + SCLogError( + "invalid action scope '%s' in action '%s': only 'packet', 'flow', 'tx' and " + "'hook' allowed", + o, action_in); + return -1; + } + s->action_scope = scope_flags; } else { SCLogError("invalid action scope '%s' in action '%s': scope only supported for actions " "'drop', 'pass' and 'reject'", @@ -1564,6 +1585,22 @@ static int SigParseAction(Signature *s, const char *action_in) } } + /* require explicit action scope for fw rules */ + if (s->init_data->firewall_rule && s->action_scope == 0) { + SCLogError("firewall rules require setting an explicit action scope"); + return -1; + } + + if (!s->init_data->firewall_rule && (flags & ACTION_ACCEPT)) { + SCLogError("'accept' action only supported for firewall rules"); + return -1; + } + + if (s->init_data->firewall_rule && (flags & ACTION_PASS)) { + SCLogError("'pass' action not supported for firewall rules"); + return -1; + } + s->action = flags; return 0; } @@ -1719,6 +1756,11 @@ static int SigParseBasics(DetectEngineCtx *de_ctx, Signature *s, const char *sig if (strcmp(parser->direction, "<>") == 0) { s->init_data->init_flags |= SIG_FLAG_INIT_BIDIREC; } else if (strcmp(parser->direction, "=>") == 0) { + if (s->flags & SIG_FLAG_FIREWALL) { + SCLogError("transactional bidirectional rules not supported for firewall rules"); + goto error; + } + s->flags |= SIG_FLAG_TXBOTHDIR; } else if (strcmp(parser->direction, "->") != 0) { SCLogError("\"%s\" is not a valid direction modifier, " @@ -2398,6 +2440,17 @@ static void SigSetupPrefilter(DetectEngineCtx *de_ctx, Signature *s) SCReturn; } +static bool DetectFirewallRuleValidate(const DetectEngineCtx *de_ctx, const Signature *s) +{ + if (s->init_data->hook.type == SIGNATURE_HOOK_TYPE_NOT_SET) { + SCLogError("rule %u is loaded as a firewall rule, but does not specify an " + "explicit hook", + s->id); + return false; + } + return true; +} + /** * \internal * \brief validate a just parsed signature for internal inconsistencies @@ -2411,6 +2464,11 @@ static int SigValidate(DetectEngineCtx *de_ctx, Signature *s) { SCEnter(); + if (s->init_data->firewall_rule) { + if (!DetectFirewallRuleValidate(de_ctx, s)) + SCReturnInt(0); + } + uint32_t sig_flags = 0; int nlists = 0; for (uint32_t x = 0; x < s->init_data->buffer_index; x++) { @@ -2668,8 +2726,8 @@ static int SigValidate(DetectEngineCtx *de_ctx, Signature *s) * \internal * \brief Helper function for SigInit(). */ -static Signature *SigInitHelper(DetectEngineCtx *de_ctx, const char *sigstr, - uint8_t dir) +static Signature *SigInitHelper( + DetectEngineCtx *de_ctx, const char *sigstr, uint8_t dir, const bool firewall_rule) { SignatureParser parser; memset(&parser, 0x00, sizeof(parser)); @@ -2677,6 +2735,10 @@ static Signature *SigInitHelper(DetectEngineCtx *de_ctx, const char *sigstr, Signature *sig = SigAlloc(); if (sig == NULL) goto error; + if (firewall_rule) { + sig->init_data->firewall_rule = true; + sig->flags |= SIG_FLAG_FIREWALL; + } sig->sig_str = SCStrdup(sigstr); if (unlikely(sig->sig_str == NULL)) { @@ -2857,16 +2919,7 @@ static bool SigHasSameSourceAndDestination(const Signature *s) return true; } -/** - * \brief Parses a signature and adds it to the Detection Engine Context. - * - * \param de_ctx Pointer to the Detection Engine Context. - * \param sigstr Pointer to a character string containing the signature to be - * parsed. - * - * \retval Pointer to the Signature instance on success; NULL on failure. - */ -Signature *SigInit(DetectEngineCtx *de_ctx, const char *sigstr) +static Signature *SigInitDo(DetectEngineCtx *de_ctx, const char *sigstr, const bool firewall_rule) { SCEnter(); @@ -2875,9 +2928,8 @@ Signature *SigInit(DetectEngineCtx *de_ctx, const char *sigstr) de_ctx->sigerror_silent = false; de_ctx->sigerror_requires = false; - Signature *sig; - - if ((sig = SigInitHelper(de_ctx, sigstr, SIG_DIREC_NORMAL)) == NULL) { + Signature *sig = SigInitHelper(de_ctx, sigstr, SIG_DIREC_NORMAL, firewall_rule); + if (sig == NULL) { goto error; } @@ -2888,7 +2940,7 @@ Signature *SigInit(DetectEngineCtx *de_ctx, const char *sigstr) sig->init_data->init_flags &= ~SIG_FLAG_INIT_BIDIREC; } else { - sig->next = SigInitHelper(de_ctx, sigstr, SIG_DIREC_SWITCHED); + sig->next = SigInitHelper(de_ctx, sigstr, SIG_DIREC_SWITCHED, firewall_rule); if (sig->next == NULL) { goto error; } @@ -2908,6 +2960,25 @@ error: SCReturnPtr(NULL, "Signature"); } +/** + * \brief Parses a signature and adds it to the Detection Engine Context. + * + * \param de_ctx Pointer to the Detection Engine Context. + * \param sigstr Pointer to a character string containing the signature to be + * parsed. + * + * \retval Pointer to the Signature instance on success; NULL on failure. + */ +Signature *SigInit(DetectEngineCtx *de_ctx, const char *sigstr) +{ + return SigInitDo(de_ctx, sigstr, false); +} + +static Signature *DetectFirewallRuleNew(DetectEngineCtx *de_ctx, const char *sigstr) +{ + return SigInitDo(de_ctx, sigstr, true); +} + /** * \brief The hash free function to be the used by the hash table - * DetectEngineCtx->dup_sig_hash_table. @@ -3148,6 +3219,78 @@ end: return ret; } +/** + * \brief Parse and append a Signature into the Detection Engine Context + * signature list. + * + * If the signature is bidirectional it should append two signatures + * (with the addresses switched) into the list. Also handle duplicate + * signatures. In case of duplicate sigs, use the ones that have the + * latest revision. We use the sid and the msg to identify duplicate + * sigs. If 2 sigs have the same sid and gid, they are duplicates. + * + * \param de_ctx Pointer to the Detection Engine Context. + * \param sigstr Pointer to a character string containing the signature to be + * parsed. + * \param sig_file Pointer to a character string containing the filename from + * which signature is read + * \param lineno Line number from where signature is read + * + * \retval Pointer to the head Signature in the detection engine ctx sig_list + * on success; NULL on failure. + */ +Signature *DetectFirewallRuleAppendNew(DetectEngineCtx *de_ctx, const char *sigstr) +{ + Signature *sig = DetectFirewallRuleNew(de_ctx, sigstr); + if (sig == NULL) { + return NULL; + } + + /* checking for the status of duplicate signature */ + int dup_sig = DetectEngineSignatureIsDuplicate(de_ctx, sig); + /* a duplicate signature that should be chucked out. Check the previously + * called function details to understand the different return values */ + if (dup_sig == 1) { + SCLogError("Duplicate signature \"%s\"", sigstr); + goto error; + } else if (dup_sig == 2) { + SCLogWarning("Signature with newer revision," + " so the older sig replaced by this new signature \"%s\"", + sigstr); + } + + if (sig->init_data->init_flags & SIG_FLAG_INIT_BIDIREC) { + if (sig->next != NULL) { + sig->next->next = de_ctx->sig_list; + } else { + goto error; + } + } else { + /* if this sig is the first one, sig_list should be null */ + sig->next = de_ctx->sig_list; + } + + de_ctx->sig_list = sig; + + /** + * In DetectEngineAppendSig(), the signatures are prepended and we always return the first one + * so if the signature is bidirectional, the returned sig will point through "next" ptr + * to the cloned signatures with the switched addresses + */ + return (dup_sig == 0 || dup_sig == 2) ? sig : NULL; + +error: + /* free the 2nd sig bidir may have set up */ + if (sig != NULL && sig->next != NULL) { + SigFree(de_ctx, sig->next); + sig->next = NULL; + } + if (sig != NULL) { + SigFree(de_ctx, sig); + } + return NULL; +} + /** * \brief Parse and append a Signature into the Detection Engine Context * signature list. diff --git a/src/detect-parse.h b/src/detect-parse.h index 3e12abfba5..18c998f3e2 100644 --- a/src/detect-parse.h +++ b/src/detect-parse.h @@ -73,6 +73,7 @@ Signature *SigInit(DetectEngineCtx *, const char *sigstr); SigMatchData* SigMatchList2DataArray(SigMatch *head); void SigParseRegisterTests(void); Signature *DetectEngineAppendSig(DetectEngineCtx *, const char *); +Signature *DetectFirewallRuleAppendNew(DetectEngineCtx *, const char *); SigMatch *SigMatchAppendSMToList(DetectEngineCtx *, Signature *, uint16_t, SigMatchCtx *, int); void SigMatchRemoveSMFromList(Signature *, SigMatch *, int); diff --git a/src/detect.c b/src/detect.c index 8216fce8c8..d662e66e99 100644 --- a/src/detect.c +++ b/src/detect.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2022 Open Information Security Foundation +/* Copyright (C) 2007-2025 Open Information Security Foundation * * You can copy, redistribute or modify this Program under the terms of * the GNU General Public License version 2 as published by the Free @@ -83,9 +83,9 @@ static inline void DetectRunGetRuleGroup(const DetectEngineCtx *de_ctx, static inline void DetectRunPrefilterPkt(ThreadVars *tv, DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, Packet *p, DetectRunScratchpad *scratch); -static inline void DetectRulePacketRules(ThreadVars * const tv, - DetectEngineCtx * const de_ctx, DetectEngineThreadCtx * const det_ctx, - Packet * const p, Flow * const pflow, const DetectRunScratchpad *scratch); +static inline uint8_t DetectRulePacketRules(ThreadVars *const tv, DetectEngineCtx *const de_ctx, + DetectEngineThreadCtx *const det_ctx, Packet *const p, Flow *const pflow, + const DetectRunScratchpad *scratch); static void DetectRunTx(ThreadVars *tv, DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, Packet *p, Flow *f, DetectRunScratchpad *scratch); @@ -96,6 +96,7 @@ static inline void DetectRunPostRules(ThreadVars *tv, DetectEngineCtx *de_ctx, DetectRunScratchpad *scratch); static void DetectRunCleanup(DetectEngineThreadCtx *det_ctx, Packet *p, Flow * const pflow); +static inline void DetectRunAppendDefaultAccept(DetectEngineThreadCtx *det_ctx, Packet *p); /** \internal */ @@ -132,13 +133,26 @@ static void DetectRun(ThreadVars *th_v, PACKET_PROFILING_DETECT_START(p, PROF_DETECT_RULES); /* inspect the rules against the packet */ - DetectRulePacketRules(th_v, de_ctx, det_ctx, p, pflow, &scratch); + const uint8_t pkt_policy = DetectRulePacketRules(th_v, de_ctx, det_ctx, p, pflow, &scratch); PACKET_PROFILING_DETECT_END(p, PROF_DETECT_RULES); + /* Only FW rules will already have set the action, IDS rules go through PacketAlertFinalize + * + * If rules told us to drop or accept:packet/accept:flow, we skip app_filter and app_td. + * + * accept:hook won't have set the pkt_policy, so we simply continue. + * + * TODO what about app state progression, cleanup and such? */ + if (pkt_policy & (ACTION_DROP | ACTION_ACCEPT)) { + goto end; + } + /* run tx/state inspection. Don't call for ICMP error msgs. */ if (pflow && pflow->alstate && likely(pflow->proto == p->proto)) { if (p->proto == IPPROTO_TCP) { if ((p->flags & PKT_STREAM_EST) == 0) { + SCLogDebug("packet %" PRIu64 ": skip tcp non-established", p->pcap_cnt); + DetectRunAppendDefaultAccept(det_ctx, p); goto end; } const TcpSession *ssn = p->flow->protoctx; @@ -157,6 +171,8 @@ static void DetectRun(ThreadVars *th_v, if (!PKT_IS_PSEUDOPKT(p) && p->app_update_direction == 0 && ((PKT_IS_TOSERVER(p) && (p->flow->flags & FLOW_TS_APP_UPDATED) == 0) || (PKT_IS_TOCLIENT(p) && (p->flow->flags & FLOW_TC_APP_UPDATED) == 0))) { + SCLogDebug("packet %" PRIu64 ": no app-layer update", p->pcap_cnt); + DetectRunAppendDefaultAccept(det_ctx, p); goto end; } } else if (p->proto == IPPROTO_UDP) { @@ -171,6 +187,9 @@ static void DetectRun(ThreadVars *th_v, AppLayerParserSetTransactionInspectId( pflow, pflow->alparser, pflow->alstate, scratch.flow_flags, (scratch.sgh == NULL)); PACKET_PROFILING_DETECT_END(p, PROF_DETECT_TX_UPDATE); + } else { + SCLogDebug("packet %" PRIu64 ": no flow / app-layer", p->pcap_cnt); + DetectRunAppendDefaultAccept(det_ctx, p); } end: @@ -590,15 +609,13 @@ static int SortHelper(const void *a, const void *b) return sa->num > sb->num ? 1 : -1; } -static inline void DetectRulePacketRules( - ThreadVars * const tv, - DetectEngineCtx * const de_ctx, - DetectEngineThreadCtx * const det_ctx, - Packet * const p, - Flow * const pflow, - const DetectRunScratchpad *scratch -) +static inline uint8_t DetectRulePacketRules(ThreadVars *const tv, DetectEngineCtx *const de_ctx, + DetectEngineThreadCtx *const det_ctx, Packet *const p, Flow *const pflow, + const DetectRunScratchpad *scratch) { + uint8_t action = 0; + bool fw_verdict = false; + const bool have_fw_rules = (de_ctx->flags & DE_HAS_FIREWALL) != 0; const Signature *next_s = NULL; /* inspect the sigs against the packet */ @@ -618,6 +635,7 @@ static inline void DetectRulePacketRules( RulesDumpMatchArray(det_ctx, scratch->sgh, p); #endif + bool skip_fw = false; uint32_t sflags, next_sflags = 0; if (match_cnt) { next_s = *match_array++; @@ -625,6 +643,7 @@ static inline void DetectRulePacketRules( } while (match_cnt--) { RULE_PROFILING_START(p); + bool break_out_of_packet_filter = false; uint8_t alert_flags = 0; #ifdef PROFILE_RULES bool smatch = false; /* signature match */ @@ -639,6 +658,24 @@ static inline void DetectRulePacketRules( SCLogDebug("packet %" PRIu64 ": inspecting signature id %" PRIu32 "", p->pcap_cnt, s->id); + /* if we accept:hook'd the `packet_filter` hook, we skip the rest of the firewall rules. */ + if (s->flags & SIG_FLAG_FIREWALL) { + if (skip_fw) { + SCLogDebug("skipping firewall rule %u", s->id); + goto next; + } + } else if (have_fw_rules) { + /* fw mode, we skip anything after the fw rules if: + * - flow pass is set + * - packet pass (e.g. exception policy) */ + if (p->flags & PKT_NOPACKET_INSPECTION || + (pflow != NULL && pflow->flags & (FLOW_ACTION_PASS))) { + SCLogDebug("skipping firewall rule %u", s->id); + break_out_of_packet_filter = true; + goto next; + } + } + if (s->app_inspect != NULL) { goto next; // handle sig in DetectRunTx } @@ -760,12 +797,76 @@ static inline void DetectRulePacketRules( PMQ_RESET(&det_ctx->pmq); } } + + /* firewall logic in the packet:filter table: + * 1. firewall rules preceed the packet:td rules in the list + * 2. if no rule issues an accept, we drop + * 3. drop is immediate + * 4. accept: + * - hook: skip rest of fw rules, inspect packet:td rules + * - packet: immediate accept, no packet:td or app:* inspect + * - flow: as packet, but applied to all future packets in the + * flow as well + */ + if (s->flags & SIG_FLAG_FIREWALL) { + if (s->action & (ACTION_ACCEPT)) { + fw_verdict = true; + + enum ActionScope as = s->action_scope; + if (as == ACTION_SCOPE_HOOK) { + /* accept:hook: jump to first TD. Implemented as: + * skip until the first TD rule. + * Don't update action as we're just continuing to the next hook. */ + skip_fw = true; + + } else if (as == ACTION_SCOPE_PACKET) { + /* accept:packet: break loop, return accept */ + action |= s->action; + break_out_of_packet_filter = true; + + } else if (as == ACTION_SCOPE_FLOW) { + /* accept:flow: break loop, return accept */ + action |= s->action; + break_out_of_packet_filter = true; + + /* set immediately, as we're in hook "packet_filter" */ + if (pflow) { + pflow->flags |= FLOW_ACTION_ACCEPT; + } + } + } else if (s->action & ACTION_DROP) { + /* apply a drop immediately here */ + fw_verdict = true; + action |= s->action; + break_out_of_packet_filter = true; + } + } next: DetectVarProcessList(det_ctx, pflow, p); DetectReplaceFree(det_ctx); RULE_PROFILING_END(det_ctx, s, smatch, p); + + /* fw accept:packet or accept:flow means we're done here */ + if (break_out_of_packet_filter) + break; + continue; } + + /* if no rule told us to accept, and no rule explicitly dropped, we invoke the default drop + * policy + */ + if (have_fw_rules) { + if (!fw_verdict) { + DEBUG_VALIDATE_BUG_ON(action & ACTION_DROP); + PacketDrop(p, ACTION_DROP, PKT_DROP_REASON_DEFAULT_PACKET_POLICY); + action |= ACTION_DROP; + } else { + /* apply fw action */ + p->action |= action; + } + } + return action; } static DetectRunScratchpad DetectRunSetup( @@ -890,6 +991,16 @@ static inline void DetectRunPostRules( StatsAddUI64(tv, det_ctx->counter_alerts_suppressed, (uint64_t)p->alerts.suppressed); } PACKET_PROFILING_DETECT_END(p, PROF_DETECT_ALERT); + + /* firewall: "fail" closed if we don't have an ACCEPT. This can happen + * if there was no rule group. */ + // TODO review packet src types here + if (de_ctx->flags & DE_HAS_FIREWALL && !(p->action & ACTION_ACCEPT) && + p->pkt_src == PKT_SRC_WIRE) { + SCLogDebug("packet %" PRIu64 ": droppit as no ACCEPT set %02x (pkt %s)", p->pcap_cnt, + p->action, PktSrcToString(p->pkt_src)); + PacketDrop(p, ACTION_DROP, PKT_DROP_REASON_DEFAULT_PACKET_POLICY); + } } static void DetectRunCleanup(DetectEngineThreadCtx *det_ctx, @@ -1116,6 +1227,10 @@ static bool DetectRunTxInspectRule(ThreadVars *tv, } else if (engine->v2.Callback == NULL) { /* TODO is this the cleanest way to support a non-app sig on a app hook? */ + if (tx->tx_progress > engine->progress) { + mpm_before_progress = true; // TODO needs a new name now + } + /* we don't have to store a "hook" match, also don't want to keep any state to make * sure the hook gets invoked again until tx progress progresses. */ if (tx->tx_progress <= engine->progress) @@ -1378,6 +1493,133 @@ static inline void RuleMatchCandidateMergeStateRules( // and come before any other element later in the list } +/** + * \internal + * \brief Check and update firewall rules state. + * + * \param skip_fw_hook bool to indicate firewall rules skips + * For state `skip_before_progress` should be skipped. + * + * \param skip_before_progress progress value to skip rules before. + * Only used if `skip_fw_hook` is set. + * + * \param last_for_progress[out] set to true if this is the last rule for a progress value + * + * \param fw_next_progress_missing[out] set to true if the next fw rule does not target the next + * progress value, or there is no fw rule for that value. + * + * \retval 0 no action needed + * \retval 1 rest of rules shouldn't inspected + * \retval -1 skip this rule + */ +static int DetectRunTxCheckFirewallPolicy(DetectEngineThreadCtx *det_ctx, Packet *p, Flow *f, + DetectTransaction *tx, const Signature *s, const uint32_t can_idx, const uint32_t can_size, + bool *skip_fw_hook, const uint8_t skip_before_progress, bool *last_for_progress, + bool *fw_next_progress_missing) +{ + if (s->flags & SIG_FLAG_FIREWALL) { + /* check if the next sig is on the same progress hook. If not, we need to apply our + * default policy in case the current sig doesn't apply one. If the next sig has a + * progress beyond our progress + 1, it means the next progress has no rules and needs + * the default policy applied. But only after we evaluate the current rule first, as + * that may override it. + * TODO should we do this after dedup below? */ + + if (can_idx + 1 < can_size) { + const Signature *next_s = det_ctx->tx_candidates[can_idx + 1].s; + SCLogDebug( + "peek: peeking at sid %u / progress %u", next_s->id, next_s->app_progress_hook); + if (next_s->flags & SIG_FLAG_FIREWALL) { + if (s->app_progress_hook != next_s->app_progress_hook) { + SCLogDebug("peek: next sid progress %u != current progress %u, so current " + "is last for progress", + next_s->app_progress_hook, s->app_progress_hook); + *last_for_progress = true; + + if (next_s->app_progress_hook - s->app_progress_hook > 1) { + SCLogDebug("peek: missing progress, so we'll drop that unless we get a " + "sweeping accept first"); + *fw_next_progress_missing = true; + } + } + } else { + SCLogDebug("peek: next sid not a fw rule, so current is last for progress"); + *last_for_progress = true; + } + } else { + SCLogDebug("peek: no peek beyond last rule"); + if (s->app_progress_hook < tx->tx_progress) { + SCLogDebug("peek: there are no rules to allow the state after this rule"); + *fw_next_progress_missing = true; + } + } + + if ((*skip_fw_hook) == true) { + if (s->app_progress_hook <= skip_before_progress) { + return -1; + } + *skip_fw_hook = false; + } + } else { + /* fw mode, we skip anything after the fw rules if: + * - flow pass is set + * - packet pass (e.g. exception policy) */ + if (p->flags & PKT_NOPACKET_INSPECTION || (f->flags & (FLOW_ACTION_PASS))) { + SCLogDebug("skipping firewall rule %u", s->id); + return 1; + } + } + return 0; +} + +// TODO move into det_ctx? +thread_local Signature default_accept; +static inline void DetectRunAppendDefaultAccept(DetectEngineThreadCtx *det_ctx, Packet *p) +{ + if (det_ctx->de_ctx->flags & DE_HAS_FIREWALL) { + memset(&default_accept, 0, sizeof(default_accept)); + default_accept.action = ACTION_ACCEPT; + default_accept.action_scope = ACTION_SCOPE_PACKET; + default_accept.num = UINT32_MAX; + default_accept.type = SIG_TYPE_PKT; + default_accept.flags = SIG_FLAG_FIREWALL; + AlertQueueAppend(det_ctx, &default_accept, p, 0, PACKET_ALERT_FLAG_APPLY_ACTION_TO_PACKET); + } +} + +/** \internal + * \brief see if the accept rule needs to apply to the packet + */ +static inline bool ApplyAcceptToPacket( + const uint64_t total_txs, const DetectTransaction *tx, const Signature *s) +{ + if ((s->flags & SIG_FLAG_FIREWALL) == 0) { + return false; + } + if ((s->action & ACTION_ACCEPT) == 0) { + return false; + } + + /* for accept:tx we need: + * - packet will only be accepted if this is set on the last tx + */ + if (s->action_scope == ACTION_SCOPE_TX) { + if (total_txs == tx->tx_id + 1) { + return true; + } + } + /* for accept:hook we need a bit more checking: + * - packet will only be accepted if this is set on the last tx + * - the hook accepted should be the last progress available. */ + if (s->action_scope == ACTION_SCOPE_HOOK) { + if ((total_txs == tx->tx_id + 1) && /* last tx */ + (s->app_progress_hook == tx->tx_progress)) { + return true; + } + } + return false; +} + static void DetectRunTx(ThreadVars *tv, DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, @@ -1398,6 +1640,12 @@ static void DetectRunTx(ThreadVars *tv, AppLayerGetTxIteratorFunc IterFunc = AppLayerGetTxIterator(ipproto, alproto); AppLayerGetTxIterState state = { 0 }; + uint32_t fw_verdicted = 0; + uint32_t tx_inspected = 0; + const bool have_fw_rules = (de_ctx->flags & DE_HAS_FIREWALL) != 0; + + SCLogDebug("packet %" PRIu64, p->pcap_cnt); + while (1) { AppLayerGetTxIterTuple ires = IterFunc(ipproto, alproto, alstate, tx_id_min, total_txs, &state); if (ires.tx_ptr == NULL) @@ -1413,6 +1661,9 @@ static void DetectRunTx(ThreadVars *tv, goto next; } tx_id_min = tx.tx_id + 1; // next look for cur + 1 + tx_inspected++; + + SCLogDebug("%p/%" PRIu64 " txd flags %02x", tx.tx_ptr, tx_id_min, tx.tx_data_ptr->flags); bool do_sort = false; // do we need to sort the tx candidate list? uint32_t array_idx = 0; @@ -1517,11 +1768,34 @@ static void DetectRunTx(ThreadVars *tv, SCLogDebug("%u: sid %u flags %p", i, s->id, can->flags); } #endif + bool skip_fw_hook = false; + uint8_t skip_before_progress = 0; + bool fw_next_progress_missing = false; + + /* if there are no rules / rule candidates, make sure we don't + * invoke the default drop */ + if (have_fw_rules && array_idx == 0 && (tx.tx_data_ptr->flags & APP_LAYER_TX_ACCEPT)) { + fw_verdicted++; + + /* current tx is the last we have, append a blank accept:packet */ + if (total_txs == tx.tx_id + 1) { + DetectRunAppendDefaultAccept(det_ctx, p); + return; + } + goto next; + } + + bool tx_fw_verdict = false; /* run rules: inspect the match candidates */ for (uint32_t i = 0; i < array_idx; i++) { RuleMatchCandidateTx *can = &det_ctx->tx_candidates[i]; const Signature *s = det_ctx->tx_candidates[i].s; uint32_t *inspect_flags = det_ctx->tx_candidates[i].flags; + bool break_out_of_app_filter = false; + + SCLogDebug("%" PRIu64 ": sid:%u: %s tx %u/%u/%u sig %u", p->pcap_cnt, s->id, + flow_flags & STREAM_TOSERVER ? "toserver" : "toclient", tx.tx_progress, + tx.detect_progress, tx.detect_progress_orig, s->app_progress_hook); /* deduplicate: rules_array is sorted, but not deduplicated: * both mpm and stored state could give us the same sid. @@ -1535,6 +1809,27 @@ static void DetectRunTx(ThreadVars *tv, i++; } + /* skip fw rules if we're in accept:tx mode */ + if (have_fw_rules && (tx.tx_data_ptr->flags & APP_LAYER_TX_ACCEPT)) { + /* append a blank accept:packet action for the APP_LAYER_TX_ACCEPT, + * if this is the last tx */ + if (!tx_fw_verdict) { + const bool accept_tx_applies_to_packet = total_txs == tx.tx_id + 1; + if (accept_tx_applies_to_packet) { + SCLogDebug("accept:(tx|hook): should be applied to the packet"); + DetectRunAppendDefaultAccept(det_ctx, p); + } + } + tx_fw_verdict = true; + + if (s->flags & SIG_FLAG_FIREWALL) { + SCLogDebug("APP_LAYER_TX_ACCEPT, so skip rule"); + continue; + } + + /* threat detect rules will be inspected */ + } + SCLogDebug("%p/%"PRIu64" inspecting: sid %u (%u), flags %08x", tx.tx_ptr, tx.tx_id, s->id, s->num, inspect_flags ? *inspect_flags : 0); @@ -1554,6 +1849,17 @@ static void DetectRunTx(ThreadVars *tv, SCLogDebug("%p/%"PRIu64" Start sid %u", tx.tx_ptr, tx.tx_id, s->id); } + bool last_for_progress = false; + if (have_fw_rules) { + int fw_r = DetectRunTxCheckFirewallPolicy(det_ctx, p, f, &tx, s, i, array_idx, + &skip_fw_hook, skip_before_progress, &last_for_progress, + &fw_next_progress_missing); + if (fw_r == -1) + continue; + if (fw_r == 1) + break; + } + /* call individual rule inspection */ RULE_PROFILING_START(p); const int r = DetectRunTxInspectRule(tv, de_ctx, det_ctx, p, f, flow_flags, @@ -1562,9 +1868,65 @@ static void DetectRunTx(ThreadVars *tv, /* match */ DetectRunPostMatch(tv, det_ctx, p, s); - const uint8_t alert_flags = (PACKET_ALERT_FLAG_STATE_MATCH | PACKET_ALERT_FLAG_TX); + /* 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.*/ + const bool fw_accept_to_packet = ApplyAcceptToPacket(total_txs, &tx, s); + + uint8_t alert_flags = (PACKET_ALERT_FLAG_STATE_MATCH | PACKET_ALERT_FLAG_TX); + if (fw_accept_to_packet) { + SCLogDebug("accept:(tx|hook): should be applied to the packet"); + alert_flags |= PACKET_ALERT_FLAG_APPLY_ACTION_TO_PACKET; + } + SCLogDebug("%p/%"PRIu64" sig %u (%u) matched", tx.tx_ptr, tx.tx_id, s->id, s->num); AlertQueueAppend(det_ctx, s, p, tx.tx_id, alert_flags); + + if ((s->flags & SIG_FLAG_FIREWALL) && (s->action & ACTION_ACCEPT)) { + tx_fw_verdict = true; + + const enum ActionScope as = s->action_scope; + /* accept:hook: jump to first rule of next state. + * Implemented as skip until the first rule of next state. */ + if (as == ACTION_SCOPE_HOOK) { + skip_fw_hook = true; + skip_before_progress = s->app_progress_hook; + + /* if there is no fw rule for the next progress value, + * we invoke the default drop policy. */ + if (fw_next_progress_missing) { + SCLogDebug("%" PRIu64 ": %s default drop for progress", p->pcap_cnt, + flow_flags & STREAM_TOSERVER ? "toserver" : "toclient"); + PacketDrop(p, ACTION_DROP, PKT_DROP_REASON_DEFAULT_APP_POLICY); + p->flow->flags |= FLOW_ACTION_DROP; + break_out_of_app_filter = true; + } + } else if (as == ACTION_SCOPE_TX) { + tx.tx_data_ptr->flags |= APP_LAYER_TX_ACCEPT; + skip_fw_hook = true; + skip_before_progress = (uint8_t)tx_end_state + 1; // skip all hooks + SCLogDebug("accept:tx applied, skip_fw_hook, skip_before_progress %u", + skip_before_progress); + } else if (as == ACTION_SCOPE_PACKET) { + break_out_of_app_filter = true; + } else if (as == ACTION_SCOPE_FLOW) { + break_out_of_app_filter = true; + } + } + } else if (last_for_progress) { + SCLogDebug("sid %u: not a match: %s rule, last_for_progress %s", s->id, + (s->flags & SIG_FLAG_FIREWALL) ? "firewall" : "regular", + BOOL2STR(last_for_progress)); + if (s->flags & SIG_FLAG_FIREWALL) { + SCLogDebug("%" PRIu64 ": %s default drop for progress", p->pcap_cnt, + flow_flags & STREAM_TOSERVER ? "toserver" : "toclient"); + /* if this rule was the last for our progress state, and it didn't match, + * we have to invoke the default drop policy. */ + PacketDrop(p, ACTION_DROP, PKT_DROP_REASON_DEFAULT_APP_POLICY); + p->flow->flags |= FLOW_ACTION_DROP; + break_out_of_app_filter = true; + tx_fw_verdict = true; + } } DetectVarProcessList(det_ctx, p->flow, p); RULE_PROFILING_END(det_ctx, s, r, p); @@ -1600,7 +1962,12 @@ static void DetectRunTx(ThreadVars *tv, det_ctx->post_rule_work_queue.len = 0; PMQ_RESET(&det_ctx->pmq); } + + if (break_out_of_app_filter) + break; } + if (tx_fw_verdict) + fw_verdicted++; det_ctx->tx_id = 0; det_ctx->tx_id_set = false; @@ -1622,6 +1989,9 @@ static void DetectRunTx(ThreadVars *tv, } if (tx.detect_progress != tx.detect_progress_orig) { + SCLogDebug("%" PRIu64 ": %s tx state change %u -> %u", p->pcap_cnt, + flow_flags & STREAM_TOSERVER ? "toserver" : "toclient", tx.detect_progress_orig, + tx.detect_progress); SCLogDebug("%p/%" PRIu64 " Storing new progress %02x (was %02x)", tx.tx_ptr, tx.tx_id, tx.detect_progress, tx.detect_progress_orig); @@ -1634,6 +2004,22 @@ static void DetectRunTx(ThreadVars *tv, if (!ires.has_next) break; } + + /* apply default policy if there were txs to inspect, we have fw rules and non of the rules + * applied a policy. */ + SCLogDebug("packet %" PRIu64 ": tx_inspected %u fw_verdicted %u", p->pcap_cnt, tx_inspected, + fw_verdicted); + if (tx_inspected && have_fw_rules && tx_inspected != fw_verdicted) { + SCLogDebug("%" PRIu64 ": %s default drop", p->pcap_cnt, + flow_flags & STREAM_TOSERVER ? "toserver" : "toclient"); + PacketDrop(p, ACTION_DROP, PKT_DROP_REASON_DEFAULT_APP_POLICY); + p->flow->flags |= FLOW_ACTION_DROP; + return; + } + /* if all tables have been bypassed, we accept:packet */ + if (tx_inspected == 0 && fw_verdicted == 0 && have_fw_rules) { + DetectRunAppendDefaultAccept(det_ctx, p); + } } static void DetectRunFrames(ThreadVars *tv, DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, @@ -1796,11 +2182,23 @@ static void DetectFlow(ThreadVars *tv, SCReturn; } - if (p->flags & PKT_NOPACKET_INSPECTION || f->flags & (FLOW_ACTION_PASS)) { + /* in firewall mode, we still need to run the fw rulesets even for exception policy pass */ + bool skip = false; + if (de_ctx->flags & DE_HAS_FIREWALL) { + skip = (f->flags & (FLOW_ACTION_ACCEPT)); + + } else { + skip = (p->flags & PKT_NOPACKET_INSPECTION || f->flags & (FLOW_ACTION_PASS)); + } + if (skip) { + /* enfore prior accept:flow */ + if (f->flags & FLOW_ACTION_ACCEPT) { + p->action |= ACTION_ACCEPT; + } /* hack: if we are in pass the entire flow mode, we need to still * update the inspect_id forward. So test for the condition here, * and call the update code if necessary. */ - const int pass = ((f->flags & (FLOW_ACTION_PASS))); + const int pass = (f->flags & (FLOW_ACTION_PASS | FLOW_ACTION_ACCEPT)); if (pass) { uint8_t flags = STREAM_FLAGS_FOR_PACKET(p); flags = FlowGetDisruptionFlags(f, flags); diff --git a/src/detect.h b/src/detect.h index 9abb96be9d..f424d40153 100644 --- a/src/detect.h +++ b/src/detect.h @@ -242,7 +242,7 @@ typedef struct DetectPort_ { #define SIG_FLAG_SP_ANY BIT_U32(2) /**< source port is any */ #define SIG_FLAG_DP_ANY BIT_U32(3) /**< destination port is any */ -// vacancy +#define SIG_FLAG_FIREWALL BIT_U32(4) /**< sig is a firewall rule */ #define SIG_FLAG_DSIZE BIT_U32(5) /**< signature has a dsize setting */ #define SIG_FLAG_APPLAYER BIT_U32(6) /**< signature applies to app layer instead of packets */ @@ -327,6 +327,7 @@ typedef struct DetectPort_ { /* Detection Engine flags */ #define DE_QUIET 0x01 /**< DE is quiet (esp for unittests) */ +#define DE_HAS_FIREWALL 0x02 /**< firewall rules loaded, default policies active */ typedef struct IPOnlyCIDRItem_ { /* address data for this item */ @@ -652,6 +653,9 @@ typedef struct SignatureInitData_ { uint32_t rule_state_dependant_sids_idx; uint32_t *rule_state_flowbits_ids_array; uint32_t rule_state_flowbits_ids_size; + + /* Signature is a "firewall" rule. */ + bool firewall_rule; } SignatureInitData; /** \brief Signature container */ @@ -688,6 +692,9 @@ typedef struct Signature_ { /** classification id **/ uint16_t class_id; + /** firewall: progress value for this signature */ + uint8_t app_progress_hook; + DetectMatchAddressIPv4 *addr_dst_match4; DetectMatchAddressIPv4 *addr_src_match4; /** ipv6 match arrays */ @@ -1111,6 +1118,8 @@ typedef struct DetectEngineCtx_ { /* name store for non-prefilter engines. Used in profiling but * part of the API, so hash is always used. */ HashTable *non_pf_engine_names; + + const char *firewall_rule_file_exclusive; } DetectEngineCtx; /* Engine groups profiles (low, medium, high, custom) */ diff --git a/src/flow.h b/src/flow.h index 6b30909e52..02d8ee7656 100644 --- a/src/flow.h +++ b/src/flow.h @@ -58,7 +58,8 @@ typedef struct AppLayerParserState_ AppLayerParserState; /** Flow is marked an elephant flow */ #define FLOW_IS_ELEPHANT BIT_U32(3) -// vacancy bit 4 +/** All packets in this flow should be accepted */ +#define FLOW_ACTION_ACCEPT BIT_U32(4) // vacancy bit 5 diff --git a/src/output-json-flow.c b/src/output-json-flow.c index fa40453603..f62c117e49 100644 --- a/src/output-json-flow.c +++ b/src/output-json-flow.c @@ -339,6 +339,8 @@ static void EveFlowLogJSON(OutputJsonThreadCtx *aft, SCJsonBuilder *jb, Flow *f) if (f->flags & FLOW_ACTION_DROP) { JB_SET_STRING(jb, "action", "drop"); + } else if (f->flags & FLOW_ACTION_ACCEPT) { + JB_SET_STRING(jb, "action", "accept"); } else if (f->flags & FLOW_ACTION_PASS) { JB_SET_STRING(jb, "action", "pass"); } diff --git a/src/suricata.c b/src/suricata.c index 771456ee3d..6313b01bab 100644 --- a/src/suricata.c +++ b/src/suricata.c @@ -618,6 +618,8 @@ static void PrintUsage(const char *progname) printf("\t--fatal-unittests : enable fatal failure on unittest error\n"); printf("\t--unittests-coverage : display unittest coverage report\n"); #endif /* UNITTESTS */ + printf("\t--firewall-rules-exclusive= : path to firewall rule file loaded " + "exclusively\n"); printf("\t--list-app-layer-protos : list supported app layer protocols\n"); printf("\t--list-keywords[=all|csv|] : list keywords implemented by the engine\n"); printf("\t--list-runmodes : list supported runmodes\n"); @@ -1413,6 +1415,8 @@ TmEcode SCParseCommandLine(int argc, char **argv) {"qa-skip-prefilter", 0, &g_skip_prefilter, 1 }, + {"firewall-rules-exclusive", required_argument, 0, 0}, + {"include", required_argument, 0, 0}, {NULL, 0, NULL, 0} @@ -1819,6 +1823,13 @@ TmEcode SCParseCommandLine(int argc, char **argv) } } } + } else if (strcmp((long_opts[option_index]).name, "firewall-rules-exclusive") == 0) { + if (suri->firewall_rule_file != NULL) { + SCLogError("can't have multiple --firewall-rules-exclusive options"); + return TM_ECODE_FAILED; + } + suri->firewall_rule_file = optarg; + suri->firewall_rule_file_exclusive = true; } else { int r = ExceptionSimulationCommandLineParser( (long_opts[option_index]).name, optarg); @@ -2044,8 +2055,8 @@ TmEcode SCParseCommandLine(int argc, char **argv) } } - if (suri->disabled_detect && suri->sig_file != NULL) { - SCLogError("can't use -s/-S when detection is disabled"); + if (suri->disabled_detect && (suri->sig_file != NULL || suri->firewall_rule_file != NULL)) { + SCLogError("can't use -s/-S or --firewall-rules-exclusive when detection is disabled"); return TM_ECODE_FAILED; } @@ -2411,6 +2422,8 @@ static void SetupDelayedDetect(SCInstance *suri) static int LoadSignatures(DetectEngineCtx *de_ctx, SCInstance *suri) { + de_ctx->firewall_rule_file_exclusive = suri->firewall_rule_file; + if (SigLoadSignatures(de_ctx, suri->sig_file, suri->sig_file_exclusive) < 0) { SCLogError("Loading signatures failed."); if (de_ctx->failure_fatal) diff --git a/src/suricata.h b/src/suricata.h index ba83c16b16..093f58bd76 100644 --- a/src/suricata.h +++ b/src/suricata.h @@ -129,6 +129,8 @@ typedef struct SCInstance_ { bool sig_file_exclusive; char *pid_filename; char *regex_arg; + char *firewall_rule_file; + bool firewall_rule_file_exclusive; char *keyword_info; char *runmode_custom_mode; diff --git a/suricata.yaml.in b/suricata.yaml.in index de07871fe4..28d539ac3a 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -2263,6 +2263,20 @@ default-rule-path: @e_defaultruledir@ rule-files: - suricata.rules +## +## Suricata as a Firewall options (experimental) +## + +# Firewall rule file are in their own path and are not managed +# by Suricata-Update. +#firewall-rule-path: /etc/suricata/firewall/ + +# List of files with firewall rules. Order matters, files are loaded +# in order and rules are applied in that order (per state, see docs) +#firewall-rule-files: +# - firewall.rules + + ## ## Auxiliary configuration files. ## -- 2.47.2