"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
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" {
#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)
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 */
/** 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. */
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;
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;
/** 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
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,
};
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",
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);
}
* - 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);
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
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 */
/* 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++;
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);
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) {
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;
}
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;
/* 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) {
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
* \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] = "";
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) {
} 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);
}
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
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);
}
}
+skip_regular_rules:
/* now we should have signatures to work with */
if (sig_stat->good_sigs_total <= 0) {
if (sig_stat->total_files > 0) {
}
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
}
}
}
+ /* 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
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
*
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 */
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);
+ }
}
/**
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;
}
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;
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'",
}
}
+ /* 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;
}
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, "
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
{
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++) {
* \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));
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)) {
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();
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;
}
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;
}
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.
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.
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);
-/* 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
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);
DetectRunScratchpad *scratch);
static void DetectRunCleanup(DetectEngineThreadCtx *det_ctx,
Packet *p, Flow * const pflow);
+static inline void DetectRunAppendDefaultAccept(DetectEngineThreadCtx *det_ctx, Packet *p);
/** \internal
*/
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;
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) {
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:
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 */
RulesDumpMatchArray(det_ctx, scratch->sgh, p);
#endif
+ bool skip_fw = false;
uint32_t sflags, next_sflags = 0;
if (match_cnt) {
next_s = *match_array++;
}
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 */
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
}
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(
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,
} 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)
// 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,
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)
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;
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.
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);
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,
/* 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);
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;
}
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);
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,
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);
#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 */
/* 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 */
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 */
/** 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 */
/* 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) */
/** 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
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");
}
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");
{"qa-skip-prefilter", 0, &g_skip_prefilter, 1 },
+ {"firewall-rules-exclusive", required_argument, 0, 0},
+
{"include", required_argument, 0, 0},
{NULL, 0, NULL, 0}
}
}
}
+ } 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);
}
}
- 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;
}
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)
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;
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.
##