detect/firewall: improve handle fw alert handling

More clearly define the relationship between PacketAlerts for firewall
and threat detection events.

Also no longer count firewall_discarded if drop rule came before a
another rule, causing the later rule to not be evaluated/alerted.

When packet:filter and app:filter alert appear in a single alert queue,
handle accept:hook by keeping track of the detect_table.
pull/15475/head
Victor Julien 2 months ago
parent 7e9402a2e0
commit 485f5243d5

@ -158,7 +158,8 @@ int PacketAlertCheck(Packet *p, uint32_t sid)
static inline void RuleActionToFlow(const uint8_t action, Flow *f, const bool fw_rule)
{
if (action & ACTION_ACCEPT) {
f->flags |= FLOW_ACTION_ACCEPT;
DEBUG_VALIDATE_BUG_ON(!fw_rule);
f->flags |= (FLOW_ACTION_ACCEPT | FLOW_ACTION_BY_FIREWALL);
SCLogDebug("setting flow action accept");
}
@ -179,8 +180,10 @@ static inline void RuleActionToFlow(const uint8_t action, Flow *f, const bool fw
/* drop:flow from TD rules will override a accept:flow from
* firewall rules. */
if (f->flags & FLOW_ACTION_ACCEPT) {
f->flags &= ~FLOW_ACTION_ACCEPT;
f->flags &= ~(FLOW_ACTION_ACCEPT | FLOW_ACTION_BY_FIREWALL);
f->flags |= FLOW_ACTION_DROP;
if (fw_rule)
f->flags |= FLOW_ACTION_BY_FIREWALL;
SCLogDebug("replaced FLOW_ACTION_ACCEPT with FLOW_ACTION_DROP");
}
if (f->flags & (FLOW_ACTION_DROP | FLOW_ACTION_PASS)) {
@ -491,6 +494,97 @@ static inline void FlowApplySignatureActions(
}
}
/** \internal
* \brief handle immediate actions for a firewall rule match
*
* Delayed action (accept) is handled after TD has also run, as TD drop
* can overrule a FW accept. So returning `accept` here means "will accept if TD agrees".
*
* \retval pol firewall policy with action that is (drop) or should possibly be (accept)
* applied to the packet and flow.
*/
static struct DetectFirewallPolicy HandleFirewallRule(
const DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, Packet *p, PacketAlert *pa)
{
const Signature *s = pa->s;
struct DetectFirewallPolicy pol = { .action = 0, .action_scope = 0 };
int res = PacketAlertHandle(de_ctx, det_ctx, s, p, pa);
SCLogDebug("packet %" PRIu64 ": fw sid %u: res %d", PcapPacketCntGet(p), s->id, res);
if (res > 0) {
/* Now, if we have an alert, we have to check if we want
* to tag this session or src/dst host */
if (s->sm_arrays[DETECT_SM_LIST_TMATCH] != NULL) {
KEYWORD_PROFILING_SET_LIST(det_ctx, DETECT_SM_LIST_TMATCH);
SigMatchData *smd = s->sm_arrays[DETECT_SM_LIST_TMATCH];
while (1) {
/* tags are set only for alerts */
KEYWORD_PROFILING_START;
sigmatch_table[smd->type].Match(det_ctx, p, (Signature *)s, smd->ctx);
KEYWORD_PROFILING_END(det_ctx, smd->type, 1);
if (smd->is_last)
break;
smd++;
}
}
/* if this is a terminating action, pass the action to the caller. */
if (s->action_scope != ACTION_SCOPE_HOOK ||
pa->flags & PACKET_ALERT_FLAG_APPLY_ACTION_TO_PACKET) {
pol.action = s->action;
pol.action_scope = s->action_scope;
if (pol.action & ACTION_DROP) {
uint8_t drop_reason = PKT_DROP_REASON_FW_RULES;
if (s->detect_table == DETECT_TABLE_PACKET_PRE_STREAM) {
drop_reason = PKT_DROP_REASON_FW_STREAM_PRE_HOOK;
} else if (s->detect_table == DETECT_TABLE_PACKET_PRE_FLOW) {
drop_reason = PKT_DROP_REASON_FW_FLOW_PRE_HOOK;
}
PacketDrop(p, pol.action, drop_reason);
if (p->flow && pol.action_scope == ACTION_SCOPE_FLOW) {
p->flow->flags |= FLOW_ACTION_DROP | FLOW_ACTION_BY_FIREWALL;
SCLogDebug("packet %" PRIu64 ": FLOW_ACTION_DROP set by firewall by sid %u",
PcapPacketCntGet(p), s->id);
}
}
if (pol.action & ACTION_PASS) {
if (p->flow && pol.action_scope == ACTION_SCOPE_FLOW) {
p->flow->flags |= FLOW_ACTION_PASS;
SCLogDebug("packet %" PRIu64 ": FLOW_ACTION_PASS set by firewall by sid %u",
PcapPacketCntGet(p), s->id);
}
}
}
/* add the alert for logging if required. */
if (s->action & ACTION_ALERT) {
if (p->alerts.cnt < packet_alert_max) {
p->alerts.alerts[p->alerts.cnt++] = *pa;
} else {
p->alerts.firewall_discarded++;
}
}
}
return pol;
}
/*
* Queue order after sorting:
*
* Firewall use case: rules are sorted by Signature::detect_table, Signature::iid (which reflects
* order in the rule file(s)) This means:
* - pre_flow
* - pre_stream
* - packet:filter (firewall)
* - packet:td (IDS/IPS rules)
* - app:filter (firewall)
* - app:td (IDS/IPS rules)
*
* Firewall DROPs are immediate.
* Firewall ACCEPTs depend on TD not dropping.
* Firewall rules are not affected by PASS.
* TD rules are affected by PASS, both set by Firewall and TD rules.
*
* Non-Firewall use case: rules are sorted by Signature::iid (which reflect order based on flowbits,
* action-order, priority, etc)
*/
static inline void PacketAlertFinalizeProcessQueue(
const DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, Packet *p)
{
@ -505,17 +599,85 @@ static inline void PacketAlertFinalizeProcessQueue(
bool alerted = false;
bool dropped = false;
bool skip_td = false;
bool skip_fw = false;
bool fw_accept_packet = false; // same as skip_fw?
bool fw_accept_flow = false;
#ifdef DEBUG
SCLogDebug("packet %" PRIu64 ": starting alert event queue", PcapPacketCntGet(p));
for (uint16_t x = 0; x < det_ctx->alert_queue_size; x++) {
const PacketAlert *pa = &det_ctx->alert_queue[x];
const Signature *s = pa->s;
SCLogDebug("(list) %s sid %u: action %02x scope %u iid %u detect_table %u",
(s->flags & SIG_FLAG_FIREWALL) ? "fw" : "td", s->id, s->action, s->action_scope,
s->iid, s->detect_table);
}
#endif /* DEBUG */
uint8_t skip_table_id = 0;
bool skip_table = false;
for (uint16_t i = 0; i < det_ctx->alert_queue_size; i++) {
PacketAlert *pa = &det_ctx->alert_queue[i];
const Signature *s = pa->s;
if (s->flags & SIG_FLAG_FIREWALL) {
if (skip_fw) {
continue;
}
/* skip for this table, but not for later. Mostly for showing
* alerts for both packet:filter and app:filter together. */
if (skip_table) {
if (s->detect_table <= skip_table_id)
continue;
skip_table = false;
}
if (dropped) {
SCLogDebug("Skippig firewall signature after a drop.");
p->alerts.firewall_discarded++;
continue;
}
const uint16_t pre_alert_cnt = p->alerts.cnt;
struct DetectFirewallPolicy pol = HandleFirewallRule(de_ctx, det_ctx, p, pa);
/* drop is immediate */
if (pol.action & ACTION_DROP) {
dropped = true;
} else if (pol.action & ACTION_ACCEPT) {
SCLogDebug("fw sid %u setting skip_fw for action %02x scope %s", s->id, pol.action,
ActionScopeToString(pol.action_scope));
/* see if we want to skip other alerts for this table */
if (pol.action_scope == ACTION_SCOPE_HOOK) {
skip_table_id = s->detect_table;
skip_table = true;
} else {
skip_fw = true;
}
fw_accept_packet = true;
if (pol.action_scope == ACTION_SCOPE_FLOW)
fw_accept_flow = true;
}
if (pol.action & ACTION_PASS) {
skip_td = true;
}
if (pre_alert_cnt < p->alerts.cnt)
alerted = true;
if (dropped)
goto fw_dropped;
continue;
}
SCLogDebug("sid: %u, action %02x, firewall? %s", s->id, pa->action,
BOOL2STR(s->flags & SIG_FLAG_FIREWALL));
/* if a firewall rule told us to skip, we don't count the skipped
* alerts. */
if (have_fw_rules && skip_td && (s->flags & SIG_FLAG_FIREWALL) == 0) {
if (have_fw_rules && skip_td) {
continue;
}
int res = PacketAlertHandle(de_ctx, det_ctx, s, p, pa);
SCLogDebug("sid %u: res %d", pa->s->id, res);
if (res > 0) {
/* Now, if we have an alert, we have to check if we want
* to tag this session or src/dst host */
@ -551,7 +713,7 @@ static inline void PacketAlertFinalizeProcessQueue(
/* set actions on the flow */
FlowApplySignatureActions(p, pa, s, pa->flags);
SCLogDebug("det_ctx->alert_queue[i].action %02x (DROP %s, PASS %s)", pa->action,
SCLogDebug("sid %u: action %02x (DROP %s, PASS %s)", pa->s->id, pa->action,
BOOL2STR(pa->action & ACTION_DROP), BOOL2STR(pa->action & ACTION_PASS));
/* set actions on packet */
@ -559,13 +721,8 @@ static inline void PacketAlertFinalizeProcessQueue(
}
}
/* skip firewall sigs following a drop: IDS mode still shows alerts after a drop. */
if ((s->flags & SIG_FLAG_FIREWALL) && dropped) {
SCLogDebug("Skippig firewall signature after a drop.");
p->alerts.firewall_discarded++;
/* Thresholding removes this alert */
} else if (res == 0 || res == 2 || (s->action & (ACTION_ALERT | ACTION_PASS)) == 0) {
/* Thresholding removes this alert */
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 */
@ -596,6 +753,22 @@ static inline void PacketAlertFinalizeProcessQueue(
}
}
fw_dropped:
/* after threat detection has been handled, see if the fw intended to accept (drop is handled
* immediately by the fw), as fw accept can be overruled by td drop. */
if (have_fw_rules) {
if (p->action & ACTION_DROP) {
SCLogDebug("packet %" PRIu64 ": dropped by TD", PcapPacketCntGet(p));
} else if (fw_accept_packet) {
p->action |= ACTION_ACCEPT;
if (p->flow && fw_accept_flow) {
p->flow->flags |= FLOW_ACTION_ACCEPT;
SCLogDebug("packet %" PRIu64 ": FLOW_ACTION_ACCEPT set from firewall",
PcapPacketCntGet(p));
}
}
}
/* Set flag on flow to indicate that it has alerts. We use the bool to
* exclude pass-only entries in `Packet::alerts` */
if (alerted && p->flow != NULL) {

@ -161,7 +161,10 @@ static void DetectRun(ThreadVars *th_v,
if (p->proto == IPPROTO_TCP) {
if ((p->flags & PKT_STREAM_EST) == 0) {
SCLogDebug("packet %" PRIu64 ": skip tcp non-established", PcapPacketCntGet(p));
DetectRunAppendDefaultAccept(det_ctx, p);
if (EngineModeIsFirewall()) {
SCLogDebug("default accept: no PKT_STREAM_EST");
DetectRunAppendDefaultAccept(det_ctx, p);
}
goto end;
}
const TcpSession *ssn = p->flow->protoctx;
@ -181,7 +184,10 @@ static void DetectRun(ThreadVars *th_v,
((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", PcapPacketCntGet(p));
DetectRunAppendDefaultAccept(det_ctx, p);
if (EngineModeIsFirewall()) {
SCLogDebug("default accept: no app update");
DetectRunAppendDefaultAccept(det_ctx, p);
}
goto end;
}
} else if (p->proto == IPPROTO_UDP) {
@ -198,7 +204,10 @@ static void DetectRun(ThreadVars *th_v,
PACKET_PROFILING_DETECT_END(p, PROF_DETECT_TX_UPDATE);
} else {
SCLogDebug("packet %" PRIu64 ": no flow / app-layer", PcapPacketCntGet(p));
DetectRunAppendDefaultAccept(det_ctx, p);
if (EngineModeIsFirewall()) {
SCLogDebug("default accept: no flow/app");
DetectRunAppendDefaultAccept(det_ctx, p);
}
}
end:
@ -1727,6 +1736,7 @@ static enum DetectTxFirewallFlowControl DetectFirewallApplyDefaultPolicies(
SCLogDebug("%" PRIu64 ": %s default policy for hook %u", PcapPacketCntGet(p),
direction & STREAM_TOSERVER ? "toserver" : "toclient", hook);
const bool apply_to_packet = is_last && hook == tx->tx_end_state;
const struct DetectFirewallPolicy *policy = DetectFirewallApplyDefaultAppPolicy(
det_ctx, det_ctx->de_ctx->fw_policies->app, p, alproto, direction, hook);
SCLogDebug("fw: hook:%u policy:%02x apply_to_packet:%s", hook, policy->action,
@ -1741,10 +1751,12 @@ static enum DetectTxFirewallFlowControl DetectFirewallApplyDefaultPolicies(
/* accepting flow, so skip rest of the fw rules */
if (policy->action_scope == ACTION_SCOPE_FLOW) {
SCLogDebug("fw: accept flow");
if (policy->action & ACTION_ALERT) {
DetectRunAppendDefaultAppPolicyAlert(
det_ctx, p, is_last, direction, tx->tx_id, alproto, hook);
det_ctx, p, apply_to_packet, direction, tx->tx_id, alproto, hook);
} else {
SCLogDebug("default accept");
DetectRunAppendDefaultAccept(det_ctx, p);
}
return DETECT_TX_FW_FC_SKIP;
@ -1764,8 +1776,8 @@ static enum DetectTxFirewallFlowControl DetectFirewallApplyDefaultPolicies(
} else if (policy->action_scope == ACTION_SCOPE_HOOK) {
if (policy->action & ACTION_ALERT) {
DetectRunAppendDefaultAppPolicyAlert(
det_ctx, p, is_last, direction, tx->tx_id, alproto, hook);
} else if (is_last) {
det_ctx, p, apply_to_packet, direction, tx->tx_id, alproto, hook);
} else if (apply_to_packet) {
DetectRunAppendDefaultAccept(det_ctx, p);
SCLogDebug("DetectRunAppendDefaultAccept for last tx");
}
@ -1773,12 +1785,13 @@ static enum DetectTxFirewallFlowControl DetectFirewallApplyDefaultPolicies(
} else {
if (policy->action & ACTION_ALERT) {
DetectRunAppendDefaultAppPolicyAlert(
det_ctx, p, is_last, direction, tx->tx_id, alproto, hook);
det_ctx, p, apply_to_packet, direction, tx->tx_id, alproto, hook);
}
}
}
if ((is_last && (actions & (ACTION_ACCEPT | ACTION_DROP)) == ACTION_ACCEPT)) {
SCLogDebug("default accept: last tx and at least one accept");
DetectRunAppendDefaultAccept(det_ctx, p);
}
return DETECT_TX_FW_FC_OK;
@ -1787,9 +1800,10 @@ static enum DetectTxFirewallFlowControl DetectFirewallApplyDefaultPolicies(
/** \internal
* \brief run pre-rule inspection firewall policy checks
*
* Check if:
* Check for:
* - check if we're in accept:tx mode
* - check for missing accept hooks
* -
*
* \retval DETECT_TX_FW_FC_OK no action needed
* \retval DETECT_TX_FW_FC_BREAK rest of rules shouldn't be inspected
@ -1806,6 +1820,7 @@ static enum DetectTxFirewallFlowControl DetectRunTxPreCheckFirewallPolicy(
if (p->flow->flags & FLOW_ACTION_ACCEPT) {
if (fw_state->tx_fw_verdict == false) {
fw_state->tx_fw_verdict = true;
SCLogDebug("default accept due to flow accept");
DetectRunAppendDefaultAccept(det_ctx, p);
}
if (s->flags & SIG_FLAG_FIREWALL) {
@ -1819,7 +1834,7 @@ static enum DetectTxFirewallFlowControl DetectRunTxPreCheckFirewallPolicy(
if (!fw_state->tx_fw_verdict) {
const bool accept_tx_applies_to_packet = last_tx;
if (accept_tx_applies_to_packet) {
SCLogDebug("accept:(tx|hook): should be applied to the packet");
SCLogDebug("accept:tx: should be applied to the packet");
DetectRunAppendDefaultAccept(det_ctx, p);
}
fw_state->tx_fw_verdict = true;
@ -1864,10 +1879,10 @@ static enum DetectTxFirewallFlowControl DetectRunTxPreCheckFirewallPolicy(
* \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 skip_before_progress progress value to skip rules before.* Only used if `skip_fw_hook` is
* set.
*
* \param fw_last_for_progress[out] set to true if this is the last firewall rule for a progress
* \param fw_last_for_progress[out] set to true if this is firewall rule for a progress
* value
*
* \param fw_next_progress_missing[out] set to true if the next fw rule does not target the next
@ -1942,17 +1957,19 @@ static enum DetectTxFirewallFlowControl DetectRunTxCheckFirewallPolicy(
// TODO move into det_ctx?
thread_local Signature default_accept;
static inline void DetectRunAppendDefaultAccept(DetectEngineThreadCtx *det_ctx, Packet *p)
static void DetectRunAppendDefaultAccept(DetectEngineThreadCtx *det_ctx, Packet *p)
{
if (EngineModeIsFirewall()) {
memset(&default_accept, 0, sizeof(default_accept));
default_accept.action = ACTION_ACCEPT;
default_accept.action_scope = ACTION_SCOPE_PACKET;
default_accept.iid = 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);
}
DEBUG_VALIDATE_BUG_ON(!EngineModeIsFirewall());
SCLogDebug("packet %" PRIu64 ": appending default firewall accept", PcapPacketCntGet(p));
memset(&default_accept, 0, sizeof(default_accept));
default_accept.action = ACTION_ACCEPT;
default_accept.action_scope = ACTION_SCOPE_PACKET;
default_accept.iid = UINT32_MAX;
default_accept.type = SIG_TYPE_PKT;
default_accept.flags = SIG_FLAG_FIREWALL;
default_accept.detect_table =
DETECT_TABLE_APP_FILTER; // TODO review, hope this makes it last in sorting
AlertQueueAppend(det_ctx, &default_accept, p, 0, PACKET_ALERT_FLAG_APPLY_ACTION_TO_PACKET);
}
/** \internal
@ -2033,7 +2050,7 @@ static bool ApplyAccept(DetectEngineThreadCtx *det_ctx, Packet *p, const uint8_t
} else if (as == ACTION_SCOPE_PACKET) {
return true;
} else if (as == ACTION_SCOPE_FLOW) {
SCLogDebug("ACTION_ACCEPT with ACTION_SCOPE_FLOW");
SCLogDebug("sid %u: ACTION_ACCEPT with ACTION_SCOPE_FLOW", s->id);
return true;
}
return false;
@ -2058,12 +2075,14 @@ static int DetectTxFirewallNoRulesApplyPolicies(DetectEngineThreadCtx *det_ctx,
/* if there are no rules, make sure to handle accept:flow and accept:tx */
if (rule_cnt == 0) {
if (f->flags & FLOW_ACTION_ACCEPT) {
SCLogDebug("default accept:flow: no rules");
DetectRunAppendDefaultAccept(det_ctx, p);
return 1;
}
if (tx->tx_data_ptr->flags & APP_LAYER_TX_ACCEPT) {
/* current tx is the last we have, append a blank accept:packet */
if (last_tx) {
SCLogDebug("default accept:tx: no rules");
DetectRunAppendDefaultAccept(det_ctx, p);
return 1;
}
@ -2337,8 +2356,11 @@ static void DetectRunTx(ThreadVars *tv,
const bool fw_accept_to_packet = ApplyAcceptToPacket(last_tx, &tx, s);
fw_state.fw_skip_app_filter = ApplyAccept(
det_ctx, p, flow_flags, s, &tx, tx_end_state, last_tx, &fw_state);
if (fw_accept_to_packet)
if (fw_accept_to_packet) {
SCLogDebug("packet %" PRIu64 ": apply accept to packet",
PcapPacketCntGet(p));
DetectRunAppendDefaultAccept(det_ctx, p);
}
}
continue;
}
@ -2395,6 +2417,8 @@ static void DetectRunTx(ThreadVars *tv,
* accept:tx or accept:hook is the last match.*/
const bool fw_accept_to_packet = ApplyAcceptToPacket(last_tx, &tx, s);
if (fw_accept_to_packet) {
SCLogDebug("packet %" PRIu64 ": apply accept to packet",
PcapPacketCntGet(p));
SCLogDebug("accept:(tx|hook): should be applied to the packet");
alert_flags |= PACKET_ALERT_FLAG_APPLY_ACTION_TO_PACKET;
}
@ -2449,6 +2473,7 @@ static void DetectRunTx(ThreadVars *tv,
/* if this is also the last fw rule we'll inspect we have to issue a default
* accept to the packet */
if (last_tx && s->app_progress_hook == tx.tx_progress) {
SCLogDebug("default accept: last_tx");
DetectRunAppendDefaultAccept(det_ctx, p);
}
}
@ -2532,6 +2557,7 @@ static void DetectRunTx(ThreadVars *tv,
tx_inspected, fw_verdicted);
/* if all tables have been bypassed, we accept:packet */
if (tx_inspected == 0 && fw_verdicted == 0 && have_fw_rules) {
SCLogDebug("default accept: no app inspect performed");
DetectRunAppendDefaultAccept(det_ctx, p);
}
}

@ -550,6 +550,9 @@ enum SignatureHookType {
SIGNATURE_HOOK_TYPE_APP,
};
/** detect table identifiers, ordered by how they logically
* evaluated. Used in rule ordering to ensure the correct order
* of rule actions. */
enum DetectTable {
DETECT_TABLE_NOT_SET = 0,
DETECT_TABLE_PACKET_PRE_FLOW,

Loading…
Cancel
Save