detect/firewall: fix later LTE rule confusing policy checks

A non-matching rule followed by an LTE rule (<hook) with an overlapping
hook can be decided by the wrong default policy. E.g.:

        drop:flow tls:client_hello .. tls.sni; content:"match" \
                content:"no-match";
        accept:flow tls:<client_hello .. tls.sni; content:"match"

Here the `tls:client_started` state needs to be accepted by the second
rule, which acts as `accept:hook` on that state.

A failing `<` rule defers the per-state defaults of the states it
promised to cover; the last pending covering rule decides them, applying
its own hook default and sweeping forward from its own hook. There is no
backward sweep, so the states below that hook get no default of their
own.

The pending LTE coverage accounting was the problem: it counted
candidates before their header predicates were checked, compared against
a max that included the rule being evaluated, and kept rules that
resolved during the pass. Count only rules that pass the header checks,
keep per-hook coverage counts in per-thread scratch, drop a rule from
the counts once it resolves at or past its hook, and let the hook checks
consult the counts instead of the raw LTE flag.

Also fixes two latent issues in the same walk: the candidate list is now
sorted whenever the tx state store adds entries - the old condition
skipped the sort when the prefilter contributed nothing, so the
last-for-progress peek could see an unsorted list; and rule profiling
now receives `r == 1` instead of the raw return code, which used to
decrement the match counter on a no-match.

Ticket: #8944.
pull/16368/head
Victor Julien 1 month ago
parent 26b9c1d78d
commit 9d4d448a90

@ -1322,7 +1322,9 @@ static int DetectRunTxInspectRule(ThreadVars *tv, DetectEngineCtx *de_ctx,
/* for a new inspection we inspect pkt header and packet matches */
if (likely(stored_flags == NULL)) {
TRACE_SID_TXS(s->id, tx, "first inspect, run packet matches");
if (DetectRunInspectRuleHeader(p, f, s, s->flags) == false) {
/* the firewall LTE coverage build already ran the same header check for
* this candidate, so skip repeating it */
if (!can->fw_lte_header_ok && DetectRunInspectRuleHeader(p, f, s, s->flags) == false) {
TRACE_SID_TXS(s->id, tx, "DetectRunInspectRuleHeader() no match");
return -1;
}
@ -1533,7 +1535,7 @@ static int DetectRunTxInspectRule(ThreadVars *tv, DetectEngineCtx *de_ctx,
} else if ((inspect_flags & DE_STATE_FLAG_FULL_INSPECT) == 0 && mpm_in_progress) {
TRACE_SID_TXS(s->id, tx, "no need to store no-match sig, "
"mpm will revisit it");
return -1; /* no match */
return -2; /* no match, but not final: mpm will revisit it */
} else if (inspect_flags != 0 || file_no_match != 0) {
TRACE_SID_TXS(s->id, tx, "storing state: flags %08x", inspect_flags);
DetectRunStoreStateTx(scratch->sgh, f, tx->tx_ptr, tx->tx_id, s,
@ -1641,7 +1643,7 @@ static inline void StoreDetectProgress(
// Merge 'state' rules from the regular prefilter
// updates array_idx on the way
static inline void RuleMatchCandidateMergeStateRules(
DetectEngineThreadCtx *det_ctx, uint32_t *array_idx)
DetectEngineThreadCtx *det_ctx, uint32_t *array_idx, bool *fw_lte)
{
// Now, we will merge 2 sorted lists :
// the one in det_ctx->tx_candidates
@ -1663,6 +1665,9 @@ static inline void RuleMatchCandidateMergeStateRules(
for (uint32_t i = 0; i < det_ctx->match_array_cnt; i++) {
const Signature *s = det_ctx->match_array[i];
if (s->app_inspect != NULL) {
if (s->flags & SIG_FLAG_FW_HOOK_LTE) {
*fw_lte = true;
}
(*array_idx)++;
}
}
@ -1695,6 +1700,9 @@ static inline void RuleMatchCandidateMergeStateRules(
// take the element from tx_candidates before merge
det_ctx->tx_candidates[k].s = det_ctx->tx_candidates[j].s;
det_ctx->tx_candidates[k].id = det_ctx->tx_candidates[j].id;
det_ctx->tx_candidates[k].fw_lte_counted = det_ctx->tx_candidates[j].fw_lte_counted;
det_ctx->tx_candidates[k].fw_lte_header_ok =
det_ctx->tx_candidates[j].fw_lte_header_ok;
det_ctx->tx_candidates[k].flags = det_ctx->tx_candidates[j].flags;
det_ctx->tx_candidates[k].stream_reset = det_ctx->tx_candidates[j].stream_reset;
continue;
@ -1705,6 +1713,8 @@ static inline void RuleMatchCandidateMergeStateRules(
// take the element from match_array
det_ctx->tx_candidates[k].s = s;
det_ctx->tx_candidates[k].id = s->iid;
det_ctx->tx_candidates[k].fw_lte_counted = false;
det_ctx->tx_candidates[k].fw_lte_header_ok = false;
det_ctx->tx_candidates[k].flags = NULL;
det_ctx->tx_candidates[k].stream_reset = 0;
}
@ -1720,8 +1730,156 @@ struct DetectFirewallAppTxState {
bool fw_last_for_progress;
bool fw_next_progress_missing;
bool last_fw_rule; /**< processing the last fw rule, so we need to eval all hooks after it. */
/** det_ctx->fw_lte_cover holds the counts for this walk. The counts answer
* "does another pending rule cover hook H": per hook, how many pending LTE
* rules cover it (their own hook and every prior hook). Progress values
* stay under APP_LAYER_MAX_PROGRESS. */
bool fw_lte_cover_active;
};
/** \internal
* \brief initialize the per-thread LTE hook coverage for this walk.
*
* The counts live in the thread ctx so IDS traffic does not pay to clear them.
* They are cleared lazily, on the first LTE rule of the walk.
*/
static inline void DetectFwEnsureLteCoverage(
DetectEngineThreadCtx *det_ctx, struct DetectFirewallAppTxState *fw_state)
{
if (!fw_state->fw_lte_cover_active) {
memset(det_ctx->fw_lte_cover, 0, sizeof(det_ctx->fw_lte_cover));
fw_state->fw_lte_cover_active = true;
}
}
/** \internal
* \brief build the per hook coverage of the pending LTE rules.
*
* Counts, per hook, how many pending LTE rules cover it (their own hook and
* every prior hook). The candidate list is sorted, so duplicates - the state
* store and the per-state registrations can both add a rule - are adjacent.
*/
static void DetectFwBuildLteCoverage(DetectEngineThreadCtx *det_ctx, Packet *p, Flow *f,
struct DetectFirewallAppTxState *fw_state, const uint32_t array_idx)
{
DetectFwEnsureLteCoverage(det_ctx, fw_state);
for (uint32_t i = 0; i < array_idx; i++) {
const Signature *s = det_ctx->tx_candidates[i].s;
/* only firewall rules implement the per-state policy: a threat
* detection rule using the <hook header must not count as coverage */
if (!(s->flags & SIG_FLAG_FW_HOOK_LTE) || !(s->flags & SIG_FLAG_FIREWALL)) {
continue;
}
RuleMatchCandidateTx *can = &det_ctx->tx_candidates[i];
can->fw_lte_counted = false;
const uint32_t *flags = can->flags;
if (flags == NULL) {
if (!DetectRunInspectRuleHeader(p, f, s, s->flags)) {
/* an out of scope rule never covered anything */
continue;
}
/* the walk inspects this candidate with the same arguments, so it
* can skip repeating the header check */
can->fw_lte_header_ok = true;
}
if (flags != NULL &&
(*flags & (DE_STATE_FLAG_FULL_INSPECT | DE_STATE_FLAG_SIG_CANT_MATCH)) != 0) {
/* already resolved in an earlier pass */
continue;
}
/* duplicates of the same signature (state store + per-state
* registrations) are adjacent: the candidate list is sorted by id with
* the flagged entry first */
DEBUG_VALIDATE_BUG_ON(
i > 0 && det_ctx->tx_candidates[i].id < det_ctx->tx_candidates[i - 1].id);
if (i > 0 && det_ctx->tx_candidates[i - 1].s == s) {
continue;
}
if (unlikely(s->app_progress_hook >= APP_LAYER_MAX_PROGRESS)) {
/* defensive: parse rejects this; skip it so the arrays stay in
* bounds and the rule applies its own default policy */
DEBUG_VALIDATE_BUG_ON(1);
continue;
}
can->fw_lte_counted = true;
for (uint16_t h = 0; h <= s->app_progress_hook; h++) {
det_ctx->fw_lte_cover[h]++;
}
}
}
/** \internal
* \brief check if a pending LTE rule other than the current one covers its hook.
*/
static inline bool DetectFwOtherLteCoversHook(DetectEngineThreadCtx *det_ctx,
const struct DetectFirewallAppTxState *fw_state, const Signature *s, const bool counted)
{
if (!fw_state->fw_lte_cover_active) {
/* no LTE candidate in this walk, so nothing covers the hook */
return false;
}
if (unlikely(s->app_progress_hook >= APP_LAYER_MAX_PROGRESS)) {
DEBUG_VALIDATE_BUG_ON(1);
return false;
}
const uint32_t covered = det_ctx->fw_lte_cover[s->app_progress_hook];
if (s->flags & SIG_FLAG_FW_HOOK_LTE) {
/* an uncounted candidate (header mismatch, already retired) carries
* no own count in the coverage */
return covered > (counted ? 1 : 0);
}
return covered > 0;
}
/** \internal
* \brief count an LTE rule appended mid-walk into the hook coverage.
*
* Appended candidates are the tail of the list, so duplicates cannot be
* detected by adjacency like in DetectFwBuildLteCoverage().
*/
static inline void DetectFwCountAppendedLteRule(DetectEngineThreadCtx *det_ctx, Packet *p, Flow *f,
struct DetectFirewallAppTxState *fw_state, const Signature *s, const uint32_t can_idx)
{
if (!(s->flags & SIG_FLAG_FW_HOOK_LTE) || !(s->flags & SIG_FLAG_FIREWALL) ||
s->app_progress_hook >= APP_LAYER_MAX_PROGRESS ||
!DetectRunInspectRuleHeader(p, f, s, s->flags)) {
return;
}
for (uint32_t k = 0; k < can_idx; k++) {
if (det_ctx->tx_candidates[k].s == s && det_ctx->tx_candidates[k].fw_lte_counted) {
return;
}
}
DetectFwEnsureLteCoverage(det_ctx, fw_state);
for (uint16_t h = 0; h <= s->app_progress_hook; h++) {
det_ctx->fw_lte_cover[h]++;
}
det_ctx->tx_candidates[can_idx].fw_lte_counted = true;
det_ctx->tx_candidates[can_idx].fw_lte_header_ok = true;
}
/** \internal
* \brief remove an LTE rule that resolved definitively from the hook coverage.
*/
static void DetectFwRetireLteRule(DetectEngineThreadCtx *det_ctx, RuleMatchCandidateTx *can)
{
const Signature *s = can->s;
if (unlikely(s->app_progress_hook >= APP_LAYER_MAX_PROGRESS)) {
DEBUG_VALIDATE_BUG_ON(1);
return;
}
for (uint16_t h = 0; h <= s->app_progress_hook; h++) {
/* the caller only retires counted candidates: a zero here means the
* accounting is broken, not a legitimate case */
DEBUG_VALIDATE_BUG_ON(det_ctx->fw_lte_cover[h] == 0);
if (det_ctx->fw_lte_cover[h] > 0) {
det_ctx->fw_lte_cover[h]--;
}
}
/* the candidate no longer contributes to the counts */
can->fw_lte_counted = false;
}
static inline void DetectRunAppendDefaultAppPolicyAlert(DetectEngineThreadCtx *det_ctx, Packet *p,
const bool apply_to_packet, const DetectTransaction *tx,
const struct DetectFirewallAppPolicy *ap)
@ -1914,7 +2072,8 @@ static enum DetectTxFirewallFlowControl DetectFirewallApplyDefaultPolicies(
*/
static enum DetectTxFirewallFlowControl DetectRunTxPreCheckFirewallPolicy(
DetectEngineThreadCtx *det_ctx, Packet *p, DetectTransaction *tx, const uint8_t direction,
const Signature *s, const uint32_t can_idx, struct DetectFirewallAppTxState *fw_state)
const Signature *s, const uint32_t can_idx, const bool lte_counted,
struct DetectFirewallAppTxState *fw_state)
{
SCLogDebug("packet %" PRIu64 ": running pre-checks before sid %u", PcapPacketCntGet(p), s->id);
@ -1963,23 +2122,36 @@ static enum DetectTxFirewallFlowControl DetectRunTxPreCheckFirewallPolicy(
SCLogDebug("SIG_FLAG_FW_HOOK_LTE");
return DETECT_TX_FW_FC_OK; // TODO check for other cases
}
/* if our first rule is beyond the starting state, we need to check if
* there are rules missing for states in between. */
if (s->app_progress_hook > tx->detect_progress_orig && can_idx == 0) {
SCLogDebug("missing fw rules at list start: sid %u, progress %u (%u:%u)", s->id,
s->app_progress_hook, tx->detect_progress, tx->detect_progress_orig);
/* if this rule was after the state we expected meaning that there are
* no rules for that state. Invoke the default policies. */
enum DetectTxFirewallFlowControl r =
DetectFirewallApplyDefaultPolicies(det_ctx, det_ctx->de_ctx->fw_policies, tx, p,
s->alproto, direction, tx->detect_progress_orig, s->app_progress_hook - 1);
if (r != DETECT_TX_FW_FC_OK) {
/* both SKIP and BREAK mean: no more fw rules to inspect.
* SKIP applies to just this TX.
* DROP applies to everything. */
fw_state->fw_skip_app_filter = true;
}
return r;
if (can_idx == 0) {
SCLogDebug("check if prior hooks are satisfied: s->app_progress_hook %u, "
"tx->detect_progress_orig %u",
s->app_progress_hook, tx->detect_progress_orig);
if (DetectFwOtherLteCoversHook(det_ctx, fw_state, s, lte_counted)) {
SCLogDebug("later LTE in-progress rule brought us here: us:%u covered:%u",
s->app_progress_hook, det_ctx->fw_lte_cover[s->app_progress_hook]);
return DETECT_TX_FW_FC_OK;
}
/* if our first rule is beyond the starting state, we need to check if
* there are rules missing for states in between. */
if (s->app_progress_hook > tx->detect_progress_orig) {
SCLogDebug("missing fw rules at list start: sid %u, progress %u (%u:%u)", s->id,
s->app_progress_hook, tx->detect_progress, tx->detect_progress_orig);
/* if this rule was after the state we expected meaning that there are
* no rules for that state. Invoke the default policies. */
enum DetectTxFirewallFlowControl r = DetectFirewallApplyDefaultPolicies(det_ctx,
det_ctx->de_ctx->fw_policies, tx, p, s->alproto, direction,
tx->detect_progress_orig, s->app_progress_hook - 1);
if (r != DETECT_TX_FW_FC_OK) {
/* both SKIP and BREAK mean: no more fw rules to inspect.
* SKIP applies to just this TX.
* DROP applies to everything. */
fw_state->fw_skip_app_filter = true;
}
return r;
}
}
return DETECT_TX_FW_FC_OK;
}
@ -1996,7 +2168,7 @@ static enum DetectTxFirewallFlowControl DetectRunTxPreCheckFirewallPolicy(
*/
static enum DetectTxFirewallFlowControl DetectRunTxCheckRuleState(DetectEngineThreadCtx *det_ctx,
Packet *p, Flow *f, DetectTransaction *tx, const Signature *s, const uint32_t can_idx,
const uint32_t can_size, struct DetectFirewallAppTxState *fw_state)
const uint32_t can_size, const bool lte_counted, struct DetectFirewallAppTxState *fw_state)
{
if (s->flags & SIG_FLAG_FIREWALL) {
/* check if the next sig is on the same progress hook. If not, we need to apply our
@ -2011,15 +2183,20 @@ static enum DetectTxFirewallFlowControl DetectRunTxCheckRuleState(DetectEngineTh
"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);
fw_state->fw_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_state->fw_next_progress_missing = true;
if (DetectFwOtherLteCoversHook(det_ctx, fw_state, s, lte_counted)) {
SCLogDebug("we have another LTE in-progress. Us:%u covered:%u",
s->app_progress_hook, det_ctx->fw_lte_cover[s->app_progress_hook]);
} else {
SCLogDebug("peek: next sid progress %u != current progress %u, so current "
"is last for progress",
next_s->app_progress_hook, s->app_progress_hook);
fw_state->fw_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_state->fw_next_progress_missing = true;
}
}
}
} else {
@ -2029,12 +2206,14 @@ static enum DetectTxFirewallFlowControl DetectRunTxCheckRuleState(DetectEngineTh
}
} 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_state->fw_next_progress_missing = true;
if (!DetectFwOtherLteCoversHook(det_ctx, fw_state, s, lte_counted)) {
if (s->app_progress_hook < tx->tx_progress) {
SCLogDebug("peek: there are no rules to allow the state after this rule");
fw_state->fw_next_progress_missing = true;
}
fw_state->fw_last_for_progress = true;
fw_state->last_fw_rule = true;
}
fw_state->fw_last_for_progress = true;
fw_state->last_fw_rule = true;
}
if (fw_state->skip_fw_hook == true) {
@ -2428,6 +2607,7 @@ static void DetectRunTx(ThreadVars *tv,
det_ctx->p = p;
bool do_sort = false; // do we need to sort the tx candidate list?
bool fw_lte_candidates = false; // does the list have a pending LTE rule?
uint32_t array_idx = 0;
uint32_t total_rules = det_ctx->match_array_cnt;
total_rules += (tx.de_state ? tx.de_state->cnt : 0);
@ -2452,7 +2632,12 @@ static void DetectRunTx(ThreadVars *tv,
det_ctx->tx_candidates[array_idx].s = s;
det_ctx->tx_candidates[array_idx].id = id;
det_ctx->tx_candidates[array_idx].flags = NULL;
det_ctx->tx_candidates[array_idx].fw_lte_counted = false;
det_ctx->tx_candidates[array_idx].fw_lte_header_ok = false;
det_ctx->tx_candidates[array_idx].stream_reset = 0;
if (s->flags & SIG_FLAG_FW_HOOK_LTE) {
fw_lte_candidates = true;
}
array_idx++;
}
PMQ_RESET(&det_ctx->pmq);
@ -2466,7 +2651,7 @@ static void DetectRunTx(ThreadVars *tv,
#ifdef PROFILING
uint32_t x = array_idx;
#endif
RuleMatchCandidateMergeStateRules(det_ctx, &array_idx);
RuleMatchCandidateMergeStateRules(det_ctx, &array_idx, &fw_lte_candidates);
/* merge stored state into results */
if (tx.de_state != NULL) {
@ -2499,14 +2684,21 @@ static void DetectRunTx(ThreadVars *tv,
item->flags &= ~(DE_STATE_FLAG_SIG_CANT_MATCH|DE_STATE_FLAG_FULL_INSPECT|DE_STATE_FLAG_FILE_INSPECT);
SCLogDebug("rule id %u, post file reset inspect_flags %u", item->sid, item->flags);
}
det_ctx->tx_candidates[array_idx].s = de_ctx->sig_array[item->sid];
const Signature *ss = de_ctx->sig_array[item->sid];
det_ctx->tx_candidates[array_idx].s = ss;
det_ctx->tx_candidates[array_idx].id = item->sid;
det_ctx->tx_candidates[array_idx].flags = &item->flags;
det_ctx->tx_candidates[array_idx].fw_lte_counted = false;
det_ctx->tx_candidates[array_idx].fw_lte_header_ok = false;
det_ctx->tx_candidates[array_idx].stream_reset = 0;
if (ss->flags & SIG_FLAG_FW_HOOK_LTE) {
fw_lte_candidates = true;
}
array_idx++;
}
}
do_sort |= (old && old != array_idx); // sort if continue list adds sids
do_sort |= (array_idx > old); // sort if continue list adds sids
SCLogDebug("%p/%" PRIu64 " rules added from 'continue' list: %u", tx.tx_ptr, tx.tx_id,
array_idx - old);
}
@ -2528,14 +2720,13 @@ static void DetectRunTx(ThreadVars *tv,
}
#endif
struct DetectFirewallAppTxState fw_state = {
false,
false,
0,
false,
false,
false,
};
/* Only firewall mode uses the per-tx firewall state. The LTE flag is
* tracked where the candidates are added: the list mixes fw and TD
* rules across tables in iid order, so a scan cannot stop early. */
struct DetectFirewallAppTxState fw_state = { 0 };
if (have_fw_rules && fw_lte_candidates) {
DetectFwBuildLteCoverage(det_ctx, p, f, &fw_state, array_idx);
}
SCLogDebug("%s: tx_progress %u tx %p have_fw_rules %s array_idx %u detect_progress_orig %u "
"cur detect_progress %u",
@ -2567,9 +2758,9 @@ static void DetectRunTx(ThreadVars *tv,
tx.detect_progress, tx.detect_progress_orig, s->app_progress_hook);
if (have_fw_rules) {
const enum DetectTxFirewallFlowControl fw_r =
DetectRunTxPreCheckFirewallPolicy(det_ctx, p, &tx,
flow_flags & (STREAM_TOSERVER | STREAM_TOCLIENT), s, i, &fw_state);
const enum DetectTxFirewallFlowControl fw_r = DetectRunTxPreCheckFirewallPolicy(
det_ctx, p, &tx, flow_flags & (STREAM_TOSERVER | STREAM_TOCLIENT), s, i,
can->fw_lte_counted, &fw_state);
SCLogDebug("fw fw_skip_app_filter:%s skip_fw_hook:%s "
"skip_before_progress:%u fw_last_for_progress:%s "
"fw_next_progress_missing:%s",
@ -2631,8 +2822,8 @@ static void DetectRunTx(ThreadVars *tv,
if (have_fw_rules) {
/* check if we should run this rule and update the firewall flow state */
const enum DetectTxFirewallFlowControl fw_r =
DetectRunTxCheckRuleState(det_ctx, p, f, &tx, s, i, array_idx, &fw_state);
const enum DetectTxFirewallFlowControl fw_r = DetectRunTxCheckRuleState(
det_ctx, p, f, &tx, s, i, array_idx, can->fw_lte_counted, &fw_state);
SCLogDebug("fw fw_skip_app_filter:%s skip_fw_hook:%s "
"skip_before_progress:%u fw_last_for_progress:%s "
"fw_next_progress_missing:%s",
@ -2650,6 +2841,10 @@ static void DetectRunTx(ThreadVars *tv,
const int r = DetectRunTxInspectRule(tv, de_ctx, det_ctx, p, f, flow_flags,
alstate, &tx, s, inspect_flags, can, scratch);
SCLogDebug("s %u r %d", s->id, r);
if (have_fw_rules && r == -1 && (s->flags & SIG_FLAG_FW_HOOK_LTE) &&
can->fw_lte_counted && tx.tx_progress >= s->app_progress_hook) {
DetectFwRetireLteRule(det_ctx, can);
}
if (r == 1) {
/* match */
DetectRunPostMatch(tv, det_ctx, p, s);
@ -2667,14 +2862,16 @@ static void DetectRunTx(ThreadVars *tv,
if (DetectRunTxFirewallRulePartialMatch(det_ctx, s, &tx, p) == 1) {
break;
}
} else {
if (DetectRunTxFirewallRuleNoMatch(det_ctx, s, &tx, &fw_state, p, flow_flags) ==
1) {
} else if (r == -1) {
if ((s->flags & SIG_FLAG_FIREWALL) != 0 &&
DetectRunTxFirewallRuleNoMatch(det_ctx, s, &tx, &fw_state, p, flow_flags) ==
1) {
return;
}
}
} /* r == -2: no match, but not final: the rule is revisited, so a
* hook default now could decide the packet before the revisit can */
DetectVarProcessList(det_ctx, p->flow, p);
RULE_PROFILING_END(det_ctx, s, r, p);
RULE_PROFILING_END(det_ctx, s, r == 1, p);
if (det_ctx->post_rule_work_queue.len > 0) {
SCLogDebug("%p/%" PRIu64 " post_rule_work_queue len %u", tx.tx_ptr, tx.tx_id,
@ -2696,8 +2893,16 @@ static void DetectRunTx(ThreadVars *tv,
const SigIntId id = ts->iid;
det_ctx->tx_candidates[array_idx].s = ts;
det_ctx->tx_candidates[array_idx].id = id;
det_ctx->tx_candidates[array_idx].fw_lte_counted = false;
det_ctx->tx_candidates[array_idx].fw_lte_header_ok = false;
det_ctx->tx_candidates[array_idx].flags = NULL;
det_ctx->tx_candidates[array_idx].stream_reset = 0;
/* a mid-walk enabled LTE rule must count as coverage
* for the remaining checks of this walk */
if (have_fw_rules) {
DetectFwCountAppendedLteRule(det_ctx, p, f, &fw_state, ts, array_idx);
}
array_idx++;
SCLogDebug("%p/%" PRIu64 " rule %u (%u) added from 'post match' prefilter",
@ -2826,6 +3031,8 @@ static void DetectRunFrames(ThreadVars *tv, DetectEngineCtx *de_ctx, DetectEngin
det_ctx->tx_candidates[array_idx].s = s;
det_ctx->tx_candidates[array_idx].id = id;
det_ctx->tx_candidates[array_idx].flags = NULL;
det_ctx->tx_candidates[array_idx].fw_lte_counted = false;
det_ctx->tx_candidates[array_idx].fw_lte_header_ok = false;
det_ctx->tx_candidates[array_idx].stream_reset = 0;
array_idx++;
}
@ -2840,6 +3047,8 @@ static void DetectRunFrames(ThreadVars *tv, DetectEngineCtx *de_ctx, DetectEngin
det_ctx->tx_candidates[array_idx].s = s;
det_ctx->tx_candidates[array_idx].id = id;
det_ctx->tx_candidates[array_idx].flags = NULL;
det_ctx->tx_candidates[array_idx].fw_lte_counted = false;
det_ctx->tx_candidates[array_idx].fw_lte_header_ok = false;
det_ctx->tx_candidates[array_idx].stream_reset = 0;
array_idx++;
@ -2897,7 +3106,7 @@ static void DetectRunFrames(ThreadVars *tv, DetectEngineCtx *de_ctx, DetectEngin
}
}
DetectVarProcessList(det_ctx, p->flow, p);
RULE_PROFILING_END(det_ctx, s, r, p);
RULE_PROFILING_END(det_ctx, s, r == 1, p);
}
/* update Frame::inspect_progress here instead of in the code above. The reason is that a

@ -27,6 +27,7 @@
#include "suricata-common.h"
#include "flow.h"
#include "app-layer-parser.h"
#include "detect-engine-proto.h"
#include "detect-reference.h"
#include "detect-metadata.h"
@ -1258,8 +1259,10 @@ typedef struct SignatureNonPrefilterStore_ {
/** array of TX inspect rule candidates */
typedef struct RuleMatchCandidateTx {
SigIntId id; /**< internal signature id */
uint32_t *flags; /**< inspect flags ptr */
SigIntId id; /**< internal signature id */
bool fw_lte_counted; /**< counted in the firewall LTE hook coverage */
bool fw_lte_header_ok; /**< firewall LTE coverage already passed the rule header check */
uint32_t *flags; /**< inspect flags ptr */
union {
struct {
bool stream_stored;
@ -1407,6 +1410,11 @@ typedef struct DetectEngineThreadCtx_ {
RuleMatchCandidateTx *tx_candidates;
uint32_t tx_candidates_size;
/** Per hook LTE coverage of the current tx walk. Kept here instead of in
* the per-tx firewall state so IDS traffic does not pay to clear it.
* Cleared by the firewall coverage build on first use in a walk. */
uint32_t fw_lte_cover[APP_LAYER_MAX_PROGRESS];
MpmThreadCtx mtc; /**< thread ctx for the mpm */
/* work queue for post-rule matching affecting prefilter */
PostRuleMatchWorkQueue post_rule_work_queue;

@ -4629,11 +4629,191 @@ static int DetectAddressYamlParsing04 (void)
PASS;
}
/** \internal
* \brief fill a synthetic signature with the fields the LTE coverage helpers read.
*/
static void DetectFwLteCoverageTestInitSig(
Signature *s, const uint32_t id, const uint8_t hook, const bool lte, const bool firewall)
{
memset(s, 0, sizeof(*s));
s->id = id;
s->iid = (SigIntId)id;
s->app_progress_hook = hook;
s->flags = SIG_FLAG_SRC_ANY | SIG_FLAG_DST_ANY | SIG_FLAG_SP_ANY | SIG_FLAG_DP_ANY;
if (lte) {
s->flags |= SIG_FLAG_FW_HOOK_LTE;
}
if (firewall) {
s->flags |= SIG_FLAG_FIREWALL;
}
}
/** \test DetectFwBuildLteCoverage/DetectFwOtherLteCoversHook on synthetic rules */
static int DetectFwLteCoverageTest01(void)
{
struct DetectFirewallAppTxState fw_state = { 0 };
DetectEngineThreadCtx det_ctx = { 0 };
RuleMatchCandidateTx candidates[4] = { 0 };
det_ctx.tx_candidates = candidates;
Packet p = { 0 };
Flow f = { 0 };
Signature lte_a, lte_b, non_lte, td_lte;
DetectFwLteCoverageTestInitSig(&lte_a, 1, 3, true, true);
DetectFwLteCoverageTestInitSig(&lte_b, 2, 3, true, true);
DetectFwLteCoverageTestInitSig(&non_lte, 3, 2, false, true);
/* threat detection rule with a lower-bound hook: not firewall coverage */
DetectFwLteCoverageTestInitSig(&td_lte, 4, 2, true, false);
candidates[0].s = &lte_a;
candidates[0].id = lte_a.iid;
candidates[1].s = &lte_b;
candidates[1].id = lte_b.iid;
candidates[2].s = &non_lte;
candidates[2].id = non_lte.iid;
candidates[3].s = &td_lte;
candidates[3].id = td_lte.iid;
DetectFwBuildLteCoverage(&det_ctx, &p, &f, &fw_state, 4);
/* both LTE rules cover their own hook and every prior hook */
for (uint16_t h = 0; h <= 3; h++) {
FAIL_IF(det_ctx.fw_lte_cover[h] != 2);
}
FAIL_IF(det_ctx.fw_lte_cover[4] != 0);
FAIL_IF_NOT(candidates[0].fw_lte_counted);
FAIL_IF_NOT(candidates[1].fw_lte_counted);
FAIL_IF(candidates[2].fw_lte_counted);
FAIL_IF(candidates[3].fw_lte_counted);
/* a non-LTE rule is covered by any pending LTE rule; a counted LTE rule
* needs a coverage greater than itself (covered > 1) */
FAIL_IF_NOT(DetectFwOtherLteCoversHook(&det_ctx, &fw_state, &non_lte, false));
FAIL_IF_NOT(DetectFwOtherLteCoversHook(&det_ctx, &fw_state, &lte_a, true));
/* an uncounted LTE candidate carries no own count: the other rule alone
* already covers the hook */
FAIL_IF_NOT(DetectFwOtherLteCoversHook(&det_ctx, &fw_state, &lte_a, false));
/* a hook covered by exactly one other rule: counted needs 2, uncounted
* is already covered */
struct DetectFirewallAppTxState one = { 0 };
one.fw_lte_cover_active = true;
memset(det_ctx.fw_lte_cover, 0, sizeof(det_ctx.fw_lte_cover));
det_ctx.fw_lte_cover[3] = 1; /* lte_a's hook */
FAIL_IF(DetectFwOtherLteCoversHook(&det_ctx, &one, &lte_a, true));
FAIL_IF_NOT(DetectFwOtherLteCoversHook(&det_ctx, &one, &lte_a, false));
/* out-of-range hook is rejected defensively without crashing */
Signature out_of_range;
DetectFwLteCoverageTestInitSig(&out_of_range, 4, APP_LAYER_MAX_PROGRESS, true, true);
FAIL_IF(DetectFwOtherLteCoversHook(&det_ctx, &fw_state, &out_of_range, true));
PASS;
}
/** \test DetectFwRetireLteRule decrements only its own hook range */
static int DetectFwLteCoverageTest02(void)
{
struct DetectFirewallAppTxState fw_state = { 0 };
DetectEngineThreadCtx det_ctx = { 0 };
RuleMatchCandidateTx candidates[2] = { 0 };
det_ctx.tx_candidates = candidates;
Packet p = { 0 };
Flow f = { 0 };
Signature lte_a, lte_b;
DetectFwLteCoverageTestInitSig(&lte_a, 1, 1, true, true);
DetectFwLteCoverageTestInitSig(&lte_b, 2, 4, true, true);
candidates[0].s = &lte_a;
candidates[0].id = lte_a.iid;
candidates[1].s = &lte_b;
candidates[1].id = lte_b.iid;
DetectFwBuildLteCoverage(&det_ctx, &p, &f, &fw_state, 2);
FAIL_IF(det_ctx.fw_lte_cover[0] != 2);
FAIL_IF(det_ctx.fw_lte_cover[1] != 2);
FAIL_IF(det_ctx.fw_lte_cover[2] != 1);
FAIL_IF(det_ctx.fw_lte_cover[4] != 1);
FAIL_IF(det_ctx.fw_lte_cover[5] != 0);
/* retiring the short rule removes only its contribution */
DetectFwRetireLteRule(&det_ctx, &candidates[0]);
FAIL_IF(det_ctx.fw_lte_cover[0] != 1);
FAIL_IF(det_ctx.fw_lte_cover[1] != 1);
FAIL_IF(det_ctx.fw_lte_cover[2] != 1);
FAIL_IF(det_ctx.fw_lte_cover[3] != 1);
FAIL_IF(det_ctx.fw_lte_cover[4] != 1);
/* the retired rule no longer counts as its own coverage: it is covered
* by the still pending rule, while the stale counted state would read
* the single remaining count as its own */
FAIL_IF(candidates[0].fw_lte_counted);
FAIL_IF_NOT(
DetectFwOtherLteCoversHook(&det_ctx, &fw_state, &lte_a, candidates[0].fw_lte_counted));
FAIL_IF(DetectFwOtherLteCoversHook(&det_ctx, &fw_state, &lte_a, true));
DetectFwRetireLteRule(&det_ctx, &candidates[1]);
for (uint16_t h = 0; h <= 4; h++) {
FAIL_IF(det_ctx.fw_lte_cover[h] != 0);
}
PASS;
}
/** \test DetectFwCountAppendedLteRule dedup and counting */
static int DetectFwLteCoverageTest03(void)
{
struct DetectFirewallAppTxState fw_state = { 0 };
DetectEngineThreadCtx det_ctx = { 0 };
RuleMatchCandidateTx candidates[2] = { 0 };
det_ctx.tx_candidates = candidates;
Packet p = { 0 };
Flow f = { 0 };
Signature lte_a, lte_c, non_lte;
DetectFwLteCoverageTestInitSig(&lte_a, 1, 1, true, true);
DetectFwLteCoverageTestInitSig(&lte_c, 3, 2, true, true);
DetectFwLteCoverageTestInitSig(&non_lte, 4, 1, false, true);
/* the rule is already counted earlier in the list: must not count again */
candidates[0].s = &lte_a;
candidates[0].id = lte_a.iid;
candidates[0].fw_lte_counted = true;
DetectFwCountAppendedLteRule(&det_ctx, &p, &f, &fw_state, &lte_a, 1);
FAIL_IF(det_ctx.fw_lte_cover[0] != 0);
FAIL_IF(det_ctx.fw_lte_cover[1] != 0);
FAIL_IF(candidates[1].fw_lte_counted);
/* a non-LTE rule does not increase coverage */
DetectFwCountAppendedLteRule(&det_ctx, &p, &f, &fw_state, &non_lte, 1);
FAIL_IF(det_ctx.fw_lte_cover[0] != 0);
/* a new LTE rule covers its own hook and every prior hook */
candidates[1].s = &lte_c;
candidates[1].id = lte_c.iid;
DetectFwCountAppendedLteRule(&det_ctx, &p, &f, &fw_state, &lte_c, 1);
FAIL_IF(det_ctx.fw_lte_cover[0] != 1);
FAIL_IF(det_ctx.fw_lte_cover[1] != 1);
FAIL_IF(det_ctx.fw_lte_cover[2] != 1);
FAIL_IF(det_ctx.fw_lte_cover[3] != 0);
FAIL_IF_NOT(candidates[1].fw_lte_counted);
PASS;
}
void SigRegisterTests(void)
{
SigParseRegisterTests();
IPOnlyRegisterTests();
UtRegisterTest("DetectFwLteCoverageTest01", DetectFwLteCoverageTest01);
UtRegisterTest("DetectFwLteCoverageTest02", DetectFwLteCoverageTest02);
UtRegisterTest("DetectFwLteCoverageTest03", DetectFwLteCoverageTest03);
UtRegisterTest("SigTest01", SigTest01);
UtRegisterTest("SigTest02 -- Offset/Depth match", SigTest02);
UtRegisterTest("SigTest03 -- offset/depth mismatch", SigTest03);

Loading…
Cancel
Save