Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

flow/output: log triggered exception policy - v1 #12683

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 49 additions & 3 deletions doc/userguide/configuration/exception-policies.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ It is possible to disable this default, by setting the exception policies'
**In IDS mode**, setting ``auto`` mode actually means disabling the
``master-switch``, or ignoring the exception policies.

.. note::

If no exception policy is enabled, Suricata will not log exception policy stats.

.. _eps_settings:

Specific settings
~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -210,12 +216,52 @@ Notes:
* Not valid means that Suricata will error out and won't start.
* ``REJECT`` will make Suricata send a Reset-packet unreach error to the sender of the matching packet.

.. _eps_output:

Log Output
----------

.. _eps_flow_event:

Flow Event
~~~~~~~~~~

When an Exception Policy is triggered, this will be indicated in the flow log
event for the associated flow, also indicating which target triggered that, and
what policy was applied. If no exception policy is triggered, that field won't
be present in the logs.

Note that this is true even if the policy is applied only to certain packets from
a flow.

In the log below, the flow triggered the ``midstream policy``, leading to Suricata
applying the configured behavior for such scenario: *to pass the flow* (``pass_flow``)::

"flow": {
"pkts_toserver": 4,
"pkts_toclient": 5,
"bytes_toserver": 495,
"bytes_toclient": 351,
"start": "2016-07-13T22:42:07.199672+0000",
"end": "2016-07-13T22:42:07.573174+0000",
"age": 0,
"state": "new",
"reason": "shutdown",
"alerted": false,
"action": "pass",
"exception_policy_triggered": {
"target": "stream_midstream",
"policy": "pass_flow"
}
},

.. _eps_stats:

Available Stats
---------------
~~~~~~~~~~~~~~~

There are stats counters for each supported exception policy scenario:
There are stats counters for each supported exception policy scenario that will
be logged when exception policies are enabled:

.. list-table:: **Exception Policy Stats Counters**
:widths: 50 50
Expand Down Expand Up @@ -244,7 +290,7 @@ Stats for application layer errors are available in summarized form or per
application layer protocol. As the latter is extremely verbose, by default
Suricata logs only the summary. If any further investigation is needed, it
is recommended to enable per-app-proto exception policy error counters
temporarily (for :ref:`stats configuration<suricata_yaml_outputs>`).
temporarily (for more, read :ref:`stats configuration<suricata_yaml_outputs>`).


Command-line Options for Simulating Exceptions
Expand Down
13 changes: 12 additions & 1 deletion doc/userguide/output/eve/eve-json-format.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1669,6 +1669,12 @@ Fields
* "state": display state of the flow (include "new", "established", "closed", "bypassed")
* "reason": mechanism that did trigger the end of the flow (include "timeout", "forced" and "shutdown")
* "alerted": "true" or "false" depending if an alert has been seen on flow
* "action": "pass" or "drop" depending if flow was PASS'ed or DROP'ed (no present if none)
* "exception_policy_triggered.target": if an exception policy was triggered,
what setting exception led to this (cf. :ref:`Exception Policy - Specific
Settings<eps_settings>`).
* "exception_policy_triggered.policy": if an exception policy was triggered,
what policy was applied (to the flow or to any packet(s) from it)

Example ::

Expand All @@ -1689,7 +1695,12 @@ Example ::
"bypass": "capture",
"state": "bypassed",
"reason": "timeout",
"alerted": false
"alerted": false,
"action": "pass",
"exception_policy_triggered": {
"target": "stream_midstream",
"policy": "pass_flow"
}
}

Event type: RDP
Expand Down
14 changes: 14 additions & 0 deletions etc/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1872,6 +1872,20 @@
"end": {
"type": "string"
},
"exception_policy_triggered": {
"description": "The exception policy triggered by the flow. Not logged if none was triggered",
"type": "object",
"properties": {
"target": {
"description": "What triggered the exception",
"type": "string"
},
"policy": {
"description": "Which exception policy was applied",
"type": "string"
}
}
},
"pkts_toclient": {
"type": "integer"
},
Expand Down
7 changes: 6 additions & 1 deletion src/app-layer-parser.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Copyright (C) 2007-2021 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
Expand Down Expand Up @@ -153,6 +153,11 @@ static void AppLayerConfig(void)
g_applayerparser_error_policy = ExceptionPolicyParse("app-layer.error-policy", true);
}

enum ExceptionPolicy AppLayerErrorGetExceptionPolicy(void)
{
return g_applayerparser_error_policy;
}

static void AppLayerParserFramesFreeContainer(FramesContainer *frames)
{
if (frames != NULL) {
Expand Down
4 changes: 3 additions & 1 deletion src/app-layer-parser.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Copyright (C) 2007-2020 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
Expand Down Expand Up @@ -136,6 +136,8 @@ void AppLayerParserThreadCtxFree(AppLayerParserThreadCtx *tctx);
int AppLayerParserConfParserEnabled(const char *ipproto,
const char *alproto_name);

enum ExceptionPolicy AppLayerErrorGetExceptionPolicy(void);

/** \brief Prototype for parsing functions */
typedef AppLayerResult (*AppLayerParserFPtr)(Flow *f, void *protocol_state,
AppLayerParserState *pstate, StreamSlice stream_slice, void *local_storage);
Expand Down
8 changes: 6 additions & 2 deletions src/flow.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Copyright (C) 2007-2024 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
Expand Down Expand Up @@ -55,7 +55,8 @@ typedef struct AppLayerParserState_ AppLayerParserState;
/** next packet in toclient direction will act on updated app-layer state */
#define FLOW_TC_APP_UPDATE_NEXT BIT_U32(2)

// vacancy bit 3
/** if an exception policy was triggered, use this to log about that */
#define FLOW_TRIGGERED_EXCEPTION_POLICY BIT_U32(3)

// vacancy bit 4

Expand Down Expand Up @@ -480,6 +481,9 @@ typedef struct Flow_
* has been set. */
const struct SigGroupHead_ *sgh_toserver;

/** which exception policy was applied, if any */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by putting this between 2 pointers we're essentially use 8 bytes for this, which is a lot for something from which we just use 6 bits

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I didn't realize that. >__<'

uint16_t applied_exception_policy;

/* pointer to the var list */
GenericVar *flowvar;

Expand Down
11 changes: 10 additions & 1 deletion src/output-json-flow.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Copyright (C) 2007-2020 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
Expand Down Expand Up @@ -50,6 +50,7 @@
#include "stream-tcp.h"
#include "stream-tcp-private.h"
#include "flow-storage.h"
#include "util-exception-policy.h"

static JsonBuilder *CreateEveHeaderFromFlow(const Flow *f)
{
Expand Down Expand Up @@ -280,6 +281,14 @@ static void EveFlowLogJSON(OutputJsonThreadCtx *aft, JsonBuilder *jb, Flow *f)
} else if (f->flags & FLOW_ACTION_PASS) {
JB_SET_STRING(jb, "action", "pass");
}
if (f->flags & FLOW_TRIGGERED_EXCEPTION_POLICY) {
jb_open_object(jb, "exception_policy_triggered");
jb_set_string(jb, "target", ExceptionPolicyTargetFlagToString(f->applied_exception_policy));
jb_set_string(jb, "policy",
ExceptionPolicyEnumToString(
ExceptionPolicyTargetPolicy(f->applied_exception_policy), true));
jb_close(jb);
}

/* Close flow. */
jb_close(jb);
Expand Down
17 changes: 16 additions & 1 deletion src/stream-tcp.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Copyright (C) 2007-2024 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
Expand Down Expand Up @@ -896,6 +896,21 @@ static void StreamTcpSsnMemcapExceptionPolicyStatsIncr(
}
}

enum ExceptionPolicy StreamTcpSsnMemcapGetExceptionPolicy(void)
{
return stream_config.ssn_memcap_policy;
}

enum ExceptionPolicy StreamTcpReassemblyMemcapGetExceptionPolicy(void)
{
return stream_config.reassembly_memcap_policy;
}

enum ExceptionPolicy StreamMidstreamGetExceptionPolicy(void)
{
return stream_config.midstream_policy;
}

/** \internal
* \brief The function is used to fetch a TCP session from the
* ssn_pool, when a TCP SYN is received.
Expand Down
6 changes: 5 additions & 1 deletion src/stream-tcp.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Copyright (C) 2007-2024 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
Expand Down Expand Up @@ -158,6 +158,10 @@ void StreamTcpDetectLogFlush(ThreadVars *tv, StreamTcpThread *stt, Flow *f, Pack
const char *StreamTcpStateAsString(const enum TcpState);
const char *StreamTcpSsnStateAsString(const TcpSession *ssn);

enum ExceptionPolicy StreamTcpSsnMemcapGetExceptionPolicy(void);
enum ExceptionPolicy StreamTcpReassemblyMemcapGetExceptionPolicy(void);
enum ExceptionPolicy StreamMidstreamGetExceptionPolicy(void);

/** ------- Inline functions: ------ */

/**
Expand Down
9 changes: 9 additions & 0 deletions src/util-exception-policy-types.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ enum ExceptionPolicy {
* "tcp.reassembly_exception_policy.drop_packet" + 1 */
#define EXCEPTION_POLICY_COUNTER_MAX_LEN 45

/** exception policy flags */
#define EXCEPTION_DEFRAG_MEMCAP BIT_U16(1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a EXCEPTION_NONE BIT_U16(0) here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually just 0, the other bit should start at offset 0
BIT_U16(0) is just 1 << 0 == 1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙇🏽‍♀️

#define EXCEPTION_SESSION_MEMCAP BIT_U16(2)
#define EXCEPTION_REASSEMBLY_MEMCAP BIT_U16(3)
#define EXCEPTION_FLOW_MEMCAP BIT_U16(4)
#define EXCEPTION_MIDSTREAM BIT_U16(5)
#define EXCEPTION_APPLAYER_ERROR BIT_U16(6)
/** 6 - 15 free */

typedef struct ExceptionPolicyCounters_ {
/* Follows enum order */
uint16_t eps_id[EXCEPTION_POLICY_MAX];
Expand Down
69 changes: 66 additions & 3 deletions src/util-exception-policy.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Copyright (C) 2022-2024 Open Information Security Foundation
/* Copyright (C) 2022-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
Expand All @@ -19,14 +19,18 @@
* \file
*/

#include "util-exception-policy.h"
#include "suricata-common.h"
#include "suricata.h"
#include "packet.h"
#include "util-exception-policy.h"
#include "util-misc.h"
#include "stream-tcp-reassemble.h"
#include "action-globals.h"
#include "conf.h"
#include "flow.h"
#include "stream-tcp.h"
#include "defrag-hash.h"
#include "app-layer-parser.h"

enum ExceptionPolicy g_eps_master_switch = EXCEPTION_POLICY_NOT_SET;
/** true if exception policy was defined in config */
Expand Down Expand Up @@ -61,14 +65,73 @@ void SetMasterExceptionPolicy(void)
g_eps_master_switch = ExceptionPolicyParse("exception-policy", true);
}

static enum ExceptionPolicy GetMasterExceptionPolicy(void)
enum ExceptionPolicy GetMasterExceptionPolicy(void)
{
return g_eps_master_switch;
}

static uint16_t ExceptionPolicyFlag(enum PacketDropReason drop_reason)
{
switch (drop_reason) {
case PKT_DROP_REASON_DEFRAG_MEMCAP:
return EXCEPTION_DEFRAG_MEMCAP;
case PKT_DROP_REASON_STREAM_MEMCAP:
return EXCEPTION_SESSION_MEMCAP;
case PKT_DROP_REASON_STREAM_REASSEMBLY:
return EXCEPTION_REASSEMBLY_MEMCAP;
case PKT_DROP_REASON_FLOW_MEMCAP:
return EXCEPTION_FLOW_MEMCAP;
case PKT_DROP_REASON_STREAM_MIDSTREAM:
return EXCEPTION_MIDSTREAM;
case PKT_DROP_REASON_APPLAYER_ERROR:
return EXCEPTION_APPLAYER_ERROR;
default:
return BIT_U16(0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then use that EXCEPTION_NONE here?

}
return BIT_U16(0);
}

const char *ExceptionPolicyTargetFlagToString(uint16_t target_flag)
{
if (target_flag & EXCEPTION_DEFRAG_MEMCAP)
return "defrag_memcap";
if (target_flag & EXCEPTION_SESSION_MEMCAP)
return "stream_memcap";
if (target_flag & EXCEPTION_REASSEMBLY_MEMCAP)
return "stream_reassembly_memcap";
if (target_flag & EXCEPTION_FLOW_MEMCAP)
return "flow_memcap";
if (target_flag & EXCEPTION_MIDSTREAM)
return "stream_midstream";
if (target_flag & EXCEPTION_APPLAYER_ERROR)
return "app_layer_error";
return "ignore";
}

enum ExceptionPolicy ExceptionPolicyTargetPolicy(uint16_t target_flag)
{
if (target_flag & EXCEPTION_DEFRAG_MEMCAP)
return DefragGetMemcapExceptionPolicy();
if (target_flag & EXCEPTION_SESSION_MEMCAP)
return StreamTcpSsnMemcapGetExceptionPolicy();
if (target_flag & EXCEPTION_REASSEMBLY_MEMCAP)
return StreamTcpReassemblyMemcapGetExceptionPolicy();
if (target_flag & EXCEPTION_FLOW_MEMCAP)
return FlowGetMemcapExceptionPolicy();
if (target_flag & EXCEPTION_MIDSTREAM)
return StreamMidstreamGetExceptionPolicy();
if (target_flag & EXCEPTION_APPLAYER_ERROR)
return AppLayerErrorGetExceptionPolicy();
return EXCEPTION_POLICY_NOT_SET;
}

void ExceptionPolicyApply(Packet *p, enum ExceptionPolicy policy, enum PacketDropReason drop_reason)
{
SCLogDebug("start: pcap_cnt %" PRIu64 ", policy %u", p->pcap_cnt, policy);
if (p->flow) {
p->flow->flags |= FLOW_TRIGGERED_EXCEPTION_POLICY;
p->flow->applied_exception_policy |= ExceptionPolicyFlag(drop_reason);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when would we have more than one? Or should this be a simple assignment instead?

In fact, if it is possible that this is already set, what do that mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when would we have more than one? Or should this be a simple assignment instead?

Should it maybe be a counter, then?
And then if we have it >1, we check all flags that match?

In fact, if it is possible that this is already set, what do that mean?

This did cross my mind, but I wasn't sure this was a reasonable concern, as I didn't recall seeing such a scenario. (although probably because I didn't look for such situations when creating most tests/checks)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could applied_exception_policy itself work as the counter, maybe?

Copy link
Contributor Author

@jufajardini jufajardini Feb 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for now, going with a different approach (inspired by the exception policy counters struct)

}
switch (policy) {
case EXCEPTION_POLICY_AUTO:
break;
Expand Down
5 changes: 4 additions & 1 deletion src/util-exception-policy.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Copyright (C) 2022-2024 Open Information Security Foundation
/* Copyright (C) 2022-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
Expand Down Expand Up @@ -26,7 +26,10 @@
#include "util-exception-policy-types.h"

const char *ExceptionPolicyEnumToString(enum ExceptionPolicy policy, bool is_json);
const char *ExceptionPolicyTargetFlagToString(uint16_t target_flag);
enum ExceptionPolicy ExceptionPolicyTargetPolicy(uint16_t target_flag);
void SetMasterExceptionPolicy(void);
enum ExceptionPolicy GetMasterExceptionPolicy(void);
void ExceptionPolicyApply(
Packet *p, enum ExceptionPolicy policy, enum PacketDropReason drop_reason);
enum ExceptionPolicy ExceptionPolicyParse(const char *option, const bool support_flow);
Expand Down
Loading