]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
firewall: start of firewall rules support
authorVictor Julien <vjulien@oisf.net>
Wed, 26 Feb 2025 10:38:35 +0000 (11:38 +0100)
committerVictor Julien <victor@inliniac.net>
Mon, 7 Apr 2025 20:04:14 +0000 (22:04 +0200)
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.

20 files changed:
etc/schema.json
rust/src/applayer.rs
src/action-globals.h
src/app-layer-parser.h
src/decode.c
src/decode.h
src/detect-engine-alert.c
src/detect-engine-analyzer.c
src/detect-engine-loader.c
src/detect-engine-prefilter.c
src/detect-engine-sigorder.c
src/detect-parse.c
src/detect-parse.h
src/detect.c
src/detect.h
src/flow.h
src/output-json-flow.c
src/suricata.c
src/suricata.h
suricata.yaml.in

index 3f7385c0082b606697e3c6d6787ea73bc605f8fb..c0b4718d94c563a54710d7dd127399371cdbe2ae 100644 (file)
                                     "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
index debfb56cbbb26e03bdbd7ab622f70c3132e2cd71..4cd36339ad4c222c82bd5e19195e2e8c59535bdb 100644 (file)
@@ -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" {
index d6509e98be633fbc95497e05d104707f8393f493..7aac5e17dfa62f2cc05dd8329b9093009269e3ed 100644 (file)
@@ -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 */
index 421a9778cacbf637fab3b31a3d5880387e0794d6..3be6d6a99af6ff4842fef358cf166c032431ccc5 100644 (file)
@@ -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. */
index 3ba77b92292bf2c4646bfa4c46cb63d32af4d757..446c29d51a1e34ec2f1da4dd874d1dfd44acddc1 100644 (file)
@@ -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;
index 4753497298c38dea349308e0cc91d9e7bad6795b..cef710c49bc3d2b87c8d12a32e43547a482d80c0 100644 (file)
@@ -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,
 };
 
index 1ec4790f520c9497c6e787dc5ea5a6d54690406f..0c5a84ebaa597d0d5b96160b3f130b4dabda9a85 100644 (file)
@@ -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++;
index e362a6ad8ae3f14c9d72d199e6ea4c2174dfc8b1..b5d12164edf4e4c7badd95ebb12905b80010b64d 100644 (file)
@@ -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;
         }
index 3dd6954d454d07ded597e996f7c3718660e4d6c3..7b63b29b40afe9fb7350c2bd475c4f168327bcdb 100644 (file)
@@ -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) {
index 8bb53060540641efed83a754f5d08babcb68f840..bf0b9984fb9939c3a8aeb1cdd00b1e5d5591c72e 100644 (file)
@@ -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
index b6ce4a8ab624110af6d51cbf9b654b31dadc2619..aeb4d8db35ababa3c1e35ce2d6958430a058b622 100644 (file)
@@ -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);
+    }
 }
 
 /**
index 4647aa4f68bff253d683439ddaeb4b38ca59a84c..009106f13e953707cf70471937949038f6d7ab04 100644 (file)
@@ -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.
index 3e12abfba54cf0569c3a3cfef62c4fd8ccc6b7c3..18c998f3e27669b793e462ff6acdd6b7865e3d0d 100644 (file)
@@ -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);
index 8216fce8c8aadb7ce4b8fdbbc31afa0007c831f2..d662e66e999487c0fd445596cae29a52bc2de302 100644 (file)
@@ -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);
index 9abb96be9d469d7aedffbbf07cfb73e9d8702b9c..f424d40153576e355011e95db0751360ec627d4b 100644 (file)
@@ -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) */
index 6b30909e52f09d92f3ae75e07390657a7245973d..02d8ee7656b4931697a57b81c62d7a61d54d9992 100644 (file)
@@ -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
 
index fa404536030323ffe8a362a555d46ccc2f854130..f62c117e4978d6721901b0ee327950b1469c359d 100644 (file)
@@ -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");
     }
index 771456ee3d7d76f0c32d0300710a1f532fd87180..6313b01babcc02f134b3b080902354bede664f4c 100644 (file)
@@ -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>    : 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|<kword>]    : 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)
index ba83c16b167b70fb3f0b358ded80e297ecfc93d8..093f58bd76d72124f98a96f91005c03dcaac7ec1 100644 (file)
@@ -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;
index de07871fe42994cef3e5a8bb3cbc987685f876c0..28d539ac3a6284c71b4be3e899d1f884947e49a4 100644 (file)
@@ -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.
 ##