From ec70644bce93380c2831c35f27d5ac8cbc19f29f Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Thu, 15 Aug 2024 16:50:28 +0100 Subject: [PATCH 01/25] Extend exists operator to support key paths and negation --- cmake/objects.cmake | 1 + src/condition/exists.cpp | 121 ++++++++++++++++++ src/condition/exists.hpp | 32 +++-- src/parser/expression_parser.cpp | 20 ++- .../exists_condition_test.cpp | 32 +++++ ...ch.yaml => 002_rule1_exists_no_match.yaml} | 0 .../003_rule2_key_path_exists_match.yaml | 27 ++++ .../004_rule2_key_path_exists_no_match.yaml | 17 +++ ...5_rule3_key_path_exists_negated_match.yaml | 27 ++++ ...ule3_key_path_exists_negated_no_match.yaml | 17 +++ .../tests/rules/operators/exists/ruleset.yaml | 22 ++++ 11 files changed, 301 insertions(+), 15 deletions(-) create mode 100644 src/condition/exists.cpp rename tests/{operator => condition}/exists_condition_test.cpp (74%) rename validator/tests/rules/operators/exists/{002_rule1_no_exists_match.yaml => 002_rule1_exists_no_match.yaml} (100%) create mode 100644 validator/tests/rules/operators/exists/003_rule2_key_path_exists_match.yaml create mode 100644 validator/tests/rules/operators/exists/004_rule2_key_path_exists_no_match.yaml create mode 100644 validator/tests/rules/operators/exists/005_rule3_key_path_exists_negated_match.yaml create mode 100644 validator/tests/rules/operators/exists/006_rule3_key_path_exists_negated_no_match.yaml diff --git a/cmake/objects.cmake b/cmake/objects.cmake index 0c50f21f..efc8011d 100644 --- a/cmake/objects.cmake +++ b/cmake/objects.cmake @@ -47,6 +47,7 @@ set(LIBDDWAF_SOURCE ${libddwaf_SOURCE_DIR}/src/parser/exclusion_parser.cpp ${libddwaf_SOURCE_DIR}/src/processor/extract_schema.cpp ${libddwaf_SOURCE_DIR}/src/processor/fingerprint.cpp + ${libddwaf_SOURCE_DIR}/src/condition/exists.cpp ${libddwaf_SOURCE_DIR}/src/condition/lfi_detector.cpp ${libddwaf_SOURCE_DIR}/src/condition/sqli_detector.cpp ${libddwaf_SOURCE_DIR}/src/condition/ssrf_detector.cpp diff --git a/src/condition/exists.cpp b/src/condition/exists.cpp new file mode 100644 index 00000000..38ce0c6c --- /dev/null +++ b/src/condition/exists.cpp @@ -0,0 +1,121 @@ +// 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 "condition/exists.hpp" +#include "utils.hpp" + +namespace ddwaf { + +namespace { + +enum class search_outcome { found, not_found, unknown }; + +search_outcome exists(const ddwaf_object *root, std::span key_path, + const exclusion::object_set_ref &objects_excluded, const object_limits &limits) +{ + if (objects_excluded.contains(root) || key_path.size() > limits.max_container_depth) { + // The object might be present, but we can't know for sure + return search_outcome::unknown; + } + + if (key_path.empty()) { + return search_outcome::found; + } + + // Since there's a key path, the object must be a map + if (root->type != DDWAF_OBJ_MAP) { + return search_outcome::not_found; + } + + const ddwaf_object *parent = root; + auto it = key_path.begin(); + + std::size_t depth = 0; + std::size_t size = parent->nbEntries; + + for (std::size_t i = 0; i < size;) { + const auto &child = parent->array[i++]; + + if (child.parameterName == nullptr) [[unlikely]] { + continue; + } + std::string_view key{ + child.parameterName, static_cast(child.parameterNameLength)}; + + if (key == *it) { + if (objects_excluded.contains(&child)) { + // We found the next child but it has been excluded, so we + // can't know for sure if the required key path exists + return search_outcome::unknown; + } + + if (++it == key_path.end()) { + return search_outcome::found; + } + + if (child.type != DDWAF_OBJ_MAP) { + return search_outcome::not_found; + } + + if (++depth >= limits.max_container_depth) { + return search_outcome::unknown; + } + + // Reset the loop and iterate child + parent = &child; + i = 0; + size = std::min(static_cast(child.nbEntries), limits.max_container_size); + } + } + + return search_outcome::not_found; +} + +} // namespace + +[[nodiscard]] eval_result exists_condition::eval_impl( + const variadic_argument &inputs, condition_cache &cache, + const exclusion::object_set_ref &objects_excluded, ddwaf::timer &deadline) const +{ + if (inputs.empty()) { + return {false, false}; + } + // We only care about the first input + for (const auto &input : inputs) { + if (deadline.expired()) { + throw ddwaf::timeout_exception(); + } + + if (exists(input.value, input.key_path, objects_excluded, limits_) == + search_outcome::found) { + std::vector key_path{input.key_path.begin(), input.key_path.end()}; + cache.match = {{{{"input", {}, input.address, std::move(key_path)}}, {}, "exists", {}, + input.ephemeral}}; + return {true, input.ephemeral}; + } + } + return {false, false}; +} + +[[nodiscard]] eval_result exists_negated_condition::eval_impl( + const unary_argument &input, condition_cache &cache, + const exclusion::object_set_ref &objects_excluded, ddwaf::timer & /*deadline*/) const +{ + // We need to make sure the key path hasn't been found. If the result is + // unknown, we can't guarantee that the key path isn't actually present in + // the data set + if (exists(input.value, input.key_path, objects_excluded, limits_) != + search_outcome::not_found) { + return {false, false}; + } + + std::vector key_path{input.key_path.begin(), input.key_path.end()}; + cache.match = { + {{{"input", {}, input.address, std::move(key_path)}}, {}, "!exists", {}, input.ephemeral}}; + return {true, input.ephemeral}; +} + +} // namespace ddwaf diff --git a/src/condition/exists.hpp b/src/condition/exists.hpp index 74c5f0d8..c65078be 100644 --- a/src/condition/exists.hpp +++ b/src/condition/exists.hpp @@ -7,6 +7,8 @@ #pragma once #include "condition/structured_condition.hpp" +#include "exception.hpp" +#include "iterator.hpp" namespace ddwaf { @@ -21,19 +23,27 @@ class exists_condition : public base_impl { protected: [[nodiscard]] eval_result eval_impl(const variadic_argument &inputs, - condition_cache &cache, const exclusion::object_set_ref & /*objects_excluded*/, - ddwaf::timer & /*deadline*/) const - { - if (inputs.empty()) { - return {false, false}; - } - // We only care about the first input - auto input = inputs.front(); - cache.match = {{{{"input", {}, input.address, {}}}, {}, "exists", {}, input.ephemeral}}; - return {true, input.ephemeral}; - } + condition_cache &cache, const exclusion::object_set_ref &objects_excluded, + ddwaf::timer &deadline) const; friend class base_impl; }; +class exists_negated_condition : public base_impl { +public: + static constexpr std::array param_names{"inputs"}; + + explicit exists_negated_condition( + std::vector args, const object_limits &limits = {}) + : base_impl(std::move(args), limits) + {} + +protected: + [[nodiscard]] eval_result eval_impl(const unary_argument &input, + condition_cache &cache, const exclusion::object_set_ref &objects_excluded, + ddwaf::timer & /*deadline*/) const; + + friend class base_impl; +}; + } // namespace ddwaf diff --git a/src/parser/expression_parser.cpp b/src/parser/expression_parser.cpp index b15990e6..dd8b8317 100644 --- a/src/parser/expression_parser.cpp +++ b/src/parser/expression_parser.cpp @@ -99,6 +99,11 @@ std::shared_ptr parse_expression(const parameter::vector &conditions auto operator_name = at(root, "operator"); auto params = at(root, "parameters"); + bool negated = operator_name.starts_with("!"); + if (negated) { + operator_name = operator_name.substr(1); + } + if (operator_name == "lfi_detector") { auto arguments = parse_arguments(params, source, transformers, addresses, limits); @@ -116,10 +121,17 @@ std::shared_ptr parse_expression(const parameter::vector &conditions parse_arguments(params, source, transformers, addresses, limits); conditions.emplace_back(std::make_unique(std::move(arguments), limits)); } else if (operator_name == "exists") { - auto arguments = - parse_arguments(params, source, transformers, addresses, limits); - conditions.emplace_back( - std::make_unique(std::move(arguments), limits)); + if (negated) { + auto arguments = parse_arguments( + params, source, transformers, addresses, limits); + conditions.emplace_back( + std::make_unique(std::move(arguments), limits)); + } else { + auto arguments = parse_arguments( + params, source, transformers, addresses, limits); + conditions.emplace_back( + std::make_unique(std::move(arguments), limits)); + } } else { auto [data_id, matcher] = parse_matcher(operator_name, params); diff --git a/tests/operator/exists_condition_test.cpp b/tests/condition/exists_condition_test.cpp similarity index 74% rename from tests/operator/exists_condition_test.cpp rename to tests/condition/exists_condition_test.cpp index c238cc51..11f6d9d3 100644 --- a/tests/operator/exists_condition_test.cpp +++ b/tests/condition/exists_condition_test.cpp @@ -36,6 +36,38 @@ TEST(TestExistsCondition, AddressAvailable) ASSERT_TRUE(res.outcome); } +TEST(TestExistsCondition, KeyPathAvailable) +{ + exists_condition cond{{{{{{"server.request.uri_raw", get_target_index("server.request.uri_raw"), + {"path", "to", "object"}}}}}}}; + + ddwaf_object tmp; + ddwaf_object path; + ddwaf_object to; + ddwaf_object object; + + ddwaf_object_map(&object); + ddwaf_object_map_add(&object, "object", ddwaf_object_invalid(&tmp)); + + ddwaf_object_map(&to); + ddwaf_object_map_add(&to, "to", &object); + + ddwaf_object_map(&path); + ddwaf_object_map_add(&path, "path", &to); + + ddwaf_object root; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "server.request.uri_raw", &path); + + object_store store; + store.insert(root); + + ddwaf::timer deadline{2s}; + condition_cache cache; + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_TRUE(res.outcome); +} + TEST(TestExistsCondition, AddressNotAvaialble) { exists_condition cond{{gen_variadic_param("server.request.uri_raw")}}; diff --git a/validator/tests/rules/operators/exists/002_rule1_no_exists_match.yaml b/validator/tests/rules/operators/exists/002_rule1_exists_no_match.yaml similarity index 100% rename from validator/tests/rules/operators/exists/002_rule1_no_exists_match.yaml rename to validator/tests/rules/operators/exists/002_rule1_exists_no_match.yaml diff --git a/validator/tests/rules/operators/exists/003_rule2_key_path_exists_match.yaml b/validator/tests/rules/operators/exists/003_rule2_key_path_exists_match.yaml new file mode 100644 index 00000000..61320bb9 --- /dev/null +++ b/validator/tests/rules/operators/exists/003_rule2_key_path_exists_match.yaml @@ -0,0 +1,27 @@ +{ + name: "Basic run with exists operator with key_path", + runs: [ + { + persistent-input: { + rule2-input: { + "path": { + "to": { + "object": "something else" + } + } + } + }, + rules: [ + { + 2: [ + { + address: rule2-input, + key_path: ["path", "to", "object"] + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/operators/exists/004_rule2_key_path_exists_no_match.yaml b/validator/tests/rules/operators/exists/004_rule2_key_path_exists_no_match.yaml new file mode 100644 index 00000000..50ffca65 --- /dev/null +++ b/validator/tests/rules/operators/exists/004_rule2_key_path_exists_no_match.yaml @@ -0,0 +1,17 @@ +{ + name: "Basic run with exists operator with key_path and no match", + runs: [ + { + persistent-input: { + rule2-input: { + "path": { + "to": { + "nowhere": "something else" + } + } + } + }, + code: ok + } + ] +} diff --git a/validator/tests/rules/operators/exists/005_rule3_key_path_exists_negated_match.yaml b/validator/tests/rules/operators/exists/005_rule3_key_path_exists_negated_match.yaml new file mode 100644 index 00000000..9524e627 --- /dev/null +++ b/validator/tests/rules/operators/exists/005_rule3_key_path_exists_negated_match.yaml @@ -0,0 +1,27 @@ +{ + name: "Basic run with !exists operator with key_path", + runs: [ + { + persistent-input: { + rule3-input: { + "path": { + "to": { + "nowhere": "something else" + } + } + } + }, + rules: [ + { + 3: [ + { + address: rule3-input, + key_path: ["path", "to", "object"] + } + ] + } + ], + code: match + } + ] +} diff --git a/validator/tests/rules/operators/exists/006_rule3_key_path_exists_negated_no_match.yaml b/validator/tests/rules/operators/exists/006_rule3_key_path_exists_negated_no_match.yaml new file mode 100644 index 00000000..e2005b6f --- /dev/null +++ b/validator/tests/rules/operators/exists/006_rule3_key_path_exists_negated_no_match.yaml @@ -0,0 +1,17 @@ +{ + name: "Basic run with !exists operator with key_path and no match", + runs: [ + { + persistent-input: { + rule3-input: { + "path": { + "to": { + "object": "something else" + } + } + } + }, + code: ok + } + ] +} diff --git a/validator/tests/rules/operators/exists/ruleset.yaml b/validator/tests/rules/operators/exists/ruleset.yaml index fe598250..b96a2be5 100644 --- a/validator/tests/rules/operators/exists/ruleset.yaml +++ b/validator/tests/rules/operators/exists/ruleset.yaml @@ -10,3 +10,25 @@ rules: parameters: inputs: - address: rule1-input + - id: "2" + name: rule2-exists-keypath + tags: + type: flow2 + category: category + conditions: + - operator: exists + parameters: + inputs: + - address: rule2-input + key_path: ["path", "to", "object"] + - id: "3" + name: rule3-does-not-exist-keypath + tags: + type: flow3 + category: category + conditions: + - operator: "!exists" + parameters: + inputs: + - address: rule3-input + key_path: ["path", "to", "object"] From f6a656da15dbfba0caf3ccbc084812b5652d337e Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Thu, 15 Aug 2024 17:31:51 +0100 Subject: [PATCH 02/25] Add tests --- tests/condition/exists_condition_test.cpp | 73 +++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/tests/condition/exists_condition_test.cpp b/tests/condition/exists_condition_test.cpp index 11f6d9d3..2bbb6864 100644 --- a/tests/condition/exists_condition_test.cpp +++ b/tests/condition/exists_condition_test.cpp @@ -86,6 +86,34 @@ TEST(TestExistsCondition, AddressNotAvaialble) ASSERT_FALSE(res.outcome); } +TEST(TestExistsCondition, KeyPathNotAvailable) +{ + exists_condition cond{{{{{{"server.request.uri_raw", get_target_index("server.request.uri_raw"), + {"path", "to", "object"}}}}}}}; + + ddwaf_object tmp; + ddwaf_object path; + ddwaf_object to; + + ddwaf_object_map(&to); + ddwaf_object_map_add(&to, "to", ddwaf_object_invalid(&tmp)); + + ddwaf_object_map(&path); + ddwaf_object_map_add(&path, "path", &to); + + ddwaf_object root; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "server.request.uri_raw", &path); + + object_store store; + store.insert(root); + + ddwaf::timer deadline{2s}; + condition_cache cache; + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_FALSE(res.outcome); +} + TEST(TestExistsCondition, MultipleAddresses) { exists_condition cond{ @@ -113,4 +141,49 @@ TEST(TestExistsCondition, MultipleAddresses) validate_address("usr.session_id", false); } +TEST(TestExistsCondition, MultipleAddressesAndKeyPaths) +{ + exists_condition cond{{{{{"server.request.uri_raw", get_target_index("server.request.uri_raw"), + {"path", "to", "object"}}, + {"usr.id", get_target_index("usr.id")}, + {"server.request.body", get_target_index("server.request.body"), {"key"}}}}}}; + + auto validate_address = [&](const std::string &address, const std::vector &kp, + bool expected = true) { + ddwaf_object tmp; + ddwaf_object root; + ddwaf_object_map(&root); + ddwaf_object_invalid(&tmp); + + for (auto it = kp.rbegin(); it != kp.rend(); ++it) { + ddwaf_object path; + ddwaf_object_map(&path); + ddwaf_object_map_add(&path, it->c_str(), &tmp); + + tmp = path; + } + + ddwaf_object_map_add(&root, address.c_str(), &tmp); + + object_store store; + store.insert(root); + + ddwaf::timer deadline{2s}; + condition_cache cache; + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_EQ(res.outcome, expected); + }; + + validate_address("usr.id", {}); + validate_address("usr.id", {"whatever"}); + validate_address("server.request.uri_raw", {"path", "to", "object"}); + validate_address("server.request.body", {"key"}); + validate_address("server.request.body", {}, false); + validate_address("server.request.uri_raw", {"path", "to"}, false); + validate_address("server.request.uri_raw", {"path"}, false); + validate_address("server.request.uri_raw", {}, false); + validate_address("server.request.query", {}, false); + validate_address("usr.session_id", {}, false); +} + } // namespace From 039d73d5a6ccd536e9042521423f0235c6ebd81f Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Fri, 16 Aug 2024 09:46:21 +0100 Subject: [PATCH 03/25] More tests --- tests/condition/exists_condition_test.cpp | 61 +++++++++++++++++++ ...ilable_exists_negated_inconsequential.yaml | 17 ++++++ ...ilable_exists_negated_inconsequential.yaml | 14 +++++ .../tests/rules/operators/exists/ruleset.yaml | 10 +++ 4 files changed, 102 insertions(+) create mode 100644 validator/tests/rules/operators/exists/007_rule4_address_available_exists_negated_inconsequential.yaml create mode 100644 validator/tests/rules/operators/exists/008_rule4_address_unavailable_exists_negated_inconsequential.yaml diff --git a/tests/condition/exists_condition_test.cpp b/tests/condition/exists_condition_test.cpp index 2bbb6864..37d7cb37 100644 --- a/tests/condition/exists_condition_test.cpp +++ b/tests/condition/exists_condition_test.cpp @@ -155,6 +155,7 @@ TEST(TestExistsCondition, MultipleAddressesAndKeyPaths) ddwaf_object_map(&root); ddwaf_object_invalid(&tmp); + // NOLINTNEXTLINE(modernize-loop-convert) for (auto it = kp.rbegin(); it != kp.rend(); ++it) { ddwaf_object path; ddwaf_object_map(&path); @@ -186,4 +187,64 @@ TEST(TestExistsCondition, MultipleAddressesAndKeyPaths) validate_address("usr.session_id", {}, false); } +TEST(TestExistsNegatedCondition, KeyPathAvailable) +{ + exists_negated_condition cond{{{{{{"server.request.uri_raw", + get_target_index("server.request.uri_raw"), {"path", "to", "object"}}}}}}}; + + ddwaf_object tmp; + ddwaf_object path; + ddwaf_object to; + ddwaf_object object; + + ddwaf_object_map(&object); + ddwaf_object_map_add(&object, "object", ddwaf_object_invalid(&tmp)); + + ddwaf_object_map(&to); + ddwaf_object_map_add(&to, "to", &object); + + ddwaf_object_map(&path); + ddwaf_object_map_add(&path, "path", &to); + + ddwaf_object root; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "server.request.uri_raw", &path); + + object_store store; + store.insert(root); + + ddwaf::timer deadline{2s}; + condition_cache cache; + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_FALSE(res.outcome); +} + +TEST(TestExistsNegatedCondition, KeyPathNotAvailable) +{ + exists_negated_condition cond{{{{{{"server.request.uri_raw", + get_target_index("server.request.uri_raw"), {"path", "to", "object"}}}}}}}; + + ddwaf_object tmp; + ddwaf_object path; + ddwaf_object to; + + ddwaf_object_map(&to); + ddwaf_object_map_add(&to, "to", ddwaf_object_invalid(&tmp)); + + ddwaf_object_map(&path); + ddwaf_object_map_add(&path, "path", &to); + + ddwaf_object root; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "server.request.uri_raw", &path); + + object_store store; + store.insert(root); + + ddwaf::timer deadline{2s}; + condition_cache cache; + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_TRUE(res.outcome); +} + } // namespace diff --git a/validator/tests/rules/operators/exists/007_rule4_address_available_exists_negated_inconsequential.yaml b/validator/tests/rules/operators/exists/007_rule4_address_available_exists_negated_inconsequential.yaml new file mode 100644 index 00000000..f52ee1c1 --- /dev/null +++ b/validator/tests/rules/operators/exists/007_rule4_address_available_exists_negated_inconsequential.yaml @@ -0,0 +1,17 @@ +{ + name: "Validate that the !exists operator without key path doesn't match when an address is present", + runs: [ + { + persistent-input: { + rule4-input: { + "path": { + "to": { + "nowhere": "something else" + } + } + } + }, + code: ok + } + ] +} diff --git a/validator/tests/rules/operators/exists/008_rule4_address_unavailable_exists_negated_inconsequential.yaml b/validator/tests/rules/operators/exists/008_rule4_address_unavailable_exists_negated_inconsequential.yaml new file mode 100644 index 00000000..514546ce --- /dev/null +++ b/validator/tests/rules/operators/exists/008_rule4_address_unavailable_exists_negated_inconsequential.yaml @@ -0,0 +1,14 @@ +{ + name: "Validate that the !exists operator without key path doesn't match when an address + isn't present Note that this is due to the fact that", + runs: [ + { + persistent-input: { + random-input: { + "nowhere": "something else" + } + }, + code: ok + } + ] +} diff --git a/validator/tests/rules/operators/exists/ruleset.yaml b/validator/tests/rules/operators/exists/ruleset.yaml index b96a2be5..d546ac31 100644 --- a/validator/tests/rules/operators/exists/ruleset.yaml +++ b/validator/tests/rules/operators/exists/ruleset.yaml @@ -32,3 +32,13 @@ rules: inputs: - address: rule3-input key_path: ["path", "to", "object"] + - id: "4" + name: rule4-does-not-exist-invalid + tags: + type: flow4 + category: category + conditions: + - operator: "!exists" + parameters: + inputs: + - address: rule4-input From 758ce05fe1ef21ceff370d382cad8409dfa9de2b Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Fri, 16 Aug 2024 10:42:40 +0100 Subject: [PATCH 04/25] More tests and improvements --- src/condition/exists.cpp | 11 +--- src/parser/expression_parser.cpp | 4 ++ tests/condition/exists_condition_test.cpp | 78 +++++++++++++++++++++++ tests/parser_v2_rules_test.cpp | 44 +++++++++++++ 4 files changed, 127 insertions(+), 10 deletions(-) diff --git a/src/condition/exists.cpp b/src/condition/exists.cpp index 38ce0c6c..47e44be9 100644 --- a/src/condition/exists.cpp +++ b/src/condition/exists.cpp @@ -16,11 +16,6 @@ enum class search_outcome { found, not_found, unknown }; search_outcome exists(const ddwaf_object *root, std::span key_path, const exclusion::object_set_ref &objects_excluded, const object_limits &limits) { - if (objects_excluded.contains(root) || key_path.size() > limits.max_container_depth) { - // The object might be present, but we can't know for sure - return search_outcome::unknown; - } - if (key_path.empty()) { return search_outcome::found; } @@ -60,7 +55,7 @@ search_outcome exists(const ddwaf_object *root, std::span key return search_outcome::not_found; } - if (++depth >= limits.max_container_depth) { + if (++depth >= limits.max_container_depth) [[unlikely]] { return search_outcome::unknown; } @@ -80,10 +75,6 @@ search_outcome exists(const ddwaf_object *root, std::span key const variadic_argument &inputs, condition_cache &cache, const exclusion::object_set_ref &objects_excluded, ddwaf::timer &deadline) const { - if (inputs.empty()) { - return {false, false}; - } - // We only care about the first input for (const auto &input : inputs) { if (deadline.expired()) { throw ddwaf::timeout_exception(); diff --git a/src/parser/expression_parser.cpp b/src/parser/expression_parser.cpp index dd8b8317..00abc05d 100644 --- a/src/parser/expression_parser.cpp +++ b/src/parser/expression_parser.cpp @@ -57,6 +57,10 @@ std::vector parse_arguments(const parameter::map ¶ms, d } auto kp = at>(input, "key_path", {}); + if (kp.size() > limits.max_container_depth) { + throw ddwaf::parsing_error("key_path beyond maximum container depth"); + } + for (const auto &path : kp) { if (path.empty()) { throw ddwaf::parsing_error("empty key_path"); diff --git a/tests/condition/exists_condition_test.cpp b/tests/condition/exists_condition_test.cpp index 37d7cb37..416ec4ca 100644 --- a/tests/condition/exists_condition_test.cpp +++ b/tests/condition/exists_condition_test.cpp @@ -114,6 +114,47 @@ TEST(TestExistsCondition, KeyPathNotAvailable) ASSERT_FALSE(res.outcome); } +TEST(TestExistsCondition, KeyPathAvailableButExcluded) +{ + exists_condition cond{{{{{{"server.request.uri_raw", get_target_index("server.request.uri_raw"), + {"path", "to", "object"}}}}}}}; + + ddwaf_object tmp; + ddwaf_object path; + ddwaf_object to; + ddwaf_object object; + + ddwaf_object_map(&object); + ddwaf_object_map_add(&object, "object", ddwaf_object_invalid(&tmp)); + + ddwaf_object_map(&to); + ddwaf_object_map_add(&to, "to", &object); + + ddwaf_object_map(&path); + ddwaf_object_map_add(&path, "path", &to); + + ddwaf_object root; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "server.request.uri_raw", &path); + + object_store store; + store.insert(root); + + ddwaf::timer deadline{2s}; + condition_cache cache; + + std::unordered_set excluded = {&root.array[0]}; + + exclusion::object_set_ref excluded_ref; + excluded_ref.persistent = excluded; + + // While the key path is present, since part of the path was excluded + // the evaluation fails to determine the presence of the full key path, + // for that reason, no match is generated. + auto res = cond.eval(cache, store, excluded_ref, {}, deadline); + ASSERT_FALSE(res.outcome); +} + TEST(TestExistsCondition, MultipleAddresses) { exists_condition cond{ @@ -247,4 +288,41 @@ TEST(TestExistsNegatedCondition, KeyPathNotAvailable) ASSERT_TRUE(res.outcome); } +TEST(TestExistsNegatedCondition, KeyPathAvailableButExcluded) +{ + exists_negated_condition cond{{{{{{"server.request.uri_raw", + get_target_index("server.request.uri_raw"), {"path", "to", "object"}}}}}}}; + + ddwaf_object tmp; + ddwaf_object path; + ddwaf_object to; + + ddwaf_object_map(&to); + ddwaf_object_map_add(&to, "to", ddwaf_object_invalid(&tmp)); + + ddwaf_object_map(&path); + ddwaf_object_map_add(&path, "path", &to); + + ddwaf_object root; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "server.request.uri_raw", &path); + + object_store store; + store.insert(root); + + ddwaf::timer deadline{2s}; + condition_cache cache; + + std::unordered_set excluded = {&root.array[0]}; + + exclusion::object_set_ref excluded_ref; + excluded_ref.persistent = excluded; + + // While the key path is not present, since part of the path was excluded + // the evaluation fails to determine the presence of the full key path, + // for that reason, no match is generated. + auto res = cond.eval(cache, store, excluded_ref, {}, deadline); + ASSERT_FALSE(res.outcome); +} + } // namespace diff --git a/tests/parser_v2_rules_test.cpp b/tests/parser_v2_rules_test.cpp index 07f9ca93..5b66d369 100644 --- a/tests/parser_v2_rules_test.cpp +++ b/tests/parser_v2_rules_test.cpp @@ -368,4 +368,48 @@ TEST(TestParserV2Rules, ParseMultipleRulesOneDuplicate) EXPECT_STR(rule.tags["category"], "category1"); } } + +TEST(TestParserV2Rules, KeyPathTooLong) +{ + 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}], regex: .*}}, {operator: match_regex, parameters: {inputs: [{address: arg2, key_path: [x, y, z]}], 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("key_path beyond maximum container depth"); + 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 From 9ffd7dfde76e42199996c8a0c161d9efaf413445 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Fri, 16 Aug 2024 11:45:13 +0100 Subject: [PATCH 05/25] Add exists integration tests --- src/condition/exists.cpp | 7 +- tests/integration/conditions/exists/test.cpp | 330 ++++++++++++++++++ .../conditions/exists/yaml/exists.yaml | 36 ++ .../exists/yaml/exists_negated.yaml | 23 ++ .../conditions/{ => transformers}/test.cpp | 16 +- .../yaml/global_transformer.yaml | 0 .../yaml/input_transformer.yaml | 0 .../yaml/overlapping_transformers.yaml | 0 8 files changed, 399 insertions(+), 13 deletions(-) create mode 100644 tests/integration/conditions/exists/test.cpp create mode 100644 tests/integration/conditions/exists/yaml/exists.yaml create mode 100644 tests/integration/conditions/exists/yaml/exists_negated.yaml rename tests/integration/conditions/{ => transformers}/test.cpp (96%) rename tests/integration/conditions/{ => transformers}/yaml/global_transformer.yaml (100%) rename tests/integration/conditions/{ => transformers}/yaml/input_transformer.yaml (100%) rename tests/integration/conditions/{ => transformers}/yaml/overlapping_transformers.yaml (100%) diff --git a/src/condition/exists.cpp b/src/condition/exists.cpp index 47e44be9..a54e7602 100644 --- a/src/condition/exists.cpp +++ b/src/condition/exists.cpp @@ -28,9 +28,10 @@ search_outcome exists(const ddwaf_object *root, std::span key const ddwaf_object *parent = root; auto it = key_path.begin(); - std::size_t depth = 0; std::size_t size = parent->nbEntries; + // The parser ensures that the key path is within the limits specified by + // the user, hence we don't need to check for depth for (std::size_t i = 0; i < size;) { const auto &child = parent->array[i++]; @@ -55,10 +56,6 @@ search_outcome exists(const ddwaf_object *root, std::span key return search_outcome::not_found; } - if (++depth >= limits.max_container_depth) [[unlikely]] { - return search_outcome::unknown; - } - // Reset the loop and iterate child parent = &child; i = 0; diff --git a/tests/integration/conditions/exists/test.cpp b/tests/integration/conditions/exists/test.cpp new file mode 100644 index 00000000..1fd604ac --- /dev/null +++ b/tests/integration/conditions/exists/test.cpp @@ -0,0 +1,330 @@ +// 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_utils.hpp" + +using namespace ddwaf; + +namespace { +constexpr std::string_view base_dir = "integration/conditions/exists"; + +TEST(TestConditionExistsIntegration, AddressAvailable) +{ + auto rule = read_file("exists.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_free(&rule); + + ddwaf_context context = ddwaf_context_init(handle); + ASSERT_NE(context, nullptr); + + ddwaf_object map = DDWAF_OBJECT_MAP; + ddwaf_object value; + ddwaf_object_map_add(&map, "input-1", ddwaf_object_invalid(&value)); + + ddwaf_result out; + ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_FALSE(out.timeout); + EXPECT_EVENTS(out, {.id = "1", + .name = "rule1-exists", + .tags = {{"type", "flow"}, {"category", "category"}}, + .matches = {{.op = "exists", + .highlight = "", + .args = {{ + .address = "input-1", + }}}}}); + + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestConditionExistsIntegration, AddressNotAvailable) +{ + auto rule = read_file("exists.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_free(&rule); + + ddwaf_context context = ddwaf_context_init(handle); + ASSERT_NE(context, nullptr); + + ddwaf_object map = DDWAF_OBJECT_MAP; + ddwaf_object value; + ddwaf_object_map_add(&map, "input", ddwaf_object_invalid(&value)); + + ddwaf_result out; + ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_OK); + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestConditionExistsIntegration, KeyPathAvailable) +{ + auto rule = read_file("exists.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_free(&rule); + + ddwaf_context context = ddwaf_context_init(handle); + ASSERT_NE(context, nullptr); + + ddwaf_object intermediate = DDWAF_OBJECT_MAP; + ddwaf_object map = DDWAF_OBJECT_MAP; + ddwaf_object value; + ddwaf_object_map_add(&intermediate, "path", ddwaf_object_invalid(&value)); + ddwaf_object_map_add(&map, "input-2", &intermediate); + + ddwaf_result out; + ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_FALSE(out.timeout); + EXPECT_EVENTS(out, {.id = "2", + .name = "rule2-exists-kp", + .tags = {{"type", "flow"}, {"category", "category"}}, + .matches = {{.op = "exists", + .highlight = "", + .args = {{.address = "input-2", .path = {"path"}}}}}}); + + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestConditionExistsIntegration, KeyPathNotAvailable) +{ + auto rule = read_file("exists.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_free(&rule); + + ddwaf_context context = ddwaf_context_init(handle); + ASSERT_NE(context, nullptr); + + ddwaf_object intermediate = DDWAF_OBJECT_MAP; + ddwaf_object map = DDWAF_OBJECT_MAP; + ddwaf_object value; + ddwaf_object_map_add(&intermediate, "poth", ddwaf_object_invalid(&value)); + ddwaf_object_map_add(&map, "input-2", &intermediate); + + ddwaf_result out; + ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_OK); + + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestConditionExistsIntegration, AddressAvailableVariadicRule) +{ + auto rule = read_file("exists.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_free(&rule); + + ddwaf_context context = ddwaf_context_init(handle); + ASSERT_NE(context, nullptr); + + ddwaf_object map = DDWAF_OBJECT_MAP; + ddwaf_object value; + ddwaf_object_map_add(&map, "input-3-1", ddwaf_object_invalid(&value)); + + ddwaf_result out; + ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_FALSE(out.timeout); + EXPECT_EVENTS(out, {.id = "3", + .name = "rule3-exists-multi", + .tags = {{"type", "flow"}, {"category", "category"}}, + .matches = {{.op = "exists", + .highlight = "", + .args = {{ + .address = "input-3-1", + }}}}}); + + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestConditionExistsIntegration, KeyPathAvailableVariadicRule) +{ + auto rule = read_file("exists.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_free(&rule); + + ddwaf_context context = ddwaf_context_init(handle); + ASSERT_NE(context, nullptr); + + ddwaf_object intermediate = DDWAF_OBJECT_MAP; + ddwaf_object map = DDWAF_OBJECT_MAP; + ddwaf_object value; + ddwaf_object_map_add(&intermediate, "path", ddwaf_object_invalid(&value)); + ddwaf_object_map_add(&map, "input-3-2", &intermediate); + + ddwaf_result out; + ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_FALSE(out.timeout); + EXPECT_EVENTS(out, {.id = "3", + .name = "rule3-exists-multi", + .tags = {{"type", "flow"}, {"category", "category"}}, + .matches = {{.op = "exists", + .highlight = "", + .args = {{.address = "input-3-2", .path = {"path"}}}}}}); + + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestConditionExistsIntegration, AddressAvailableKeyPathNotAvailableVariadicRule) +{ + auto rule = read_file("exists.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_free(&rule); + + ddwaf_context context = ddwaf_context_init(handle); + ASSERT_NE(context, nullptr); + + ddwaf_object intermediate = DDWAF_OBJECT_MAP; + ddwaf_object map = DDWAF_OBJECT_MAP; + ddwaf_object value; + ddwaf_object_map_add(&intermediate, "poth", ddwaf_object_invalid(&value)); + ddwaf_object_map_add(&map, "input-3-2", &intermediate); + ddwaf_object_map_add(&map, "input-3-1", ddwaf_object_invalid(&value)); + + ddwaf_result out; + ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_FALSE(out.timeout); + EXPECT_EVENTS(out, {.id = "3", + .name = "rule3-exists-multi", + .tags = {{"type", "flow"}, {"category", "category"}}, + .matches = {{.op = "exists", + .highlight = "", + .args = {{ + .address = "input-3-1", + }}}}}); + + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestConditionExistsNegatedIntegration, AddressAvailable) +{ + auto rule = read_file("exists_negated.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_free(&rule); + + ddwaf_context context = ddwaf_context_init(handle); + ASSERT_NE(context, nullptr); + + ddwaf_object map = DDWAF_OBJECT_MAP; + ddwaf_object value; + ddwaf_object_map_add(&map, "input-1", ddwaf_object_invalid(&value)); + + ddwaf_result out; + ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_OK); + + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestConditionExistsNegatedIntegration, AddressNotAvailable) +{ + auto rule = read_file("exists_negated.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_free(&rule); + + ddwaf_context context = ddwaf_context_init(handle); + ASSERT_NE(context, nullptr); + + ddwaf_object map = DDWAF_OBJECT_MAP; + ddwaf_object value; + ddwaf_object_map_add(&map, "input", ddwaf_object_invalid(&value)); + + // Even though the address isn't present, this test shouldn't result in a match + // as the !exists operator only supports address + key path, since we can't + // assert the absence of an address given that these are provided in stages + ddwaf_result out; + ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_OK); + + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestConditionExistsNegatedIntegration, KeyPathNotAvailable) +{ + auto rule = read_file("exists_negated.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_free(&rule); + + ddwaf_context context = ddwaf_context_init(handle); + ASSERT_NE(context, nullptr); + + ddwaf_object intermediate = DDWAF_OBJECT_MAP; + ddwaf_object map = DDWAF_OBJECT_MAP; + ddwaf_object value; + ddwaf_object_map_add(&intermediate, "poth", ddwaf_object_invalid(&value)); + ddwaf_object_map_add(&map, "input-2", &intermediate); + + ddwaf_result out; + ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_FALSE(out.timeout); + EXPECT_EVENTS(out, {.id = "2", + .name = "rule2-not-exists-kp", + .tags = {{"type", "flow"}, {"category", "category"}}, + .matches = {{.op = "!exists", + .highlight = "", + .args = {{.address = "input-2", .path = {"path"}}}}}}); + + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +TEST(TestConditionExistsNegatedIntegration, KeyPathAvailable) +{ + auto rule = read_file("exists_negated.yaml", base_dir); + ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); + ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr); + ASSERT_NE(handle, nullptr); + ddwaf_object_free(&rule); + + ddwaf_context context = ddwaf_context_init(handle); + ASSERT_NE(context, nullptr); + + ddwaf_object intermediate = DDWAF_OBJECT_MAP; + ddwaf_object map = DDWAF_OBJECT_MAP; + ddwaf_object value; + ddwaf_object_map_add(&intermediate, "path", ddwaf_object_invalid(&value)); + ddwaf_object_map_add(&map, "input-2", &intermediate); + + ddwaf_result out; + ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_OK); + + ddwaf_result_free(&out); + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + +} // namespace diff --git a/tests/integration/conditions/exists/yaml/exists.yaml b/tests/integration/conditions/exists/yaml/exists.yaml new file mode 100644 index 00000000..132b929e --- /dev/null +++ b/tests/integration/conditions/exists/yaml/exists.yaml @@ -0,0 +1,36 @@ +version: '2.1' +rules: + - id: 1 + name: rule1-exists + tags: + type: flow + category: category + conditions: + - operator: exists + parameters: + inputs: + - address: input-1 + - id: 2 + name: rule2-exists-kp + tags: + type: flow + category: category + conditions: + - operator: exists + parameters: + inputs: + - address: input-2 + key_path: ["path"] + - id: 3 + name: rule3-exists-multi + tags: + type: flow + category: category + conditions: + - operator: exists + parameters: + inputs: + - address: input-3-1 + - address: input-3-2 + key_path: ["path"] + diff --git a/tests/integration/conditions/exists/yaml/exists_negated.yaml b/tests/integration/conditions/exists/yaml/exists_negated.yaml new file mode 100644 index 00000000..67e7330e --- /dev/null +++ b/tests/integration/conditions/exists/yaml/exists_negated.yaml @@ -0,0 +1,23 @@ +version: '2.1' +rules: + - id: 1 + name: rule1-not-exists-inconsequential + tags: + type: flow + category: category + conditions: + - operator: "!exists" + parameters: + inputs: + - address: input-1 + - id: 2 + name: rule2-not-exists-kp + tags: + type: flow + category: category + conditions: + - operator: "!exists" + parameters: + inputs: + - address: input-2 + key_path: ["path"] diff --git a/tests/integration/conditions/test.cpp b/tests/integration/conditions/transformers/test.cpp similarity index 96% rename from tests/integration/conditions/test.cpp rename to tests/integration/conditions/transformers/test.cpp index 58144023..533edd2b 100644 --- a/tests/integration/conditions/test.cpp +++ b/tests/integration/conditions/transformers/test.cpp @@ -4,15 +4,15 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2021 Datadog, Inc. -#include "../../test_utils.hpp" +#include "../../../test_utils.hpp" #include "ddwaf.h" using namespace ddwaf; namespace { -constexpr std::string_view base_dir = "integration/conditions/"; +constexpr std::string_view base_dir = "integration/conditions/transformers/"; -TEST(TestConditionsIntegration, GlobalTransformer) +TEST(TestConditionTransformersIntegration, GlobalTransformer) { auto rule = read_file("global_transformer.yaml", base_dir); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); @@ -75,7 +75,7 @@ TEST(TestConditionsIntegration, GlobalTransformer) ddwaf_destroy(handle); } -TEST(TestConditionsIntegration, GlobalTransformerKeysOnly) +TEST(TestConditionTransformersIntegration, GlobalTransformerKeysOnly) { auto rule = read_file("global_transformer.yaml", base_dir); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); @@ -146,7 +146,7 @@ TEST(TestConditionsIntegration, GlobalTransformerKeysOnly) ddwaf_destroy(handle); } -TEST(TestConditionsIntegration, InputTransformer) +TEST(TestConditionTransformersIntegration, InputTransformer) { auto rule = read_file("input_transformer.yaml", base_dir); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); @@ -209,7 +209,7 @@ TEST(TestConditionsIntegration, InputTransformer) ddwaf_destroy(handle); } -TEST(TestConditionsIntegration, InputTransformerKeysOnly) +TEST(TestConditionTransformersIntegration, InputTransformerKeysOnly) { auto rule = read_file("input_transformer.yaml", base_dir); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); @@ -280,7 +280,7 @@ TEST(TestConditionsIntegration, InputTransformerKeysOnly) ddwaf_destroy(handle); } -TEST(TestConditionsIntegration, OverlappingTransformer) +TEST(TestConditionTransformersIntegration, OverlappingTransformer) { auto rule = read_file("overlapping_transformers.yaml", base_dir); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); @@ -379,7 +379,7 @@ TEST(TestConditionsIntegration, OverlappingTransformer) ddwaf_destroy(handle); } -TEST(TestConditionsIntegration, OverlappingTransformerKeysOnly) +TEST(TestConditionTransformersIntegration, OverlappingTransformerKeysOnly) { auto rule = read_file("overlapping_transformers.yaml", base_dir); ASSERT_TRUE(rule.type != DDWAF_OBJ_INVALID); diff --git a/tests/integration/conditions/yaml/global_transformer.yaml b/tests/integration/conditions/transformers/yaml/global_transformer.yaml similarity index 100% rename from tests/integration/conditions/yaml/global_transformer.yaml rename to tests/integration/conditions/transformers/yaml/global_transformer.yaml diff --git a/tests/integration/conditions/yaml/input_transformer.yaml b/tests/integration/conditions/transformers/yaml/input_transformer.yaml similarity index 100% rename from tests/integration/conditions/yaml/input_transformer.yaml rename to tests/integration/conditions/transformers/yaml/input_transformer.yaml diff --git a/tests/integration/conditions/yaml/overlapping_transformers.yaml b/tests/integration/conditions/transformers/yaml/overlapping_transformers.yaml similarity index 100% rename from tests/integration/conditions/yaml/overlapping_transformers.yaml rename to tests/integration/conditions/transformers/yaml/overlapping_transformers.yaml From 7e03b50d0039f3105325d993da0d172e74d93e7e Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Sun, 18 Aug 2024 14:04:24 +0100 Subject: [PATCH 06/25] Simplify parser --- src/parser/expression_parser.cpp | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/parser/expression_parser.cpp b/src/parser/expression_parser.cpp index 00abc05d..c2772b87 100644 --- a/src/parser/expression_parser.cpp +++ b/src/parser/expression_parser.cpp @@ -103,11 +103,6 @@ std::shared_ptr parse_expression(const parameter::vector &conditions auto operator_name = at(root, "operator"); auto params = at(root, "parameters"); - bool negated = operator_name.starts_with("!"); - if (negated) { - operator_name = operator_name.substr(1); - } - if (operator_name == "lfi_detector") { auto arguments = parse_arguments(params, source, transformers, addresses, limits); @@ -125,17 +120,15 @@ std::shared_ptr parse_expression(const parameter::vector &conditions parse_arguments(params, source, transformers, addresses, limits); conditions.emplace_back(std::make_unique(std::move(arguments), limits)); } else if (operator_name == "exists") { - if (negated) { - auto arguments = parse_arguments( - params, source, transformers, addresses, limits); - conditions.emplace_back( - std::make_unique(std::move(arguments), limits)); - } else { - auto arguments = parse_arguments( - params, source, transformers, addresses, limits); - conditions.emplace_back( - std::make_unique(std::move(arguments), limits)); - } + auto arguments = + parse_arguments(params, source, transformers, addresses, limits); + conditions.emplace_back( + std::make_unique(std::move(arguments), limits)); + } else if (operator_name == "!exists") { + auto arguments = parse_arguments( + params, source, transformers, addresses, limits); + conditions.emplace_back( + std::make_unique(std::move(arguments), limits)); } else { auto [data_id, matcher] = parse_matcher(operator_name, params); From ba427a76475b82b82cd4e801510e06f5164e2d2a Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Wed, 21 Aug 2024 17:09:43 +0100 Subject: [PATCH 07/25] Lint fixes --- src/condition/exists.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/condition/exists.cpp b/src/condition/exists.cpp index a54e7602..b9e0408d 100644 --- a/src/condition/exists.cpp +++ b/src/condition/exists.cpp @@ -3,8 +3,22 @@ // // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2021 Datadog, Inc. - +#include +#include +#include +#include +#include +#include +#include +#include + +#include "argument_retriever.hpp" +#include "clock.hpp" +#include "condition/base.hpp" #include "condition/exists.hpp" +#include "ddwaf.h" +#include "exception.hpp" +#include "exclusion/common.hpp" #include "utils.hpp" namespace ddwaf { @@ -38,7 +52,7 @@ search_outcome exists(const ddwaf_object *root, std::span key if (child.parameterName == nullptr) [[unlikely]] { continue; } - std::string_view key{ + const std::string_view key{ child.parameterName, static_cast(child.parameterNameLength)}; if (key == *it) { From a5d8f5d214a1bfa4f8c70809f419d3db9d78f3d9 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Wed, 18 Sep 2024 16:08:08 +0100 Subject: [PATCH 08/25] Address review comment --- src/condition/exists.cpp | 60 +++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/src/condition/exists.cpp b/src/condition/exists.cpp index b9e0408d..d96fb425 100644 --- a/src/condition/exists.cpp +++ b/src/condition/exists.cpp @@ -27,6 +27,28 @@ namespace { enum class search_outcome { found, not_found, unknown }; +const ddwaf_object *find_key( + const ddwaf_object &parent, std::string_view key, const object_limits &limits) +{ + const std::size_t size = + std::min(static_cast(parent.nbEntries), limits.max_container_size); + for (std::size_t i = 0; i < size; ++i) { + const auto &child = parent.array[i]; + + if (child.parameterName == nullptr) [[unlikely]] { + continue; + } + const std::string_view child_key{ + child.parameterName, static_cast(child.parameterNameLength)}; + + if (key == child_key) { + return &child; + } + } + + return nullptr; +} + search_outcome exists(const ddwaf_object *root, std::span key_path, const exclusion::object_set_ref &objects_excluded, const object_limits &limits) { @@ -39,41 +61,23 @@ search_outcome exists(const ddwaf_object *root, std::span key return search_outcome::not_found; } - const ddwaf_object *parent = root; auto it = key_path.begin(); - std::size_t size = parent->nbEntries; - // The parser ensures that the key path is within the limits specified by // the user, hence we don't need to check for depth - for (std::size_t i = 0; i < size;) { - const auto &child = parent->array[i++]; + while ((root = find_key(*root, *it, limits)) != nullptr) { + if (objects_excluded.contains(root)) { + // We found the next root but it has been excluded, so we + // can't know for sure if the required key path exists + return search_outcome::unknown; + } - if (child.parameterName == nullptr) [[unlikely]] { - continue; + if (++it == key_path.end()) { + return search_outcome::found; } - const std::string_view key{ - child.parameterName, static_cast(child.parameterNameLength)}; - if (key == *it) { - if (objects_excluded.contains(&child)) { - // We found the next child but it has been excluded, so we - // can't know for sure if the required key path exists - return search_outcome::unknown; - } - - if (++it == key_path.end()) { - return search_outcome::found; - } - - if (child.type != DDWAF_OBJ_MAP) { - return search_outcome::not_found; - } - - // Reset the loop and iterate child - parent = &child; - i = 0; - size = std::min(static_cast(child.nbEntries), limits.max_container_size); + if (root->type != DDWAF_OBJ_MAP) { + return search_outcome::not_found; } } From b72db052d0ff07500cbc0d63e05d31c4ae08c3da Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Fri, 16 Aug 2024 14:00:33 +0100 Subject: [PATCH 09/25] Negated scalar condition for matchers --- src/condition/scalar_condition.cpp | 101 ++++++++++++++---- src/condition/scalar_condition.hpp | 46 +++++++- src/parser/expression_parser.cpp | 23 +++- .../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 +++ 8 files changed, 218 insertions(+), 29 deletions(-) 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 diff --git a/src/condition/scalar_condition.cpp b/src/condition/scalar_condition.cpp index 4e2a7441..6d07adc2 100644 --- a/src/condition/scalar_condition.cpp +++ b/src/condition/scalar_condition.cpp @@ -31,8 +31,8 @@ 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) { @@ -60,8 +60,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,12 +77,16 @@ 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) { @@ -91,8 +99,8 @@ std::optional eval_target(Iterator &it, std::string_view addres 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 +109,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 +125,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,11 +161,11 @@ 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); } @@ -169,4 +178,56 @@ eval_result scalar_condition::eval(condition_cache &cache, const object_store &s return {false, 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); + } + + // This type of scalar condition only accepts a single target + const auto &target = targets_[0]; + + 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_name_, matcher->to_string(), ephemeral}}; + return {true, ephemeral}; + } + + return {false, false}; +} + } // namespace ddwaf diff --git a/src/condition/scalar_condition.hpp b/src/condition/scalar_condition.hpp index 6d7c9843..e98d8b48 100644 --- a/src/condition/scalar_condition.hpp +++ b/src/condition/scalar_condition.hpp @@ -39,17 +39,55 @@ class scalar_condition : public base_condition { static constexpr auto arguments() { - return std::array{{{"inputs", true, false}}}; + return std::array{{{"inputs", /*variadic*/ true, 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_; +}; +class scalar_negated_condition : public base_condition { +public: + scalar_negated_condition(std::unique_ptr &&matcher, std::string data_id, + std::vector args, std::string matcher_name, + const object_limits &limits = {}) + : matcher_(std::move(matcher)), data_id_(std::move(data_id)), + matcher_name_(std::move(matcher_name)), 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"); + } + + targets_ = std::move(args[0].targets); + } + + 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 + { + for (const auto &target : targets_) { addresses.emplace(target.index, target.name); } + } + + static constexpr auto arguments() + { + return std::array{{{"inputs", /*variadic*/ false, false}}}; + } + +protected: std::unique_ptr matcher_; std::string data_id_; std::vector targets_; + std::string matcher_name_; const object_limits limits_; }; diff --git a/src/parser/expression_parser.cpp b/src/parser/expression_parser.cpp index a49d2013..573fa078 100644 --- a/src/parser/expression_parser.cpp +++ b/src/parser/expression_parser.cpp @@ -141,17 +141,30 @@ std::shared_ptr parse_expression(const parameter::vector &conditions conditions.emplace_back( std::make_unique(std::move(arguments), limits)); } else { + auto raw_operator_name = operator_name; + auto negated = operator_name.starts_with('!'); + if (negated) { + operator_name = operator_name.substr(1); + } + 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)); + if (!negated) { + auto arguments = parse_arguments( + params, source, transformers, addresses, limits); + conditions.emplace_back(std::make_unique( + std::move(matcher), data_id, std::move(arguments), limits)); + } else { + auto arguments = parse_arguments( + params, source, transformers, addresses, limits); + conditions.emplace_back( + std::make_unique(std::move(matcher), data_id, + std::move(arguments), std::string{raw_operator_name}, limits)); + } } } 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 00000000..6b87e9dc --- /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 00000000..f887f799 --- /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 00000000..e9ad8312 --- /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 00000000..d8b3fb0e --- /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 9d64896f..b2a2318e 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-ip-match-negated-with-cidr + 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" From d1ccef421175aedb4d4e135e38c0a8363bed28f5 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Fri, 16 Aug 2024 16:08:43 +0100 Subject: [PATCH 10/25] More tests --- 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 +++++++++++++++++++ .../exact_match/003_rule2_exact_match.yaml | 21 +++++++ .../exact_match/004_rule2_no_exact_match.yaml | 11 ++++ .../rules/operators/exact_match/ruleset.yaml | 14 +++++ .../phrase_match/005_rule3_pm_match.yaml | 21 +++++++ .../phrase_match/006_rule3_pm_no_match.yaml | 11 ++++ .../rules/operators/phrase_match/ruleset.yaml | 13 ++++ 21 files changed, 279 insertions(+), 3 deletions(-) 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_exact_match.yaml create mode 100644 validator/tests/rules/operators/exact_match/004_rule2_no_exact_match.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/validator/runner.cpp b/validator/runner.cpp index 346444ed..55f631b4 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 00000000..6737268f --- /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 00000000..921fa707 --- /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 00000000..4307aa66 --- /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 00000000..408d1a95 --- /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 00000000..5caa88f5 --- /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 00000000..0f7b36f0 --- /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 00000000..f21fff3d --- /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 00000000..741ddb69 --- /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 00000000..a242c001 --- /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 16785418..f059d176 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_exact_match.yaml b/validator/tests/rules/operators/exact_match/003_rule2_exact_match.yaml new file mode 100644 index 00000000..8c5fcbe1 --- /dev/null +++ b/validator/tests/rules/operators/exact_match/003_rule2_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_no_exact_match.yaml b/validator/tests/rules/operators/exact_match/004_rule2_no_exact_match.yaml new file mode 100644 index 00000000..006d0930 --- /dev/null +++ b/validator/tests/rules/operators/exact_match/004_rule2_no_exact_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 a9f92ec8..8603bcb5 100644 --- a/validator/tests/rules/operators/exact_match/ruleset.yaml +++ b/validator/tests/rules/operators/exact_match/ruleset.yaml @@ -14,3 +14,17 @@ rules: - "string" - "other" - "something else" + - id: "2" + name: rule2-ip-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/phrase_match/005_rule3_pm_match.yaml b/validator/tests/rules/operators/phrase_match/005_rule3_pm_match.yaml new file mode 100644 index 00000000..6e20de09 --- /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 00000000..d3adf86d --- /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 7baf837a..6f51c6cb 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-phrase-match + tags: + type: flow + category: category + conditions: + - operator: "!phrase_match" + parameters: + inputs: + - address: rule3-input + list: + - string00 + - string01 From 4f28a1cfbe66cc10bd51b510b1567e21b3daa071 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Wed, 11 Sep 2024 11:52:57 +0100 Subject: [PATCH 11/25] Support multiple types per matcher --- src/condition/scalar_condition.cpp | 2 +- src/matcher/base.hpp | 16 ++++++++-------- src/matcher/equals.hpp | 29 +++++++++++++++++------------ src/matcher/exact_match.hpp | 5 ++++- src/matcher/greater_than.hpp | 24 +++++++++++------------- src/matcher/ip_match.hpp | 5 ++++- src/matcher/is_sqli.hpp | 5 ++++- src/matcher/is_xss.hpp | 5 ++++- src/matcher/lower_than.hpp | 23 +++++++++++------------ src/matcher/phrase_match.hpp | 6 +++++- src/matcher/regex_match.hpp | 5 ++++- 11 files changed, 73 insertions(+), 52 deletions(-) diff --git a/src/condition/scalar_condition.cpp b/src/condition/scalar_condition.cpp index 6d07adc2..1856e399 100644 --- a/src/condition/scalar_condition.cpp +++ b/src/condition/scalar_condition.cpp @@ -95,7 +95,7 @@ ResultType eval_target(Iterator &it, std::string_view address, bool ephemeral, throw ddwaf::timeout_exception(); } - if (it.type() != matcher.supported_type()) { + if (!matcher.is_supported_type(it.type())) { continue; } diff --git a/src/matcher/base.hpp b/src/matcher/base.hpp index 38f7e2f4..0b448ce8 100644 --- a/src/matcher/base.hpp +++ b/src/matcher/base.hpp @@ -33,7 +33,7 @@ class base { [[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; }; @@ -54,9 +54,9 @@ template class base_impl : public base { 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 +68,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 79597cc3..04ed8671 100644 --- a/src/matcher/equals.hpp +++ b/src/matcher/equals.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "matcher/base.hpp" #include "utils.hpp" @@ -30,29 +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 + template + [[nodiscard]] std::pair match_impl(const U &obtained) const requires(!std::is_same_v) { - return {expected_ == obtained, {}}; + if constexpr (std::is_same_v || std::is_same_v) { + return {std::cmp_equal(expected_, obtained), {}}; + } else { + return {expected_ == obtained, {}}; + } } [[nodiscard]] std::pair match_impl(std::string_view obtained) const @@ -79,7 +81,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 { diff --git a/src/matcher/exact_match.hpp b/src/matcher/exact_match.hpp index 469357ef..3de97e71 100644 --- a/src/matcher/exact_match.hpp +++ b/src/matcher/exact_match.hpp @@ -30,7 +30,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 0949c503..1ccc2fcc 100644 --- a/src/matcher/greater_than.hpp +++ b/src/matcher/greater_than.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "ddwaf.h" #include "matcher/base.hpp" @@ -30,25 +31,22 @@ 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>; diff --git a/src/matcher/ip_match.hpp b/src/matcher/ip_match.hpp index e3fb84b2..83e9bf01 100644 --- a/src/matcher/ip_match.hpp +++ b/src/matcher/ip_match.hpp @@ -44,7 +44,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 1f933312..3455f922 100644 --- a/src/matcher/is_sqli.hpp +++ b/src/matcher/is_sqli.hpp @@ -26,7 +26,10 @@ class is_sqli : public base_impl { 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 625aaf5f..4f579ced 100644 --- a/src/matcher/is_xss.hpp +++ b/src/matcher/is_xss.hpp @@ -24,7 +24,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 03746173..62a248ea 100644 --- a/src/matcher/lower_than.hpp +++ b/src/matcher/lower_than.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "ddwaf.h" #include "matcher/base.hpp" @@ -30,23 +31,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_; diff --git a/src/matcher/phrase_match.hpp b/src/matcher/phrase_match.hpp index f2b094dd..54c5c5ab 100644 --- a/src/matcher/phrase_match.hpp +++ b/src/matcher/phrase_match.hpp @@ -8,6 +8,7 @@ #include #include +#include #include "matcher/base.hpp" @@ -26,7 +27,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 ec13b359..13388a85 100644 --- a/src/matcher/regex_match.hpp +++ b/src/matcher/regex_match.hpp @@ -26,7 +26,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; From 4a1c05b136ba6b7f1637b69048d151bb52e8692a Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Wed, 11 Sep 2024 22:59:28 +0100 Subject: [PATCH 12/25] Refactor matcher parser --- src/matcher/base.hpp | 2 +- src/matcher/equals.hpp | 31 +++- src/matcher/exact_match.hpp | 3 +- src/matcher/greater_than.hpp | 36 ++++- src/matcher/ip_match.hpp | 3 +- src/matcher/is_sqli.hpp | 3 +- src/matcher/is_xss.hpp | 3 +- src/matcher/lower_than.hpp | 36 ++++- src/matcher/phrase_match.hpp | 3 +- src/matcher/regex_match.hpp | 3 +- src/parser/expression_parser.cpp | 3 +- src/parser/matcher_parser.cpp | 255 +++++++++++++++++++------------ src/parser/matcher_parser.hpp | 52 +++++++ src/parser/parser.hpp | 4 +- src/parser/scanner_parser.cpp | 3 +- 15 files changed, 312 insertions(+), 128 deletions(-) create mode 100644 src/parser/matcher_parser.hpp diff --git a/src/matcher/base.hpp b/src/matcher/base.hpp index 0b448ce8..0f8e05d8 100644 --- a/src/matcher/base.hpp +++ b/src/matcher/base.hpp @@ -47,7 +47,7 @@ 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 to_string() const override { diff --git a/src/matcher/equals.hpp b/src/matcher/equals.hpp index 04ed8671..70cc9c84 100644 --- a/src/matcher/equals.hpp +++ b/src/matcher/equals.hpp @@ -8,16 +8,16 @@ #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"; + explicit equals(T expected) requires(!std::is_floating_point_v) : expected_(std::move(expected)) @@ -30,7 +30,6 @@ 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 bool is_supported_type_impl(DDWAF_OBJ_TYPE type) { if constexpr (std::is_same_v || std::is_same_v) { @@ -70,6 +69,8 @@ template class equals : public base_impl> { template <> class equals : public base_impl> { public: + static constexpr std::string_view matcher_name = "equals"; + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) equals(double expected, double delta) : expected_(expected), delta_(delta) {} ~equals() override = default; @@ -80,7 +81,6 @@ 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 bool is_supported_type_impl(DDWAF_OBJ_TYPE type) { return type == DDWAF_OBJ_FLOAT; @@ -97,4 +97,25 @@ 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"; + + // 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 3de97e71..c0adba51 100644 --- a/src/matcher/exact_match.hpp +++ b/src/matcher/exact_match.hpp @@ -18,6 +18,8 @@ class exact_match : public base_impl { public: using data_type = std::vector>; + static constexpr std::string_view matcher_name = "exact_match"; + exact_match() = default; explicit exact_match(std::vector &&data); explicit exact_match(const data_type &data); @@ -29,7 +31,6 @@ 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 bool is_supported_type_impl(DDWAF_OBJ_TYPE type) { return type == DDWAF_OBJ_STRING; diff --git a/src/matcher/greater_than.hpp b/src/matcher/greater_than.hpp index 1ccc2fcc..8247eb40 100644 --- a/src/matcher/greater_than.hpp +++ b/src/matcher/greater_than.hpp @@ -8,20 +8,22 @@ #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"; + + 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; @@ -30,7 +32,6 @@ 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 bool is_supported_type_impl(DDWAF_OBJ_TYPE type) { return type == DDWAF_OBJ_SIGNED || type == DDWAF_OBJ_UNSIGNED || type == DDWAF_OBJ_FLOAT; @@ -52,4 +53,25 @@ class greater_than : public base_impl> { friend class base_impl>; }; +template <> class greater_than : public base_impl> { +public: + static constexpr std::string_view 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 83e9bf01..04e55d11 100644 --- a/src/matcher/ip_match.hpp +++ b/src/matcher/ip_match.hpp @@ -19,6 +19,8 @@ class ip_match : public base_impl { public: using data_type = std::vector>; + static constexpr std::string_view matcher_name = "ip_match"; + ip_match() = default; explicit ip_match(const std::vector &ip_list); template @@ -43,7 +45,6 @@ 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 bool is_supported_type_impl(DDWAF_OBJ_TYPE type) { return type == DDWAF_OBJ_STRING; diff --git a/src/matcher/is_sqli.hpp b/src/matcher/is_sqli.hpp index 3455f922..a58b5645 100644 --- a/src/matcher/is_sqli.hpp +++ b/src/matcher/is_sqli.hpp @@ -14,6 +14,8 @@ namespace ddwaf::matcher { class is_sqli : public base_impl { public: + static constexpr std::string_view matcher_name = "is_sqli"; + is_sqli() = default; ~is_sqli() override = default; is_sqli(const is_sqli &) = delete; @@ -25,7 +27,6 @@ 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 bool is_supported_type_impl(DDWAF_OBJ_TYPE type) { return type == DDWAF_OBJ_STRING; diff --git a/src/matcher/is_xss.hpp b/src/matcher/is_xss.hpp index 4f579ced..fc03e3cd 100644 --- a/src/matcher/is_xss.hpp +++ b/src/matcher/is_xss.hpp @@ -14,6 +14,8 @@ namespace ddwaf::matcher { class is_xss : public base_impl { public: + static constexpr std::string_view matcher_name = "is_xss"; + is_xss() = default; ~is_xss() override = default; is_xss(const is_xss &) = delete; @@ -23,7 +25,6 @@ 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 bool is_supported_type_impl(DDWAF_OBJ_TYPE type) { return type == DDWAF_OBJ_STRING; diff --git a/src/matcher/lower_than.hpp b/src/matcher/lower_than.hpp index 62a248ea..e3269494 100644 --- a/src/matcher/lower_than.hpp +++ b/src/matcher/lower_than.hpp @@ -8,20 +8,22 @@ #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"; + + 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; @@ -30,7 +32,6 @@ 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; @@ -53,4 +54,25 @@ 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"; + + ~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 54c5c5ab..d5974f59 100644 --- a/src/matcher/phrase_match.hpp +++ b/src/matcher/phrase_match.hpp @@ -16,6 +16,8 @@ namespace ddwaf::matcher { class phrase_match : public base_impl { public: + static constexpr std::string_view matcher_name = "phrase_match"; + phrase_match(std::vector pattern, std::vector lengths, bool enforce_word_boundary = false); ~phrase_match() override = default; @@ -26,7 +28,6 @@ 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 bool is_supported_type_impl(DDWAF_OBJ_TYPE type) { return type == DDWAF_OBJ_STRING; diff --git a/src/matcher/regex_match.hpp b/src/matcher/regex_match.hpp index 13388a85..7b948a8e 100644 --- a/src/matcher/regex_match.hpp +++ b/src/matcher/regex_match.hpp @@ -16,6 +16,8 @@ namespace ddwaf::matcher { class regex_match : public base_impl { public: + static constexpr std::string_view 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,7 +27,6 @@ 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 bool is_supported_type_impl(DDWAF_OBJ_TYPE type) { return type == DDWAF_OBJ_STRING; diff --git a/src/parser/expression_parser.cpp b/src/parser/expression_parser.cpp index 573fa078..b09e4d36 100644 --- a/src/parser/expression_parser.cpp +++ b/src/parser/expression_parser.cpp @@ -22,6 +22,7 @@ #include "log.hpp" #include "parameter.hpp" #include "parser/common.hpp" +#include "parser/matcher_parser.hpp" #include "parser/parser.hpp" #include "transformer/base.hpp" #include "utils.hpp" @@ -147,7 +148,7 @@ std::shared_ptr parse_expression(const parameter::vector &conditions operator_name = operator_name.substr(1); } - auto [data_id, matcher] = parse_matcher(operator_name, params); + auto [data_id, matcher] = parse_all_matchers(operator_name, params); if (!matcher && !data_id.empty()) { data_ids_to_type.emplace(data_id, operator_name); diff --git a/src/parser/matcher_parser.cpp b/src/parser/matcher_parser.cpp index 4b738753..9ff473f9 100644 --- a/src/parser/matcher_parser.cpp +++ b/src/parser/matcher_parser.cpp @@ -30,122 +30,181 @@ #include "matcher/regex_match.hpp" #include "parameter.hpp" #include "parser/common.hpp" +#include "parser/matcher_parser.hpp" 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; 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); - std::vector patterns; - std::vector lengths; + auto list = at(params, "list"); + options = at(params, "options", options); + auto word_boundary = at(options, "enforce_word_boundary", false); - patterns.reserve(list.size()); - lengths.reserve(list.size()); + std::vector patterns; + std::vector lengths; - for (auto &pattern : list) { - if (pattern.type != DDWAF_OBJ_STRING) { - throw ddwaf::parsing_error("phrase_match list item not a string"); - } + patterns.reserve(list.size()); + lengths.reserve(list.size()); - patterns.push_back(pattern.stringValue); - lengths.push_back((uint32_t)pattern.nbEntries); + for (auto &pattern : list) { + if (pattern.type != DDWAF_OBJ_STRING) { + throw ddwaf::parsing_error("phrase_match list item not a string"); } - matcher = std::make_unique(patterns, lengths, word_boundary); - } else if (name == "match_regex") { - auto regex = at(params, "regex"); - options = at(params, "options", options); + patterns.push_back(pattern.stringValue); + lengths.push_back((uint32_t)pattern.nbEntries); + } - 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(patterns, lengths, word_boundary)}; +} - 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); - } +template <> +std::pair> parse_matcher( + const parameter::map ¶ms) +{ + parameter::map options; + std::unique_ptr matcher; + + 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; + + 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; + + auto it = params.find("list"); + if (it == params.end()) { + rule_data_id = at(params, "data"); } else { - throw ddwaf::parsing_error("unknown matcher: " + std::string(name)); + 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; + 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)}; +} + +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); + } + + return {std::string{}, std::move(matcher)}; +} + +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 greater_than " + value_type); + } + + 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 00000000..278ef03d --- /dev/null +++ b/src/parser/matcher_parser.hpp @@ -0,0 +1,52 @@ +// 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_all_matchers( + std::string_view name, const parameter::map ¶ms) +{ + return parse_matcher< + matcher::equals<>, 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 cf3bb64a..3e3bd5a0 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 74cb5e74..22b2c4b0 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_all_matchers(matcher_name, matcher_params); if (!rule_data_id.empty()) { throw ddwaf::parsing_error("dynamic data on scanner condition"); } From ca28b9300bb11098538418a0dbef18d5313d22dd Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Wed, 11 Sep 2024 23:03:26 +0100 Subject: [PATCH 13/25] Reduce set of negated matchers --- src/parser/expression_parser.cpp | 29 ++++++++++++++++++++--------- src/parser/matcher_parser.hpp | 2 +- src/parser/scanner_parser.cpp | 2 +- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/parser/expression_parser.cpp b/src/parser/expression_parser.cpp index b09e4d36..3bc4315b 100644 --- a/src/parser/expression_parser.cpp +++ b/src/parser/expression_parser.cpp @@ -20,6 +20,11 @@ #include "exception.hpp" #include "expression.hpp" #include "log.hpp" +#include "matcher/equals.hpp" +#include "matcher/exact_match.hpp" +#include "matcher/ip_match.hpp" +#include "matcher/phrase_match.hpp" +#include "matcher/regex_match.hpp" #include "parameter.hpp" #include "parser/common.hpp" #include "parser/matcher_parser.hpp" @@ -144,22 +149,28 @@ std::shared_ptr parse_expression(const parameter::vector &conditions } else { auto raw_operator_name = operator_name; auto negated = operator_name.starts_with('!'); - if (negated) { - operator_name = operator_name.substr(1); - } - - auto [data_id, matcher] = parse_all_matchers(operator_name, params); + if (!negated) { + auto [data_id, matcher] = parse_any_matcher(operator_name, params); - if (!matcher && !data_id.empty()) { - data_ids_to_type.emplace(data_id, operator_name); - } + if (!matcher && !data_id.empty()) { + data_ids_to_type.emplace(data_id, operator_name); + } - if (!negated) { auto arguments = parse_arguments( params, source, transformers, addresses, limits); conditions.emplace_back(std::make_unique( std::move(matcher), data_id, std::move(arguments), limits)); + } else { + operator_name = operator_name.substr(1); + 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( diff --git a/src/parser/matcher_parser.hpp b/src/parser/matcher_parser.hpp index 278ef03d..478bdde8 100644 --- a/src/parser/matcher_parser.hpp +++ b/src/parser/matcher_parser.hpp @@ -42,7 +42,7 @@ std::pair> parse_matcher( } } -inline std::pair> parse_all_matchers( +inline std::pair> parse_any_matcher( std::string_view name, const parameter::map ¶ms) { return parse_matcher< diff --git a/src/parser/scanner_parser.cpp b/src/parser/scanner_parser.cpp index 22b2c4b0..6255b816 100644 --- a/src/parser/scanner_parser.cpp +++ b/src/parser/scanner_parser.cpp @@ -30,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_all_matchers(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"); } From 42fc3fb5870371b489732f9e7558b7e45539adf3 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Thu, 12 Sep 2024 08:17:49 +0100 Subject: [PATCH 14/25] Format and lint --- src/parser/matcher_parser.cpp | 4 +--- src/parser/matcher_parser.hpp | 7 ++++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/parser/matcher_parser.cpp b/src/parser/matcher_parser.cpp index 9ff473f9..e1cee07b 100644 --- a/src/parser/matcher_parser.cpp +++ b/src/parser/matcher_parser.cpp @@ -30,7 +30,7 @@ #include "matcher/regex_match.hpp" #include "parameter.hpp" #include "parser/common.hpp" -#include "parser/matcher_parser.hpp" +#include "parser/matcher_parser.hpp" // IWYU pragma: keep namespace ddwaf::parser::v2 { @@ -39,7 +39,6 @@ std::pair> parse_matcher matcher; auto list = at(params, "list"); options = at(params, "options", options); @@ -69,7 +68,6 @@ std::pair> parse_matcher matcher; auto regex = at(params, "regex"); options = at(params, "options", options); diff --git a/src/parser/matcher_parser.hpp b/src/parser/matcher_parser.hpp index 478bdde8..177c2d4d 100644 --- a/src/parser/matcher_parser.hpp +++ b/src/parser/matcher_parser.hpp @@ -26,7 +26,7 @@ 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) @@ -45,8 +45,9 @@ std::pair> parse_matcher( inline std::pair> parse_any_matcher( std::string_view name, const parameter::map ¶ms) { - return parse_matcher< - matcher::equals<>, 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); + 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 From 4013b37d0dc17d2c1d59b5dad5b644939f4ede4c Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Thu, 19 Sep 2024 20:49:45 +0100 Subject: [PATCH 15/25] More tests --- tests/matcher/greater_than_test.cpp | 39 ++++++++++++++++++ tests/matcher/lower_than_test.cpp | 40 +++++++++++++++++++ ...aml => 003_rule2_negated_exact_match.yaml} | 0 ...4_rule2_negated_exact_match_no_match.yaml} | 0 .../rules/operators/exact_match/ruleset.yaml | 4 +- .../tests/rules/operators/exists/ruleset.yaml | 4 +- .../rules/operators/ip_match/ruleset.yaml | 2 +- .../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 ++++++++++++++ .../rules/operators/phrase_match/ruleset.yaml | 2 +- 13 files changed, 179 insertions(+), 6 deletions(-) rename validator/tests/rules/operators/exact_match/{003_rule2_exact_match.yaml => 003_rule2_negated_exact_match.yaml} (100%) rename validator/tests/rules/operators/exact_match/{004_rule2_no_exact_match.yaml => 004_rule2_negated_exact_match_no_match.yaml} (100%) 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 diff --git a/tests/matcher/greater_than_test.cpp b/tests/matcher/greater_than_test.cpp index 1f52ccad..80806db4 100644 --- a/tests/matcher/greater_than_test.cpp +++ b/tests/matcher/greater_than_test.cpp @@ -19,6 +19,19 @@ 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)); + + 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 +41,19 @@ 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)); + + 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 +64,19 @@ 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)); + + 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 4bf6697f..53f06f8a 100644 --- a/tests/matcher/lower_than_test.cpp +++ b/tests/matcher/lower_than_test.cpp @@ -20,6 +20,20 @@ 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)); + + 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 +43,19 @@ 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)); + + 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 +66,19 @@ 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)); + + 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/validator/tests/rules/operators/exact_match/003_rule2_exact_match.yaml b/validator/tests/rules/operators/exact_match/003_rule2_negated_exact_match.yaml similarity index 100% rename from validator/tests/rules/operators/exact_match/003_rule2_exact_match.yaml rename to validator/tests/rules/operators/exact_match/003_rule2_negated_exact_match.yaml diff --git a/validator/tests/rules/operators/exact_match/004_rule2_no_exact_match.yaml b/validator/tests/rules/operators/exact_match/004_rule2_negated_exact_match_no_match.yaml similarity index 100% rename from validator/tests/rules/operators/exact_match/004_rule2_no_exact_match.yaml rename to validator/tests/rules/operators/exact_match/004_rule2_negated_exact_match_no_match.yaml diff --git a/validator/tests/rules/operators/exact_match/ruleset.yaml b/validator/tests/rules/operators/exact_match/ruleset.yaml index 8603bcb5..bd9f89d4 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 @@ -15,7 +15,7 @@ rules: - "other" - "something else" - id: "2" - name: rule2-ip-match + name: rule2-negated-exact-match tags: type: flow2 category: category diff --git a/validator/tests/rules/operators/exists/ruleset.yaml b/validator/tests/rules/operators/exists/ruleset.yaml index d546ac31..2eecbe75 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/ruleset.yaml b/validator/tests/rules/operators/ip_match/ruleset.yaml index b2a2318e..832382df 100644 --- a/validator/tests/rules/operators/ip_match/ruleset.yaml +++ b/validator/tests/rules/operators/ip_match/ruleset.yaml @@ -41,7 +41,7 @@ rules: - "192.188.0.0/16" - "abcd::1234:0:0:0/96" - id: "3" - name: rule3-ip-match-negated-with-cidr + name: rule3-negated-ip-match tags: type: flow3 category: category 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 00000000..bdc84351 --- /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 00000000..c164ce68 --- /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 00000000..47b220f3 --- /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 00000000..e663cc7a --- /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 00000000..7ad9eb69 --- /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/ruleset.yaml b/validator/tests/rules/operators/phrase_match/ruleset.yaml index 6f51c6cb..e6b834ab 100644 --- a/validator/tests/rules/operators/phrase_match/ruleset.yaml +++ b/validator/tests/rules/operators/phrase_match/ruleset.yaml @@ -29,7 +29,7 @@ rules: options: enforce_word_boundary: true - id: "3" - name: rule3-phrase-match + name: rule3-negated-phrase-match tags: type: flow category: category From bd5bd2f60e93eee2145e425074515438059695c2 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Thu, 19 Sep 2024 20:52:57 +0100 Subject: [PATCH 16/25] Fix format --- tests/matcher/lower_than_test.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/matcher/lower_than_test.cpp b/tests/matcher/lower_than_test.cpp index 53f06f8a..90f0c515 100644 --- a/tests/matcher/lower_than_test.cpp +++ b/tests/matcher/lower_than_test.cpp @@ -33,7 +33,6 @@ TEST(TestlowerThanInt, Basic) 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) From 600e66af82e8d355f8ee20a2c046635080be1b91 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Thu, 19 Sep 2024 21:06:54 +0100 Subject: [PATCH 17/25] Add test cases --- tests/matcher/greater_than_test.cpp | 19 +++++++++++++++++++ tests/matcher/lower_than_test.cpp | 18 ++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/tests/matcher/greater_than_test.cpp b/tests/matcher/greater_than_test.cpp index 80806db4..3297a07c 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; @@ -23,6 +24,12 @@ TEST(TestGreaterThanInt, Basic) 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); @@ -45,6 +52,12 @@ TEST(TestGreaterThanUint, Basic) 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); @@ -68,6 +81,12 @@ TEST(TestGreaterThanDouble, Basic) 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); diff --git a/tests/matcher/lower_than_test.cpp b/tests/matcher/lower_than_test.cpp index 90f0c515..85b7a68a 100644 --- a/tests/matcher/lower_than_test.cpp +++ b/tests/matcher/lower_than_test.cpp @@ -24,6 +24,12 @@ TEST(TestlowerThanInt, Basic) 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); @@ -46,6 +52,12 @@ TEST(TestlowerThanUint, Basic) 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); @@ -69,6 +81,12 @@ TEST(TestlowerThanDouble, Basic) 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); From 4ff6adabf46b96f2d788ca59cde554170981e02f Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Fri, 20 Sep 2024 17:20:49 +0100 Subject: [PATCH 18/25] Fixes and more tests --- src/condition/scalar_condition.cpp | 19 ++- src/condition/scalar_condition.hpp | 10 +- tests/condition/scalar_condition_test.cpp | 111 ++++++++++++++++ .../scalar_negated_condition_test.cpp | 123 ++++++++++++++++++ tests/expression_test.cpp | 37 ++++++ tests/test_utils.hpp | 25 +++- 6 files changed, 304 insertions(+), 21 deletions(-) create mode 100644 tests/condition/scalar_condition_test.cpp create mode 100644 tests/condition/scalar_negated_condition_test.cpp diff --git a/src/condition/scalar_condition.cpp b/src/condition/scalar_condition.cpp index 1856e399..0bb677ff 100644 --- a/src/condition/scalar_condition.cpp +++ b/src/condition/scalar_condition.cpp @@ -196,10 +196,7 @@ eval_result scalar_negated_condition::eval(condition_cache &cache, const object_ cache.targets.assign(1, nullptr); } - // This type of scalar condition only accepts a single target - const auto &target = targets_[0]; - - auto [object, attr] = store.get_target(target.index); + auto [object, attr] = store.get_target(target_.index); if (object == nullptr || object == cache.targets[0]) { return {}; } @@ -210,19 +207,19 @@ eval_result scalar_negated_condition::eval(condition_cache &cache, const object_ } bool match = false; - if (target.source == data_source::keys) { - object::key_iterator it(object, target.key_path, objects_excluded, limits_); + 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); + it, target_.name, ephemeral, *matcher, target_.transformers, limits_, deadline); } else { - object::value_iterator it(object, target.key_path, objects_excluded, limits_); + object::value_iterator it(object, target_.key_path, objects_excluded, limits_); match = eval_target( - it, target.name, ephemeral, *matcher, target.transformers, limits_, deadline); + 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()}}}, + cache.match = {{{{"input"sv, object_to_string(*object), target_.name, + {target_.key_path.begin(), target_.key_path.end()}}}, {}, matcher_name_, matcher->to_string(), ephemeral}}; return {true, ephemeral}; } diff --git a/src/condition/scalar_condition.hpp b/src/condition/scalar_condition.hpp index e98d8b48..ebd499a4 100644 --- a/src/condition/scalar_condition.hpp +++ b/src/condition/scalar_condition.hpp @@ -65,7 +65,11 @@ class scalar_negated_condition : public base_condition { throw std::invalid_argument("Matcher initialised without arguments"); } - targets_ = std::move(args[0].targets); + 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, @@ -75,7 +79,7 @@ class scalar_negated_condition : public base_condition { void get_addresses(std::unordered_map &addresses) const override { - for (const auto &target : targets_) { addresses.emplace(target.index, target.name); } + addresses.emplace(target_.index, target_.name); } static constexpr auto arguments() @@ -86,7 +90,7 @@ class scalar_negated_condition : public base_condition { protected: std::unique_ptr matcher_; std::string data_id_; - std::vector targets_; + condition_target target_; std::string matcher_name_; const object_limits limits_; }; diff --git a/tests/condition/scalar_condition_test.cpp b/tests/condition/scalar_condition_test.cpp new file mode 100644 index 00000000..d331a5e6 --- /dev/null +++ b/tests/condition/scalar_condition_test.cpp @@ -0,0 +1,111 @@ +// 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 "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, 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); + + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_TRUE(res.outcome); + ASSERT_FALSE(res.ephemeral); + } + + { + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "server.request.uri_raw", ddwaf_object_string(&tmp, "hello")); + + object_store store; + store.insert(root); + + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_FALSE(res.outcome); + ASSERT_FALSE(res.ephemeral); + } +} + +} // namespace diff --git a/tests/condition/scalar_negated_condition_test.cpp b/tests/condition/scalar_negated_condition_test.cpp new file mode 100644 index 00000000..7bc5c10c --- /dev/null +++ b/tests/condition/scalar_negated_condition_test.cpp @@ -0,0 +1,123 @@ +// 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 "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, 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); + + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_TRUE(res.outcome); + ASSERT_FALSE(res.ephemeral); + } + + { + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "server.request.uri_raw", ddwaf_object_invalid(&tmp)); + + object_store store; + store.insert(root); + + auto res = cond.eval(cache, store, {}, {}, deadline); + ASSERT_FALSE(res.outcome); + ASSERT_FALSE(res.ephemeral); + } +} + +} // namespace diff --git a/tests/expression_test.cpp b/tests/expression_test.cpp index f968738b..2c180733 100644 --- a/tests/expression_test.cpp +++ b/tests/expression_test.cpp @@ -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/test_utils.hpp b/tests/test_utils.hpp index 895b9185..01f4d8f0 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_), "!" + std::string{T::matcher_name})); + } } - 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 From 494cfaf6e1183d5e133d8da1ccff0053caf82a79 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Fri, 20 Sep 2024 18:26:40 +0100 Subject: [PATCH 19/25] Small fix --- tests/condition/scalar_condition_test.cpp | 14 ++++++-------- tests/condition/scalar_negated_condition_test.cpp | 14 ++++++-------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/tests/condition/scalar_condition_test.cpp b/tests/condition/scalar_condition_test.cpp index d331a5e6..70537fc5 100644 --- a/tests/condition/scalar_condition_test.cpp +++ b/tests/condition/scalar_condition_test.cpp @@ -82,13 +82,12 @@ TEST(TestScalarCondition, CachedMatch) ddwaf_object tmp; ddwaf_object root; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "server.request.uri_raw", ddwaf_object_string(&tmp, "hello")); { - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "server.request.uri_raw", ddwaf_object_string(&tmp, "hello")); - object_store store; - store.insert(root); + store.insert(root, object_store::attribute::none, nullptr); auto res = cond.eval(cache, store, {}, {}, deadline); ASSERT_TRUE(res.outcome); @@ -96,16 +95,15 @@ TEST(TestScalarCondition, CachedMatch) } { - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "server.request.uri_raw", ddwaf_object_string(&tmp, "hello")); - object_store store; - store.insert(root); + 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); } } // namespace diff --git a/tests/condition/scalar_negated_condition_test.cpp b/tests/condition/scalar_negated_condition_test.cpp index 7bc5c10c..bacf784e 100644 --- a/tests/condition/scalar_negated_condition_test.cpp +++ b/tests/condition/scalar_negated_condition_test.cpp @@ -94,13 +94,12 @@ TEST(TestScalarNegatedCondition, CachedMatch) ddwaf_object tmp; ddwaf_object root; + ddwaf_object_map(&root); + ddwaf_object_map_add(&root, "server.request.uri_raw", ddwaf_object_invalid(&tmp)); { - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "server.request.uri_raw", ddwaf_object_invalid(&tmp)); - object_store store; - store.insert(root); + store.insert(root, object_store::attribute::none, nullptr); auto res = cond.eval(cache, store, {}, {}, deadline); ASSERT_TRUE(res.outcome); @@ -108,16 +107,15 @@ TEST(TestScalarNegatedCondition, CachedMatch) } { - ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "server.request.uri_raw", ddwaf_object_invalid(&tmp)); - object_store store; - store.insert(root); + 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); } } // namespace From afe19e85253cf7edfa129bfa6afd484ed17b0a21 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Fri, 20 Sep 2024 22:25:48 +0100 Subject: [PATCH 20/25] Small extra test cases --- src/matcher/equals.hpp | 3 +- tests/matcher/equals_test.cpp | 81 +++++++++++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/src/matcher/equals.hpp b/src/matcher/equals.hpp index 70cc9c84..b65f4c2a 100644 --- a/src/matcher/equals.hpp +++ b/src/matcher/equals.hpp @@ -51,7 +51,8 @@ template class equals : public base_impl> { { if constexpr (std::is_same_v || std::is_same_v) { return {std::cmp_equal(expected_, obtained), {}}; - } else { + } + if constexpr (std::is_same_v) { return {expected_ == obtained, {}}; } } diff --git a/tests/matcher/equals_test.cpp b/tests/matcher/equals_test.cpp index 9edbbfdd..304ce912 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) From 4e16886ef47c7fc4362735aa88eec9eba6483db9 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Fri, 20 Sep 2024 23:02:15 +0100 Subject: [PATCH 21/25] Split booleans and integer on equals --- src/matcher/equals.hpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/matcher/equals.hpp b/src/matcher/equals.hpp index b65f4c2a..12ecd1fc 100644 --- a/src/matcher/equals.hpp +++ b/src/matcher/equals.hpp @@ -47,14 +47,15 @@ template class equals : public base_impl> { template [[nodiscard]] std::pair match_impl(const U &obtained) const - requires(!std::is_same_v) + requires(std::is_same_v || std::is_same_v) && std::is_integral_v { - if constexpr (std::is_same_v || std::is_same_v) { - return {std::cmp_equal(expected_, obtained), {}}; - } - if constexpr (std::is_same_v) { - return {expected_ == obtained, {}}; - } + return {std::cmp_equal(expected_, obtained), {}}; + } + + [[nodiscard]] std::pair match_impl(bool obtained) const + requires std::is_same_v + { + return {expected_ == obtained, {}}; } [[nodiscard]] std::pair match_impl(std::string_view obtained) const From 60b8e09808549593ab8fe168d6babc2018ddc166 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Sat, 21 Sep 2024 20:08:35 +0100 Subject: [PATCH 22/25] More tests --- .clang-tidy | 2 +- src/condition/scalar_condition.hpp | 20 ++--- tests/condition/scalar_condition_test.cpp | 77 ++++++++++++++++-- .../scalar_negated_condition_test.cpp | 78 +++++++++++++++++-- tests/expression_test.cpp | 2 +- tests/parser_v2_rules_test.cpp | 45 +++++++++++ 6 files changed, 198 insertions(+), 26 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 2b29de6f..7216dcae 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.hpp b/src/condition/scalar_condition.hpp index ebd499a4..f567f95c 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,14 +39,15 @@ class scalar_condition : public base_condition { static constexpr auto arguments() { - return std::array{{{"inputs", /*variadic*/ true, false}}}; + return std::array{ + {{.name = "inputs", .variadic = true, .optional = false}}}; } protected: 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 { @@ -58,15 +59,15 @@ class scalar_negated_condition : public base_condition { matcher_name_(std::move(matcher_name)), 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"); } if (args[0].targets.size() > 1) { - throw std::invalid_argument("Negated matchers don't support variadic arguments"); + throw std::invalid_argument("negated matchers don't support variadic arguments"); } target_ = std::move(args[0].targets[0]); @@ -84,7 +85,8 @@ class scalar_negated_condition : public base_condition { static constexpr auto arguments() { - return std::array{{{"inputs", /*variadic*/ false, false}}}; + return std::array{ + {{.name = "inputs", .variadic = false, .optional = false}}}; } protected: @@ -92,7 +94,7 @@ class scalar_negated_condition : public base_condition { std::string data_id_; condition_target target_; std::string matcher_name_; - const object_limits limits_; + object_limits limits_; }; } // namespace ddwaf diff --git a/tests/condition/scalar_condition_test.cpp b/tests/condition/scalar_condition_test.cpp index 70537fc5..f38a5c92 100644 --- a/tests/condition/scalar_condition_test.cpp +++ b/tests/condition/scalar_condition_test.cpp @@ -18,10 +18,11 @@ template condition_parameter gen_variadic_param(Args... addre { 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.uri.raw"), gen_variadic_param("server.request.query")}}), std::invalid_argument); } @@ -35,12 +36,12 @@ TEST(TestScalarCondition, NoAddressesInConstructor) TEST(TestScalarCondition, NoMatch) { scalar_condition cond{std::make_unique(".*", 0, true), {}, - {gen_variadic_param("server.request.uri_raw")}}; + {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)); + ddwaf_object_map_add(&root, "server.request.uri.raw", ddwaf_object_invalid(&tmp)); object_store store; store.insert(root); @@ -55,12 +56,12 @@ TEST(TestScalarCondition, NoMatch) TEST(TestScalarCondition, SimpleMatch) { scalar_condition cond{std::make_unique(".*", 0, true), {}, - {gen_variadic_param("server.request.uri_raw")}}; + {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")); + ddwaf_object_map_add(&root, "server.request.uri.raw", ddwaf_object_string(&tmp, "hello")); object_store store; store.insert(root); @@ -75,7 +76,7 @@ TEST(TestScalarCondition, SimpleMatch) TEST(TestScalarCondition, CachedMatch) { scalar_condition cond{std::make_unique(".*", 0, true), {}, - {gen_variadic_param("server.request.uri_raw")}, {}}; + {gen_variadic_param("server.request.uri.raw")}, {}}; ddwaf::timer deadline{2s}; condition_cache cache; @@ -83,7 +84,7 @@ TEST(TestScalarCondition, CachedMatch) ddwaf_object tmp; ddwaf_object root; ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "server.request.uri_raw", ddwaf_object_string(&tmp, "hello")); + ddwaf_object_map_add(&root, "server.request.uri.raw", ddwaf_object_string(&tmp, "hello")); { object_store store; @@ -106,4 +107,66 @@ TEST(TestScalarCondition, CachedMatch) 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); + } +} + } // namespace diff --git a/tests/condition/scalar_negated_condition_test.cpp b/tests/condition/scalar_negated_condition_test.cpp index bacf784e..e4df9679 100644 --- a/tests/condition/scalar_negated_condition_test.cpp +++ b/tests/condition/scalar_negated_condition_test.cpp @@ -23,7 +23,7 @@ TEST(TestScalarNegatedCondition, VariadicTargetInConstructor) { EXPECT_THROW( (scalar_negated_condition{std::make_unique(".*", 0, true), {}, - {gen_variadic_param("server.request.uri_raw", "server.request.query")}, {}}), + {gen_variadic_param("server.request.uri.raw", "server.request.query")}, {}}), std::invalid_argument); } @@ -31,7 +31,7 @@ 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.uri.raw"), gen_variadic_param("server.request.query")}, {}}), std::invalid_argument); @@ -47,12 +47,12 @@ TEST(TestScalarNegatedCondition, NoAddressesInConstructor) TEST(TestScalarNegatedCondition, NoMatch) { scalar_negated_condition cond{std::make_unique(".*", 0, true), {}, - {gen_variadic_param("server.request.uri_raw")}, {}}; + {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")); + ddwaf_object_map_add(&root, "server.request.uri.raw", ddwaf_object_string(&tmp, "hello")); object_store store; store.insert(root); @@ -67,12 +67,12 @@ TEST(TestScalarNegatedCondition, NoMatch) TEST(TestScalarNegatedCondition, SimpleMatch) { scalar_negated_condition cond{std::make_unique(".*", 0, true), {}, - {gen_variadic_param("server.request.uri_raw")}, {}}; + {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)); + ddwaf_object_map_add(&root, "server.request.uri.raw", ddwaf_object_invalid(&tmp)); object_store store; store.insert(root); @@ -87,7 +87,7 @@ TEST(TestScalarNegatedCondition, SimpleMatch) TEST(TestScalarNegatedCondition, CachedMatch) { scalar_negated_condition cond{std::make_unique(".*", 0, true), {}, - {gen_variadic_param("server.request.uri_raw")}, {}}; + {gen_variadic_param("server.request.uri.raw")}, {}}; ddwaf::timer deadline{2s}; condition_cache cache; @@ -95,7 +95,7 @@ TEST(TestScalarNegatedCondition, CachedMatch) ddwaf_object tmp; ddwaf_object root; ddwaf_object_map(&root); - ddwaf_object_map_add(&root, "server.request.uri_raw", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&root, "server.request.uri.raw", ddwaf_object_invalid(&tmp)); { object_store store; @@ -118,4 +118,66 @@ TEST(TestScalarNegatedCondition, CachedMatch) 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); + } +} + } // namespace diff --git a/tests/expression_test.cpp b/tests/expression_test.cpp index 2c180733..5d6e5ec5 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", diff --git a/tests/parser_v2_rules_test.cpp b/tests/parser_v2_rules_test.cpp index 5b66d369..a3c894e9 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 From b7fc7a50d0059287346dfa2dbcf29cfa3f5afad5 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Sat, 21 Sep 2024 20:28:37 +0100 Subject: [PATCH 23/25] Timeout tests and missing free --- tests/condition/scalar_condition_test.cpp | 21 +++++++++++++++++++ .../scalar_negated_condition_test.cpp | 21 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/tests/condition/scalar_condition_test.cpp b/tests/condition/scalar_condition_test.cpp index f38a5c92..30ee66a9 100644 --- a/tests/condition/scalar_condition_test.cpp +++ b/tests/condition/scalar_condition_test.cpp @@ -6,6 +6,7 @@ #include "../test.hpp" #include "condition/scalar_condition.hpp" +#include "exception.hpp" #include "matcher/regex_match.hpp" #include "utils.hpp" @@ -53,6 +54,24 @@ TEST(TestScalarCondition, NoMatch) 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), {}, @@ -167,6 +186,8 @@ TEST(TestScalarCondition, SimpleEphemeralMatch) 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 index e4df9679..31b31024 100644 --- a/tests/condition/scalar_negated_condition_test.cpp +++ b/tests/condition/scalar_negated_condition_test.cpp @@ -6,6 +6,7 @@ #include "../test.hpp" #include "condition/scalar_condition.hpp" +#include "exception.hpp" #include "matcher/regex_match.hpp" #include "utils.hpp" @@ -64,6 +65,24 @@ TEST(TestScalarNegatedCondition, NoMatch) 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), {}, @@ -178,6 +197,8 @@ TEST(TestScalarNegatedCondition, SimpleEphemeralMatch) ASSERT_TRUE(res.outcome); ASSERT_TRUE(res.ephemeral); } + + ddwaf_object_free(&root); } } // namespace From 5db918aaffbc440ab358eeb59925d174005fb27b Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Wed, 2 Oct 2024 19:03:59 +0100 Subject: [PATCH 24/25] Add requires to eval_object/target --- src/condition/scalar_condition.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/condition/scalar_condition.cpp b/src/condition/scalar_condition.cpp index 0bb677ff..c96ca2ea 100644 --- a/src/condition/scalar_condition.cpp +++ b/src/condition/scalar_condition.cpp @@ -35,6 +35,8 @@ 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 @@ -89,6 +91,8 @@ 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()) { From 1416eae436c8aa8a9d95285ccf9f4ca8684ffdb3 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Wed, 2 Oct 2024 19:59:07 +0100 Subject: [PATCH 25/25] Simplify condition parsing and unify negated matcher names --- src/condition/scalar_condition.cpp | 10 ++--- src/condition/scalar_condition.hpp | 7 +--- src/matcher/base.hpp | 2 + src/matcher/equals.hpp | 3 ++ src/matcher/exact_match.hpp | 1 + src/matcher/greater_than.hpp | 2 + src/matcher/ip_match.hpp | 1 + src/matcher/is_sqli.hpp | 1 + src/matcher/is_xss.hpp | 1 + src/matcher/lower_than.hpp | 2 + src/matcher/phrase_match.hpp | 1 + src/matcher/regex_match.hpp | 1 + src/parser/expression_parser.cpp | 60 +++++++++++++++--------------- tests/test_utils.hpp | 2 +- 14 files changed, 53 insertions(+), 41 deletions(-) diff --git a/src/condition/scalar_condition.cpp b/src/condition/scalar_condition.cpp index c96ca2ea..940f4835 100644 --- a/src/condition/scalar_condition.cpp +++ b/src/condition/scalar_condition.cpp @@ -175,11 +175,11 @@ eval_result scalar_condition::eval(condition_cache &cache, const object_store &s 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, @@ -224,11 +224,11 @@ eval_result scalar_negated_condition::eval(condition_cache &cache, const object_ if (!match) { cache.match = {{{{"input"sv, object_to_string(*object), target_.name, {target_.key_path.begin(), target_.key_path.end()}}}, - {}, matcher_name_, matcher->to_string(), ephemeral}}; - return {true, ephemeral}; + {}, matcher->negated_name(), matcher->to_string(), ephemeral}}; + return {.outcome = true, .ephemeral = ephemeral}; } - return {false, false}; + return {.outcome = false, .ephemeral = false}; } } // namespace ddwaf diff --git a/src/condition/scalar_condition.hpp b/src/condition/scalar_condition.hpp index f567f95c..390aacbe 100644 --- a/src/condition/scalar_condition.hpp +++ b/src/condition/scalar_condition.hpp @@ -53,10 +53,8 @@ class scalar_condition : public base_condition { class scalar_negated_condition : public base_condition { public: scalar_negated_condition(std::unique_ptr &&matcher, std::string data_id, - std::vector args, std::string matcher_name, - const object_limits &limits = {}) - : matcher_(std::move(matcher)), data_id_(std::move(data_id)), - matcher_name_(std::move(matcher_name)), limits_(limits) + 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"); @@ -93,7 +91,6 @@ class scalar_negated_condition : public base_condition { std::unique_ptr matcher_; std::string data_id_; condition_target target_; - std::string matcher_name_; object_limits limits_; }; diff --git a/src/matcher/base.hpp b/src/matcher/base.hpp index 0f8e05d8..7c1a439f 100644 --- a/src/matcher/base.hpp +++ b/src/matcher/base.hpp @@ -28,6 +28,7 @@ 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; @@ -48,6 +49,7 @@ template class base_impl : public base { base_impl &operator=(base_impl &&) noexcept = default; [[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 { diff --git a/src/matcher/equals.hpp b/src/matcher/equals.hpp index 12ecd1fc..25209869 100644 --- a/src/matcher/equals.hpp +++ b/src/matcher/equals.hpp @@ -17,6 +17,7 @@ namespace ddwaf::matcher { 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) @@ -72,6 +73,7 @@ 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) {} @@ -102,6 +104,7 @@ 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() override = default; diff --git a/src/matcher/exact_match.hpp b/src/matcher/exact_match.hpp index c0adba51..6a5f1ffe 100644 --- a/src/matcher/exact_match.hpp +++ b/src/matcher/exact_match.hpp @@ -19,6 +19,7 @@ class exact_match : public base_impl { 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); diff --git a/src/matcher/greater_than.hpp b/src/matcher/greater_than.hpp index 8247eb40..911ea249 100644 --- a/src/matcher/greater_than.hpp +++ b/src/matcher/greater_than.hpp @@ -18,6 +18,7 @@ namespace ddwaf::matcher { 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"; explicit greater_than(T minimum) requires std::is_same_v || std::is_same_v || @@ -56,6 +57,7 @@ template class greater_than : public base_impl 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; diff --git a/src/matcher/ip_match.hpp b/src/matcher/ip_match.hpp index 04e55d11..b52c80f0 100644 --- a/src/matcher/ip_match.hpp +++ b/src/matcher/ip_match.hpp @@ -20,6 +20,7 @@ class ip_match : public base_impl { 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); diff --git a/src/matcher/is_sqli.hpp b/src/matcher/is_sqli.hpp index a58b5645..846e79fd 100644 --- a/src/matcher/is_sqli.hpp +++ b/src/matcher/is_sqli.hpp @@ -15,6 +15,7 @@ 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; diff --git a/src/matcher/is_xss.hpp b/src/matcher/is_xss.hpp index fc03e3cd..4eb88492 100644 --- a/src/matcher/is_xss.hpp +++ b/src/matcher/is_xss.hpp @@ -15,6 +15,7 @@ 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; diff --git a/src/matcher/lower_than.hpp b/src/matcher/lower_than.hpp index e3269494..a2d0ab32 100644 --- a/src/matcher/lower_than.hpp +++ b/src/matcher/lower_than.hpp @@ -18,6 +18,7 @@ namespace ddwaf::matcher { 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"; explicit lower_than(T maximum) requires std::is_same_v || std::is_same_v || @@ -57,6 +58,7 @@ template class lower_than : public 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; diff --git a/src/matcher/phrase_match.hpp b/src/matcher/phrase_match.hpp index d5974f59..9612c060 100644 --- a/src/matcher/phrase_match.hpp +++ b/src/matcher/phrase_match.hpp @@ -17,6 +17,7 @@ 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); diff --git a/src/matcher/regex_match.hpp b/src/matcher/regex_match.hpp index 7b948a8e..4dd716fc 100644 --- a/src/matcher/regex_match.hpp +++ b/src/matcher/regex_match.hpp @@ -17,6 +17,7 @@ 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; diff --git a/src/parser/expression_parser.cpp b/src/parser/expression_parser.cpp index 3bc4315b..bcb55ad6 100644 --- a/src/parser/expression_parser.cpp +++ b/src/parser/expression_parser.cpp @@ -22,7 +22,11 @@ #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" @@ -106,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, @@ -146,37 +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 raw_operator_name = operator_name; - auto negated = operator_name.starts_with('!'); - if (!negated) { - auto [data_id, matcher] = parse_any_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)); - - } else { - operator_name = operator_name.substr(1); - 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), std::string{raw_operator_name}, 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/tests/test_utils.hpp b/tests/test_utils.hpp index 01f4d8f0..5af1bb50 100644 --- a/tests/test_utils.hpp +++ b/tests/test_utils.hpp @@ -77,7 +77,7 @@ class expression_builder { } else { conditions_.emplace_back(std::make_unique( std::make_unique(std::forward(args)...), std::string{}, - std::move(arguments_), "!" + std::string{T::matcher_name})); + std::move(arguments_))); } }