diff --git a/doc/userguide/firewall/firewall-design.rst b/doc/userguide/firewall/firewall-design.rst index 36951962f851..96abd4e1558c 100644 --- a/doc/userguide/firewall/firewall-design.rst +++ b/doc/userguide/firewall/firewall-design.rst @@ -62,7 +62,7 @@ Application layer tables ~~~~~~~~~~~~~~~~~~~~~~~~ If applayer is available, rules from the following tables apply. The tables for the -application layer are per app layer protocol and per protocol state. e.g. ``http:request_line``. +application layer are per app layer protocol and per protocol state. e.g. ``http1:request_line``. .. table:: @@ -349,29 +349,57 @@ The example below accepts ARP again, using this mechanism. Default policies ================ -Each hook has a default policy. By default ``packet:filter`` enforces a ``drop:packet`` policy and the -``app:filter`` hooks applies ``drop:flow``. +Each hook has a default policy applied to traffic that no firewall rule handled. +By default ``packet.filter`` enforces ``drop:packet``, ``packet.pre-flow`` and +``packet.pre-stream`` enforce ``accept:hook``, and every ``app`` hook enforces +``drop:flow``. -The policies can be configured in ``firewall`` block in the config. - -Example for ``packet:filter``, to use reject instead of drop:: +Defaults are configured in the ``firewall.policies`` block. A ``default-policy`` +may be given at several levels; for any hook the most specific present setting +wins:: firewall: policies: - packet-filter: [ "reject:packet" ] - - -Example for DNS:: + default-policy: ["accept:hook"] # global fallback (all hooks) + packet: + default-policy: ["drop:packet"] # fallback for packet hooks + filter: ["drop:packet"] + pre-flow: ["accept:hook"] + pre-stream: ["accept:hook"] + app: + default-policy: ["drop:flow"] # fallback for all app hooks + dns: + default-policy: ["drop:flow"] # fallback for dns hooks + request-started: ["accept:hook"] + request-complete: ["drop:flow", "alert"] + response-started: ["accept:tx"] + +Protocols whose hooks are grouped into sub states, such as HTTP/2, take an extra +level for the sub state name:: firewall: policies: - dns: - request-started: ["accept:hook"] - - # Drop and alert on all DNS requests that are not allowed in - # firewall.rules. - request-complete: ["drop:flow", "alert"] - - # Accept all responses. - response-started: ["accept:tx"] - + app: + http2: + default-policy: ["drop:flow"] # fallback for all http2 hooks + stream: + default-policy: ["drop:flow"] # fallback for http2 stream hooks + request-started: ["accept:hook"] + global: + request-started: ["accept:hook"] + +Precedence: + +* packet hook: ``packet.`` > ``packet.default-policy`` > + ``policies.default-policy`` > built-in (``drop:packet`` or ``accept:hook``) +* app hook: ``app..`` > ``app..default-policy`` > + ``app.default-policy`` > ``policies.default-policy`` > built-in (``drop:flow``) +* app hook in a sub state: ``app...`` > + ``app...default-policy`` > ``app..default-policy`` > + ``app.default-policy`` > ``policies.default-policy`` > built-in (``drop:flow``) + +An action scope must be valid for the hook it is applied to. For example, +defining ``accept:tx`` as a global default policy will fail to start Suricata, +because ``packet`` policies do not accept ``tx``. +Cover such hooks with a more specific setting so the incompatible default never +reaches them. diff --git a/doc/userguide/firewall/firewall-example.rst b/doc/userguide/firewall/firewall-example.rst index 002de6da7eeb..5ada751d03a2 100644 --- a/doc/userguide/firewall/firewall-example.rst +++ b/doc/userguide/firewall/firewall-example.rst @@ -67,35 +67,36 @@ In the example below: the config auto accepts various hooks, leaving just ``http firewall: policies: - http: - request-started: - - "accept:hook" - request-line: - - "drop:flow" - - "alert" - request-headers: - - "drop:flow" - - "alert" - request-body: - - "accept:hook" - request-trailer: - - "accept:hook" - request-complete: - - "accept:hook" - - response-started: - - "accept:hook" - response-line: - - "drop:flow" - - "alert" - response-headers: - - "accept:hook" - response-body: - - "accept:hook" - response-trailer: - - "accept:hook" - response-complete: - - "accept:hook" + app: + http1: + request-started: + - "accept:hook" + request-line: + - "drop:flow" + - "alert" + request-headers: + - "drop:flow" + - "alert" + request-body: + - "accept:hook" + request-trailer: + - "accept:hook" + request-complete: + - "accept:hook" + + response-started: + - "accept:hook" + response-line: + - "drop:flow" + - "alert" + response-headers: + - "accept:hook" + response-body: + - "accept:hook" + response-trailer: + - "accept:hook" + response-complete: + - "accept:hook" :: diff --git a/rust/sys/src/sys.rs b/rust/sys/src/sys.rs index b13890f6b9f9..09ce2b8f781f 100644 --- a/rust/sys/src/sys.rs +++ b/rust/sys/src/sys.rs @@ -73,7 +73,11 @@ pub enum AppProtoEnum { } pub type AppProto = u16; extern "C" { - #[doc = " \\brief Maps the ALPROTO_*, to its string equivalent.\n\n \\param alproto App layer protocol id.\n\n \\retval String equivalent for the alproto."] + #[doc = " \\brief Maps the ALPROTO_*, to its registered string equivalent.\n \\param alproto App layer protocol id.\n \\retval String equivalent for the alproto."] + pub fn AppProtoToStringRaw(alproto: AppProto) -> *const ::std::os::raw::c_char; +} +extern "C" { + #[doc = " \\brief Maps the ALPROTO_*, to its normalized string equivalent.\n\n \\param alproto App layer protocol id.\n\n \\retval String equivalent for the alproto."] pub fn AppProtoToString(alproto: AppProto) -> *const ::std::os::raw::c_char; } extern "C" { diff --git a/src/app-layer-protos.c b/src/app-layer-protos.c index 13fe402949ea..4a648dffcdb2 100644 --- a/src/app-layer-protos.c +++ b/src/app-layer-protos.c @@ -38,6 +38,16 @@ typedef struct AppProtoStringTuple { AppProtoStringTuple *g_alproto_strings = NULL; +const char *AppProtoToStringRaw(AppProto alproto) +{ + const char *proto_name = NULL; + if (alproto < g_alproto_max) { + DEBUG_VALIDATE_BUG_ON(g_alproto_strings[alproto].alproto != alproto); + proto_name = g_alproto_strings[alproto].str; + } + return proto_name; +} + const char *AppProtoToString(AppProto alproto) { const char *proto_name = NULL; diff --git a/src/app-layer-protos.h b/src/app-layer-protos.h index 21b21359ca3a..75c4ee3db9db 100644 --- a/src/app-layer-protos.h +++ b/src/app-layer-protos.h @@ -178,7 +178,14 @@ static inline AppProto AppProtoCommon(AppProto sigproto, AppProto alproto) } /** - * \brief Maps the ALPROTO_*, to its string equivalent. + * \brief Maps the ALPROTO_*, to its registered string equivalent. + * \param alproto App layer protocol id. + * \retval String equivalent for the alproto. + */ +const char *AppProtoToStringRaw(AppProto alproto); + +/** + * \brief Maps the ALPROTO_*, to its normalized string equivalent. * * \param alproto App layer protocol id. * diff --git a/src/detect-engine.c b/src/detect-engine.c index f27112e5816b..14c1740003b7 100644 --- a/src/detect-engine.c +++ b/src/detect-engine.c @@ -885,13 +885,11 @@ const char *DetectEngineAppHookToName( int DetectEngineAppHookToSmlist( const AppProto p, const uint8_t sub_state, const uint8_t state, const uint8_t direction) { - const char *app_proto = AppProtoToString(p); + const char *app_proto = AppProtoToStringRaw(p); if (app_proto == NULL) { SCLogError("unknown app_proto %u", p); return -1; } - if (strcmp(app_proto, "http") == 0) - app_proto = "http1"; char generic_hook_name[256]; if (sub_state == 0) { diff --git a/src/detect-parse.c b/src/detect-parse.c index 758ca54d030a..9c7def237947 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -109,6 +109,35 @@ typedef struct SignatureParser_ { char opts[DETECT_MAX_RULE_SIZE]; } SignatureParser; +/** Valid action scopes per firewall hook class. Single source of truth for both + * scope validation and the human-readable "a/b/c" hint in error messages. */ +static const uint8_t fw_packet_hook_scopes[] = { + ACTION_SCOPE_PACKET, + ACTION_SCOPE_HOOK, + ACTION_SCOPE_FLOW, +}; +static const uint8_t fw_app_hook_scopes[] = { + ACTION_SCOPE_FLOW, + ACTION_SCOPE_TX, + ACTION_SCOPE_HOOK, +}; + +/** \brief max length of a firewall.policies config path */ +#define FW_POLICY_PATH_MAX 320 +/** \brief max length of a single path segment: a hook or sub state name */ +#define FW_POLICY_NAME_MAX 64 +/** \brief max number of config paths consulted to resolve one policy */ +#define FW_POLICY_CHAIN_MAX 6 + +/** + * \brief Ordered, most-specific-first list of config paths a single policy can + * be configured at. + */ +typedef struct FirewallPolicyChain { + char path[FW_POLICY_CHAIN_MAX][FW_POLICY_PATH_MAX]; + uint8_t len; +} FirewallPolicyChain; + const char *DetectListToHumanString(int list) { #define CASE_CODE_STRING(E, S) case E: return S; break @@ -1156,14 +1185,12 @@ static bool IsBuiltIn(const char *n) void DetectRegisterAppLayerHookLists(void) { for (AppProto a = ALPROTO_FAILED + 1; a < g_alproto_max; a++) { - const char *alproto_name = AppProtoToString(a); - if (strcmp(alproto_name, "http") == 0) - alproto_name = "http1"; + const char *alproto_name = AppProtoToStringRaw(a); SCLogDebug("alproto %u/%s", a, alproto_name); if (AppLayerParserSupportsSubStates(a)) { uint8_t max_sub_state = AppLayerParserGetMaxSubState(a); - SCLogDebug("%s: max sub state for %u is %u", AppProtoToString(a), a, max_sub_state); + SCLogDebug("%s: max sub state for %u is %u", alproto_name, a, max_sub_state); for (uint8_t s = 1; s <= max_sub_state; s++) { const uint8_t max_state = AppLayerParserGetSubStateCompletion( a, s); // TODO allow different completion per direction? @@ -4161,134 +4188,235 @@ static int DoParsePolicy(const char *policy_name, struct DetectFirewallPolicy *p return 1; } -static int DoParseAppSubStatePolicy(const char *prefix, const AppProto app_proto, - const uint8_t sub_state, const char *sub_state_name, const uint8_t state, - const char *hookname, const uint8_t complete_state, const int direction, - struct DetectFirewallPolicies *fw_policies) +static bool FirewallScopeValidForClass(uint8_t scope, enum DetectFirewallPolicyClass pol_class) { - char policy_name[256]; - BUG_ON(sub_state_name == NULL); - BUG_ON(hookname == NULL); + const uint8_t *set = NULL; + size_t n = 0; + switch (pol_class) { + case DETECT_FIREWALL_POLICY_CLASS_PACKET: + set = fw_packet_hook_scopes; + n = ARRAY_SIZE(fw_packet_hook_scopes); + break; + case DETECT_FIREWALL_POLICY_CLASS_APP: + set = fw_app_hook_scopes; + n = ARRAY_SIZE(fw_app_hook_scopes); + break; + default: + FatalError("Invalid firewall policy class %u", (unsigned)pol_class); + } + for (size_t i = 0; i < n; i++) { + if (set[i] == scope) { + return true; + } + } + return false; +} - char *nname = SCStrdup(hookname); - if (nname == NULL) - return -1; - for (int i = 0; nname[i] != '\0'; i++) { - if (nname[i] == '_') - nname[i] = '-'; +/** + * \brief Render the valid scopes for a hook class to a string. + */ +static void FirewallScopeHintForClass( + enum DetectFirewallPolicyClass pol_class, char *out, size_t out_size) +{ + const uint8_t *set = NULL; + size_t n = 0; + switch (pol_class) { + case DETECT_FIREWALL_POLICY_CLASS_PACKET: + set = fw_packet_hook_scopes; + n = ARRAY_SIZE(fw_packet_hook_scopes); + break; + case DETECT_FIREWALL_POLICY_CLASS_APP: + set = fw_app_hook_scopes; + n = ARRAY_SIZE(fw_app_hook_scopes); + break; + default: + FatalError("Invalid firewall policy class %u", (unsigned)pol_class); + } + out[0] = '\0'; + for (size_t i = 0; i < n; i++) { + if (i > 0) { + if (strlcat(out, "/", out_size) >= out_size) { + FatalError("firewall policy scope hint too long"); + } + } + if (strlcat(out, ActionScopeToString((enum ActionScope)set[i]), out_size) >= out_size) { + FatalError("firewall policy scope hint too long"); + } } +} - const char *app_name = AppProtoToString(app_proto); - int r = snprintf(policy_name, sizeof(policy_name), "%s.%s.%s.%s", prefix, app_name, - sub_state_name, nname); - SCLogDebug("policy_name %s", policy_name); - SCFree(nname); - if (r < 0 || (size_t)r >= sizeof(policy_name)) { - FatalError("internal error: failed to assemble firewall policy config string"); +/** + * \brief Assemble a firewall.policies config path, fatal on truncation. + */ +static void ATTR_FMT_PRINTF(3, 4) + FirewallPolicyPath(char *out_buf, size_t out_buf_sz, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + int r = vsnprintf(out_buf, out_buf_sz, fmt, ap); + if (r < 0 || (size_t)r >= out_buf_sz) { + FatalError("%s: firewall policy config path too long", out_buf); } + va_end(ap); +} - struct DetectFirewallAppPolicy *app_pol = SCCalloc(1, sizeof(*app_pol)); - if (app_pol == NULL) - return -1; +/** + * \brief Append an inheritance tier to the chain of firewall policies to query. + */ +static void ATTR_FMT_PRINTF(2, 3) + FirewallPolicyChainAdd(FirewallPolicyChain *chain, const char *fmt, ...) +{ + char path[FW_POLICY_PATH_MAX]; + va_list ap; + va_start(ap, fmt); + int r = vsnprintf(path, sizeof(path), fmt, ap); + if (r < 0 || (size_t)r >= sizeof(path)) { + FatalError("%s: firewall policy config path too long", path); + } + va_end(ap); - app_pol->alproto = app_proto; - app_pol->sub_state = sub_state; - app_pol->progress = state; - app_pol->direction = (uint8_t)direction; - /* init to drop:flow by default, will be overwritten by DoParsePolicy if there - * is a config for this hook. */ - app_pol->policy.action = ACTION_DROP; - app_pol->policy.action_scope = ACTION_SCOPE_FLOW; + for (uint8_t i = 0; i < chain->len; i++) { + if (strcmp(chain->path[i], path) == 0) + return; + } + if (chain->len >= FW_POLICY_CHAIN_MAX) { + FatalError("%s: too many firewall policy config paths", path); + } + if (strlcpy(chain->path[chain->len++], path, FW_POLICY_PATH_MAX) >= FW_POLICY_PATH_MAX) { + FatalError("%s: firewall policy config path too long", path); + } +} - r = DoParsePolicy(policy_name, &app_pol->policy); - if (r < 0) { - SCFree(app_pol); - return -1; +/** + * \brief Resolve a firewall policy from its config path chain. + * + * The first path in the chain that has a policy configured wins, with its + * action scope validated against the target hook class. + * + * \retval 1 a config source was used and stored in \p out + * \retval 0 no source present, \p out is unmodified + * \retval -1 parse error, e.g. an empty policy, or invalid scope for the target class + */ +static int ResolveFirewallPolicy(struct DetectFirewallPolicy *out, + enum DetectFirewallPolicyClass pol_class, const FirewallPolicyChain *chain) +{ + for (uint8_t i = 0; i < chain->len; i++) { + const char *path = chain->path[i]; + struct DetectFirewallPolicy tmp = { 0 }; + int r = DoParsePolicy(path, &tmp); + if (r < 0) { + return -1; + } + if (r == 1) { + if (tmp.action == 0) { + SCLogError("%s: policy is set but empty", path); + return -1; + } + if (!FirewallScopeValidForClass(tmp.action_scope, pol_class)) { + char hint[32]; // space to combine ActionScopeToString results + FirewallScopeHintForClass(pol_class, hint, sizeof(hint)); + SCLogError("%s: action scope (\"%s\") is not valid. Valid scopes: %s", path, + ActionScopeToString(tmp.action_scope), hint); + return -1; + } + *out = tmp; + return 1; + } } + return 0; +} - if (HashTableAdd(fw_policies->app_policies, app_pol, 0) != 0) { - FatalError("internal error: insert policy into hash table"); +/** + * \brief Generic start/complete hook alias for an app progress state, in config + * form (hyphens), or NULL for intermediate states. + */ +static const char *FirewallAppGenericHookName( + const uint8_t state, const uint8_t complete_state, const int direction) +{ + if (state == 0) + return (direction == STREAM_TOSERVER) ? "request-started" : "response-started"; + if (state == complete_state) + return (direction == STREAM_TOSERVER) ? "request-complete" : "response-complete"; + return NULL; +} + +static void FirewallHookNameConvertUnderscoreToDash(const char *in, char *out, size_t out_size) +{ + if (strlcpy(out, in, out_size) >= out_size) { + FatalError("%s: firewall policy config name too long", in); } - /* for policies with an alert action, create a policy sig */ - if (r == 1 && app_pol->policy.action & ACTION_ALERT) { - SCLogDebug("adding policy signature"); - return AddAppPolicySignature(app_pol); + for (size_t i = 0; out[i] != '\0'; i++) { + if (out[i] == '_') + out[i] = '-'; } - SCLogDebug("r %d", r); - return r; } -static int DoParseAppPolicy(const char *prefix, const AppProto app_proto, const char *hookname, - const uint8_t state, const uint8_t complete_state, const int direction, +/** + * \brief Resolve and store one app-layer hook default policy. + * + * Handles both plain hooks (\p sub_state_name NULL) and sub state hooks, which + * only differ by an extra path segment. + */ +static int DoParseAppPolicy(const char *prefix, const AppProto app_proto, const uint8_t sub_state, + const char *sub_state_name, const char *hookname, const uint8_t state, + const uint8_t complete_state, const int direction, struct DetectFirewallPolicies *fw_policies) { - char policy_name[256]; - const char *in_name = hookname; - if (hookname == NULL) { - if (state == 0) { - if (direction == STREAM_TOSERVER) - hookname = "request-started"; - else - hookname = "response-started"; - } else if (state == complete_state) { - if (direction == STREAM_TOSERVER) - hookname = "request-complete"; - else - hookname = "response-complete"; - } - if (hookname == NULL) - return 0; - } - char *nname = SCStrdup(hookname); - if (nname == NULL) + const char *app_proto_str = AppProtoToStringRaw(app_proto); + if (app_proto_str == NULL) { + SCLogError("Unknown app proto %u", (unsigned)app_proto); return -1; - for (int i = 0; nname[i] != '\0'; i++) { - if (nname[i] == '_') - nname[i] = '-'; } - const char *app_name = AppProtoToString(app_proto); - int r = snprintf(policy_name, sizeof(policy_name), "%s.%s.%s", prefix, app_name, nname); - SCFree(nname); - if (r < 0 || (size_t)r >= sizeof(policy_name)) { - FatalError("internal error: failed to assemble firewall policy config string"); + /* A state can lack both a parser state name and a generic alias, e.g. pgsql's + * intermediate state. Such a hook cannot be addressed directly in config, but + * it still inherits the default-policy tiers below, and it still needs an + * entry so the policy table stays fully populated for every hook. */ + const char *generic_hook = FirewallAppGenericHookName(state, complete_state, direction); + char hook[FW_POLICY_NAME_MAX] = ""; + if (hookname != NULL) { + FirewallHookNameConvertUnderscoreToDash(hookname, hook, sizeof(hook)); + } + + char scope[FW_POLICY_PATH_MAX]; + if (sub_state_name != NULL) { + char sub[FW_POLICY_NAME_MAX]; + FirewallHookNameConvertUnderscoreToDash(sub_state_name, sub, sizeof(sub)); + FirewallPolicyPath(scope, sizeof(scope), "%s.app.%s.%s", prefix, app_proto_str, sub); + } else { + FirewallPolicyPath(scope, sizeof(scope), "%s.app.%s", prefix, app_proto_str); + } + + FirewallPolicyChain chain = { .len = 0 }; + if (hookname != NULL) { + /* .app.[.]. */ + FirewallPolicyChainAdd(&chain, "%s.%s", scope, hook); } + if (generic_hook != NULL) { + /* .app.[.]. */ + FirewallPolicyChainAdd(&chain, "%s.%s", scope, generic_hook); + } + /* .app.[.].default-policy */ + FirewallPolicyChainAdd(&chain, "%s.default-policy", scope); + /* .app..default-policy */ + FirewallPolicyChainAdd(&chain, "%s.app.%s.default-policy", prefix, app_proto_str); + /* .app.default-policy */ + FirewallPolicyChainAdd(&chain, "%s.app.default-policy", prefix); + /* .default-policy */ + FirewallPolicyChainAdd(&chain, "%s.default-policy", prefix); struct DetectFirewallAppPolicy *app_pol = SCCalloc(1, sizeof(*app_pol)); if (app_pol == NULL) return -1; app_pol->alproto = app_proto; - app_pol->sub_state = 0; + app_pol->sub_state = sub_state; app_pol->progress = state; app_pol->direction = (uint8_t)direction; - /* init to drop:flow by default, will be overwritten by DoParsePolicy if there - * is a config for this hook. */ app_pol->policy.action = ACTION_DROP; app_pol->policy.action_scope = ACTION_SCOPE_FLOW; - r = DoParsePolicy(policy_name, &app_pol->policy); - if (r == 0 && in_name != NULL) { - if (state == 0) { - if (direction == STREAM_TOSERVER) - hookname = "request-started"; - else - hookname = "response-started"; - } else if (state == complete_state) { - if (direction == STREAM_TOSERVER) - hookname = "request-complete"; - else - hookname = "response-complete"; - } - if (hookname == NULL) - return 0; - r = snprintf(policy_name, sizeof(policy_name), "%s.%s.%s", prefix, app_name, hookname); - if (r < 0 || (size_t)r >= sizeof(policy_name)) { - FatalError("internal error: failed to assemble firewall policy config string"); - } - - r = DoParsePolicy(policy_name, &app_pol->policy); - } + int r = ResolveFirewallPolicy(&app_pol->policy, DETECT_FIREWALL_POLICY_CLASS_APP, &chain); if (r < 0) { SCFree(app_pol); return -1; @@ -4333,10 +4461,52 @@ int DetectFirewallInitDefaultPolicies(DetectEngineCtx *de_ctx) return 0; } +/** + * \brief Resolve and store one packet-hook default policy. + */ +static int DetectFirewallLoadPacketPolicy(struct DetectFirewallPolicies *fw_policies, + const char *prefix, enum DetectFirewallPacketPolicies id, const char *leaf) +{ + /* inheritance tiers, most specific first */ + FirewallPolicyChain chain = { .len = 0 }; + /* .packet. */ + FirewallPolicyChainAdd(&chain, "%s.packet.%s", prefix, leaf); + /* .packet.default-policy */ + FirewallPolicyChainAdd(&chain, "%s.packet.default-policy", prefix); + /* .default-policy */ + FirewallPolicyChainAdd(&chain, "%s.default-policy", prefix); + + struct DetectFirewallPolicy *pol = &fw_policies->pkt[id]; // built-in default + int r = ResolveFirewallPolicy(pol, DETECT_FIREWALL_POLICY_CLASS_PACKET, &chain); + if (r < 0) { + return -1; + } + if (r == 1 && (pol->action & ACTION_ALERT)) { + return AddPktPolicySignature(fw_policies, pol, id); + } + return 0; +} + +/** + * \brief Load the packet-hook default policies. + */ +static int DetectFirewallLoadPacketPolicies( + struct DetectFirewallPolicies *fw_policies, const char *prefix) +{ + if (DetectFirewallLoadPacketPolicy( + fw_policies, prefix, DETECT_FIREWALL_POLICY_PACKET_FILTER, "filter") < 0) + return -1; + if (DetectFirewallLoadPacketPolicy( + fw_policies, prefix, DETECT_FIREWALL_POLICY_PRE_FLOW, "pre-flow") < 0) + return -1; + if (DetectFirewallLoadPacketPolicy( + fw_policies, prefix, DETECT_FIREWALL_POLICY_PRE_STREAM, "pre-stream") < 0) + return -1; + return 0; +} + int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *de_ctx) { - int r; - char policy_name[256]; char prefix[96] = "firewall.policies"; if (strlen(de_ctx->config_prefix) > 0) { snprintf(prefix, sizeof(prefix), "%s.firewall.policies", de_ctx->config_prefix); @@ -4346,42 +4516,8 @@ int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *de_ctx) if (fw_policies == NULL) return -1; - r = snprintf(policy_name, sizeof(policy_name), "%s.packet-filter", prefix); - if (r < 0 || (size_t)r >= sizeof(policy_name)) { - FatalError("internal error: failed to assemble firewall policy config string"); - } - r = DoParsePolicy(policy_name, &fw_policies->pkt[DETECT_FIREWALL_POLICY_PACKET_FILTER]); - if (r < 0) - return -1; - if (fw_policies->pkt[DETECT_FIREWALL_POLICY_PACKET_FILTER].action & ACTION_ALERT) - if (AddPktPolicySignature(fw_policies, - &fw_policies->pkt[DETECT_FIREWALL_POLICY_PACKET_FILTER], - DETECT_FIREWALL_POLICY_PACKET_FILTER) < 0) - return -1; - - r = snprintf(policy_name, sizeof(policy_name), "%s.packet-pre-flow", prefix); - if (r < 0 || (size_t)r >= sizeof(policy_name)) { - FatalError("internal error: failed to assemble firewall policy config string"); - } - r = DoParsePolicy(policy_name, &fw_policies->pkt[DETECT_FIREWALL_POLICY_PRE_FLOW]); - if (r < 0) - return -1; - if (fw_policies->pkt[DETECT_FIREWALL_POLICY_PRE_FLOW].action & ACTION_ALERT) - if (AddPktPolicySignature(fw_policies, &fw_policies->pkt[DETECT_FIREWALL_POLICY_PRE_FLOW], - DETECT_FIREWALL_POLICY_PRE_FLOW) < 0) - return -1; - - r = snprintf(policy_name, sizeof(policy_name), "%s.packet-pre-stream", prefix); - if (r < 0 || (size_t)r >= sizeof(policy_name)) { - FatalError("internal error: failed to assemble firewall policy config string"); - } - r = DoParsePolicy(policy_name, &fw_policies->pkt[DETECT_FIREWALL_POLICY_PRE_STREAM]); - if (r < 0) + if (DetectFirewallLoadPacketPolicies(fw_policies, prefix) < 0) return -1; - if (fw_policies->pkt[DETECT_FIREWALL_POLICY_PRE_STREAM].action & ACTION_ALERT) - if (AddPktPolicySignature(fw_policies, &fw_policies->pkt[DETECT_FIREWALL_POLICY_PRE_STREAM], - DETECT_FIREWALL_POLICY_PRE_STREAM) < 0) - return -1; for (AppProto a = 0; a < g_alproto_max; a++) { if (!AppProtoIsValid(a)) @@ -4409,8 +4545,8 @@ int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *de_ctx) BUG_ON(state_name == NULL); SCLogDebug("protocol %s: sub state:%s state:%s", AppProtoToString(a), sub_state_name, state_name); - if (DoParseAppSubStatePolicy(prefix, a, s, sub_state_name, state, state_name, - max_state, STREAM_TOSERVER, fw_policies) < 0) + if (DoParseAppPolicy(prefix, a, s, sub_state_name, state_name, state, max_state, + STREAM_TOSERVER, fw_policies) < 0) return -1; } /* to_client */ @@ -4422,8 +4558,8 @@ int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *de_ctx) BUG_ON(state_name == NULL); SCLogDebug("protocol %s: to_client: sub state:%s state:%s", AppProtoToString(a), sub_state_name, state_name); - if (DoParseAppSubStatePolicy(prefix, a, s, sub_state_name, state, state_name, - max_state, STREAM_TOCLIENT, fw_policies) < 0) + if (DoParseAppPolicy(prefix, a, s, sub_state_name, state_name, state, max_state, + STREAM_TOCLIENT, fw_policies) < 0) return -1; } } @@ -4434,8 +4570,8 @@ int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *de_ctx) for (uint8_t state = 0; state <= complete_state_ts; state++) { const char *name = AppLayerParserGetStateNameById(IPPROTO_TCP, a, state, STREAM_TOSERVER); - if (DoParseAppPolicy(prefix, a, name, state, complete_state_ts, STREAM_TOSERVER, - fw_policies) < 0) + if (DoParseAppPolicy(prefix, a, 0, NULL, name, state, complete_state_ts, + STREAM_TOSERVER, fw_policies) < 0) return -1; } const uint8_t complete_state_tc = @@ -4444,8 +4580,8 @@ int DetectFirewallLoadDefaultPolicies(DetectEngineCtx *de_ctx) for (uint8_t state = 0; state <= complete_state_tc; state++) { const char *name = AppLayerParserGetStateNameById(IPPROTO_TCP, a, state, STREAM_TOCLIENT); - if (DoParseAppPolicy(prefix, a, name, state, complete_state_tc, STREAM_TOCLIENT, - fw_policies) < 0) + if (DoParseAppPolicy(prefix, a, 0, NULL, name, state, complete_state_tc, + STREAM_TOCLIENT, fw_policies) < 0) return -1; } } diff --git a/src/detect.h b/src/detect.h index 18675d367160..6cd7c8f91d4a 100644 --- a/src/detect.h +++ b/src/detect.h @@ -921,6 +921,11 @@ enum DetectEngineType DETECT_ENGINE_TYPE_TENANT = 3, }; +enum DetectFirewallPolicyClass { + DETECT_FIREWALL_POLICY_CLASS_PACKET, + DETECT_FIREWALL_POLICY_CLASS_APP +}; + enum DetectFirewallPacketPolicies { DETECT_FIREWALL_POLICY_PACKET_FILTER, DETECT_FIREWALL_POLICY_PRE_FLOW, diff --git a/src/util-running-modes.c b/src/util-running-modes.c index 668f7d1ac6a4..5aaf6bf09df6 100644 --- a/src/util-running-modes.c +++ b/src/util-running-modes.c @@ -113,9 +113,7 @@ int ListAppLayerHooks(const char *conf_filename) } } } else { - const char *alproto_name = AppProtoToString(a); - if (strcmp(alproto_name, "http") == 0) - alproto_name = "http1"; + const char *alproto_name = AppProtoToStringRaw(a); SCLogDebug("alproto %u/%s", a, alproto_name); const int max_progress_ts = @@ -164,9 +162,7 @@ int ListAppLayerFrames(const char *conf_filename) if (alprotos[a] != 1) continue; - const char *alproto_name = AppProtoToString(a); - if (strcmp(alproto_name, "http") == 0) - alproto_name = "http1"; + const char *alproto_name = AppProtoToStringRaw(a); SCLogDebug("alproto %u/%s", a, alproto_name); bool tcp_stream_once = false; diff --git a/suricata.yaml.in b/suricata.yaml.in index 2e417d94b7cc..c4a4983bd6ba 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -2392,16 +2392,20 @@ firewall: # Default policies # - # Choose a default policy for each firewall hook. - # It is also possible to specify policies by app-layer protocol. + # Choose a default policy for each firewall hook. A `default-policy` covers + # every hook below it, so hooks that are not listed still get a policy. + # The most specific setting wins. # DNS example: Drop and alert on all DNS requests that are not allowed in firewall.rules, accept all responses. # #policies: - # packet-filter: ["drop:packet"] - # dns: - # request-started: ["accept:hook"] - # request-complete: ["drop:flow", "alert"] - # response-started: ["accept:tx"] + # default-policy: ["drop:flow"] + # packet: + # filter: ["drop:packet"] + # app: + # dns: + # request-started: ["accept:hook"] + # request-complete: ["drop:flow", "alert"] + # response-started: ["accept:tx"] ## ## Include other configs