From 851a05d1622a67592ab2eff0cae25b148090ea41 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Thu, 3 Oct 2024 12:00:21 +0100 Subject: [PATCH] Negated scalar condition for matchers (#335) --- .clang-tidy | 2 +- src/condition/scalar_condition.cpp | 108 ++++++-- src/condition/scalar_condition.hpp | 57 +++- src/matcher/base.hpp | 20 +- src/matcher/equals.hpp | 65 +++-- src/matcher/exact_match.hpp | 9 +- src/matcher/greater_than.hpp | 62 +++-- src/matcher/ip_match.hpp | 9 +- src/matcher/is_sqli.hpp | 9 +- src/matcher/is_xss.hpp | 9 +- src/matcher/lower_than.hpp | 61 +++-- src/matcher/phrase_match.hpp | 10 +- src/matcher/regex_match.hpp | 9 +- src/parser/expression_parser.cpp | 47 +++- src/parser/matcher_parser.cpp | 253 +++++++++++------- src/parser/matcher_parser.hpp | 53 ++++ src/parser/parser.hpp | 4 +- src/parser/scanner_parser.cpp | 3 +- tests/condition/scalar_condition_test.cpp | 193 +++++++++++++ .../scalar_negated_condition_test.cpp | 204 ++++++++++++++ tests/expression_test.cpp | 39 ++- tests/matcher/equals_test.cpp | 81 +++++- tests/matcher/greater_than_test.cpp | 58 ++++ tests/matcher/lower_than_test.cpp | 57 ++++ tests/parser_v2_rules_test.cpp | 45 ++++ tests/test_utils.hpp | 25 +- validator/runner.cpp | 12 +- .../002_rule1_string_equals_no_match.yaml | 11 + ...equals.yaml => 003_rule2_bool_equals.yaml} | 0 .../004_rule2_bool_equals_no_match.yaml | 11 + ...uals.yaml => 005_rule3_signed_equals.yaml} | 0 .../006_rule3_signed_equals_no_match.yaml | 11 + ...ls.yaml => 007_rule4_unsigned_equals.yaml} | 0 .../008_rule4_unsigned_equals_no_match.yaml | 11 + ...uals.yaml => 009_rule5_double_equals.yaml} | 0 .../010_rule5_double_equals_no_match.yaml | 11 + .../equals/011_rule6_string_not_equals.yaml | 21 ++ .../012_rule6_string_not_equals_no_match.yaml | 11 + .../equals/013_rule7_bool_not_equals.yaml | 21 ++ .../014_rule7_bool_not_equals_no_match.yaml | 11 + .../tests/rules/operators/equals/ruleset.yaml | 60 +++++ .../003_rule2_negated_exact_match.yaml | 21 ++ ...04_rule2_negated_exact_match_no_match.yaml | 11 + .../rules/operators/exact_match/ruleset.yaml | 16 +- .../tests/rules/operators/exists/ruleset.yaml | 4 +- .../ip_match/009-rule3_ipv4_cidr_match.yaml | 21 ++ .../ip_match/010_rule3_ipv6_cidr_match.yaml | 21 ++ .../011_rule3_ipv4_cidr_no_match.yaml | 11 + .../012_rule3_ipv6_cidr_no_match.yaml | 11 + .../rules/operators/ip_match/ruleset.yaml | 13 + .../match_regex/001_rule1_match_regex.yaml | 21 ++ .../002_rule1_match_regex_no_match.yaml | 11 + .../003_rule2_negated_match_regex.yaml | 21 ++ ...04_rule2_negated_match_regex_no_match.yaml | 11 + .../rules/operators/match_regex/ruleset.yaml | 30 +++ .../phrase_match/005_rule3_pm_match.yaml | 21 ++ .../phrase_match/006_rule3_pm_no_match.yaml | 11 + .../rules/operators/phrase_match/ruleset.yaml | 13 + 58 files changed, 1713 insertions(+), 238 deletions(-) create mode 100644 src/parser/matcher_parser.hpp create mode 100644 tests/condition/scalar_condition_test.cpp create mode 100644 tests/condition/scalar_negated_condition_test.cpp create mode 100644 validator/tests/rules/operators/equals/002_rule1_string_equals_no_match.yaml rename validator/tests/rules/operators/equals/{002_rule2_bool_equals.yaml => 003_rule2_bool_equals.yaml} (100%) create mode 100644 validator/tests/rules/operators/equals/004_rule2_bool_equals_no_match.yaml rename validator/tests/rules/operators/equals/{003_rule3_signed_equals.yaml => 005_rule3_signed_equals.yaml} (100%) create mode 100644 validator/tests/rules/operators/equals/006_rule3_signed_equals_no_match.yaml rename validator/tests/rules/operators/equals/{004_rule4_unsigned_equals.yaml => 007_rule4_unsigned_equals.yaml} (100%) create mode 100644 validator/tests/rules/operators/equals/008_rule4_unsigned_equals_no_match.yaml rename validator/tests/rules/operators/equals/{005_rule5_double_equals.yaml => 009_rule5_double_equals.yaml} (100%) create mode 100644 validator/tests/rules/operators/equals/010_rule5_double_equals_no_match.yaml create mode 100644 validator/tests/rules/operators/equals/011_rule6_string_not_equals.yaml create mode 100644 validator/tests/rules/operators/equals/012_rule6_string_not_equals_no_match.yaml create mode 100644 validator/tests/rules/operators/equals/013_rule7_bool_not_equals.yaml create mode 100644 validator/tests/rules/operators/equals/014_rule7_bool_not_equals_no_match.yaml create mode 100644 validator/tests/rules/operators/exact_match/003_rule2_negated_exact_match.yaml create mode 100644 validator/tests/rules/operators/exact_match/004_rule2_negated_exact_match_no_match.yaml create mode 100644 validator/tests/rules/operators/ip_match/009-rule3_ipv4_cidr_match.yaml create mode 100644 validator/tests/rules/operators/ip_match/010_rule3_ipv6_cidr_match.yaml create mode 100644 validator/tests/rules/operators/ip_match/011_rule3_ipv4_cidr_no_match.yaml create mode 100644 validator/tests/rules/operators/ip_match/012_rule3_ipv6_cidr_no_match.yaml create mode 100644 validator/tests/rules/operators/match_regex/001_rule1_match_regex.yaml create mode 100644 validator/tests/rules/operators/match_regex/002_rule1_match_regex_no_match.yaml create mode 100644 validator/tests/rules/operators/match_regex/003_rule2_negated_match_regex.yaml create mode 100644 validator/tests/rules/operators/match_regex/004_rule2_negated_match_regex_no_match.yaml create mode 100644 validator/tests/rules/operators/match_regex/ruleset.yaml create mode 100644 validator/tests/rules/operators/phrase_match/005_rule3_pm_match.yaml create mode 100644 validator/tests/rules/operators/phrase_match/006_rule3_pm_no_match.yaml diff --git a/.clang-tidy b/.clang-tidy index 2b29de6f4..7216dcae0 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,7 +1,7 @@ --- # readability-function-cognitive-complexity temporarily disabled until clang-tidy is fixed # right now emalloc causes it to misbehave -Checks: '*,misc-const-correctness,-bugprone-reserved-identifier,-hicpp-signed-bitwise,-llvmlibc-restrict-system-libc-headers,-altera-unroll-loops,-hicpp-named-parameter,-cert-dcl37-c,-cert-dcl51-cpp,-read,-cppcoreguidelines-init-variables,-cppcoreguidelines-avoid-non-const-global-variables,-altera-id-dependent-backward-branch,-performance-no-int-to-ptr,-altera-struct-pack-align,-google-readability-casting,-modernize-use-trailing-return-type,-llvmlibc-implementation-in-namespace,-llvmlibc-callee-namespace,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-fuchsia-default-arguments-declarations,-fuchsia-overloaded-operator,-cppcoreguidelines-pro-type-union-access,-fuchsia-default-arguments-calls,-cppcoreguidelines-non-private-member-variables-in-classes,-misc-non-private-member-variables-in-classes,-google-readability-todo,-llvm-header-guard,-readability-function-cognitive-complexity,-readability-identifier-length,-cppcoreguidelines-owning-memory,-cert-err58-cpp,-fuchsia-statically-constructed-objects,-google-build-using-namespace,-hicpp-avoid-goto,-cppcoreguidelines-avoid-goto,-hicpp-no-array-decay,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cppcoreguidelines-pro-bounds-constant-array-index,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-abseil-string-find-str-contains,-bugprone-unchecked-optional-access,-readability-use-anyofallof,-modernize-loop-convert,-cppcoreguidelines-avoid-c-arrays,-hicpp-avoid-c-arrays,-cppcoreguidelines-no-malloc' +Checks: '*,misc-const-correctness,-bugprone-reserved-identifier,-hicpp-signed-bitwise,-llvmlibc-restrict-system-libc-headers,-altera-unroll-loops,-hicpp-named-parameter,-cert-dcl37-c,-cert-dcl51-cpp,-read,-cppcoreguidelines-init-variables,-cppcoreguidelines-avoid-non-const-global-variables,-altera-id-dependent-backward-branch,-performance-no-int-to-ptr,-altera-struct-pack-align,-google-readability-casting,-modernize-use-trailing-return-type,-llvmlibc-implementation-in-namespace,-llvmlibc-callee-namespace,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-fuchsia-default-arguments-declarations,-fuchsia-overloaded-operator,-cppcoreguidelines-pro-type-union-access,-fuchsia-default-arguments-calls,-cppcoreguidelines-non-private-member-variables-in-classes,-misc-non-private-member-variables-in-classes,-google-readability-todo,-llvm-header-guard,-readability-function-cognitive-complexity,-readability-identifier-length,-cppcoreguidelines-owning-memory,-cert-err58-cpp,-fuchsia-statically-constructed-objects,-google-build-using-namespace,-hicpp-avoid-goto,-cppcoreguidelines-avoid-goto,-hicpp-no-array-decay,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cppcoreguidelines-pro-bounds-constant-array-index,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-abseil-string-find-str-contains,-bugprone-unchecked-optional-access,-readability-use-anyofallof,-modernize-loop-convert,-cppcoreguidelines-avoid-c-arrays,-hicpp-avoid-c-arrays,-cppcoreguidelines-no-malloc,-llvmlibc-inline-function-decl' WarningsAsErrors: '*' HeaderFilterRegex: '' CheckOptions: diff --git a/src/condition/scalar_condition.cpp b/src/condition/scalar_condition.cpp index 4e2a74417..940f4835c 100644 --- a/src/condition/scalar_condition.cpp +++ b/src/condition/scalar_condition.cpp @@ -31,10 +31,12 @@ namespace ddwaf { namespace { -template -std::optional eval_object(Iterator &it, std::string_view address, bool ephemeral, +template +ResultType eval_object(Iterator &it, std::string_view address, bool ephemeral, const matcher::base &matcher, const std::span &transformers, const object_limits &limits) + requires(std::is_same_v || + std::is_same_v>) { // The iterator is guaranteed to be valid at this point, which means the // object pointer should not be nullptr @@ -60,8 +62,12 @@ std::optional eval_object(Iterator &it, std::string_view addres DDWAF_TRACE("Target {} matched parameter value {}", address, highlight); - return {{{{"input"sv, object_to_string(dst), address, it.get_current_path()}}, - {std::move(highlight)}, matcher.name(), matcher.to_string(), ephemeral}}; + if constexpr (std::is_same_v) { + return true; + } else { + return {{{{"input"sv, object_to_string(dst), address, it.get_current_path()}}, + {std::move(highlight)}, matcher.name(), matcher.to_string(), ephemeral}}; + } } } } @@ -73,26 +79,32 @@ std::optional eval_object(Iterator &it, std::string_view addres DDWAF_TRACE("Target {} matched parameter value {}", address, highlight); - return {{{{"input"sv, object_to_string(src), address, it.get_current_path()}}, - {std::move(highlight)}, matcher.name(), matcher.to_string(), ephemeral}}; + if constexpr (std::is_same_v) { + return true; + } else { + return {{{{"input"sv, object_to_string(src), address, it.get_current_path()}}, + {std::move(highlight)}, matcher.name(), matcher.to_string(), ephemeral}}; + } } -template -std::optional eval_target(Iterator &it, std::string_view address, bool ephemeral, +template +ResultType eval_target(Iterator &it, std::string_view address, bool ephemeral, const matcher::base &matcher, const std::span &transformers, const object_limits &limits, ddwaf::timer &deadline) + requires(std::is_same_v || + std::is_same_v>) { for (; it; ++it) { if (deadline.expired()) { throw ddwaf::timeout_exception(); } - if (it.type() != matcher.supported_type()) { + if (!matcher.is_supported_type(it.type())) { continue; } - auto match = eval_object(it, address, ephemeral, matcher, transformers, limits); - if (match.has_value()) { + auto match = eval_object(it, address, ephemeral, matcher, transformers, limits); + if (match) { // If this target matched, we can stop processing return match; } @@ -101,16 +113,15 @@ std::optional eval_target(Iterator &it, std::string_view addres return {}; } -} // namespace - -const matcher::base *scalar_condition::get_matcher( - const std::unordered_map> &dynamic_matchers) const +const matcher::base *get_matcher(const std::unique_ptr &matcher, + const std::string &data_id, + const std::unordered_map> &dynamic_matchers) { - if (matcher_ || data_id_.empty()) { - return matcher_.get(); + if (matcher || data_id.empty()) { + return matcher.get(); } - auto it = dynamic_matchers.find(data_id_); + auto it = dynamic_matchers.find(data_id); if (it != dynamic_matchers.end()) { return it->second.get(); } @@ -118,12 +129,14 @@ const matcher::base *scalar_condition::get_matcher( return nullptr; } +} // namespace + eval_result scalar_condition::eval(condition_cache &cache, const object_store &store, const exclusion::object_set_ref &objects_excluded, const std::unordered_map> &dynamic_matchers, ddwaf::timer &deadline) const { - const auto *matcher = get_matcher(dynamic_matchers); + const auto *matcher = get_matcher(matcher_, data_id_, dynamic_matchers); if (matcher == nullptr) { return {}; } @@ -152,21 +165,70 @@ eval_result scalar_condition::eval(condition_cache &cache, const object_store &s // TODO: iterators could be cached to avoid reinitialisation if (target.source == data_source::keys) { object::key_iterator it(object, target.key_path, objects_excluded, limits_); - match = eval_target( + match = eval_target>( it, target.name, ephemeral, *matcher, target.transformers, limits_, deadline); } else { object::value_iterator it(object, target.key_path, objects_excluded, limits_); - match = eval_target( + match = eval_target>( it, target.name, ephemeral, *matcher, target.transformers, limits_, deadline); } if (match.has_value()) { cache.match = std::move(match); - return {true, ephemeral}; + return {.outcome = true, .ephemeral = ephemeral}; } } - return {false, false}; + return {.outcome = false, .ephemeral = false}; +} + +eval_result scalar_negated_condition::eval(condition_cache &cache, const object_store &store, + const exclusion::object_set_ref &objects_excluded, + const std::unordered_map> &dynamic_matchers, + ddwaf::timer &deadline) const +{ + if (deadline.expired()) { + throw ddwaf::timeout_exception(); + } + + const auto *matcher = get_matcher(matcher_, data_id_, dynamic_matchers); + if (matcher == nullptr) { + return {}; + } + + if (cache.targets.size() != 1) { + cache.targets.assign(1, nullptr); + } + + auto [object, attr] = store.get_target(target_.index); + if (object == nullptr || object == cache.targets[0]) { + return {}; + } + + const bool ephemeral = (attr == object_store::attribute::ephemeral); + if (!ephemeral) { + cache.targets[0] = object; + } + + bool match = false; + if (target_.source == data_source::keys) { + object::key_iterator it(object, target_.key_path, objects_excluded, limits_); + match = eval_target( + it, target_.name, ephemeral, *matcher, target_.transformers, limits_, deadline); + } else { + object::value_iterator it(object, target_.key_path, objects_excluded, limits_); + match = eval_target( + it, target_.name, ephemeral, *matcher, target_.transformers, limits_, deadline); + } + + if (!match) { + cache.match = {{{{"input"sv, object_to_string(*object), target_.name, + {target_.key_path.begin(), target_.key_path.end()}}}, + {}, matcher->negated_name(), matcher->to_string(), ephemeral}}; + return {.outcome = true, .ephemeral = ephemeral}; + } + + return {.outcome = false, .ephemeral = false}; } } // namespace ddwaf diff --git a/src/condition/scalar_condition.hpp b/src/condition/scalar_condition.hpp index 6d7c9843f..390aacbed 100644 --- a/src/condition/scalar_condition.hpp +++ b/src/condition/scalar_condition.hpp @@ -17,11 +17,11 @@ class scalar_condition : public base_condition { : matcher_(std::move(matcher)), data_id_(std::move(data_id)), limits_(limits) { if (args.size() > 1) { - throw std::invalid_argument("Matcher initialised with more than one argument"); + throw std::invalid_argument("matcher initialised with more than one argument"); } if (args.empty()) { - throw std::invalid_argument("Matcher initialised without arguments"); + throw std::invalid_argument("matcher initialised without arguments"); } targets_ = std::move(args[0].targets); @@ -39,18 +39,59 @@ class scalar_condition : public base_condition { static constexpr auto arguments() { - return std::array{{{"inputs", true, false}}}; + return std::array{ + {{.name = "inputs", .variadic = true, .optional = false}}}; } protected: - [[nodiscard]] const matcher::base *get_matcher( - const std::unordered_map> &dynamic_matchers) - const; - std::unique_ptr matcher_; std::string data_id_; std::vector targets_; - const object_limits limits_; + object_limits limits_; +}; + +class scalar_negated_condition : public base_condition { +public: + scalar_negated_condition(std::unique_ptr &&matcher, std::string data_id, + std::vector args, const object_limits &limits = {}) + : matcher_(std::move(matcher)), data_id_(std::move(data_id)), limits_(limits) + { + if (args.size() > 1) { + throw std::invalid_argument("matcher initialised with more than one argument"); + } + + if (args.empty()) { + throw std::invalid_argument("matcher initialised without arguments"); + } + + if (args[0].targets.size() > 1) { + throw std::invalid_argument("negated matchers don't support variadic arguments"); + } + + target_ = std::move(args[0].targets[0]); + } + + eval_result eval(condition_cache &cache, const object_store &store, + const exclusion::object_set_ref &objects_excluded, + const std::unordered_map> &dynamic_matchers, + ddwaf::timer &deadline) const override; + + void get_addresses(std::unordered_map &addresses) const override + { + addresses.emplace(target_.index, target_.name); + } + + static constexpr auto arguments() + { + return std::array{ + {{.name = "inputs", .variadic = false, .optional = false}}}; + } + +protected: + std::unique_ptr matcher_; + std::string data_id_; + condition_target target_; + object_limits limits_; }; } // namespace ddwaf diff --git a/src/matcher/base.hpp b/src/matcher/base.hpp index 38f7e2f4e..7c1a439f8 100644 --- a/src/matcher/base.hpp +++ b/src/matcher/base.hpp @@ -28,12 +28,13 @@ class base { // for example, through a constexpr class static string_view initialised // with a literal. [[nodiscard]] virtual std::string_view name() const = 0; + [[nodiscard]] virtual std::string_view negated_name() const = 0; // Returns a string representing this particular instance of the operator, for example, // an operator matching regexes could provide the regex as its string representation. [[nodiscard]] virtual std::string_view to_string() const = 0; // Scalar matcher methods - [[nodiscard]] virtual DDWAF_OBJ_TYPE supported_type() const = 0; + [[nodiscard]] virtual bool is_supported_type(DDWAF_OBJ_TYPE type) const = 0; [[nodiscard]] virtual std::pair match(const ddwaf_object &obj) const = 0; }; @@ -47,16 +48,17 @@ template class base_impl : public base { base_impl &operator=(const base_impl &) = default; base_impl &operator=(base_impl &&) noexcept = default; - [[nodiscard]] std::string_view name() const override { return T::name_impl(); } + [[nodiscard]] std::string_view name() const override { return T::matcher_name; } + [[nodiscard]] std::string_view negated_name() const override { return T::negated_matcher_name; } [[nodiscard]] std::string_view to_string() const override { return static_cast(this)->to_string_impl(); } - [[nodiscard]] DDWAF_OBJ_TYPE supported_type() const override + [[nodiscard]] bool is_supported_type(DDWAF_OBJ_TYPE type) const override { - return T::supported_type_impl(); + return T::is_supported_type_impl(type); } // Helper used for testing purposes @@ -68,31 +70,31 @@ template class base_impl : public base { [[nodiscard]] std::pair match(const ddwaf_object &obj) const override { const auto *ptr = static_cast(this); - if constexpr (T::supported_type_impl() == DDWAF_OBJ_STRING) { + if constexpr (T::is_supported_type_impl(DDWAF_OBJ_STRING)) { if (obj.type == DDWAF_OBJ_STRING && obj.stringValue != nullptr) { return ptr->match_impl({obj.stringValue, static_cast(obj.nbEntries)}); } } - if constexpr (T::supported_type_impl() == DDWAF_OBJ_SIGNED) { + if constexpr (T::is_supported_type_impl(DDWAF_OBJ_SIGNED)) { if (obj.type == DDWAF_OBJ_SIGNED) { return ptr->match_impl(obj.intValue); } } - if constexpr (T::supported_type_impl() == DDWAF_OBJ_UNSIGNED) { + if constexpr (T::is_supported_type_impl(DDWAF_OBJ_UNSIGNED)) { if (obj.type == DDWAF_OBJ_UNSIGNED) { return ptr->match_impl(obj.uintValue); } } - if constexpr (T::supported_type_impl() == DDWAF_OBJ_BOOL) { + if constexpr (T::is_supported_type_impl(DDWAF_OBJ_BOOL)) { if (obj.type == DDWAF_OBJ_BOOL) { return ptr->match_impl(obj.boolean); } } - if constexpr (T::supported_type_impl() == DDWAF_OBJ_FLOAT) { + if constexpr (T::is_supported_type_impl(DDWAF_OBJ_FLOAT)) { if (obj.type == DDWAF_OBJ_FLOAT) { return ptr->match_impl(obj.f64); } diff --git a/src/matcher/equals.hpp b/src/matcher/equals.hpp index 79597cc38..25209869f 100644 --- a/src/matcher/equals.hpp +++ b/src/matcher/equals.hpp @@ -8,15 +8,17 @@ #include #include -#include +#include #include "matcher/base.hpp" -#include "utils.hpp" namespace ddwaf::matcher { -template class equals : public base_impl> { +template class equals : public base_impl> { public: + static constexpr std::string_view matcher_name = "equals"; + static constexpr std::string_view negated_matcher_name = "!equals"; + explicit equals(T expected) requires(!std::is_floating_point_v) : expected_(std::move(expected)) @@ -29,28 +31,30 @@ template class equals : public base_impl> { protected: static constexpr std::string_view to_string_impl() { return ""; } - static constexpr std::string_view name_impl() { return "equals"; } - - static constexpr DDWAF_OBJ_TYPE supported_type_impl() + static constexpr bool is_supported_type_impl(DDWAF_OBJ_TYPE type) { - if constexpr (std::is_same_v) { - return DDWAF_OBJ_SIGNED; - } - if constexpr (std::is_same_v) { - return DDWAF_OBJ_UNSIGNED; + if constexpr (std::is_same_v || std::is_same_v) { + return type == DDWAF_OBJ_SIGNED || type == DDWAF_OBJ_UNSIGNED; } if constexpr (std::is_same_v) { - return DDWAF_OBJ_BOOL; + return type == DDWAF_OBJ_BOOL; } if constexpr (std::is_same_v) { - return DDWAF_OBJ_STRING; + return type == DDWAF_OBJ_STRING; } } - [[nodiscard]] std::pair match_impl(const T &obtained) const - requires(!std::is_same_v) + template + [[nodiscard]] std::pair match_impl(const U &obtained) const + requires(std::is_same_v || std::is_same_v) && std::is_integral_v + { + return {std::cmp_equal(expected_, obtained), {}}; + } + + [[nodiscard]] std::pair match_impl(bool obtained) const + requires std::is_same_v { return {expected_ == obtained, {}}; } @@ -68,6 +72,9 @@ template class equals : public base_impl> { template <> class equals : public base_impl> { public: + static constexpr std::string_view matcher_name = "equals"; + static constexpr std::string_view negated_matcher_name = "!equals"; + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) equals(double expected, double delta) : expected_(expected), delta_(delta) {} ~equals() override = default; @@ -78,8 +85,10 @@ template <> class equals : public base_impl> { protected: static constexpr std::string_view to_string_impl() { return ""; } - static constexpr std::string_view name_impl() { return "equals"; } - static constexpr DDWAF_OBJ_TYPE supported_type_impl() { return DDWAF_OBJ_FLOAT; } + static constexpr bool is_supported_type_impl(DDWAF_OBJ_TYPE type) + { + return type == DDWAF_OBJ_FLOAT; + } [[nodiscard]] std::pair match_impl(double obtained) const { @@ -92,4 +101,26 @@ template <> class equals : public base_impl> { friend class base_impl>; }; +template <> class equals : public base_impl> { +public: + static constexpr std::string_view matcher_name = "equals"; + static constexpr std::string_view negated_matcher_name = "!equals"; + + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + ~equals() override = default; + +protected: + equals() = default; + equals(const equals &) = default; + equals(equals &&) noexcept = default; + equals &operator=(const equals &) = default; + equals &operator=(equals &&) noexcept = default; + + static constexpr std::string_view to_string_impl() { return ""; } + static constexpr bool is_supported_type_impl(DDWAF_OBJ_TYPE /*type*/) { return false; } + [[nodiscard]] static std::pair match_impl() { return {}; } + + friend class base_impl>; +}; + } // namespace ddwaf::matcher diff --git a/src/matcher/exact_match.hpp b/src/matcher/exact_match.hpp index 469357ef9..6a5f1ffe8 100644 --- a/src/matcher/exact_match.hpp +++ b/src/matcher/exact_match.hpp @@ -18,6 +18,9 @@ class exact_match : public base_impl { public: using data_type = std::vector>; + static constexpr std::string_view matcher_name = "exact_match"; + static constexpr std::string_view negated_matcher_name = "!exact_match"; + exact_match() = default; explicit exact_match(std::vector &&data); explicit exact_match(const data_type &data); @@ -29,8 +32,10 @@ class exact_match : public base_impl { protected: static constexpr std::string_view to_string_impl() { return ""; } - static constexpr std::string_view name_impl() { return "exact_match"; } - static constexpr DDWAF_OBJ_TYPE supported_type_impl() { return DDWAF_OBJ_STRING; } + static constexpr bool is_supported_type_impl(DDWAF_OBJ_TYPE type) + { + return type == DDWAF_OBJ_STRING; + } [[nodiscard]] std::pair match_impl(std::string_view str) const; diff --git a/src/matcher/greater_than.hpp b/src/matcher/greater_than.hpp index 0949c5038..911ea2497 100644 --- a/src/matcher/greater_than.hpp +++ b/src/matcher/greater_than.hpp @@ -8,19 +8,23 @@ #include #include -#include +#include #include "ddwaf.h" #include "matcher/base.hpp" -#include "utils.hpp" namespace ddwaf::matcher { -template - requires std::is_same_v || std::is_same_v || std::is_same_v -class greater_than : public base_impl> { +template class greater_than : public base_impl> { public: - explicit greater_than(T minimum) : minimum_(std::move(minimum)) {} + static constexpr std::string_view matcher_name = "greater_than"; + static constexpr std::string_view negated_matcher_name = "!greater_than"; + + explicit greater_than(T minimum) + requires std::is_same_v || std::is_same_v || + std::is_same_v + : minimum_(std::move(minimum)) + {} ~greater_than() override = default; greater_than(const greater_than &) = default; greater_than(greater_than &&) noexcept = default; @@ -29,29 +33,47 @@ class greater_than : public base_impl> { protected: static constexpr std::string_view to_string_impl() { return ""; } - static constexpr std::string_view name_impl() { return "greater_than"; } - - static constexpr DDWAF_OBJ_TYPE supported_type_impl() + static constexpr bool is_supported_type_impl(DDWAF_OBJ_TYPE type) { - if constexpr (std::is_same_v) { - return DDWAF_OBJ_SIGNED; - } - if constexpr (std::is_same_v) { - return DDWAF_OBJ_UNSIGNED; - } - if constexpr (std::is_same_v) { - return DDWAF_OBJ_FLOAT; - } + return type == DDWAF_OBJ_SIGNED || type == DDWAF_OBJ_UNSIGNED || type == DDWAF_OBJ_FLOAT; } - [[nodiscard]] std::pair match_impl(const T &obtained) const + template + [[nodiscard]] std::pair match_impl(const U &obtained) const + requires(!std::is_floating_point_v) { - return {minimum_ < obtained, {}}; + return {std::cmp_greater(obtained, minimum_), {}}; } + [[nodiscard]] std::pair match_impl(double obtained) const + { + return {obtained > minimum_, {}}; + } T minimum_; friend class base_impl>; }; +template <> class greater_than : public base_impl> { +public: + static constexpr std::string_view matcher_name = "greater_than"; + static constexpr std::string_view negated_matcher_name = "!greater_than"; + + ~greater_than() override = default; + +protected: + greater_than() = default; + greater_than(const greater_than &) = default; + greater_than(greater_than &&) noexcept = default; + greater_than &operator=(const greater_than &) = default; + greater_than &operator=(greater_than &&) noexcept = default; + + static constexpr std::string_view to_string_impl() { return ""; } + static constexpr bool is_supported_type_impl(DDWAF_OBJ_TYPE /*type*/) { return false; } + + [[nodiscard]] static std::pair match_impl() { return {}; } + + friend class base_impl>; +}; + } // namespace ddwaf::matcher diff --git a/src/matcher/ip_match.hpp b/src/matcher/ip_match.hpp index e3fb84b2a..b52c80f0e 100644 --- a/src/matcher/ip_match.hpp +++ b/src/matcher/ip_match.hpp @@ -19,6 +19,9 @@ class ip_match : public base_impl { public: using data_type = std::vector>; + static constexpr std::string_view matcher_name = "ip_match"; + static constexpr std::string_view negated_matcher_name = "!ip_match"; + ip_match() = default; explicit ip_match(const std::vector &ip_list); template @@ -43,8 +46,10 @@ class ip_match : public base_impl { protected: static constexpr std::string_view to_string_impl() { return ""; } - static constexpr std::string_view name_impl() { return "ip_match"; } - static constexpr DDWAF_OBJ_TYPE supported_type_impl() { return DDWAF_OBJ_STRING; } + static constexpr bool is_supported_type_impl(DDWAF_OBJ_TYPE type) + { + return type == DDWAF_OBJ_STRING; + } [[nodiscard]] std::pair match_impl(std::string_view str) const; diff --git a/src/matcher/is_sqli.hpp b/src/matcher/is_sqli.hpp index 1f9333122..846e79fd2 100644 --- a/src/matcher/is_sqli.hpp +++ b/src/matcher/is_sqli.hpp @@ -14,6 +14,9 @@ namespace ddwaf::matcher { class is_sqli : public base_impl { public: + static constexpr std::string_view matcher_name = "is_sqli"; + static constexpr std::string_view negated_matcher_name = "!is_sqli"; + is_sqli() = default; ~is_sqli() override = default; is_sqli(const is_sqli &) = delete; @@ -25,8 +28,10 @@ class is_sqli : public base_impl { static constexpr unsigned fingerprint_length = 16; static constexpr std::string_view to_string_impl() { return ""; } - static constexpr std::string_view name_impl() { return "is_sqli"; } - static constexpr DDWAF_OBJ_TYPE supported_type_impl() { return DDWAF_OBJ_STRING; } + static constexpr bool is_supported_type_impl(DDWAF_OBJ_TYPE type) + { + return type == DDWAF_OBJ_STRING; + } static std::pair match_impl(std::string_view pattern); diff --git a/src/matcher/is_xss.hpp b/src/matcher/is_xss.hpp index 625aaf5f0..4eb884921 100644 --- a/src/matcher/is_xss.hpp +++ b/src/matcher/is_xss.hpp @@ -14,6 +14,9 @@ namespace ddwaf::matcher { class is_xss : public base_impl { public: + static constexpr std::string_view matcher_name = "is_xss"; + static constexpr std::string_view negated_matcher_name = "!is_xss"; + is_xss() = default; ~is_xss() override = default; is_xss(const is_xss &) = delete; @@ -23,8 +26,10 @@ class is_xss : public base_impl { protected: static constexpr std::string_view to_string_impl() { return ""; } - static constexpr std::string_view name_impl() { return "is_xss"; } - static constexpr DDWAF_OBJ_TYPE supported_type_impl() { return DDWAF_OBJ_STRING; } + static constexpr bool is_supported_type_impl(DDWAF_OBJ_TYPE type) + { + return type == DDWAF_OBJ_STRING; + } static std::pair match_impl(std::string_view pattern); diff --git a/src/matcher/lower_than.hpp b/src/matcher/lower_than.hpp index 03746173f..a2d0ab328 100644 --- a/src/matcher/lower_than.hpp +++ b/src/matcher/lower_than.hpp @@ -8,19 +8,23 @@ #include #include -#include +#include #include "ddwaf.h" #include "matcher/base.hpp" -#include "utils.hpp" namespace ddwaf::matcher { -template - requires std::is_same_v || std::is_same_v || std::is_same_v -class lower_than : public base_impl> { +template class lower_than : public base_impl> { public: - explicit lower_than(T maximum) : maximum_(std::move(maximum)) {} + static constexpr std::string_view matcher_name = "lower_than"; + static constexpr std::string_view negated_matcher_name = "!lower_than"; + + explicit lower_than(T maximum) + requires std::is_same_v || std::is_same_v || + std::is_same_v + : maximum_(std::move(maximum)) + {} ~lower_than() override = default; lower_than(const lower_than &) = default; lower_than(lower_than &&) noexcept = default; @@ -29,24 +33,21 @@ class lower_than : public base_impl> { protected: static constexpr std::string_view to_string_impl() { return ""; } - static constexpr std::string_view name_impl() { return "lower_than"; } + static constexpr bool is_supported_type_impl(DDWAF_OBJ_TYPE type) + { + return type == DDWAF_OBJ_SIGNED || type == DDWAF_OBJ_UNSIGNED || type == DDWAF_OBJ_FLOAT; + } - static constexpr DDWAF_OBJ_TYPE supported_type_impl() + template + [[nodiscard]] std::pair match_impl(const U &obtained) const + requires(!std::is_floating_point_v) { - if constexpr (std::is_same_v) { - return DDWAF_OBJ_SIGNED; - } - if constexpr (std::is_same_v) { - return DDWAF_OBJ_UNSIGNED; - } - if constexpr (std::is_same_v) { - return DDWAF_OBJ_FLOAT; - } + return {std::cmp_less(obtained, maximum_), {}}; } - [[nodiscard]] std::pair match_impl(const T &obtained) const + [[nodiscard]] std::pair match_impl(double obtained) const { - return {maximum_ > obtained, {}}; + return {obtained < maximum_, {}}; } T maximum_; @@ -54,4 +55,26 @@ class lower_than : public base_impl> { friend class base_impl>; }; +template <> class lower_than : public base_impl> { +public: + static constexpr std::string_view matcher_name = "lower_than"; + static constexpr std::string_view negated_matcher_name = "!lower_than"; + + ~lower_than() override = default; + +protected: + lower_than() = default; + lower_than(const lower_than &) = default; + lower_than(lower_than &&) noexcept = default; + lower_than &operator=(const lower_than &) = default; + lower_than &operator=(lower_than &&) noexcept = default; + + static constexpr std::string_view to_string_impl() { return ""; } + static constexpr bool is_supported_type_impl(DDWAF_OBJ_TYPE /*type*/) { return false; } + + [[nodiscard]] static std::pair match_impl() { return {}; } + + friend class base_impl>; +}; + } // namespace ddwaf::matcher diff --git a/src/matcher/phrase_match.hpp b/src/matcher/phrase_match.hpp index f2b094dda..9612c060a 100644 --- a/src/matcher/phrase_match.hpp +++ b/src/matcher/phrase_match.hpp @@ -8,6 +8,7 @@ #include #include +#include #include "matcher/base.hpp" @@ -15,6 +16,9 @@ namespace ddwaf::matcher { class phrase_match : public base_impl { public: + static constexpr std::string_view matcher_name = "phrase_match"; + static constexpr std::string_view negated_matcher_name = "!phrase_match"; + phrase_match(std::vector pattern, std::vector lengths, bool enforce_word_boundary = false); ~phrase_match() override = default; @@ -25,8 +29,10 @@ class phrase_match : public base_impl { protected: static constexpr std::string_view to_string_impl() { return ""; } - static constexpr std::string_view name_impl() { return "phrase_match"; } - static constexpr DDWAF_OBJ_TYPE supported_type_impl() { return DDWAF_OBJ_STRING; } + static constexpr bool is_supported_type_impl(DDWAF_OBJ_TYPE type) + { + return type == DDWAF_OBJ_STRING; + } [[nodiscard]] std::pair match_impl(std::string_view pattern) const; diff --git a/src/matcher/regex_match.hpp b/src/matcher/regex_match.hpp index ec13b3594..4dd716fc0 100644 --- a/src/matcher/regex_match.hpp +++ b/src/matcher/regex_match.hpp @@ -16,6 +16,9 @@ namespace ddwaf::matcher { class regex_match : public base_impl { public: + static constexpr std::string_view matcher_name = "match_regex"; + static constexpr std::string_view negated_matcher_name = "!match_regex"; + regex_match(const std::string ®ex_str, std::size_t minLength, bool case_sensitive); ~regex_match() override = default; regex_match(const regex_match &) = delete; @@ -25,8 +28,10 @@ class regex_match : public base_impl { protected: [[nodiscard]] std::string_view to_string_impl() const { return regex->pattern(); } - static constexpr std::string_view name_impl() { return "match_regex"; } - static constexpr DDWAF_OBJ_TYPE supported_type_impl() { return DDWAF_OBJ_STRING; } + static constexpr bool is_supported_type_impl(DDWAF_OBJ_TYPE type) + { + return type == DDWAF_OBJ_STRING; + } [[nodiscard]] std::pair match_impl(std::string_view pattern) const; diff --git a/src/parser/expression_parser.cpp b/src/parser/expression_parser.cpp index a49d2013b..bcb55ad69 100644 --- a/src/parser/expression_parser.cpp +++ b/src/parser/expression_parser.cpp @@ -20,8 +20,18 @@ #include "exception.hpp" #include "expression.hpp" #include "log.hpp" +#include "matcher/equals.hpp" +#include "matcher/exact_match.hpp" +#include "matcher/greater_than.hpp" +#include "matcher/ip_match.hpp" +#include "matcher/is_sqli.hpp" +#include "matcher/is_xss.hpp" +#include "matcher/lower_than.hpp" +#include "matcher/phrase_match.hpp" +#include "matcher/regex_match.hpp" #include "parameter.hpp" #include "parser/common.hpp" +#include "parser/matcher_parser.hpp" #include "parser/parser.hpp" #include "transformer/base.hpp" #include "utils.hpp" @@ -100,6 +110,20 @@ std::vector parse_arguments(const parameter::map ¶ms, d return definitions; } +template +auto build_condition(auto operator_name, auto ¶ms, auto &data_ids_to_type, auto source, + auto &transformers, auto &addresses, auto &limits) +{ + auto [data_id, matcher] = parse_matcher(operator_name, params); + + if (!matcher && !data_id.empty()) { + data_ids_to_type.emplace(data_id, operator_name); + } + + auto arguments = parse_arguments(params, source, transformers, addresses, limits); + return std::make_unique(std::move(matcher), data_id, std::move(arguments), limits); +} + } // namespace std::shared_ptr parse_expression(const parameter::vector &conditions_array, @@ -140,18 +164,19 @@ std::shared_ptr parse_expression(const parameter::vector &conditions params, source, transformers, addresses, limits); conditions.emplace_back( std::make_unique(std::move(arguments), limits)); + } else if (operator_name.starts_with('!')) { + conditions.emplace_back( + build_condition>( + operator_name.substr(1), params, data_ids_to_type, source, transformers, + addresses, limits)); } else { - auto [data_id, matcher] = parse_matcher(operator_name, params); - - if (!matcher && !data_id.empty()) { - data_ids_to_type.emplace(data_id, operator_name); - } - - auto arguments = - parse_arguments(params, source, transformers, addresses, limits); - - conditions.emplace_back(std::make_unique( - std::move(matcher), data_id, std::move(arguments), limits)); + conditions.emplace_back( + build_condition, matcher::exact_match, + matcher::greater_than<>, matcher::ip_match, matcher::is_sqli, matcher::is_xss, + matcher::lower_than<>, matcher::phrase_match, matcher::regex_match>( + operator_name, params, data_ids_to_type, source, transformers, addresses, + limits)); } } diff --git a/src/parser/matcher_parser.cpp b/src/parser/matcher_parser.cpp index 4b738753e..e1cee07b5 100644 --- a/src/parser/matcher_parser.cpp +++ b/src/parser/matcher_parser.cpp @@ -30,122 +30,179 @@ #include "matcher/regex_match.hpp" #include "parameter.hpp" #include "parser/common.hpp" +#include "parser/matcher_parser.hpp" // IWYU pragma: keep namespace ddwaf::parser::v2 { -std::pair> parse_matcher( - std::string_view name, const parameter::map ¶ms) +template <> +std::pair> parse_matcher( + const parameter::map ¶ms) { parameter::map options; + + auto list = at(params, "list"); + options = at(params, "options", options); + auto word_boundary = at(options, "enforce_word_boundary", false); + + std::vector patterns; + std::vector lengths; + + patterns.reserve(list.size()); + lengths.reserve(list.size()); + + for (auto &pattern : list) { + if (pattern.type != DDWAF_OBJ_STRING) { + throw ddwaf::parsing_error("phrase_match list item not a string"); + } + + patterns.push_back(pattern.stringValue); + lengths.push_back((uint32_t)pattern.nbEntries); + } + + return { + std::string{}, std::make_unique(patterns, lengths, word_boundary)}; +} + +template <> +std::pair> parse_matcher( + const parameter::map ¶ms) +{ + parameter::map options; + + auto regex = at(params, "regex"); + options = at(params, "options", options); + + auto case_sensitive = at(options, "case_sensitive", false); + auto min_length = at(options, "min_length", 0); + if (min_length < 0) { + throw ddwaf::parsing_error("min_length is a negative number"); + } + + return { + std::string{}, std::make_unique(regex, min_length, case_sensitive)}; +} + +template <> +std::pair> parse_matcher( + const parameter::map & /*params*/) +{ + return {std::string{}, std::make_unique()}; +} + +template <> +std::pair> parse_matcher( + const parameter::map & /*params*/) +{ + return {std::string{}, std::make_unique()}; +} + +template <> +std::pair> parse_matcher( + const parameter::map ¶ms) +{ std::unique_ptr matcher; std::string rule_data_id; - if (name == "phrase_match") { - auto list = at(params, "list"); - options = at(params, "options", options); - auto word_boundary = at(options, "enforce_word_boundary", false); + auto it = params.find("list"); + if (it == params.end()) { + rule_data_id = at(params, "data"); + } else { + matcher = std::make_unique( + static_cast>(it->second)); + } + + return {std::move(rule_data_id), std::move(matcher)}; +} + +template <> +std::pair> parse_matcher( + const parameter::map ¶ms) +{ + std::unique_ptr matcher; + std::string rule_data_id; - std::vector patterns; - std::vector lengths; + auto it = params.find("list"); + if (it == params.end()) { + rule_data_id = at(params, "data"); + } else { + matcher = std::make_unique( + static_cast>(it->second)); + } - patterns.reserve(list.size()); - lengths.reserve(list.size()); + return {std::move(rule_data_id), std::move(matcher)}; +} - for (auto &pattern : list) { - if (pattern.type != DDWAF_OBJ_STRING) { - throw ddwaf::parsing_error("phrase_match list item not a string"); - } +template <> +std::pair> parse_matcher>( + const parameter::map ¶ms) +{ + std::unique_ptr matcher; + auto value_type = at(params, "type"); + if (value_type == "string") { + auto value = at(params, "value"); + matcher = std::make_unique>(std::move(value)); + } else if (value_type == "boolean") { + auto value = at(params, "value"); + matcher = std::make_unique>(value); + } else if (value_type == "unsigned") { + auto value = at(params, "value"); + matcher = std::make_unique>(value); + } else if (value_type == "signed") { + auto value = at(params, "value"); + matcher = std::make_unique>(value); + } else if (value_type == "float") { + auto value = at(params, "value"); + auto delta = at(params, "delta", 0.01); + matcher = std::make_unique>(value, delta); + } else { + throw ddwaf::parsing_error("invalid type for matcher equals " + value_type); + } + return {std::string{}, std::move(matcher)}; +} - patterns.push_back(pattern.stringValue); - lengths.push_back((uint32_t)pattern.nbEntries); - } +template <> +std::pair> parse_matcher>( + const parameter::map ¶ms) +{ + std::unique_ptr matcher; + auto value_type = at(params, "type"); + if (value_type == "unsigned") { + auto value = at(params, "value"); + matcher = std::make_unique>(value); + } else if (value_type == "signed") { + auto value = at(params, "value"); + matcher = std::make_unique>(value); + } else if (value_type == "float") { + auto value = at(params, "value"); + matcher = std::make_unique>(value); + } else { + throw ddwaf::parsing_error("invalid type for matcher lower_than " + value_type); + } - matcher = std::make_unique(patterns, lengths, word_boundary); - } else if (name == "match_regex") { - auto regex = at(params, "regex"); - options = at(params, "options", options); + return {std::string{}, std::move(matcher)}; +} - auto case_sensitive = at(options, "case_sensitive", false); - auto min_length = at(options, "min_length", 0); - if (min_length < 0) { - throw ddwaf::parsing_error("min_length is a negative number"); - } +template <> +std::pair> parse_matcher>( + const parameter::map ¶ms) +{ + std::unique_ptr matcher; - matcher = std::make_unique(regex, min_length, case_sensitive); - } else if (name == "is_xss") { - matcher = std::make_unique(); - } else if (name == "is_sqli") { - matcher = std::make_unique(); - } else if (name == "ip_match") { - auto it = params.find("list"); - if (it == params.end()) { - rule_data_id = at(params, "data"); - } else { - matcher = std::make_unique( - static_cast>(it->second)); - } - } else if (name == "exact_match") { - auto it = params.find("list"); - if (it == params.end()) { - rule_data_id = at(params, "data"); - } else { - matcher = std::make_unique( - static_cast>(it->second)); - } - } else if (name == "equals") { - auto value_type = at(params, "type"); - if (value_type == "string") { - auto value = at(params, "value"); - matcher = std::make_unique>(std::move(value)); - } else if (value_type == "boolean") { - auto value = at(params, "value"); - matcher = std::make_unique>(value); - } else if (value_type == "unsigned") { - auto value = at(params, "value"); - matcher = std::make_unique>(value); - } else if (value_type == "signed") { - auto value = at(params, "value"); - matcher = std::make_unique>(value); - } else if (value_type == "float") { - auto value = at(params, "value"); - auto delta = at(params, "delta", 0.01); - matcher = std::make_unique>(value, delta); - } else { - throw ddwaf::parsing_error("invalid type for matcher equals " + value_type); - } - } else if (name == "greater_than") { - auto value_type = at(params, "type"); - if (value_type == "unsigned") { - auto value = at(params, "value"); - matcher = std::make_unique>(value); - } else if (value_type == "signed") { - auto value = at(params, "value"); - matcher = std::make_unique>(value); - } else if (value_type == "float") { - auto value = at(params, "value"); - matcher = std::make_unique>(value); - } else { - throw ddwaf::parsing_error("invalid type for matcher greater_than " + value_type); - } - } else if (name == "lower_than") { - auto value_type = at(params, "type"); - if (value_type == "unsigned") { - auto value = at(params, "value"); - matcher = std::make_unique>(value); - } else if (value_type == "signed") { - auto value = at(params, "value"); - matcher = std::make_unique>(value); - } else if (value_type == "float") { - auto value = at(params, "value"); - matcher = std::make_unique>(value); - } else { - throw ddwaf::parsing_error("invalid type for matcher lower_than " + value_type); - } + auto value_type = at(params, "type"); + if (value_type == "unsigned") { + auto value = at(params, "value"); + matcher = std::make_unique>(value); + } else if (value_type == "signed") { + auto value = at(params, "value"); + matcher = std::make_unique>(value); + } else if (value_type == "float") { + auto value = at(params, "value"); + matcher = std::make_unique>(value); } else { - throw ddwaf::parsing_error("unknown matcher: " + std::string(name)); + throw ddwaf::parsing_error("invalid type for matcher greater_than " + value_type); } - return {std::move(rule_data_id), std::move(matcher)}; + return {std::string{}, std::move(matcher)}; } } // namespace ddwaf::parser::v2 diff --git a/src/parser/matcher_parser.hpp b/src/parser/matcher_parser.hpp new file mode 100644 index 000000000..177c2d4de --- /dev/null +++ b/src/parser/matcher_parser.hpp @@ -0,0 +1,53 @@ +// Unless explicitly stated otherwise all files in this repository are +// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2021 Datadog, Inc. + +#pragma once + +#include +#include +#include + +#include "matcher/base.hpp" +#include "matcher/equals.hpp" +#include "matcher/exact_match.hpp" +#include "matcher/greater_than.hpp" +#include "matcher/ip_match.hpp" +#include "matcher/is_sqli.hpp" +#include "matcher/is_xss.hpp" +#include "matcher/lower_than.hpp" +#include "matcher/phrase_match.hpp" +#include "matcher/regex_match.hpp" +#include "parameter.hpp" + +namespace ddwaf::parser::v2 { + +template +std::pair> parse_matcher(const parameter::map ¶ms); + +template +std::pair> parse_matcher( + std::string_view name, const parameter::map ¶ms) +{ + if (Matcher::matcher_name == name) { + return parse_matcher(params); + } + + if constexpr (sizeof...(Rest) > 0) { + return parse_matcher(name, params); + } else { + throw ddwaf::parsing_error("unknown matcher: " + std::string(name)); + } +} + +inline std::pair> parse_any_matcher( + std::string_view name, const parameter::map ¶ms) +{ + return parse_matcher, matcher::exact_match, matcher::greater_than<>, + matcher::ip_match, matcher::is_sqli, matcher::is_xss, matcher::lower_than<>, + matcher::phrase_match, matcher::regex_match>(name, params); +} + +} // namespace ddwaf::parser::v2 diff --git a/src/parser/parser.hpp b/src/parser/parser.hpp index cf3bb64aa..3e3bd5a08 100644 --- a/src/parser/parser.hpp +++ b/src/parser/parser.hpp @@ -60,8 +60,8 @@ std::shared_ptr parse_simplified_expression(const parameter::vector std::vector parse_transformers(const parameter::vector &root, data_source &source); -std::pair> parse_matcher( - std::string_view name, const parameter::map ¶ms); +// std::pair> parse_matcher( +// std::string_view name, const parameter::map ¶ms); } // namespace v2 } // namespace ddwaf::parser diff --git a/src/parser/scanner_parser.cpp b/src/parser/scanner_parser.cpp index 74cb5e74c..6255b816c 100644 --- a/src/parser/scanner_parser.cpp +++ b/src/parser/scanner_parser.cpp @@ -17,6 +17,7 @@ #include "matcher/base.hpp" #include "parameter.hpp" #include "parser/common.hpp" +#include "parser/matcher_parser.hpp" #include "parser/parser.hpp" #include "scanner.hpp" @@ -29,7 +30,7 @@ std::unique_ptr parse_scanner_matcher(const parameter::map &root) auto matcher_name = at(root, "operator"); auto matcher_params = at(root, "parameters"); - auto [rule_data_id, matcher] = parse_matcher(matcher_name, matcher_params); + auto [rule_data_id, matcher] = parse_any_matcher(matcher_name, matcher_params); if (!rule_data_id.empty()) { throw ddwaf::parsing_error("dynamic data on scanner condition"); } diff --git a/tests/condition/scalar_condition_test.cpp b/tests/condition/scalar_condition_test.cpp new file mode 100644 index 000000000..30ee66a9f --- /dev/null +++ b/tests/condition/scalar_condition_test.cpp @@ -0,0 +1,193 @@ +// Unless explicitly stated otherwise all files in this repository are +// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2021 Datadog, Inc. + +#include "../test.hpp" +#include "condition/scalar_condition.hpp" +#include "exception.hpp" +#include "matcher/regex_match.hpp" +#include "utils.hpp" + +using namespace ddwaf; +using namespace std::literals; + +namespace { + +template condition_parameter gen_variadic_param(Args... addresses) +{ + return {{{std::string{addresses}, get_target_index(addresses)}...}}; +} + +TEST(TestScalarCondition, TooManyAddressesInConstructor) +{ + EXPECT_THROW((scalar_condition{std::make_unique(".*", 0, true), {}, + {gen_variadic_param("server.request.uri.raw"), + gen_variadic_param("server.request.query")}}), + std::invalid_argument); +} + +TEST(TestScalarCondition, NoAddressesInConstructor) +{ + EXPECT_THROW((scalar_condition{std::make_unique(".*", 0, true), {}, {}}), + std::invalid_argument); +} + +TEST(TestScalarCondition, NoMatch) +{ + scalar_condition cond{std::make_unique(".*", 0, true), {}, + {gen_variadic_param("server.request.uri.raw")}}; + + ddwaf_object tmp; + ddwaf_object root; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "server.request.uri.raw", ddwaf_object_invalid(&tmp)); + + object_store store; + store.insert(root); + + ddwaf::timer deadline{2s}; + condition_cache cache; + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_FALSE(res.outcome); + ASSERT_FALSE(res.ephemeral); +} + +TEST(TestScalarCondition, Timeout) +{ + scalar_condition cond{std::make_unique(".*", 0, true), {}, + {gen_variadic_param("server.request.uri.raw")}}; + + ddwaf_object tmp; + ddwaf_object root; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "server.request.uri.raw", ddwaf_object_invalid(&tmp)); + + object_store store; + store.insert(root); + + ddwaf::timer deadline{0s}; + condition_cache cache; + EXPECT_THROW(cond.eval(cache, store, {}, {}, deadline), ddwaf::timeout_exception); +} + +TEST(TestScalarCondition, SimpleMatch) +{ + scalar_condition cond{std::make_unique(".*", 0, true), {}, + {gen_variadic_param("server.request.uri.raw")}}; + + ddwaf_object tmp; + ddwaf_object root; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "server.request.uri.raw", ddwaf_object_string(&tmp, "hello")); + + object_store store; + store.insert(root); + + ddwaf::timer deadline{2s}; + condition_cache cache; + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_TRUE(res.outcome); + ASSERT_FALSE(res.ephemeral); +} + +TEST(TestScalarCondition, CachedMatch) +{ + scalar_condition cond{std::make_unique(".*", 0, true), {}, + {gen_variadic_param("server.request.uri.raw")}, {}}; + + ddwaf::timer deadline{2s}; + condition_cache cache; + + ddwaf_object tmp; + ddwaf_object root; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "server.request.uri.raw", ddwaf_object_string(&tmp, "hello")); + + { + object_store store; + store.insert(root, object_store::attribute::none, nullptr); + + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_TRUE(res.outcome); + ASSERT_FALSE(res.ephemeral); + } + + { + object_store store; + store.insert(root, object_store::attribute::none, nullptr); + + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_FALSE(res.outcome); + ASSERT_FALSE(res.ephemeral); + } + + ddwaf_object_free(&root); +} + +TEST(TestScalarCondition, SimpleMatchOnKeys) +{ + auto param = gen_variadic_param("server.request.uri.raw"); + param.targets[0].source = data_source::keys; + + scalar_condition cond{ + std::make_unique(".*", 0, true), {}, {std::move(param)}}; + + ddwaf_object tmp; + ddwaf_object root; + ddwaf_object map; + ddwaf_object_map(&map); + ddwaf_object_map_add(&map, "hello", ddwaf_object_string(&tmp, "hello")); + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "server.request.uri.raw", &map); + + object_store store; + store.insert(root); + + ddwaf::timer deadline{2s}; + condition_cache cache; + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_TRUE(res.outcome); + ASSERT_FALSE(res.ephemeral); +} + +TEST(TestScalarCondition, SimpleEphemeralMatch) +{ + scalar_condition cond{std::make_unique(".*", 0, true), {}, + {gen_variadic_param("server.request.uri.raw")}}; + + ddwaf_object tmp; + ddwaf_object root; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "server.request.uri.raw", ddwaf_object_string(&tmp, "hello")); + + object_store store; + { + auto scope = store.get_eval_scope(); + + store.insert(root, object_store::attribute::ephemeral, nullptr); + + ddwaf::timer deadline{2s}; + condition_cache cache; + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_TRUE(res.outcome); + ASSERT_TRUE(res.ephemeral); + } + + { + auto scope = store.get_eval_scope(); + + store.insert(root, object_store::attribute::ephemeral, nullptr); + + ddwaf::timer deadline{2s}; + condition_cache cache; + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_TRUE(res.outcome); + ASSERT_TRUE(res.ephemeral); + } + + ddwaf_object_free(&root); +} + +} // namespace diff --git a/tests/condition/scalar_negated_condition_test.cpp b/tests/condition/scalar_negated_condition_test.cpp new file mode 100644 index 000000000..31b31024c --- /dev/null +++ b/tests/condition/scalar_negated_condition_test.cpp @@ -0,0 +1,204 @@ +// Unless explicitly stated otherwise all files in this repository are +// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2021 Datadog, Inc. + +#include "../test.hpp" +#include "condition/scalar_condition.hpp" +#include "exception.hpp" +#include "matcher/regex_match.hpp" +#include "utils.hpp" + +using namespace ddwaf; +using namespace std::literals; + +namespace { + +template condition_parameter gen_variadic_param(Args... addresses) +{ + return {{{std::string{addresses}, get_target_index(addresses)}...}}; +} + +TEST(TestScalarNegatedCondition, VariadicTargetInConstructor) +{ + EXPECT_THROW( + (scalar_negated_condition{std::make_unique(".*", 0, true), {}, + {gen_variadic_param("server.request.uri.raw", "server.request.query")}, {}}), + std::invalid_argument); +} + +TEST(TestScalarNegatedCondition, TooManyAddressesInConstructor) +{ + EXPECT_THROW( + (scalar_negated_condition{std::make_unique(".*", 0, true), {}, + {gen_variadic_param("server.request.uri.raw"), + gen_variadic_param("server.request.query")}, + {}}), + std::invalid_argument); +} + +TEST(TestScalarNegatedCondition, NoAddressesInConstructor) +{ + EXPECT_THROW((scalar_negated_condition{ + std::make_unique(".*", 0, true), {}, {}, {}}), + std::invalid_argument); +} + +TEST(TestScalarNegatedCondition, NoMatch) +{ + scalar_negated_condition cond{std::make_unique(".*", 0, true), {}, + {gen_variadic_param("server.request.uri.raw")}, {}}; + + ddwaf_object tmp; + ddwaf_object root; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "server.request.uri.raw", ddwaf_object_string(&tmp, "hello")); + + object_store store; + store.insert(root); + + ddwaf::timer deadline{2s}; + condition_cache cache; + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_FALSE(res.outcome); + ASSERT_FALSE(res.ephemeral); +} + +TEST(TestScalarNegatedCondition, Timeout) +{ + scalar_negated_condition cond{std::make_unique(".*", 0, true), {}, + {gen_variadic_param("server.request.uri.raw")}, {}}; + + ddwaf_object tmp; + ddwaf_object root; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "server.request.uri.raw", ddwaf_object_string(&tmp, "hello")); + + object_store store; + store.insert(root); + + ddwaf::timer deadline{0s}; + condition_cache cache; + EXPECT_THROW(cond.eval(cache, store, {}, {}, deadline), ddwaf::timeout_exception); +} + +TEST(TestScalarNegatedCondition, SimpleMatch) +{ + scalar_negated_condition cond{std::make_unique(".*", 0, true), {}, + {gen_variadic_param("server.request.uri.raw")}, {}}; + + ddwaf_object tmp; + ddwaf_object root; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "server.request.uri.raw", ddwaf_object_invalid(&tmp)); + + object_store store; + store.insert(root); + + ddwaf::timer deadline{2s}; + condition_cache cache; + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_TRUE(res.outcome); + ASSERT_FALSE(res.ephemeral); +} + +TEST(TestScalarNegatedCondition, CachedMatch) +{ + scalar_negated_condition cond{std::make_unique(".*", 0, true), {}, + {gen_variadic_param("server.request.uri.raw")}, {}}; + + ddwaf::timer deadline{2s}; + condition_cache cache; + + ddwaf_object tmp; + ddwaf_object root; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "server.request.uri.raw", ddwaf_object_invalid(&tmp)); + + { + object_store store; + store.insert(root, object_store::attribute::none, nullptr); + + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_TRUE(res.outcome); + ASSERT_FALSE(res.ephemeral); + } + + { + object_store store; + store.insert(root, object_store::attribute::none, nullptr); + + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_FALSE(res.outcome); + ASSERT_FALSE(res.ephemeral); + } + + ddwaf_object_free(&root); +} + +TEST(TestScalarNegatedCondition, SimpleMatchOnKeys) +{ + auto target = gen_variadic_param("server.request.uri.raw"); + target.targets[0].source = data_source::keys; + + scalar_negated_condition cond{ + std::make_unique("hello", 0, true), {}, {std::move(target)}, {}}; + + ddwaf_object tmp; + ddwaf_object root; + ddwaf_object map; + ddwaf_object_map(&map); + ddwaf_object_map_add(&map, "bye", ddwaf_object_string(&tmp, "hello")); + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "server.request.uri.raw", &map); + + object_store store; + store.insert(root); + + ddwaf::timer deadline{2s}; + condition_cache cache; + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_TRUE(res.outcome); + ASSERT_FALSE(res.ephemeral); +} + +TEST(TestScalarNegatedCondition, SimpleEphemeralMatch) +{ + scalar_negated_condition cond{std::make_unique(".*", 0, true), {}, + {gen_variadic_param("server.request.uri.raw")}, {}}; + + ddwaf_object tmp; + ddwaf_object root; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "server.request.uri.raw", ddwaf_object_invalid(&tmp)); + + object_store store; + { + auto scope = store.get_eval_scope(); + + store.insert(root, object_store::attribute::ephemeral, nullptr); + + ddwaf::timer deadline{2s}; + condition_cache cache; + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_TRUE(res.outcome); + ASSERT_TRUE(res.ephemeral); + } + + { + auto scope = store.get_eval_scope(); + + store.insert(root, object_store::attribute::ephemeral, nullptr); + + ddwaf::timer deadline{2s}; + condition_cache cache; + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_TRUE(res.outcome); + ASSERT_TRUE(res.ephemeral); + } + + ddwaf_object_free(&root); +} + +} // namespace diff --git a/tests/expression_test.cpp b/tests/expression_test.cpp index f968738ba..5d6e5ec5f 100644 --- a/tests/expression_test.cpp +++ b/tests/expression_test.cpp @@ -37,7 +37,7 @@ TEST(TestExpression, SimpleMatch) EXPECT_TRUE(res.outcome); EXPECT_FALSE(res.ephemeral); - auto matches = expr->get_matches(cache); + auto matches = ddwaf::expression::get_matches(cache); EXPECT_EQ(matches.size(), 1); EXPECT_FALSE(matches[0].ephemeral); EXPECT_MATCHES(matches, {.op = "match_regex", @@ -49,6 +49,43 @@ TEST(TestExpression, SimpleMatch) }}}); } +TEST(TestExpression, SimpleNegatedMatch) +{ + test::expression_builder builder(1); + builder.start_condition(); + builder.add_argument(); + builder.add_target("server.request.query"); + builder.end_condition(".*", 5, true); + + auto expr = builder.build(); + + ddwaf_object root; + ddwaf_object tmp; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "server.request.query", ddwaf_object_string(&tmp, "val")); + + ddwaf::object_store store; + store.insert(root); + + ddwaf::timer deadline{2s}; + + expression::cache_type cache; + auto res = expr->eval(cache, store, {}, {}, deadline); + EXPECT_TRUE(res.outcome); + EXPECT_FALSE(res.ephemeral); + + auto matches = expr->get_matches(cache); + EXPECT_EQ(matches.size(), 1); + EXPECT_FALSE(matches[0].ephemeral); + EXPECT_MATCHES(matches, {.op = "!match_regex", + .op_value = ".*", + .highlight = "", + .args = {{ + .value = "val", + .address = "server.request.query", + }}}); +} + TEST(TestExpression, EphemeralMatch) { test::expression_builder builder(1); diff --git a/tests/matcher/equals_test.cpp b/tests/matcher/equals_test.cpp index 9edbbfddd..304ce9128 100644 --- a/tests/matcher/equals_test.cpp +++ b/tests/matcher/equals_test.cpp @@ -14,11 +14,25 @@ namespace { TEST(TestEqualsBool, Basic) { + ddwaf_object tmp; { matcher::equals matcher(false); EXPECT_TRUE(matcher.match(false).first); EXPECT_FALSE(matcher.match(true).first); + + EXPECT_TRUE(matcher.is_supported_type(DDWAF_OBJ_BOOL)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_FLOAT)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_SIGNED)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_UNSIGNED)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_STRING)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_MAP)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_ARRAY)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_NULL)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_INVALID)); + + EXPECT_TRUE(matcher.match(*ddwaf_object_bool(&tmp, false)).first); + EXPECT_FALSE(matcher.match(*ddwaf_object_bool(&tmp, true)).first); } { @@ -26,6 +40,19 @@ TEST(TestEqualsBool, Basic) EXPECT_TRUE(matcher.match(true).first); EXPECT_FALSE(matcher.match(false).first); + + EXPECT_TRUE(matcher.is_supported_type(DDWAF_OBJ_BOOL)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_FLOAT)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_SIGNED)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_UNSIGNED)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_STRING)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_MAP)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_ARRAY)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_NULL)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_INVALID)); + + EXPECT_TRUE(matcher.match(*ddwaf_object_bool(&tmp, true)).first); + EXPECT_FALSE(matcher.match(*ddwaf_object_bool(&tmp, false)).first); } } @@ -36,6 +63,23 @@ TEST(TestEqualsInt, Basic) EXPECT_TRUE(matcher.match(5).first); EXPECT_FALSE(matcher.match(1).first); EXPECT_FALSE(matcher.match(-1).first); + + EXPECT_TRUE(matcher.is_supported_type(DDWAF_OBJ_SIGNED)); + EXPECT_TRUE(matcher.is_supported_type(DDWAF_OBJ_UNSIGNED)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_FLOAT)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_STRING)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_MAP)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_ARRAY)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_NULL)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_INVALID)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_BOOL)); + + ddwaf_object tmp; + EXPECT_TRUE(matcher.match(*ddwaf_object_signed(&tmp, 5)).first); + EXPECT_TRUE(matcher.match(*ddwaf_object_unsigned(&tmp, 5)).first); + + EXPECT_FALSE(matcher.match(*ddwaf_object_signed(&tmp, 6)).first); + EXPECT_FALSE(matcher.match(*ddwaf_object_unsigned(&tmp, 6)).first); } TEST(TestEqualsUint, Basic) @@ -44,15 +88,46 @@ TEST(TestEqualsUint, Basic) EXPECT_TRUE(matcher.match(2132132).first); EXPECT_FALSE(matcher.match(1).first); + + EXPECT_TRUE(matcher.is_supported_type(DDWAF_OBJ_SIGNED)); + EXPECT_TRUE(matcher.is_supported_type(DDWAF_OBJ_UNSIGNED)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_FLOAT)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_STRING)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_MAP)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_ARRAY)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_NULL)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_INVALID)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_BOOL)); + + ddwaf_object tmp; + EXPECT_TRUE(matcher.match(*ddwaf_object_signed(&tmp, 2132132)).first); + EXPECT_TRUE(matcher.match(*ddwaf_object_unsigned(&tmp, 2132132)).first); + + EXPECT_FALSE(matcher.match(*ddwaf_object_signed(&tmp, 6)).first); + EXPECT_FALSE(matcher.match(*ddwaf_object_unsigned(&tmp, 6)).first); } TEST(TestEqualsDouble, Basic) { - matcher::equals matcher(5.1, 0.0001); + matcher::equals matcher(5.01, 0.1); - EXPECT_TRUE(matcher.match(5.1).first); - EXPECT_FALSE(matcher.match(5.11).first); + EXPECT_TRUE(matcher.match(5.01).first); + EXPECT_FALSE(matcher.match(5.12).first); EXPECT_FALSE(matcher.match(-5.1).first); + + EXPECT_TRUE(matcher.is_supported_type(DDWAF_OBJ_FLOAT)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_SIGNED)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_UNSIGNED)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_STRING)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_MAP)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_ARRAY)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_NULL)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_INVALID)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_BOOL)); + + ddwaf_object tmp; + EXPECT_TRUE(matcher.match(*ddwaf_object_float(&tmp, 5.01)).first); + EXPECT_FALSE(matcher.match(*ddwaf_object_float(&tmp, 5.5)).first); } TEST(TestEqualsString, Basic) diff --git a/tests/matcher/greater_than_test.cpp b/tests/matcher/greater_than_test.cpp index 1f52ccadb..3297a07c8 100644 --- a/tests/matcher/greater_than_test.cpp +++ b/tests/matcher/greater_than_test.cpp @@ -5,6 +5,7 @@ // Copyright 2021 Datadog, Inc. #include "../test.hpp" +#include "ddwaf.h" #include "matcher/greater_than.hpp" using namespace ddwaf; @@ -19,6 +20,25 @@ TEST(TestGreaterThanInt, Basic) EXPECT_FALSE(matcher.match(5).first); EXPECT_FALSE(matcher.match(1).first); EXPECT_FALSE(matcher.match(-1).first); + + EXPECT_TRUE(matcher.is_supported_type(DDWAF_OBJ_FLOAT)); + EXPECT_TRUE(matcher.is_supported_type(DDWAF_OBJ_SIGNED)); + EXPECT_TRUE(matcher.is_supported_type(DDWAF_OBJ_UNSIGNED)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_STRING)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_MAP)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_ARRAY)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_NULL)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_INVALID)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_BOOL)); + + ddwaf_object tmp; + EXPECT_TRUE(matcher.match(*ddwaf_object_signed(&tmp, 6)).first); + EXPECT_TRUE(matcher.match(*ddwaf_object_unsigned(&tmp, 6)).first); + EXPECT_TRUE(matcher.match(*ddwaf_object_float(&tmp, 6.0)).first); + + EXPECT_FALSE(matcher.match(*ddwaf_object_signed(&tmp, 5)).first); + EXPECT_FALSE(matcher.match(*ddwaf_object_unsigned(&tmp, 5)).first); + EXPECT_FALSE(matcher.match(*ddwaf_object_float(&tmp, 5.0)).first); } TEST(TestGreaterThanUint, Basic) @@ -28,6 +48,25 @@ TEST(TestGreaterThanUint, Basic) EXPECT_TRUE(matcher.match(2132133).first); EXPECT_FALSE(matcher.match(2132132).first); EXPECT_FALSE(matcher.match(1).first); + + EXPECT_TRUE(matcher.is_supported_type(DDWAF_OBJ_FLOAT)); + EXPECT_TRUE(matcher.is_supported_type(DDWAF_OBJ_SIGNED)); + EXPECT_TRUE(matcher.is_supported_type(DDWAF_OBJ_UNSIGNED)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_STRING)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_MAP)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_ARRAY)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_NULL)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_INVALID)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_BOOL)); + + ddwaf_object tmp; + EXPECT_TRUE(matcher.match(*ddwaf_object_signed(&tmp, 2132133)).first); + EXPECT_TRUE(matcher.match(*ddwaf_object_unsigned(&tmp, 2132133)).first); + EXPECT_TRUE(matcher.match(*ddwaf_object_float(&tmp, 2132133.1)).first); + + EXPECT_FALSE(matcher.match(*ddwaf_object_signed(&tmp, 5)).first); + EXPECT_FALSE(matcher.match(*ddwaf_object_unsigned(&tmp, 5)).first); + EXPECT_FALSE(matcher.match(*ddwaf_object_float(&tmp, 5.0)).first); } TEST(TestGreaterThanDouble, Basic) @@ -38,6 +77,25 @@ TEST(TestGreaterThanDouble, Basic) EXPECT_FALSE(matcher.match(5.1).first); EXPECT_FALSE(matcher.match(5.09).first); EXPECT_FALSE(matcher.match(-5.1).first); + + EXPECT_TRUE(matcher.is_supported_type(DDWAF_OBJ_FLOAT)); + EXPECT_TRUE(matcher.is_supported_type(DDWAF_OBJ_SIGNED)); + EXPECT_TRUE(matcher.is_supported_type(DDWAF_OBJ_UNSIGNED)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_STRING)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_MAP)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_ARRAY)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_NULL)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_INVALID)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_BOOL)); + + ddwaf_object tmp; + EXPECT_TRUE(matcher.match(*ddwaf_object_signed(&tmp, 6)).first); + EXPECT_TRUE(matcher.match(*ddwaf_object_unsigned(&tmp, 6)).first); + EXPECT_TRUE(matcher.match(*ddwaf_object_float(&tmp, 5.12)).first); + + EXPECT_FALSE(matcher.match(*ddwaf_object_signed(&tmp, 5)).first); + EXPECT_FALSE(matcher.match(*ddwaf_object_unsigned(&tmp, 5)).first); + EXPECT_FALSE(matcher.match(*ddwaf_object_float(&tmp, 5.0)).first); } } // namespace diff --git a/tests/matcher/lower_than_test.cpp b/tests/matcher/lower_than_test.cpp index 4bf6697fe..85b7a68ae 100644 --- a/tests/matcher/lower_than_test.cpp +++ b/tests/matcher/lower_than_test.cpp @@ -20,6 +20,25 @@ TEST(TestlowerThanInt, Basic) EXPECT_FALSE(matcher.match(6).first); EXPECT_FALSE(matcher.match(5).first); EXPECT_FALSE(matcher.match(99).first); + + EXPECT_TRUE(matcher.is_supported_type(DDWAF_OBJ_FLOAT)); + EXPECT_TRUE(matcher.is_supported_type(DDWAF_OBJ_SIGNED)); + EXPECT_TRUE(matcher.is_supported_type(DDWAF_OBJ_UNSIGNED)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_STRING)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_MAP)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_ARRAY)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_NULL)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_INVALID)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_BOOL)); + + ddwaf_object tmp; + EXPECT_TRUE(matcher.match(*ddwaf_object_signed(&tmp, 4)).first); + EXPECT_TRUE(matcher.match(*ddwaf_object_unsigned(&tmp, 4)).first); + EXPECT_TRUE(matcher.match(*ddwaf_object_float(&tmp, 4.0)).first); + + EXPECT_FALSE(matcher.match(*ddwaf_object_signed(&tmp, 5)).first); + EXPECT_FALSE(matcher.match(*ddwaf_object_unsigned(&tmp, 5)).first); + EXPECT_FALSE(matcher.match(*ddwaf_object_float(&tmp, 5.0)).first); } TEST(TestlowerThanUint, Basic) @@ -29,6 +48,25 @@ TEST(TestlowerThanUint, Basic) EXPECT_TRUE(matcher.match(2132131).first); EXPECT_FALSE(matcher.match(2132133).first); EXPECT_FALSE(matcher.match(2132132).first); + + EXPECT_TRUE(matcher.is_supported_type(DDWAF_OBJ_FLOAT)); + EXPECT_TRUE(matcher.is_supported_type(DDWAF_OBJ_SIGNED)); + EXPECT_TRUE(matcher.is_supported_type(DDWAF_OBJ_UNSIGNED)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_STRING)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_MAP)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_ARRAY)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_NULL)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_INVALID)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_BOOL)); + + ddwaf_object tmp; + EXPECT_TRUE(matcher.match(*ddwaf_object_signed(&tmp, 2132131)).first); + EXPECT_TRUE(matcher.match(*ddwaf_object_unsigned(&tmp, 2132131)).first); + EXPECT_TRUE(matcher.match(*ddwaf_object_float(&tmp, 2132131.9)).first); + + EXPECT_FALSE(matcher.match(*ddwaf_object_signed(&tmp, 2132133)).first); + EXPECT_FALSE(matcher.match(*ddwaf_object_unsigned(&tmp, 2132133)).first); + EXPECT_FALSE(matcher.match(*ddwaf_object_float(&tmp, 2132132.1)).first); } TEST(TestlowerThanDouble, Basic) @@ -39,6 +77,25 @@ TEST(TestlowerThanDouble, Basic) EXPECT_TRUE(matcher.match(-5.1).first); EXPECT_FALSE(matcher.match(5.1).first); EXPECT_FALSE(matcher.match(5.2).first); + + EXPECT_TRUE(matcher.is_supported_type(DDWAF_OBJ_FLOAT)); + EXPECT_TRUE(matcher.is_supported_type(DDWAF_OBJ_SIGNED)); + EXPECT_TRUE(matcher.is_supported_type(DDWAF_OBJ_UNSIGNED)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_STRING)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_MAP)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_ARRAY)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_NULL)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_INVALID)); + EXPECT_FALSE(matcher.is_supported_type(DDWAF_OBJ_BOOL)); + + ddwaf_object tmp; + EXPECT_TRUE(matcher.match(*ddwaf_object_signed(&tmp, 5)).first); + EXPECT_TRUE(matcher.match(*ddwaf_object_unsigned(&tmp, 5)).first); + EXPECT_TRUE(matcher.match(*ddwaf_object_float(&tmp, 5.09)).first); + + EXPECT_FALSE(matcher.match(*ddwaf_object_signed(&tmp, 6)).first); + EXPECT_FALSE(matcher.match(*ddwaf_object_unsigned(&tmp, 6)).first); + EXPECT_FALSE(matcher.match(*ddwaf_object_float(&tmp, 6.0)).first); } } // namespace diff --git a/tests/parser_v2_rules_test.cpp b/tests/parser_v2_rules_test.cpp index 5b66d3696..a3c894e94 100644 --- a/tests/parser_v2_rules_test.cpp +++ b/tests/parser_v2_rules_test.cpp @@ -412,4 +412,49 @@ TEST(TestParserV2Rules, KeyPathTooLong) EXPECT_EQ(rules.size(), 0); } + +TEST(TestParserV2Rules, NegatedMatcherTooManyParameters) +{ + ddwaf::object_limits limits; + limits.max_container_depth = 2; + ddwaf::ruleset_info::section_info section; + std::unordered_map rule_data_ids; + + auto rule_object = yaml_to_object( + R"([{id: 1, name: rule1, tags: {type: flow1, category: category1}, conditions: [{operator: "!match_regex", parameters: {inputs: [{address: arg1}, {address: arg2}], regex: .*}}]}])"); + + auto rule_array = static_cast(parameter(rule_object)); + EXPECT_EQ(rule_array.size(), 1); + + auto rules = parser::v2::parse_rules(rule_array, section, rule_data_ids, limits); + ddwaf_object_free(&rule_object); + + { + ddwaf::parameter root; + section.to_object(root); + + auto root_map = static_cast(root); + + auto loaded = ddwaf::parser::at(root_map, "loaded"); + EXPECT_EQ(loaded.size(), 0); + + auto failed = ddwaf::parser::at(root_map, "failed"); + EXPECT_EQ(failed.size(), 1); + EXPECT_NE(failed.find("1"), failed.end()); + + auto errors = ddwaf::parser::at(root_map, "errors"); + EXPECT_EQ(errors.size(), 1); + auto it = errors.find("multiple targets for non-variadic argument"); + EXPECT_NE(it, errors.end()); + + auto error_rules = static_cast(it->second); + EXPECT_EQ(error_rules.size(), 1); + EXPECT_NE(error_rules.find("1"), error_rules.end()); + + ddwaf_object_free(&root); + } + + EXPECT_EQ(rules.size(), 0); +} + } // namespace diff --git a/tests/test_utils.hpp b/tests/test_utils.hpp index 895b91852..5af1bb502 100644 --- a/tests/test_utils.hpp +++ b/tests/test_utils.hpp @@ -66,21 +66,32 @@ class expression_builder { void start_condition() { arguments_.clear(); } - template + template void end_condition(Args... args) requires std::is_base_of_v { - conditions_.emplace_back( - std::make_unique(std::make_unique(std::forward(args)...), - std::string{}, std::move(arguments_))); + if constexpr (Expected) { + conditions_.emplace_back( + std::make_unique(std::make_unique(std::forward(args)...), + std::string{}, std::move(arguments_))); + } else { + conditions_.emplace_back(std::make_unique( + std::make_unique(std::forward(args)...), std::string{}, + std::move(arguments_))); + } } - template + template void end_condition_with_data(std::string data_id) requires std::is_base_of_v { - conditions_.emplace_back(std::make_unique( - std::unique_ptr{}, std::move(data_id), std::move(arguments_))); + if constexpr (Expected) { + conditions_.emplace_back(std::make_unique( + std::unique_ptr{}, std::move(data_id), std::move(arguments_))); + } else { + conditions_.emplace_back(std::make_unique( + std::unique_ptr{}, std::move(data_id), std::move(arguments_))); + } } template diff --git a/validator/runner.cpp b/validator/runner.cpp index 346444edc..55f631b47 100644 --- a/validator/runner.cpp +++ b/validator/runner.cpp @@ -224,7 +224,9 @@ void test_runner::validate_matches(const YAML::Node &expected, const YAML::Node expect(expected.size(), obtained.size()); static std::set> scalar_operators{"match_regex", "phrase_match", - "exact_match", "ip_match", "equals", "is_sqli", "is_xss", "greater_than", "lower_than"}; + "exact_match", "ip_match", "equals", "is_sqli", "is_xss", "exists", "greater_than", + "lower_than", "!match_regex", "!phrase_match", "!exact_match", "!ip_match", "!equals", + "!is_sqli", "!is_xss", "!exists"}; // Iterate through matches, assume they are in the same order as rule // conditions for now. @@ -240,7 +242,9 @@ void test_runner::validate_matches(const YAML::Node &expected, const YAML::Node if (expected_match["key_path"].IsDefined()) { expect(expected_match["key_path"], obtained_match["key_path"]); } - expect(expected_match["value"], obtained_match["value"]); + if (expected_match["value"].IsDefined()) { + expect(expected_match["value"], obtained_match["value"]); + } } else { for (YAML::const_iterator it = expected_match.begin(); it != expected_match.end(); ++it) { @@ -255,7 +259,9 @@ void test_runner::validate_matches(const YAML::Node &expected, const YAML::Node if (expected_param["key_path"].IsDefined()) { expect(expected_param["key_path"], obtained_param["key_path"]); } - expect(expected_param["value"], obtained_param["value"]); + if (expected_param["value"].IsDefined()) { + expect(expected_param["value"], obtained_param["value"]); + } } } } diff --git a/validator/tests/rules/operators/equals/002_rule1_string_equals_no_match.yaml b/validator/tests/rules/operators/equals/002_rule1_string_equals_no_match.yaml new file mode 100644 index 000000000..6737268fc --- /dev/null +++ b/validator/tests/rules/operators/equals/002_rule1_string_equals_no_match.yaml @@ -0,0 +1,11 @@ +{ + name: "Basic run with equals operator", + runs: [ + { + persistent-input: { + rule1-input: "arachn" + }, + code: ok + } + ] +} diff --git a/validator/tests/rules/operators/equals/002_rule2_bool_equals.yaml b/validator/tests/rules/operators/equals/003_rule2_bool_equals.yaml similarity index 100% rename from validator/tests/rules/operators/equals/002_rule2_bool_equals.yaml rename to validator/tests/rules/operators/equals/003_rule2_bool_equals.yaml diff --git a/validator/tests/rules/operators/equals/004_rule2_bool_equals_no_match.yaml b/validator/tests/rules/operators/equals/004_rule2_bool_equals_no_match.yaml new file mode 100644 index 000000000..921fa7073 --- /dev/null +++ b/validator/tests/rules/operators/equals/004_rule2_bool_equals_no_match.yaml @@ -0,0 +1,11 @@ +{ + name: "Basic run with equals operator", + runs: [ + { + persistent-input: { + rule2-input: true + }, + code: ok + } + ] +} diff --git a/validator/tests/rules/operators/equals/003_rule3_signed_equals.yaml b/validator/tests/rules/operators/equals/005_rule3_signed_equals.yaml similarity index 100% rename from validator/tests/rules/operators/equals/003_rule3_signed_equals.yaml rename to validator/tests/rules/operators/equals/005_rule3_signed_equals.yaml diff --git a/validator/tests/rules/operators/equals/006_rule3_signed_equals_no_match.yaml b/validator/tests/rules/operators/equals/006_rule3_signed_equals_no_match.yaml new file mode 100644 index 000000000..4307aa66d --- /dev/null +++ b/validator/tests/rules/operators/equals/006_rule3_signed_equals_no_match.yaml @@ -0,0 +1,11 @@ +{ + name: "Basic run with equals operator", + runs: [ + { + persistent-input: { + rule3-input: 42 + }, + code: ok + } + ] +} diff --git a/validator/tests/rules/operators/equals/004_rule4_unsigned_equals.yaml b/validator/tests/rules/operators/equals/007_rule4_unsigned_equals.yaml similarity index 100% rename from validator/tests/rules/operators/equals/004_rule4_unsigned_equals.yaml rename to validator/tests/rules/operators/equals/007_rule4_unsigned_equals.yaml diff --git a/validator/tests/rules/operators/equals/008_rule4_unsigned_equals_no_match.yaml b/validator/tests/rules/operators/equals/008_rule4_unsigned_equals_no_match.yaml new file mode 100644 index 000000000..408d1a959 --- /dev/null +++ b/validator/tests/rules/operators/equals/008_rule4_unsigned_equals_no_match.yaml @@ -0,0 +1,11 @@ +{ + name: "Basic run with equals operator", + runs: [ + { + persistent-input: { + rule4-input: 43 + }, + code: ok + } + ] +} diff --git a/validator/tests/rules/operators/equals/005_rule5_double_equals.yaml b/validator/tests/rules/operators/equals/009_rule5_double_equals.yaml similarity index 100% rename from validator/tests/rules/operators/equals/005_rule5_double_equals.yaml rename to validator/tests/rules/operators/equals/009_rule5_double_equals.yaml diff --git a/validator/tests/rules/operators/equals/010_rule5_double_equals_no_match.yaml b/validator/tests/rules/operators/equals/010_rule5_double_equals_no_match.yaml new file mode 100644 index 000000000..5caa88f5c --- /dev/null +++ b/validator/tests/rules/operators/equals/010_rule5_double_equals_no_match.yaml @@ -0,0 +1,11 @@ +{ + name: "Basic run with equals operator", + runs: [ + { + persistent-input: { + rule5-input: 4.3 + }, + code: ok + } + ] +} diff --git a/validator/tests/rules/operators/equals/011_rule6_string_not_equals.yaml b/validator/tests/rules/operators/equals/011_rule6_string_not_equals.yaml new file mode 100644 index 000000000..0f7b36f03 --- /dev/null +++ b/validator/tests/rules/operators/equals/011_rule6_string_not_equals.yaml @@ -0,0 +1,21 @@ +{ + name: "Basic run with not equals operator", + runs: [ + { + persistent-input: { + rule6-input: "arachn" + }, + rules: [ + { + 6: [ + { + address: rule6-input, + value: "arachn" + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/operators/equals/012_rule6_string_not_equals_no_match.yaml b/validator/tests/rules/operators/equals/012_rule6_string_not_equals_no_match.yaml new file mode 100644 index 000000000..f21fff3db --- /dev/null +++ b/validator/tests/rules/operators/equals/012_rule6_string_not_equals_no_match.yaml @@ -0,0 +1,11 @@ +{ + name: "Basic run with not equals operator and no match", + runs: [ + { + persistent-input: { + rule6-input: "arachni" + }, + code: ok + } + ] +} diff --git a/validator/tests/rules/operators/equals/013_rule7_bool_not_equals.yaml b/validator/tests/rules/operators/equals/013_rule7_bool_not_equals.yaml new file mode 100644 index 000000000..741ddb699 --- /dev/null +++ b/validator/tests/rules/operators/equals/013_rule7_bool_not_equals.yaml @@ -0,0 +1,21 @@ +{ + name: "Basic run with not equals operator", + runs: [ + { + persistent-input: { + rule7-input: true + }, + rules: [ + { + 7: [ + { + address: rule7-input, + value: "true" + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/operators/equals/014_rule7_bool_not_equals_no_match.yaml b/validator/tests/rules/operators/equals/014_rule7_bool_not_equals_no_match.yaml new file mode 100644 index 000000000..a242c0014 --- /dev/null +++ b/validator/tests/rules/operators/equals/014_rule7_bool_not_equals_no_match.yaml @@ -0,0 +1,11 @@ +{ + name: "Basic run with not equals operator and no match", + runs: [ + { + persistent-input: { + rule7-input: false + }, + code: ok + } + ] +} diff --git a/validator/tests/rules/operators/equals/ruleset.yaml b/validator/tests/rules/operators/equals/ruleset.yaml index 167854187..f059d1768 100644 --- a/validator/tests/rules/operators/equals/ruleset.yaml +++ b/validator/tests/rules/operators/equals/ruleset.yaml @@ -60,3 +60,63 @@ rules: - address: rule5-input type: float value: 4.2 + - id: "6" + name: rule6-string-not-equals + tags: + type: flow1 + category: category + conditions: + - operator: "!equals" + parameters: + inputs: + - address: rule6-input + type: string + value: arachni + - id: "7" + name: rule7-bool-not-equals + tags: + type: flow1 + category: category + conditions: + - operator: "!equals" + parameters: + inputs: + - address: rule7-input + type: boolean + value: false + - id: "8" + name: rule8-signed-not-equals + tags: + type: flow1 + category: category + conditions: + - operator: "!equals" + parameters: + inputs: + - address: rule8-input + type: signed + value: -42 + - id: "9" + name: rule9-unsigned-not-equals + tags: + type: flow1 + category: category + conditions: + - operator: "!equals" + parameters: + inputs: + - address: rule9-input + type: unsigned + value: 42 + - id: "10" + name: rule10-float-not-equals + tags: + type: flow1 + category: category + conditions: + - operator: "!equals" + parameters: + inputs: + - address: rule10-input + type: float + value: 4.2 diff --git a/validator/tests/rules/operators/exact_match/003_rule2_negated_exact_match.yaml b/validator/tests/rules/operators/exact_match/003_rule2_negated_exact_match.yaml new file mode 100644 index 000000000..8c5fcbe12 --- /dev/null +++ b/validator/tests/rules/operators/exact_match/003_rule2_negated_exact_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Basic run with negated exact_match operator", + runs: [ + { + persistent-input: { + rule2-input: "something else or other" + }, + rules: [ + { + 2: [ + { + address: rule2-input, + value: "something else or other" + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/operators/exact_match/004_rule2_negated_exact_match_no_match.yaml b/validator/tests/rules/operators/exact_match/004_rule2_negated_exact_match_no_match.yaml new file mode 100644 index 000000000..006d0930c --- /dev/null +++ b/validator/tests/rules/operators/exact_match/004_rule2_negated_exact_match_no_match.yaml @@ -0,0 +1,11 @@ +{ + name: "Basic run with negated exact_match operator and no match", + runs: [ + { + persistent-input: { + rule2-input: "something else" + }, + code: ok + } + ] +} diff --git a/validator/tests/rules/operators/exact_match/ruleset.yaml b/validator/tests/rules/operators/exact_match/ruleset.yaml index a9f92ec8b..bd9f89d40 100644 --- a/validator/tests/rules/operators/exact_match/ruleset.yaml +++ b/validator/tests/rules/operators/exact_match/ruleset.yaml @@ -1,7 +1,7 @@ version: '2.1' rules: - id: "1" - name: rule1-ip-match + name: rule1-exact-match tags: type: flow1 category: category @@ -14,3 +14,17 @@ rules: - "string" - "other" - "something else" + - id: "2" + name: rule2-negated-exact-match + tags: + type: flow2 + category: category + conditions: + - operator: "!exact_match" + parameters: + inputs: + - address: rule2-input + list: + - "string" + - "other" + - "something else" diff --git a/validator/tests/rules/operators/exists/ruleset.yaml b/validator/tests/rules/operators/exists/ruleset.yaml index d546ac318..2eecbe752 100644 --- a/validator/tests/rules/operators/exists/ruleset.yaml +++ b/validator/tests/rules/operators/exists/ruleset.yaml @@ -22,7 +22,7 @@ rules: - address: rule2-input key_path: ["path", "to", "object"] - id: "3" - name: rule3-does-not-exist-keypath + name: rule3-negated-exists-keypath tags: type: flow3 category: category @@ -33,7 +33,7 @@ rules: - address: rule3-input key_path: ["path", "to", "object"] - id: "4" - name: rule4-does-not-exist-invalid + name: rule4-negated-exists-invalid tags: type: flow4 category: category diff --git a/validator/tests/rules/operators/ip_match/009-rule3_ipv4_cidr_match.yaml b/validator/tests/rules/operators/ip_match/009-rule3_ipv4_cidr_match.yaml new file mode 100644 index 000000000..6b87e9dc3 --- /dev/null +++ b/validator/tests/rules/operators/ip_match/009-rule3_ipv4_cidr_match.yaml @@ -0,0 +1,21 @@ +{ + name: "negated ip_match operator, match IPv4 CIDR", + runs: [ + { + persistent-input: { + rule3-input: 192.187.25.1 + }, + rules: [ + { + 3: [ + { + address: rule3-input, + value: 192.187.25.1 + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/operators/ip_match/010_rule3_ipv6_cidr_match.yaml b/validator/tests/rules/operators/ip_match/010_rule3_ipv6_cidr_match.yaml new file mode 100644 index 000000000..f887f7992 --- /dev/null +++ b/validator/tests/rules/operators/ip_match/010_rule3_ipv6_cidr_match.yaml @@ -0,0 +1,21 @@ +{ + name: "negated ip_match operator, match IPv6 CIDR", + runs: [ + { + persistent-input: { + rule3-input: "abce::1234:0:ab11:0" + }, + rules: [ + { + 3: [ + { + address: rule3-input, + value: "abce::1234:0:ab11:0" + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/operators/ip_match/011_rule3_ipv4_cidr_no_match.yaml b/validator/tests/rules/operators/ip_match/011_rule3_ipv4_cidr_no_match.yaml new file mode 100644 index 000000000..e9ad83129 --- /dev/null +++ b/validator/tests/rules/operators/ip_match/011_rule3_ipv4_cidr_no_match.yaml @@ -0,0 +1,11 @@ +{ + name: "negated ip_match operator, no match on IPv4 CIDR", + runs: [ + { + persistent-input: { + rule3-input: 192.188.25.1 + }, + code: ok + } + ] +} diff --git a/validator/tests/rules/operators/ip_match/012_rule3_ipv6_cidr_no_match.yaml b/validator/tests/rules/operators/ip_match/012_rule3_ipv6_cidr_no_match.yaml new file mode 100644 index 000000000..d8b3fb0ef --- /dev/null +++ b/validator/tests/rules/operators/ip_match/012_rule3_ipv6_cidr_no_match.yaml @@ -0,0 +1,11 @@ +{ + name: "negated ip_match operator, no match on IPv6 CIDR", + runs: [ + { + persistent-input: { + rule3-input: "abcd::1234:0:ab11:0" + }, + code: ok + } + ] +} diff --git a/validator/tests/rules/operators/ip_match/ruleset.yaml b/validator/tests/rules/operators/ip_match/ruleset.yaml index 9d64896fd..832382df0 100644 --- a/validator/tests/rules/operators/ip_match/ruleset.yaml +++ b/validator/tests/rules/operators/ip_match/ruleset.yaml @@ -40,3 +40,16 @@ rules: list: - "192.188.0.0/16" - "abcd::1234:0:0:0/96" + - id: "3" + name: rule3-negated-ip-match + tags: + type: flow3 + category: category + conditions: + - operator: "!ip_match" + parameters: + inputs: + - address: rule3-input + list: + - "192.188.0.0/16" + - "abcd::1234:0:0:0/96" diff --git a/validator/tests/rules/operators/match_regex/001_rule1_match_regex.yaml b/validator/tests/rules/operators/match_regex/001_rule1_match_regex.yaml new file mode 100644 index 000000000..bdc843511 --- /dev/null +++ b/validator/tests/rules/operators/match_regex/001_rule1_match_regex.yaml @@ -0,0 +1,21 @@ +{ + name: "Basic run with match_regex operator", + runs: [ + { + persistent-input: { + rule1-input: "Arachni/v1" + }, + rules: [ + { + 1: [ + { + address: rule1-input, + value: "Arachni/v1" + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/operators/match_regex/002_rule1_match_regex_no_match.yaml b/validator/tests/rules/operators/match_regex/002_rule1_match_regex_no_match.yaml new file mode 100644 index 000000000..c164ce685 --- /dev/null +++ b/validator/tests/rules/operators/match_regex/002_rule1_match_regex_no_match.yaml @@ -0,0 +1,11 @@ +{ + name: "Basic run with match_regex operator and no match", + runs: [ + { + persistent-input: { + rule1-input: "something" + }, + code: ok + } + ] +} diff --git a/validator/tests/rules/operators/match_regex/003_rule2_negated_match_regex.yaml b/validator/tests/rules/operators/match_regex/003_rule2_negated_match_regex.yaml new file mode 100644 index 000000000..47b220f3f --- /dev/null +++ b/validator/tests/rules/operators/match_regex/003_rule2_negated_match_regex.yaml @@ -0,0 +1,21 @@ +{ + name: "Basic run with negated match_regex operator", + runs: [ + { + persistent-input: { + rule2-input: "something else or other" + }, + rules: [ + { + 2: [ + { + address: rule2-input, + value: "something else or other" + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/operators/match_regex/004_rule2_negated_match_regex_no_match.yaml b/validator/tests/rules/operators/match_regex/004_rule2_negated_match_regex_no_match.yaml new file mode 100644 index 000000000..e663cc7aa --- /dev/null +++ b/validator/tests/rules/operators/match_regex/004_rule2_negated_match_regex_no_match.yaml @@ -0,0 +1,11 @@ +{ + name: "Basic run with negated match_regex operator and no match", + runs: [ + { + persistent-input: { + rule2-input: "Arachni" + }, + code: ok + } + ] +} diff --git a/validator/tests/rules/operators/match_regex/ruleset.yaml b/validator/tests/rules/operators/match_regex/ruleset.yaml new file mode 100644 index 000000000..7ad9eb69c --- /dev/null +++ b/validator/tests/rules/operators/match_regex/ruleset.yaml @@ -0,0 +1,30 @@ +version: '2.1' +rules: + - id: "1" + name: rule1-match_regex + tags: + type: flow1 + category: category + conditions: + - operator: match_regex + parameters: + inputs: + - address: rule1-input + regex: "Arachni" + options: + min_length: 7 + case_sensitive: false + - id: "2" + name: rule2-ip-match + tags: + type: flow2 + category: category + conditions: + - operator: "!match_regex" + parameters: + inputs: + - address: rule2-input + regex: "Arachni" + options: + min_length: 7 + case_sensitive: false diff --git a/validator/tests/rules/operators/phrase_match/005_rule3_pm_match.yaml b/validator/tests/rules/operators/phrase_match/005_rule3_pm_match.yaml new file mode 100644 index 000000000..6e20de09b --- /dev/null +++ b/validator/tests/rules/operators/phrase_match/005_rule3_pm_match.yaml @@ -0,0 +1,21 @@ +{ + name: "Basic run with negated phrase_match", + runs: [ + { + persistent-input: { + rule3-input: "asjkdansdasdkjasndk" + }, + rules: [ + { + 3: [ + { + address: rule3-input, + value: "asjkdansdasdkjasndk", + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/operators/phrase_match/006_rule3_pm_no_match.yaml b/validator/tests/rules/operators/phrase_match/006_rule3_pm_no_match.yaml new file mode 100644 index 000000000..d3adf86da --- /dev/null +++ b/validator/tests/rules/operators/phrase_match/006_rule3_pm_no_match.yaml @@ -0,0 +1,11 @@ +{ + name: "Basic run with negated phrase_match, no match", + runs: [ + { + persistent-input: { + rule3-input: "asjkdansdstring00asdkjasndk" + }, + code: ok + } + ] +} diff --git a/validator/tests/rules/operators/phrase_match/ruleset.yaml b/validator/tests/rules/operators/phrase_match/ruleset.yaml index 7baf837a2..e6b834abe 100644 --- a/validator/tests/rules/operators/phrase_match/ruleset.yaml +++ b/validator/tests/rules/operators/phrase_match/ruleset.yaml @@ -28,3 +28,16 @@ rules: - string01 options: enforce_word_boundary: true + - id: "3" + name: rule3-negated-phrase-match + tags: + type: flow + category: category + conditions: + - operator: "!phrase_match" + parameters: + inputs: + - address: rule3-input + list: + - string00 + - string01