From aeca2d0b2185e619ee5c41ea3fc01346e6527999 Mon Sep 17 00:00:00 2001 From: Anil Mahtani <929854+Anilm3@users.noreply.github.com> Date: Fri, 13 Sep 2024 15:29:20 +0100 Subject: [PATCH] Fingerprint regeneration based on availability of optional arguments (#331) --- .clang-tidy | 2 +- fuzzer/http_endpoint_fingerprint/src/main.cpp | 3 +- fuzzer/http_header_fingerprint/src/main.cpp | 3 +- fuzzer/http_network_fingerprint/src/main.cpp | 3 +- fuzzer/session_fingerprint/src/main.cpp | 7 +- src/mkmap.hpp | 2 +- src/processor/base.hpp | 99 ++- src/processor/extract_schema.cpp | 4 +- src/processor/extract_schema.hpp | 3 +- src/processor/fingerprint.cpp | 647 ++++++++++-------- src/processor/fingerprint.hpp | 16 +- src/sha256.cpp | 2 +- src/traits.hpp | 9 + src/transformer/common/cow_string.hpp | 3 + src/type_traits.hpp | 17 - .../fingerprint/ruleset/preprocessor.json | 8 +- .../fingerprint/ruleset/processor.json | 8 +- .../processors/fingerprint/test.cpp | 597 +++++++++++++++- tests/processor/extract_schema_test.cpp | 75 +- tests/processor/fingerprint_test.cpp | 243 +++++-- tests/processor/processor_test.cpp | 30 +- tests/processor/structured_processor_test.cpp | 10 +- tests/transformer/transformer_utils.hpp | 8 +- tools/waf_runner.cpp | 11 +- 24 files changed, 1370 insertions(+), 440 deletions(-) delete mode 100644 src/type_traits.hpp diff --git a/.clang-tidy b/.clang-tidy index 66d8b7bc8..2b29de6f4 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,-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' WarningsAsErrors: '*' HeaderFilterRegex: '' CheckOptions: diff --git a/fuzzer/http_endpoint_fingerprint/src/main.cpp b/fuzzer/http_endpoint_fingerprint/src/main.cpp index 29c9402c1..6f313e55e 100644 --- a/fuzzer/http_endpoint_fingerprint/src/main.cpp +++ b/fuzzer/http_endpoint_fingerprint/src/main.cpp @@ -42,10 +42,11 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *bytes, size_t size) http_endpoint_fingerprint gen{"id", {}, {}, false, true}; + processor_cache cache; ddwaf::timer deadline{2s}; auto [output, attr] = gen.eval_impl({{}, {}, false, buffer.get()}, {{}, {}, false, buffer.get()}, {{}, {}, false, &query}, - {{}, {}, false, &body}, deadline); + {{{}, {}, false, &body}}, cache, deadline); ddwaf_object_free(&query); ddwaf_object_free(&body); diff --git a/fuzzer/http_header_fingerprint/src/main.cpp b/fuzzer/http_header_fingerprint/src/main.cpp index f1bdcc370..6eaa50430 100644 --- a/fuzzer/http_header_fingerprint/src/main.cpp +++ b/fuzzer/http_header_fingerprint/src/main.cpp @@ -41,8 +41,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *bytes, size_t size) http_header_fingerprint gen{"id", {}, {}, false, true}; + processor_cache cache; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &header}, deadline); + auto [output, attr] = gen.eval_impl({{}, {}, false, &header}, cache, deadline); ddwaf_object_free(&header); ddwaf_object_free(&output); diff --git a/fuzzer/http_network_fingerprint/src/main.cpp b/fuzzer/http_network_fingerprint/src/main.cpp index d61f8a065..a7c14a9b1 100644 --- a/fuzzer/http_network_fingerprint/src/main.cpp +++ b/fuzzer/http_network_fingerprint/src/main.cpp @@ -40,8 +40,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *bytes, size_t size) http_network_fingerprint gen{"id", {}, {}, false, true}; + processor_cache cache; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &header}, deadline); + auto [output, attr] = gen.eval_impl({{}, {}, false, &header}, cache, deadline); ddwaf_object_free(&header); ddwaf_object_free(&output); diff --git a/fuzzer/session_fingerprint/src/main.cpp b/fuzzer/session_fingerprint/src/main.cpp index a4441f81b..2b357c7f5 100644 --- a/fuzzer/session_fingerprint/src/main.cpp +++ b/fuzzer/session_fingerprint/src/main.cpp @@ -31,10 +31,11 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *bytes, size_t size) session_fingerprint gen{"id", {}, {}, false, true}; + processor_cache cache; ddwaf::timer deadline{2s}; - auto [output, attr] = - gen.eval_impl({{}, {}, false, &cookies}, {{}, {}, false, buffer.get()}, - {{}, {}, false, buffer.get()}, deadline); + auto [output, attr] = gen.eval_impl({{{}, {}, false, &cookies}}, + {{{}, {}, false, buffer.get()}}, + {{{}, {}, false, buffer.get()}}, cache, deadline); ddwaf_object_free(&cookies); ddwaf_object_free(&output); diff --git a/src/mkmap.hpp b/src/mkmap.hpp index fc352a2d6..34968e75e 100644 --- a/src/mkmap.hpp +++ b/src/mkmap.hpp @@ -12,7 +12,7 @@ #include #include -#include "type_traits.hpp" +#include "traits.hpp" namespace ddwaf { template , diff --git a/src/processor/base.hpp b/src/processor/base.hpp index e13a2eadf..143a1668d 100644 --- a/src/processor/base.hpp +++ b/src/processor/base.hpp @@ -33,15 +33,42 @@ struct processor_mapping { processor_target output; }; +struct resolved_argument_count { + std::size_t all{0}; + std::size_t optional{0}; +}; + struct processor_cache { expression::cache_type expr_cache; std::unordered_set generated; + + std::vector evaluated; + + // Fingerprinting cache + struct { + std::vector> fragment_fields; + } fingerprint; }; template function_traits make_eval_traits( std::pair (Class::*)(Args...) const); +template constexpr std::size_t count_optionals() +{ + return (is_optional_argument::value + ...); +} +template struct tuple_optionals_trait : std::false_type {}; + +template +struct tuple_optionals_trait> + : std::bool_constant<(count_optionals() > 0)> { + static constexpr std::size_t count = count_optionals(); +}; + +template +concept is_tuple_with_optional = tuple_optionals_trait::value; + class base_processor { public: base_processor() = default; @@ -91,34 +118,69 @@ template class structured_processor : public base_processor { } using func_traits = decltype(make_eval_traits(&Self::eval_impl)); + using tuple_type = typename func_traits::tuple_type; static_assert(func_traits::nargs == Self::param_names.size()); - for (const auto &mapping : mappings_) { + if constexpr (is_tuple_with_optional) { + // If the processor has optional parameters, initialise the cache to + // ensure that we can keep track of the number of optional arguments + // seen and reevaluate as necessary. + if (cache.evaluated.size() < mappings_.size()) { + cache.evaluated.resize(mappings_.size()); + } + } + + for (std::size_t i = 0; i < mappings_.size(); ++i) { + const auto &mapping = mappings_[i]; if (deadline.expired()) { throw ddwaf::timeout_exception(); } if (store.has_target(mapping.output.index) || cache.generated.find(mapping.output.index) != cache.generated.end()) { - continue; + if constexpr (is_tuple_with_optional) { + // When the processor has optional arguments, these should still be + // resolved as there could be new ones available + if (cache.evaluated[i].optional == tuple_optionals_trait::count) { + continue; + } + } else { + continue; + } } - typename func_traits::tuple_type args; - if (!resolve_arguments( - mapping, store, args, std::make_index_sequence{})) { - continue; + tuple_type args; + auto arg_count = resolve_arguments( + mapping, store, args, std::make_index_sequence{}); + if constexpr (is_tuple_with_optional) { + // If there are no new optional arguments, or no arguments at all, skip + if (arg_count.all == 0 || (arg_count.all == cache.evaluated[i].all && + arg_count.optional == cache.evaluated[i].optional)) { + continue; + } + } else { + if (arg_count.all == 0) { + continue; + } } auto [object, attr] = std::apply( [&](auto &&...args) { return static_cast(this)->eval_impl( - std::forward(args)..., deadline); + std::forward(args)..., cache, deadline); }, std::move(args)); - if (attr != object_store::attribute::ephemeral) { // Whatever the outcome, we don't want to try and generate it again cache.generated.emplace(mapping.output.index); + + // We update the number of optionals evaluated so that we can + // eventually determine whether the processor should be called + // again or not. The number of optionals found should increase + // on every call, hence why we simply replace the value. + if constexpr (is_tuple_with_optional) { + cache.evaluated[i] = arg_count; + } } if (object.type == DDWAF_OBJ_INVALID) { @@ -177,31 +239,40 @@ template class structured_processor : public base_processor { protected: template - bool resolve_arguments(const processor_mapping &mapping, const object_store &store, Args &args, - std::index_sequence /*unused*/) const + resolved_argument_count resolve_arguments(const processor_mapping &mapping, + const object_store &store, Args &args, std::index_sequence /*unused*/, + resolved_argument_count count = {}) const { using TupleElement = std::tuple_element_t; auto arg = resolve_argument(mapping, store); if constexpr (is_unary_argument::value) { if (!arg.has_value()) { - return false; + return {}; } + ++count.all; std::get(args) = std::move(arg.value()); } else if constexpr (is_variadic_argument::value) { if (arg.empty()) { - return false; + return {}; } + ++count.all; std::get(args) = std::move(arg); } else { + // If an optional value is not available, the resolution of said + // argument doesn't increase the number of arguments resolsved. + // This ensures that when all arguments in a method are optional, + // we can prevent calling it if none of the arguments are available. + count.all += static_cast(arg.has_value()); + count.optional += static_cast(arg.has_value()); std::get(args) = std::move(arg); } if constexpr (sizeof...(Is) > 0) { - return resolve_arguments(mapping, store, args, std::index_sequence{}); + return resolve_arguments(mapping, store, args, std::index_sequence{}, count); } else { - return true; + return count; } } diff --git a/src/processor/extract_schema.cpp b/src/processor/extract_schema.cpp index 01412f3e2..f1a9dbe65 100644 --- a/src/processor/extract_schema.cpp +++ b/src/processor/extract_schema.cpp @@ -22,6 +22,7 @@ #include "ddwaf.h" #include "exception.hpp" #include "object_store.hpp" +#include "processor/base.hpp" #include "processor/extract_schema.hpp" #include "scanner.hpp" @@ -349,7 +350,8 @@ ddwaf_object generate( } // namespace schema std::pair extract_schema::eval_impl( - const unary_argument &input, ddwaf::timer &deadline) const + const unary_argument &input, processor_cache & /*cache*/, + ddwaf::timer &deadline) const { if (input.value == nullptr) { return {}; diff --git a/src/processor/extract_schema.hpp b/src/processor/extract_schema.hpp index bb4312c26..917bdfbdc 100644 --- a/src/processor/extract_schema.hpp +++ b/src/processor/extract_schema.hpp @@ -30,7 +30,8 @@ class extract_schema : public structured_processor { {} std::pair eval_impl( - const unary_argument &input, ddwaf::timer &deadline) const; + const unary_argument &input, processor_cache &cache, + ddwaf::timer &deadline) const; protected: std::set scanners_; diff --git a/src/processor/fingerprint.cpp b/src/processor/fingerprint.cpp index ada14309a..415968287 100644 --- a/src/processor/fingerprint.cpp +++ b/src/processor/fingerprint.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -23,6 +24,7 @@ #include "exception.hpp" #include "log.hpp" #include "object_store.hpp" +#include "processor/base.hpp" #include "processor/fingerprint.hpp" #include "sha256.hpp" #include "transformer/common/cow_string.hpp" @@ -35,7 +37,7 @@ namespace { struct string_buffer { explicit string_buffer(std::size_t length) // NOLINTNEXTLINE(hicpp-no-malloc,cppcoreguidelines-pro-type-reinterpret-cast) - : buffer(reinterpret_cast(malloc(sizeof(char) * length))), length(length) + : buffer(reinterpret_cast(malloc(sizeof(char) * (length + 1)))), length(length) { if (buffer == nullptr) { throw std::bad_alloc{}; @@ -51,67 +53,29 @@ struct string_buffer { string_buffer &operator=(const string_buffer &) = delete; string_buffer &operator=(string_buffer &&) = delete; - template - [[nodiscard]] std::span subspan() - requires(N > 0) - { - if ((index + N - 1) >= length) { - throw std::out_of_range("span[index, N) beyond buffer limit"); - } - - const std::span res{&buffer[index], N}; - index += N; - return res; - } - void append(std::string_view str) { - if (str.empty()) { - return; - } - - if ((index + str.length() - 1) >= length) { - throw std::out_of_range("appending string beyond buffer limit"); + if (!str.empty() && (index + str.size()) <= length) [[likely]] { + memcpy(&buffer[index], str.data(), str.size()); + index += str.size(); } - memcpy(&buffer[index], str.data(), str.size()); - index += str.size(); } - void append_lowercase(std::string_view str) + void append(char c) { - if (str.empty()) { - return; + if (index < length) [[likely]] { + buffer[index++] = c; } - - if ((index + str.length() - 1) >= length) { - throw std::out_of_range("appending string beyond buffer limit"); - } - - for (auto c : str) { buffer[index++] = ddwaf::tolower(c); } - } - - template - void append(std::array str) - requires(N > 0) - { - append(std::string_view{str.data(), N}); } - template - void append_lowercase(std::array str) - requires(N > 0) + ddwaf_object to_object() { - append_lowercase(std::string_view{str.data(), N}); - } - - void append(char c) { append(std::string_view{&c, 1}); } + buffer[index] = '\0'; - ddwaf_object move() - { - ddwaf_object res; - ddwaf_object_stringl_nc(&res, buffer, index); + ddwaf_object object; + ddwaf_object_stringl_nc(&object, buffer, index); buffer = nullptr; - return res; // NOLINT(clang-analyzer-unix.Malloc) + return object; // NOLINT(clang-analyzer-unix.Malloc) } char *buffer{nullptr}; @@ -119,145 +83,19 @@ struct string_buffer { std::size_t length; }; -struct field_generator { - field_generator() = default; - virtual ~field_generator() = default; - field_generator(const field_generator &) = default; - field_generator(field_generator &&) = default; - field_generator &operator=(const field_generator &) = default; - field_generator &operator=(field_generator &&) = default; - - virtual std::size_t length() = 0; - virtual void operator()(string_buffer &output) = 0; -}; - -struct string_field : field_generator { - explicit string_field(std::string_view input) : value(input) {} - ~string_field() override = default; - string_field(const string_field &) = default; - string_field(string_field &&) = default; - string_field &operator=(const string_field &) = default; - string_field &operator=(string_field &&) = default; - - std::size_t length() override { return value.size(); } - void operator()(string_buffer &output) override { output.append_lowercase(value); } - - std::string_view value; -}; - -struct unsigned_field : field_generator { - template - explicit unsigned_field(T input) - requires std::is_unsigned_v - : value(ddwaf::to_string(input)) - {} - ~unsigned_field() override = default; - unsigned_field(const unsigned_field &) = default; - unsigned_field(unsigned_field &&) = default; - unsigned_field &operator=(const unsigned_field &) = default; - unsigned_field &operator=(unsigned_field &&) = default; - - std::size_t length() override { return value.size(); } - void operator()(string_buffer &output) override { output.append(value); } - - std::string value; -}; - -struct string_hash_field : field_generator { - explicit string_hash_field(std::string_view input) : value(input) {} - ~string_hash_field() override = default; - string_hash_field(const string_hash_field &) = default; - string_hash_field(string_hash_field &&) = default; - string_hash_field &operator=(const string_hash_field &) = default; - string_hash_field &operator=(string_hash_field &&) = default; - - std::size_t length() override { return 8; } - void operator()(string_buffer &output) override; - - std::string_view value; -}; - -struct key_hash_field : field_generator { - explicit key_hash_field(const ddwaf_object &input) : value(input) {} - ~key_hash_field() override = default; - key_hash_field(const key_hash_field &) = default; - key_hash_field(key_hash_field &&) = default; - key_hash_field &operator=(const key_hash_field &) = default; - key_hash_field &operator=(key_hash_field &&) = default; - - std::size_t length() override - { - return value.type == DDWAF_OBJ_MAP && value.nbEntries > 0 ? 8 : 0; - } - void operator()(string_buffer &output) override; - - ddwaf_object value; -}; - -struct vector_hash_field : field_generator { - explicit vector_hash_field(const std::vector &input) : value(input) {} - ~vector_hash_field() override = default; - vector_hash_field(const vector_hash_field &) = default; - vector_hash_field(vector_hash_field &&) = default; - vector_hash_field &operator=(const vector_hash_field &) = delete; - vector_hash_field &operator=(vector_hash_field &&) = delete; - - std::size_t length() override { return value.empty() ? 0 : 8; } - void operator()(string_buffer &output) override; - - // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members) - const std::vector &value; -}; - -// This particular generator generates multiple fields (hence the fields name) -// This is to prevent having to create intermediate structures for key and value -// when both have to be processed together. This generator also includes the -// relevant separator, whether the map is empty or not. -struct kv_hash_fields : field_generator { - explicit kv_hash_fields(const ddwaf_object &input) : value(input) {} - ~kv_hash_fields() override = default; - kv_hash_fields(const kv_hash_fields &) = default; - kv_hash_fields(kv_hash_fields &&) = default; - kv_hash_fields &operator=(const kv_hash_fields &) = default; - kv_hash_fields &operator=(kv_hash_fields &&) = default; - - std::size_t length() override - { - return value.type == DDWAF_OBJ_MAP && value.nbEntries > 0 ? (8 + 1 + 8) : 1; - } - void operator()(string_buffer &output) override; - - ddwaf_object value; -}; - -template std::size_t generate_fragment_length(Generators &...generators) +std::string str_lowercase(std::string_view str) { - static_assert(sizeof...(generators) > 0, "At least one generator is required"); - return (generators.length() + ...) + sizeof...(generators) - 1; -} + auto buffer = std::string{str}; -template -void generate_fragment_field(string_buffer &buffer, T &generator, Rest... rest) -{ - generator(buffer); - if constexpr (sizeof...(rest) > 0) { - buffer.append('-'); - generate_fragment_field(buffer, rest...); - } -} + // By initialising the cow_string with the underlying data + // contained within the std::string, we ensure a new one won't be + // allocated once the string is modified. + cow_string str_lc{buffer.data(), buffer.size()}; + transformer::lowercase::transform(str_lc); -template -ddwaf_object generate_fragment(std::string_view header, Generators... generators) -{ - const std::size_t total_length = header.size() + 1 + generate_fragment_length(generators...); + str_lc.move(); // move to avoid freeing the string - string_buffer buffer{total_length}; - buffer.append_lowercase(header); - buffer.append('-'); - - generate_fragment_field(buffer, generators...); - - return buffer.move(); + return buffer; // NOLINT(clang-analyzer-unix.Malloc) } // Return true if the first argument is less than (i.e. is ordered before) the second @@ -342,130 +180,343 @@ void normalize_value(std::string_view key, std::string &buffer, bool trailing_se } } -void string_hash_field::operator()(string_buffer &output) -{ - if (value.empty()) { - return; +template struct field_generator { + using output_type = Output; + + field_generator() = default; + ~field_generator() = default; + field_generator(const field_generator &) = default; + field_generator(field_generator &&) noexcept = default; + field_generator &operator=(const field_generator &) = default; + field_generator &operator=(field_generator &&) noexcept = default; + + [[nodiscard]] static constexpr bool has_value() { return true; } + [[nodiscard]] static constexpr std::size_t fields() + { + if constexpr (is_pair_v) { + return 2; + } else { + return 1; + } } + output_type operator()() { return static_cast(this)->generate(); } +}; - cow_string value_lc{value}; - transformer::lowercase::transform(value_lc); +struct string_field : field_generator { + explicit string_field(std::string_view input) : value(input) {} + // NOLINTNEXTLINE(readability-make-member-function-const) + [[nodiscard]] std::string generate() { return str_lowercase(value); } - sha256_hash hasher; - hasher << static_cast(value_lc); + std::string_view value; +}; - hasher.write_digest(output.subspan<8>()); -} +template + requires std::is_unsigned_v +struct unsigned_field : field_generator> { + explicit unsigned_field(T input) : value(input) {} -void key_hash_field::operator()(string_buffer &output) -{ - if (value.type != DDWAF_OBJ_MAP || value.nbEntries == 0) { - return; + [[nodiscard]] std::string generate() { return ddwaf::to_string(value); } + + T value; +}; + +struct string_hash_field : field_generator { + explicit string_hash_field(std::string_view input) : value(input) {} + + // NOLINTNEXTLINE(readability-make-member-function-const) + [[nodiscard]] std::string generate() + { + if (value.empty()) { + return {}; + } + + cow_string value_lc{value}; + transformer::lowercase::transform(value_lc); + + sha256_hash hasher; + hasher << static_cast(value_lc); + + return hasher.digest<8>(); } - std::vector keys; - keys.reserve(value.nbEntries); + std::string_view value; +}; - std::size_t max_string_size = 0; - for (unsigned i = 0; i < value.nbEntries; ++i) { - const auto &child = value.array[i]; +struct key_hash_field : field_generator { + explicit key_hash_field(const ddwaf_object *input) : value(input) {} - const std::string_view key{ - child.parameterName, static_cast(child.parameterNameLength)}; - if (max_string_size > key.size()) { - max_string_size = key.size(); + // NOLINTNEXTLINE(readability-make-member-function-const) + [[nodiscard]] std::string generate() + { + if (value == nullptr || value->type != DDWAF_OBJ_MAP || value->nbEntries == 0) { + return {}; } - keys.emplace_back(key); + std::vector keys; + keys.reserve(value->nbEntries); + + std::size_t max_string_size = 0; + for (unsigned i = 0; i < value->nbEntries; ++i) { + const auto &child = value->array[i]; + + const std::string_view key{ + child.parameterName, static_cast(child.parameterNameLength)}; + if (max_string_size > key.size()) { + max_string_size = key.size(); + } + + keys.emplace_back(key); + } + + std::sort(keys.begin(), keys.end(), str_casei_cmp); + + std::string normalized; + // By reserving the largest possible size, it should reduce reallocations + // We also add +1 to account for the trailing comma + normalized.reserve(max_string_size + 1); + + sha256_hash hasher; + for (unsigned i = 0; i < keys.size(); ++i) { + const bool trailing_comma = ((i + 1) < keys.size()); + normalize_key(keys[i], normalized, trailing_comma); + hasher << normalized; + } + return hasher.digest<8>(); } - std::sort(keys.begin(), keys.end(), str_casei_cmp); + const ddwaf_object *value; +}; - sha256_hash hasher; - std::string normalized; - // By reserving the largest possible size, it should reduce reallocations - // We also add +1 to account for the trailing comma - normalized.reserve(max_string_size + 1); - for (unsigned i = 0; i < keys.size(); ++i) { - const bool trailing_comma = ((i + 1) < keys.size()); - normalize_key(keys[i], normalized, trailing_comma); - hasher << normalized; +struct vector_hash_field : field_generator { + explicit vector_hash_field(std::vector &&input) : value(std::move(input)) {} + + // NOLINTNEXTLINE(readability-make-member-function-const) + [[nodiscard]] std::string generate() + { + if (value.empty()) { + return {}; + } + + sha256_hash hasher; + for (unsigned i = 0; i < value.size(); ++i) { + hasher << value[i]; + if ((i + 1) < value.size()) { + hasher << ","; + } + } + return hasher.digest<8>(); } - hasher.write_digest(output.subspan<8>()); -} + std::vector value; +}; -void vector_hash_field::operator()(string_buffer &output) -{ - if (value.empty()) { - return; +// This particular generator generates multiple fields (hence the fields name) +// This is to prevent having to create intermediate structures for key and value +// when both have to be processed together. This generator also includes the +// relevant separator, whether the map is empty or not. +struct kv_hash_fields : field_generator> { + explicit kv_hash_fields(const ddwaf_object *input) : value(input) {} + + // NOLINTNEXTLINE(readability-make-member-function-const) + [[nodiscard]] std::pair generate() + { + if (value == nullptr || value->type != DDWAF_OBJ_MAP || value->nbEntries == 0) { + return {}; + } + + std::vector> kv_sorted; + kv_sorted.reserve(value->nbEntries); + + std::size_t max_string_size = 0; + for (std::size_t i = 0; i < value->nbEntries; ++i) { + const auto &child = value->array[i]; + + const std::string_view key{ + child.parameterName, static_cast(child.parameterNameLength)}; + + std::string_view val; + if (child.type == DDWAF_OBJ_STRING) { + val = + std::string_view{child.stringValue, static_cast(child.nbEntries)}; + } + + auto larger_size = std::max(key.size(), val.size()); + if (max_string_size < larger_size) { + max_string_size = larger_size; + } + + kv_sorted.emplace_back(key, val); + } + + std::sort(kv_sorted.begin(), kv_sorted.end(), + [](auto &left, auto &right) { return str_casei_cmp(left.first, right.first); }); + + std::string normalized; + // By reserving the largest possible size, it should reduce reallocations + // We also add +1 to account for the trailing comma + normalized.reserve(max_string_size + 1); + sha256_hash key_hasher; + sha256_hash val_hasher; + for (unsigned i = 0; i < kv_sorted.size(); ++i) { + auto [key, val] = kv_sorted[i]; + + const bool trailing_comma = ((i + 1) < kv_sorted.size()); + + normalize_key(key, normalized, trailing_comma); + key_hasher << normalized; + + normalize_value(val, normalized, trailing_comma); + val_hasher << normalized; + } + + return {key_hasher.digest<8>(), val_hasher.digest<8>()}; } - sha256_hash hasher; - for (unsigned i = 0; i < value.size(); ++i) { - hasher << value[i]; - if ((i + 1) < value.size()) { - hasher << ","; + const ddwaf_object *value; +}; + +template struct optional_generator { + using output_type = typename Generator::output_type; + + template explicit optional_generator(const optional_argument &input) + { + if (input.has_value()) { + generator = Generator{input.value().value}; } } - hasher.write_digest(output.subspan<8>()); -} + ~optional_generator() = default; + optional_generator(const optional_generator &) = default; + optional_generator(optional_generator &&) noexcept = default; + optional_generator &operator=(const optional_generator &) = default; + optional_generator &operator=(optional_generator &&) noexcept = default; -void kv_hash_fields::operator()(string_buffer &output) -{ - if (value.type != DDWAF_OBJ_MAP || value.nbEntries == 0) { - output.append('-'); - return; + [[nodiscard]] bool has_value() const { return generator.has_value(); } + [[nodiscard]] static constexpr std::size_t fields() { return Generator::fields(); } + output_type operator()() + { + if (generator.has_value()) { + return (*generator)(); + } + return {}; } - std::vector> kv_sorted; - kv_sorted.reserve(value.nbEntries); + std::optional generator; +}; + +template std::size_t generate_fragment_length(Generators &...generators) +{ + static_assert(sizeof...(generators) > 0, "At least one generator is required"); + return ((generators.length() * generators.fields()) + ...) + (generators.fields() + ...); +} - std::size_t max_string_size = 0; - for (std::size_t i = 0; i < value.nbEntries; ++i) { - const auto &child = value.array[i]; +template constexpr std::size_t generate_num_fields() +{ + static_assert(sizeof...(Generators) > 0, "At least one generator is required"); + return (Generators::fields() + ...); +} - const std::string_view key{ - child.parameterName, static_cast(child.parameterNameLength)}; +template +std::size_t generate_fragment_field(std::span fields, T &generator, Rest &&...rest) +{ + std::size_t length = 0; + auto value = generator(); + if constexpr (is_pair_v && N >= 2) { + length += value.first.size() + value.second.size(); + fields[0] = std::move(value.first); + fields[1] = std::move(value.second); + } else { + length += value.size(); + fields[0] = std::move(value); + } - std::string_view val; - if (child.type == DDWAF_OBJ_STRING) { - val = std::string_view{child.stringValue, static_cast(child.nbEntries)}; + if constexpr (sizeof...(rest) > 0) { + if constexpr (is_pair_v) { + return length + generate_fragment_field(fields.subspan(2), std::forward(rest)...); + } else { + return length + generate_fragment_field(fields.subspan(1), std::forward(rest)...); } + } else { + return length; + } +} - auto larger_size = std::max(key.size(), val.size()); - if (max_string_size < larger_size) { - max_string_size = larger_size; - } +template +ddwaf_object generate_fragment(std::string_view header, Generators... generators) +{ + constexpr std::size_t num_fields = generate_num_fields(); + std::array fields; - kv_sorted.emplace_back(key, val); + auto length = + generate_fragment_field(std::span{fields}, generators...); + + string_buffer buffer{length + header.size() + num_fields}; + buffer.append(header); + for (const auto &field : fields) { + buffer.append('-'); + buffer.append(field); } - std::sort(kv_sorted.begin(), kv_sorted.end(), - [](auto &left, auto &right) { return str_casei_cmp(left.first, right.first); }); + return buffer.to_object(); +} - sha256_hash key_hasher; - sha256_hash val_hasher; +template +std::size_t generate_fragment_field_cached( + std::span> cache, T &generator, Rest &&...rest) +{ + std::size_t length = 0; + if constexpr (is_pair_v) { + // We can assume that cache[1] will have a value as well + if (cache[0].has_value() && cache[1].has_value()) { + length += cache[0]->size() + cache[1]->size(); + } else if (generator.has_value()) { + auto value = generator(); + cache[0] = value.first; + cache[1] = value.second; + length += cache[0]->size() + cache[1]->size(); + } + } else { + if (cache[0].has_value()) { + length += cache[0]->size(); + } else if (generator.has_value()) { + cache[0] = generator(); + length += cache[0]->size(); + } + } - std::string normalized; - // By reserving the largest possible size, it should reduce reallocations - // We also add +1 to account for the trailing comma - normalized.reserve(max_string_size + 1); - for (unsigned i = 0; i < kv_sorted.size(); ++i) { - auto [key, val] = kv_sorted[i]; + if constexpr (sizeof...(rest) > 0) { + if constexpr (is_pair_v) { + return length + + generate_fragment_field_cached(cache.subspan(2), std::forward(rest)...); + } else { + return length + + generate_fragment_field_cached(cache.subspan(1), std::forward(rest)...); + } + } else { + return length; + } +} - const bool trailing_comma = ((i + 1) < kv_sorted.size()); +template +ddwaf_object generate_fragment_cached(std::string_view header, + std::vector> &cache, Generators... generators) +{ + constexpr std::size_t num_fields = generate_num_fields(); + if (cache.empty()) { + cache.resize(num_fields); + } - normalize_key(key, normalized, trailing_comma); - key_hasher << normalized; + auto length = generate_fragment_field_cached(cache, generators...); - normalize_value(val, normalized, trailing_comma); - val_hasher << normalized; + string_buffer buffer{length + header.size() + num_fields}; + buffer.append(header); + for (const auto &field : cache) { + buffer.append('-'); + if (field.has_value()) { + buffer.append(*field); + } } - key_hasher.write_digest(output.subspan<8>()); - output.append('-'); - val_hasher.write_digest(output.subspan<8>()); + return buffer.to_object(); } enum class header_type { unknown, standard, ip_origin, user_agent, datadog }; @@ -510,7 +561,8 @@ std::pair get_header_type_and_index(std::string_view head std::pair http_endpoint_fingerprint::eval_impl( const unary_argument &method, const unary_argument &uri_raw, const unary_argument &query, - const unary_argument &body, ddwaf::timer &deadline) const + const optional_argument &body, processor_cache &cache, + ddwaf::timer &deadline) const { if (deadline.expired()) { throw ddwaf::timeout_exception(); @@ -526,8 +578,9 @@ std::pair http_endpoint_fingerprint::eval ddwaf_object res; ddwaf_object_invalid(&res); try { - res = generate_fragment("http", string_field{method.value}, string_hash_field{stripped_uri}, - key_hash_field{*query.value}, key_hash_field{*body.value}); + res = generate_fragment_cached("http", cache.fingerprint.fragment_fields, + string_field{method.value}, string_hash_field{stripped_uri}, + key_hash_field{query.value}, optional_generator{body}); } catch (const std::out_of_range &e) { DDWAF_WARN("Failed to generate http endpoint fingerprint: {}", e.what()); } @@ -537,7 +590,8 @@ std::pair http_endpoint_fingerprint::eval // NOLINTNEXTLINE(readability-convert-member-functions-to-static) std::pair http_header_fingerprint::eval_impl( - const unary_argument &headers, ddwaf::timer &deadline) const + const unary_argument &headers, processor_cache & /*cache*/, + ddwaf::timer &deadline) const { std::string known_header_bitset; known_header_bitset.resize(standard_headers_length, '0'); @@ -566,16 +620,24 @@ std::pair http_header_fingerprint::eval_i } std::sort(unknown_headers.begin(), unknown_headers.end()); - auto res = - generate_fragment("hdr", string_field{known_header_bitset}, string_hash_field{user_agent}, - unsigned_field{unknown_headers.size()}, vector_hash_field{unknown_headers}); + auto unknown_header_size = unknown_headers.size(); + ddwaf_object res; + ddwaf_object_invalid(&res); + try { + res = generate_fragment("hdr", string_field{known_header_bitset}, + string_hash_field{user_agent}, unsigned_field{unknown_header_size}, + vector_hash_field{std::move(unknown_headers)}); + } catch (const std::out_of_range &e) { + DDWAF_WARN("Failed to generate http header fingerprint: {}", e.what()); + } return {res, object_store::attribute::none}; } // NOLINTNEXTLINE(readability-convert-member-functions-to-static) std::pair http_network_fingerprint::eval_impl( - const unary_argument &headers, ddwaf::timer &deadline) const + const unary_argument &headers, processor_cache & /*cache*/, + ddwaf::timer &deadline) const { std::string ip_origin_bitset; ip_origin_bitset.resize(ip_origin_headers_length, '0'); @@ -615,23 +677,40 @@ std::pair http_network_fingerprint::eval_ for (auto c : chosen_header_value) { ip_count += static_cast(c == ','); } } - auto res = generate_fragment("net", unsigned_field{ip_count}, string_field{ip_origin_bitset}); + ddwaf_object res; + ddwaf_object_invalid(&res); + try { + res = generate_fragment("net", unsigned_field{ip_count}, string_field{ip_origin_bitset}); + } catch (const std::out_of_range &e) { + DDWAF_WARN("Failed to generate http network fingerprint: {}", e.what()); + } return {res, object_store::attribute::none}; } // NOLINTNEXTLINE(readability-convert-member-functions-to-static) std::pair session_fingerprint::eval_impl( - const unary_argument &cookies, - const unary_argument &session_id, - const unary_argument &user_id, ddwaf::timer &deadline) const + const optional_argument &cookies, + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + const optional_argument &session_id, + const optional_argument &user_id, processor_cache &cache, + ddwaf::timer &deadline) const { if (deadline.expired()) { throw ddwaf::timeout_exception(); } - auto res = generate_fragment("ssn", string_hash_field{user_id.value}, - kv_hash_fields{*cookies.value}, string_hash_field{session_id.value}); + ddwaf_object res; + ddwaf_object_invalid(&res); + try { + res = generate_fragment_cached("ssn", cache.fingerprint.fragment_fields, + optional_generator{user_id}, + optional_generator{cookies}, + optional_generator{session_id}); + } catch (const std::out_of_range &e) { + DDWAF_WARN("Failed to generate session fingerprint: {}", e.what()); + } + return {res, object_store::attribute::none}; } diff --git a/src/processor/fingerprint.hpp b/src/processor/fingerprint.hpp index 1e6d6c178..13cecb9f2 100644 --- a/src/processor/fingerprint.hpp +++ b/src/processor/fingerprint.hpp @@ -27,7 +27,8 @@ class http_endpoint_fingerprint : public structured_processor &method, const unary_argument &uri_raw, const unary_argument &query, - const unary_argument &body, ddwaf::timer &deadline) const; + const optional_argument &body, processor_cache &cache, + ddwaf::timer &deadline) const; }; class http_header_fingerprint : public structured_processor { @@ -41,7 +42,8 @@ class http_header_fingerprint : public structured_processor eval_impl( - const unary_argument &headers, ddwaf::timer &deadline) const; + const unary_argument &headers, processor_cache &cache, + ddwaf::timer &deadline) const; }; class http_network_fingerprint : public structured_processor { @@ -55,7 +57,8 @@ class http_network_fingerprint : public structured_processor eval_impl( - const unary_argument &headers, ddwaf::timer &deadline) const; + const unary_argument &headers, processor_cache &cache, + ddwaf::timer &deadline) const; }; class session_fingerprint : public structured_processor { @@ -70,9 +73,10 @@ class session_fingerprint : public structured_processor { {} std::pair eval_impl( - const unary_argument &cookies, - const unary_argument &session_id, - const unary_argument &user_id, ddwaf::timer &deadline) const; + const optional_argument &cookies, + const optional_argument &session_id, + const optional_argument &user_id, processor_cache &cache, + ddwaf::timer &deadline) const; }; } // namespace ddwaf diff --git a/src/sha256.cpp b/src/sha256.cpp index 2ba7c2c7f..128cba194 100644 --- a/src/sha256.cpp +++ b/src/sha256.cpp @@ -155,7 +155,7 @@ std::string sha256_hash::digest() requires(DigestLength % 8 == 0 && DigestLength <= 64) { std::string final_digest; - final_digest.resize(DigestLength, 0); + final_digest.resize(DigestLength); write_digest(std::span{final_digest}); return final_digest; } diff --git a/src/traits.hpp b/src/traits.hpp index 8be8629f0..fa89a9e3e 100644 --- a/src/traits.hpp +++ b/src/traits.hpp @@ -7,6 +7,8 @@ #pragma once #include +#include +#include // Generate a tuple containing a subset of the arguments // Reference: https://stackoverflow.com/questions/71301988 @@ -49,3 +51,10 @@ function_traits make_traits(Result (Class::*)(Args...) const) template function_traits make_traits(Result (Class::*)(Args...)); + +// https://stackoverflow.com/questions/43992510/enable-if-to-check-if-value-type-of-iterator-is-a-pair +template struct is_pair : std::false_type {}; +template struct is_pair> : std::true_type {}; + +template +concept is_pair_v = is_pair::value; diff --git a/src/transformer/common/cow_string.hpp b/src/transformer/common/cow_string.hpp index 68c7bd936..237a2d7cf 100644 --- a/src/transformer/common/cow_string.hpp +++ b/src/transformer/common/cow_string.hpp @@ -27,6 +27,9 @@ class cow_string { } } + explicit cow_string(char *str, std::size_t length) + : modified_(true), buffer_(str), length_(length) + {} cow_string(const cow_string &) = delete; cow_string &operator=(const cow_string &) = delete; cow_string(cow_string &&other) = delete; diff --git a/src/type_traits.hpp b/src/type_traits.hpp deleted file mode 100644 index ed569a13c..000000000 --- a/src/type_traits.hpp +++ /dev/null @@ -1,17 +0,0 @@ -// 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 - -namespace ddwaf { - -// https://stackoverflow.com/questions/43992510/enable-if-to-check-if-value-type-of-iterator-is-a-pair -template struct is_pair : std::false_type {}; -template struct is_pair> : std::true_type {}; - -} // namespace ddwaf diff --git a/tests/integration/processors/fingerprint/ruleset/preprocessor.json b/tests/integration/processors/fingerprint/ruleset/preprocessor.json index f818efd14..3d32bec6a 100644 --- a/tests/integration/processors/fingerprint/ruleset/preprocessor.json +++ b/tests/integration/processors/fingerprint/ruleset/preprocessor.json @@ -19,7 +19,7 @@ "address": "_dd.appsec.fp.http.endpoint" } ], - "regex": ".*" + "regex": "http-put-729d56c3-2c70e12b-2c70e12b" }, "operator": "match_regex" } @@ -40,7 +40,7 @@ "address": "_dd.appsec.fp.http.header" } ], - "regex": ".*" + "regex": "hdr-1111111111-a441b15f-0-" }, "operator": "match_regex" } @@ -61,7 +61,7 @@ "address": "_dd.appsec.fp.http.network" } ], - "regex": ".*" + "regex": "net-1-1111111111" }, "operator": "match_regex" } @@ -82,7 +82,7 @@ "address": "_dd.appsec.fp.session" } ], - "regex": ".*" + "regex": "ssn-8c6976e5-df6143bc-60ba1602-269500d3" }, "operator": "match_regex" } diff --git a/tests/integration/processors/fingerprint/ruleset/processor.json b/tests/integration/processors/fingerprint/ruleset/processor.json index a473a6d02..04881f77c 100644 --- a/tests/integration/processors/fingerprint/ruleset/processor.json +++ b/tests/integration/processors/fingerprint/ruleset/processor.json @@ -19,7 +19,7 @@ "address": "_dd.appsec.fp.http.endpoint" } ], - "regex": ".*" + "regex": "http-put-729d56c3-2c70e12b-2c70e12b" }, "operator": "match_regex" } @@ -40,7 +40,7 @@ "address": "_dd.appsec.fp.http.header" } ], - "regex": ".*" + "regex": "hdr-1111111111-a441b15f-0-" }, "operator": "match_regex" } @@ -61,7 +61,7 @@ "address": "_dd.appsec.fp.http.network" } ], - "regex": ".*" + "regex": "net-1-1111111111" }, "operator": "match_regex" } @@ -82,7 +82,7 @@ "address": "_dd.appsec.fp.session" } ], - "regex": ".*" + "regex": "ssn-8c6976e5-df6143bc-60ba1602-269500d3" }, "operator": "match_regex" } diff --git a/tests/integration/processors/fingerprint/test.cpp b/tests/integration/processors/fingerprint/test.cpp index 2c8b5f2a2..41fb8d672 100644 --- a/tests/integration/processors/fingerprint/test.cpp +++ b/tests/integration/processors/fingerprint/test.cpp @@ -113,6 +113,181 @@ TEST(TestFingerprintIntegration, Postprocessor) ddwaf_destroy(handle); } +TEST(TestFingerprintIntegration, PostprocessorRegeneration) +{ + auto rule = read_json_file("postprocessor.json", 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); + + uint32_t size; + const char *const *addresses = ddwaf_known_addresses(handle, &size); + EXPECT_EQ(size, 9); + std::unordered_set address_set(addresses, addresses + size); + EXPECT_TRUE(address_set.contains("server.request.body")); + EXPECT_TRUE(address_set.contains("server.request.uri.raw")); + EXPECT_TRUE(address_set.contains("server.request.method")); + EXPECT_TRUE(address_set.contains("server.request.query")); + EXPECT_TRUE(address_set.contains("server.request.headers.no_cookies")); + EXPECT_TRUE(address_set.contains("server.request.cookies")); + EXPECT_TRUE(address_set.contains("usr.id")); + EXPECT_TRUE(address_set.contains("usr.session_id")); + EXPECT_TRUE(address_set.contains("waf.context.processor")); + + ddwaf_context context = ddwaf_context_init(handle); + ASSERT_NE(context, nullptr); + + { + ddwaf_object tmp; + + ddwaf_object map = DDWAF_OBJECT_MAP; + ddwaf_object settings = DDWAF_OBJECT_MAP; + + ddwaf_object query = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(&query, "key", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&map, "server.request.query", &query); + + ddwaf_object_map_add( + &map, "server.request.uri.raw", ddwaf_object_string(&tmp, "/path/to/resource/?key=")); + ddwaf_object_map_add(&map, "server.request.method", ddwaf_object_string(&tmp, "PuT")); + + ddwaf_object headers; + ddwaf_object_map(&headers); + ddwaf_object_map_add(&headers, "referer", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "connection", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-encoding", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "content-encoding", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cache-control", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "te", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-charset", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "content-type", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-language", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "user-agent", ddwaf_object_string(&tmp, "Random")); + ddwaf_object_map_add(&headers, "x-forwarded-for", ddwaf_object_string(&tmp, "::1")); + ddwaf_object_map_add(&headers, "x-real-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "true-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-forwarded", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "forwarded-for", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-cluster-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "fastly-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cf-connecting-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cf-connecting-ipv6", ddwaf_object_invalid(&tmp)); + + ddwaf_object_map_add(&map, "server.request.headers.no_cookies", &headers); + + ddwaf_object_map_add(&settings, "fingerprint", ddwaf_object_bool(&tmp, true)); + ddwaf_object_map_add(&map, "waf.context.processor", &settings); + + ddwaf_result out; + ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_OK); + EXPECT_FALSE(out.timeout); + + EXPECT_EQ(ddwaf_object_size(&out.derivatives), 3); + + auto derivatives = test::object_to_map(out.derivatives); + EXPECT_STRV(derivatives["_dd.appsec.fp.http.endpoint"], "http-put-729d56c3-2c70e12b-"); + EXPECT_STRV(derivatives["_dd.appsec.fp.http.header"], "hdr-1111111111-a441b15f-0-"); + EXPECT_STRV(derivatives["_dd.appsec.fp.http.network"], "net-1-1111111111"); + + ddwaf_result_free(&out); + } + + { + ddwaf_object tmp; + + ddwaf_object map = DDWAF_OBJECT_MAP; + + ddwaf_object body = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(&body, "key", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&map, "server.request.body", &body); + + ddwaf_result out; + ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_OK); + EXPECT_FALSE(out.timeout); + + EXPECT_EQ(ddwaf_object_size(&out.derivatives), 1); + + auto derivatives = test::object_to_map(out.derivatives); + EXPECT_STRV( + derivatives["_dd.appsec.fp.http.endpoint"], "http-put-729d56c3-2c70e12b-2c70e12b"); + + ddwaf_result_free(&out); + } + + { + ddwaf_object tmp; + + ddwaf_object map = DDWAF_OBJECT_MAP; + + ddwaf_object cookies; + ddwaf_object_map(&cookies); + ddwaf_object_map_add(&cookies, "name", ddwaf_object_string(&tmp, "albert")); + ddwaf_object_map_add(&cookies, "theme", ddwaf_object_string(&tmp, "dark")); + ddwaf_object_map_add(&cookies, "language", ddwaf_object_string(&tmp, "en-GB")); + ddwaf_object_map_add(&cookies, "tracking_id", ddwaf_object_string(&tmp, "xyzabc")); + ddwaf_object_map_add(&cookies, "gdpr_consent", ddwaf_object_string(&tmp, "yes")); + ddwaf_object_map_add(&cookies, "session_id", ddwaf_object_string(&tmp, "ansd0182u2n")); + ddwaf_object_map_add( + &cookies, "last_visit", ddwaf_object_string(&tmp, "2024-07-16T12:00:00Z")); + ddwaf_object_map_add(&map, "server.request.cookies", &cookies); + + ddwaf_result out; + ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_OK); + EXPECT_FALSE(out.timeout); + + EXPECT_EQ(ddwaf_object_size(&out.derivatives), 1); + + auto derivatives = test::object_to_map(out.derivatives); + EXPECT_STRV(derivatives["_dd.appsec.fp.session"], "ssn--df6143bc-60ba1602-"); + + ddwaf_result_free(&out); + } + + { + ddwaf_object tmp; + + ddwaf_object map = DDWAF_OBJECT_MAP; + + ddwaf_object_map_add(&map, "usr.id", ddwaf_object_string(&tmp, "admin")); + + ddwaf_result out; + ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_OK); + EXPECT_FALSE(out.timeout); + + EXPECT_EQ(ddwaf_object_size(&out.derivatives), 1); + + auto derivatives = test::object_to_map(out.derivatives); + EXPECT_STRV(derivatives["_dd.appsec.fp.session"], "ssn-8c6976e5-df6143bc-60ba1602-"); + + ddwaf_result_free(&out); + } + + { + ddwaf_object tmp; + + ddwaf_object map = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(&map, "usr.session_id", ddwaf_object_string(&tmp, "ansd0182u2n")); + + ddwaf_result out; + ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_OK); + EXPECT_FALSE(out.timeout); + + EXPECT_EQ(ddwaf_object_size(&out.derivatives), 1); + + auto derivatives = test::object_to_map(out.derivatives); + EXPECT_STRV( + derivatives["_dd.appsec.fp.session"], "ssn-8c6976e5-df6143bc-60ba1602-269500d3"); + + ddwaf_result_free(&out); + } + + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + TEST(TestFingerprintIntegration, Preprocessor) { auto rule = read_json_file("preprocessor.json", base_dir); @@ -211,7 +386,7 @@ TEST(TestFingerprintIntegration, Preprocessor) .name = "rule1", .tags = {{"type", "flow1"}, {"category", "category1"}}, .matches = {{.op = "match_regex", - .op_value = ".*", + .op_value = "http-put-729d56c3-2c70e12b-2c70e12b", .highlight = "http-put-729d56c3-2c70e12b-2c70e12b", .args = {{ .value = "http-put-729d56c3-2c70e12b-2c70e12b", @@ -222,7 +397,7 @@ TEST(TestFingerprintIntegration, Preprocessor) .name = "rule2", .tags = {{"type", "flow2"}, {"category", "category2"}}, .matches = {{.op = "match_regex", - .op_value = ".*", + .op_value = "hdr-1111111111-a441b15f-0-", .highlight = "hdr-1111111111-a441b15f-0-", .args = {{ .value = "hdr-1111111111-a441b15f-0-", @@ -233,7 +408,7 @@ TEST(TestFingerprintIntegration, Preprocessor) .name = "rule3", .tags = {{"type", "flow3"}, {"category", "category3"}}, .matches = {{.op = "match_regex", - .op_value = ".*", + .op_value = "net-1-1111111111", .highlight = "net-1-1111111111", .args = {{ .value = "net-1-1111111111", @@ -244,7 +419,7 @@ TEST(TestFingerprintIntegration, Preprocessor) .name = "rule4", .tags = {{"type", "flow4"}, {"category", "category4"}}, .matches = {{.op = "match_regex", - .op_value = ".*", + .op_value = "ssn-8c6976e5-df6143bc-60ba1602-269500d3", .highlight = "ssn-8c6976e5-df6143bc-60ba1602-269500d3", .args = {{ .value = "ssn-8c6976e5-df6143bc-60ba1602-269500d3", @@ -259,6 +434,199 @@ TEST(TestFingerprintIntegration, Preprocessor) ddwaf_destroy(handle); } +TEST(TestFingerprintIntegration, PreprocessorRegeneration) +{ + auto rule = read_json_file("preprocessor.json", 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); + + uint32_t size; + const char *const *addresses = ddwaf_known_addresses(handle, &size); + EXPECT_EQ(size, 13); + std::unordered_set address_set(addresses, addresses + size); + EXPECT_TRUE(address_set.contains("server.request.body")); + EXPECT_TRUE(address_set.contains("server.request.uri.raw")); + EXPECT_TRUE(address_set.contains("server.request.method")); + EXPECT_TRUE(address_set.contains("server.request.query")); + EXPECT_TRUE(address_set.contains("server.request.headers.no_cookies")); + EXPECT_TRUE(address_set.contains("server.request.cookies")); + EXPECT_TRUE(address_set.contains("usr.id")); + EXPECT_TRUE(address_set.contains("usr.session_id")); + EXPECT_TRUE(address_set.contains("waf.context.processor")); + EXPECT_TRUE(address_set.contains("_dd.appsec.fp.http.endpoint")); + EXPECT_TRUE(address_set.contains("_dd.appsec.fp.http.header")); + EXPECT_TRUE(address_set.contains("_dd.appsec.fp.http.network")); + EXPECT_TRUE(address_set.contains("_dd.appsec.fp.session")); + + ddwaf_context context = ddwaf_context_init(handle); + ASSERT_NE(context, nullptr); + + { + ddwaf_object tmp; + + ddwaf_object map = DDWAF_OBJECT_MAP; + ddwaf_object settings = DDWAF_OBJECT_MAP; + + ddwaf_object query = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(&query, "key", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&map, "server.request.query", &query); + + ddwaf_object_map_add( + &map, "server.request.uri.raw", ddwaf_object_string(&tmp, "/path/to/resource/?key=")); + ddwaf_object_map_add(&map, "server.request.method", ddwaf_object_string(&tmp, "PuT")); + + ddwaf_object headers; + ddwaf_object_map(&headers); + ddwaf_object_map_add(&headers, "referer", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "connection", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-encoding", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "content-encoding", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cache-control", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "te", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-charset", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "content-type", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-language", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "user-agent", ddwaf_object_string(&tmp, "Random")); + ddwaf_object_map_add(&headers, "x-forwarded-for", ddwaf_object_string(&tmp, "::1")); + ddwaf_object_map_add(&headers, "x-real-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "true-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-forwarded", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "forwarded-for", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-cluster-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "fastly-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cf-connecting-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cf-connecting-ipv6", ddwaf_object_invalid(&tmp)); + + ddwaf_object_map_add(&map, "server.request.headers.no_cookies", &headers); + + ddwaf_object_map_add(&settings, "fingerprint", ddwaf_object_bool(&tmp, true)); + ddwaf_object_map_add(&map, "waf.context.processor", &settings); + + ddwaf_result out; + ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_FALSE(out.timeout); + + EXPECT_EVENTS(out, + {.id = "rule2", + .name = "rule2", + .tags = {{"type", "flow2"}, {"category", "category2"}}, + .matches = {{.op = "match_regex", + .op_value = "hdr-1111111111-a441b15f-0-", + .highlight = "hdr-1111111111-a441b15f-0-", + .args = {{ + .value = "hdr-1111111111-a441b15f-0-", + .address = "_dd.appsec.fp.http.header", + .path = {}, + }}}}}, + {.id = "rule3", + .name = "rule3", + .tags = {{"type", "flow3"}, {"category", "category3"}}, + .matches = {{.op = "match_regex", + .op_value = "net-1-1111111111", + .highlight = "net-1-1111111111", + .args = {{ + .value = "net-1-1111111111", + .address = "_dd.appsec.fp.http.network", + .path = {}, + }}}}}, ); + + EXPECT_EQ(ddwaf_object_size(&out.derivatives), 0); + ddwaf_result_free(&out); + } + + { + ddwaf_object tmp; + + ddwaf_object map = DDWAF_OBJECT_MAP; + + ddwaf_object body = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(&body, "key", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&map, "server.request.body", &body); + + ddwaf_result out; + ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_FALSE(out.timeout); + + EXPECT_EVENTS(out, + {.id = "rule1", + .name = "rule1", + .tags = {{"type", "flow1"}, {"category", "category1"}}, + .matches = {{.op = "match_regex", + .op_value = "http-put-729d56c3-2c70e12b-2c70e12b", + .highlight = "http-put-729d56c3-2c70e12b-2c70e12b", + .args = {{ + .value = "http-put-729d56c3-2c70e12b-2c70e12b", + .address = "_dd.appsec.fp.http.endpoint", + .path = {}, + }}}}}, ); + + EXPECT_EQ(ddwaf_object_size(&out.derivatives), 0); + ddwaf_result_free(&out); + } + + { + ddwaf_object tmp; + + ddwaf_object map = DDWAF_OBJECT_MAP; + + ddwaf_object cookies; + ddwaf_object_map(&cookies); + ddwaf_object_map_add(&cookies, "name", ddwaf_object_string(&tmp, "albert")); + ddwaf_object_map_add(&cookies, "theme", ddwaf_object_string(&tmp, "dark")); + ddwaf_object_map_add(&cookies, "language", ddwaf_object_string(&tmp, "en-GB")); + ddwaf_object_map_add(&cookies, "tracking_id", ddwaf_object_string(&tmp, "xyzabc")); + ddwaf_object_map_add(&cookies, "gdpr_consent", ddwaf_object_string(&tmp, "yes")); + ddwaf_object_map_add(&cookies, "session_id", ddwaf_object_string(&tmp, "ansd0182u2n")); + ddwaf_object_map_add( + &cookies, "last_visit", ddwaf_object_string(&tmp, "2024-07-16T12:00:00Z")); + + ddwaf_object_map_add(&map, "server.request.cookies", &cookies); + + ddwaf_result out; + ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_OK); + EXPECT_FALSE(out.timeout); + EXPECT_EQ(ddwaf_object_size(&out.events), 0); + EXPECT_EQ(ddwaf_object_size(&out.derivatives), 0); + ddwaf_result_free(&out); + } + + { + ddwaf_object tmp; + + ddwaf_object map = DDWAF_OBJECT_MAP; + + ddwaf_object_map_add(&map, "usr.id", ddwaf_object_string(&tmp, "admin")); + ddwaf_object_map_add(&map, "usr.session_id", ddwaf_object_string(&tmp, "ansd0182u2n")); + + ddwaf_result out; + ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_FALSE(out.timeout); + + EXPECT_EVENTS(out, + {.id = "rule4", + .name = "rule4", + .tags = {{"type", "flow4"}, {"category", "category4"}}, + .matches = {{.op = "match_regex", + .op_value = "ssn-8c6976e5-df6143bc-60ba1602-269500d3", + .highlight = "ssn-8c6976e5-df6143bc-60ba1602-269500d3", + .args = {{ + .value = "ssn-8c6976e5-df6143bc-60ba1602-269500d3", + .address = "_dd.appsec.fp.session", + .path = {}, + }}}}}, ); + + EXPECT_EQ(ddwaf_object_size(&out.derivatives), 0); + ddwaf_result_free(&out); + } + + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + TEST(TestFingerprintIntegration, Processor) { auto rule = read_json_file("processor.json", base_dir); @@ -358,7 +726,7 @@ TEST(TestFingerprintIntegration, Processor) .name = "rule1", .tags = {{"type", "flow1"}, {"category", "category1"}}, .matches = {{.op = "match_regex", - .op_value = ".*", + .op_value = "http-put-729d56c3-2c70e12b-2c70e12b", .highlight = "http-put-729d56c3-2c70e12b-2c70e12b", .args = {{ .value = "http-put-729d56c3-2c70e12b-2c70e12b", @@ -369,7 +737,7 @@ TEST(TestFingerprintIntegration, Processor) .name = "rule2", .tags = {{"type", "flow2"}, {"category", "category2"}}, .matches = {{.op = "match_regex", - .op_value = ".*", + .op_value = "hdr-1111111111-a441b15f-0-", .highlight = "hdr-1111111111-a441b15f-0-", .args = {{ .value = "hdr-1111111111-a441b15f-0-", @@ -380,7 +748,7 @@ TEST(TestFingerprintIntegration, Processor) .name = "rule3", .tags = {{"type", "flow3"}, {"category", "category3"}}, .matches = {{.op = "match_regex", - .op_value = ".*", + .op_value = "net-1-1111111111", .highlight = "net-1-1111111111", .args = {{ .value = "net-1-1111111111", @@ -391,7 +759,7 @@ TEST(TestFingerprintIntegration, Processor) .name = "rule4", .tags = {{"type", "flow4"}, {"category", "category4"}}, .matches = {{.op = "match_regex", - .op_value = ".*", + .op_value = "ssn-8c6976e5-df6143bc-60ba1602-269500d3", .highlight = "ssn-8c6976e5-df6143bc-60ba1602-269500d3", .args = {{ .value = "ssn-8c6976e5-df6143bc-60ba1602-269500d3", @@ -412,4 +780,217 @@ TEST(TestFingerprintIntegration, Processor) ddwaf_destroy(handle); } +TEST(TestFingerprintIntegration, ProcessorRegeneration) +{ + auto rule = read_json_file("processor.json", 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); + + uint32_t size; + const char *const *addresses = ddwaf_known_addresses(handle, &size); + + EXPECT_EQ(size, 13); + std::unordered_set address_set(addresses, addresses + size); + EXPECT_TRUE(address_set.contains("server.request.body")); + EXPECT_TRUE(address_set.contains("server.request.uri.raw")); + EXPECT_TRUE(address_set.contains("server.request.method")); + EXPECT_TRUE(address_set.contains("server.request.query")); + EXPECT_TRUE(address_set.contains("server.request.headers.no_cookies")); + EXPECT_TRUE(address_set.contains("server.request.cookies")); + EXPECT_TRUE(address_set.contains("usr.id")); + EXPECT_TRUE(address_set.contains("usr.session_id")); + EXPECT_TRUE(address_set.contains("waf.context.processor")); + EXPECT_TRUE(address_set.contains("_dd.appsec.fp.http.endpoint")); + EXPECT_TRUE(address_set.contains("_dd.appsec.fp.http.header")); + EXPECT_TRUE(address_set.contains("_dd.appsec.fp.http.network")); + EXPECT_TRUE(address_set.contains("_dd.appsec.fp.session")); + + ddwaf_context context = ddwaf_context_init(handle); + ASSERT_NE(context, nullptr); + + { + ddwaf_object tmp; + + ddwaf_object map = DDWAF_OBJECT_MAP; + ddwaf_object settings = DDWAF_OBJECT_MAP; + + ddwaf_object query = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(&query, "key", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&map, "server.request.query", &query); + + ddwaf_object_map_add( + &map, "server.request.uri.raw", ddwaf_object_string(&tmp, "/path/to/resource/?key=")); + ddwaf_object_map_add(&map, "server.request.method", ddwaf_object_string(&tmp, "PuT")); + + ddwaf_object headers; + ddwaf_object_map(&headers); + ddwaf_object_map_add(&headers, "referer", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "connection", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-encoding", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "content-encoding", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cache-control", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "te", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-charset", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "content-type", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "accept-language", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "user-agent", ddwaf_object_string(&tmp, "Random")); + ddwaf_object_map_add(&headers, "x-forwarded-for", ddwaf_object_string(&tmp, "::1")); + ddwaf_object_map_add(&headers, "x-real-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "true-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-forwarded", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "forwarded-for", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "x-cluster-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "fastly-client-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cf-connecting-ip", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&headers, "cf-connecting-ipv6", ddwaf_object_invalid(&tmp)); + + ddwaf_object_map_add(&map, "server.request.headers.no_cookies", &headers); + + ddwaf_object_map_add(&settings, "fingerprint", ddwaf_object_bool(&tmp, true)); + ddwaf_object_map_add(&map, "waf.context.processor", &settings); + + ddwaf_result out; + ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_FALSE(out.timeout); + + EXPECT_EVENTS(out, + {.id = "rule2", + .name = "rule2", + .tags = {{"type", "flow2"}, {"category", "category2"}}, + .matches = {{.op = "match_regex", + .op_value = "hdr-1111111111-a441b15f-0-", + .highlight = "hdr-1111111111-a441b15f-0-", + .args = {{ + .value = "hdr-1111111111-a441b15f-0-", + .address = "_dd.appsec.fp.http.header", + .path = {}, + }}}}}, + {.id = "rule3", + .name = "rule3", + .tags = {{"type", "flow3"}, {"category", "category3"}}, + .matches = {{.op = "match_regex", + .op_value = "net-1-1111111111", + .highlight = "net-1-1111111111", + .args = {{ + .value = "net-1-1111111111", + .address = "_dd.appsec.fp.http.network", + .path = {}, + }}}}}, ); + + EXPECT_EQ(ddwaf_object_size(&out.derivatives), 3); + + auto derivatives = test::object_to_map(out.derivatives); + EXPECT_STRV(derivatives["_dd.appsec.fp.http.endpoint"], "http-put-729d56c3-2c70e12b-"); + EXPECT_STRV(derivatives["_dd.appsec.fp.http.header"], "hdr-1111111111-a441b15f-0-"); + EXPECT_STRV(derivatives["_dd.appsec.fp.http.network"], "net-1-1111111111"); + + ddwaf_result_free(&out); + } + + { + ddwaf_object tmp; + + ddwaf_object map = DDWAF_OBJECT_MAP; + + ddwaf_object body = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(&body, "key", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&map, "server.request.body", &body); + + ddwaf_result out; + ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_FALSE(out.timeout); + + EXPECT_EVENTS(out, + {.id = "rule1", + .name = "rule1", + .tags = {{"type", "flow1"}, {"category", "category1"}}, + .matches = {{.op = "match_regex", + .op_value = "http-put-729d56c3-2c70e12b-2c70e12b", + .highlight = "http-put-729d56c3-2c70e12b-2c70e12b", + .args = {{ + .value = "http-put-729d56c3-2c70e12b-2c70e12b", + .address = "_dd.appsec.fp.http.endpoint", + .path = {}, + }}}}}, ); + + EXPECT_EQ(ddwaf_object_size(&out.derivatives), 1); + + auto derivatives = test::object_to_map(out.derivatives); + EXPECT_STRV( + derivatives["_dd.appsec.fp.http.endpoint"], "http-put-729d56c3-2c70e12b-2c70e12b"); + + ddwaf_result_free(&out); + } + + { + ddwaf_object tmp; + + ddwaf_object map = DDWAF_OBJECT_MAP; + + ddwaf_object cookies; + ddwaf_object_map(&cookies); + ddwaf_object_map_add(&cookies, "name", ddwaf_object_string(&tmp, "albert")); + ddwaf_object_map_add(&cookies, "theme", ddwaf_object_string(&tmp, "dark")); + ddwaf_object_map_add(&cookies, "language", ddwaf_object_string(&tmp, "en-GB")); + ddwaf_object_map_add(&cookies, "tracking_id", ddwaf_object_string(&tmp, "xyzabc")); + ddwaf_object_map_add(&cookies, "gdpr_consent", ddwaf_object_string(&tmp, "yes")); + ddwaf_object_map_add(&cookies, "session_id", ddwaf_object_string(&tmp, "ansd0182u2n")); + ddwaf_object_map_add( + &cookies, "last_visit", ddwaf_object_string(&tmp, "2024-07-16T12:00:00Z")); + + ddwaf_object_map_add(&map, "server.request.cookies", &cookies); + + ddwaf_result out; + ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_OK); + EXPECT_FALSE(out.timeout); + EXPECT_EQ(ddwaf_object_size(&out.events), 0); + EXPECT_EQ(ddwaf_object_size(&out.derivatives), 1); + + auto derivatives = test::object_to_map(out.derivatives); + EXPECT_STRV(derivatives["_dd.appsec.fp.session"], "ssn--df6143bc-60ba1602-"); + + ddwaf_result_free(&out); + } + + { + ddwaf_object tmp; + + ddwaf_object map = DDWAF_OBJECT_MAP; + ddwaf_object_map_add(&map, "usr.id", ddwaf_object_string(&tmp, "admin")); + ddwaf_object_map_add(&map, "usr.session_id", ddwaf_object_string(&tmp, "ansd0182u2n")); + + ddwaf_result out; + ASSERT_EQ(ddwaf_run(context, &map, nullptr, &out, LONG_TIME), DDWAF_MATCH); + EXPECT_FALSE(out.timeout); + + EXPECT_EVENTS(out, + {.id = "rule4", + .name = "rule4", + .tags = {{"type", "flow4"}, {"category", "category4"}}, + .matches = {{.op = "match_regex", + .op_value = "ssn-8c6976e5-df6143bc-60ba1602-269500d3", + .highlight = "ssn-8c6976e5-df6143bc-60ba1602-269500d3", + .args = {{ + .value = "ssn-8c6976e5-df6143bc-60ba1602-269500d3", + .address = "_dd.appsec.fp.session", + .path = {}, + }}}}}, ); + + EXPECT_EQ(ddwaf_object_size(&out.derivatives), 1); + + auto derivatives = test::object_to_map(out.derivatives); + EXPECT_STRV( + derivatives["_dd.appsec.fp.session"], "ssn-8c6976e5-df6143bc-60ba1602-269500d3"); + + ddwaf_result_free(&out); + } + + ddwaf_context_destroy(context); + ddwaf_destroy(handle); +} + } // namespace diff --git a/tests/processor/extract_schema_test.cpp b/tests/processor/extract_schema_test.cpp index 1274e9d41..11de50396 100644 --- a/tests/processor/extract_schema_test.cpp +++ b/tests/processor/extract_schema_test.cpp @@ -21,7 +21,8 @@ TEST(TestExtractSchema, UnknownScalarSchema) extract_schema gen{"id", {}, {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, cache, deadline); EXPECT_SCHEMA_EQ(output, R"([0])"); ddwaf_object_free(&output); @@ -35,7 +36,8 @@ TEST(TestExtractSchema, NullScalarSchema) extract_schema gen{"id", {}, {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, cache, deadline); EXPECT_SCHEMA_EQ(output, R"([1])"); ddwaf_object_free(&output); @@ -49,7 +51,8 @@ TEST(TestExtractSchema, BoolScalarSchema) extract_schema gen{"id", {}, {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, cache, deadline); EXPECT_SCHEMA_EQ(output, R"([2])"); ddwaf_object_free(&output); @@ -64,7 +67,8 @@ TEST(TestExtractSchema, IntScalarSchema) extract_schema gen{"id", {}, {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, cache, deadline); EXPECT_SCHEMA_EQ(output, R"([4])"); ddwaf_object_free(&output); @@ -75,7 +79,8 @@ TEST(TestExtractSchema, IntScalarSchema) extract_schema gen{"id", {}, {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, cache, deadline); EXPECT_SCHEMA_EQ(output, R"([4])"); ddwaf_object_free(&output); @@ -90,7 +95,8 @@ TEST(TestExtractSchema, StringScalarSchema) extract_schema gen{"id", {}, {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, cache, deadline); EXPECT_SCHEMA_EQ(output, R"([8])"); ddwaf_object_free(&output); @@ -105,7 +111,8 @@ TEST(TestExtractSchema, FloatScalarSchema) extract_schema gen{"id", {}, {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, cache, deadline); EXPECT_SCHEMA_EQ(output, R"([16])"); ddwaf_object_free(&output); @@ -119,7 +126,8 @@ TEST(TestExtractSchema, EmptyArraySchema) extract_schema gen{"id", {}, {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, cache, deadline); EXPECT_SCHEMA_EQ(output, R"([[],{"len":0}])"); ddwaf_object_free(&output); @@ -138,7 +146,8 @@ TEST(TestExtractSchema, ArraySchema) extract_schema gen{"id", {}, {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, cache, deadline); EXPECT_SCHEMA_EQ(output, R"([[[1],[0],[8],[4]],{"len":4}])"); ddwaf_object_free(&output); @@ -158,7 +167,8 @@ TEST(TestExtractSchema, ArrayWithDuplicateScalarSchema) extract_schema gen{"id", {}, {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, cache, deadline); EXPECT_SCHEMA_EQ(output, R"([[[8]],{"len":4}])"); ddwaf_object_free(&output); @@ -194,7 +204,8 @@ TEST(TestExtractSchema, ArrayWithDuplicateMapsSchema) extract_schema gen{"id", {}, {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, cache, deadline); EXPECT_SCHEMA_EQ(output, R"([[[{"unsigned":[4]}],[{"signed":[4]}],[{"string":[8],"unsigned":[4]}]],{"len":4}])"); @@ -231,7 +242,8 @@ TEST(TestExtractSchema, ArrayWithDuplicateArraysSchema) extract_schema gen{"id", {}, {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, cache, deadline); EXPECT_SCHEMA_EQ(output, R"([[[[[4]],{"len":1}],[[[8],[4]],{"len":2}]],{"len":4}])"); ddwaf_object_free(&output); @@ -267,7 +279,8 @@ TEST(TestExtractSchema, ArrayWithDuplicateContainersSchema) extract_schema gen{"id", {}, {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, cache, deadline); EXPECT_SCHEMA_EQ(output, R"([[[[[4]],{"len":1}],[{"string":[8],"unsigned":[4]}]],{"len":4}])"); ddwaf_object_free(&output); @@ -282,7 +295,8 @@ TEST(TestExtractSchema, EmptyMapSchema) extract_schema gen{"id", {}, {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, cache, deadline); EXPECT_SCHEMA_EQ(output, R"([{}])"); ddwaf_object_free(&output); @@ -311,7 +325,8 @@ TEST(TestExtractSchema, MapSchema) extract_schema gen{"id", {}, {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, cache, deadline); EXPECT_SCHEMA_EQ(output, R"([{"array":[[[4]],{"len":1}],"invalid":[0],"map":[{"unsigned":[4],"string":[8]}],"null":[1],"string":[8],"unsigned":[4]}])"); ddwaf_object_free(&output); @@ -334,7 +349,8 @@ TEST(TestExtractSchema, DepthLimit) extract_schema gen{"id", {}, {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, cache, deadline); EXPECT_SCHEMA_EQ(output, R"([[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[],{"len":1}]],{"len":1}]],{"len":1}]],{"len":1}]],{"len":1}]],{"len":1}]],{"len":1}]],{"len":1}]],{"len":1}]],{"len":1}]],{"len":1}]],{"len":1}]],{"len":1}]],{"len":1}]],{"len":1}]],{"len":1}]],{"len":1}]],{"len":1}])"); @@ -354,7 +370,8 @@ TEST(TestExtractSchema, ArrayNodesLimit) extract_schema gen{"id", {}, {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, cache, deadline); EXPECT_SCHEMA_EQ(output, R"([[[[],{"len":0}]],{"len":20,"truncated":true}])"); ddwaf_object_free(&output); @@ -373,7 +390,8 @@ TEST(TestExtractSchema, RecordNodesLimit) extract_schema gen{"id", {}, {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, cache, deadline); EXPECT_SCHEMA_EQ(output, R"([{"child":[[],{"len":0}]},{"truncated":true}])"); ddwaf_object_free(&output); @@ -391,7 +409,8 @@ TEST(TestExtractSchema, SchemaWithSingleScanner) extract_schema gen{"id", {}, {}, {&scnr}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, cache, deadline); EXPECT_SCHEMA_EQ(output, R"([8,{"type":"PII","category":"IP"}])"); ddwaf_object_free(&output); @@ -413,7 +432,8 @@ TEST(TestExtractSchema, SchemaWithMultipleScanners) extract_schema gen{"id", {}, {}, {&scnr0, &scnr1, &scnr2}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, cache, deadline); EXPECT_SCHEMA_EQ(output, R"([8,{"type":"PII","category":"second"}])"); ddwaf_object_free(&output); @@ -435,7 +455,8 @@ TEST(TestExtractSchema, SchemaWithScannerNoMatch) extract_schema gen{"id", {}, {}, {&scnr0, &scnr1, &scnr2}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, cache, deadline); EXPECT_SCHEMA_EQ(output, R"([8])"); ddwaf_object_free(&output); @@ -454,7 +475,8 @@ TEST(TestExtractSchema, SchemaWithScannerSingleValueNoKey) extract_schema gen{"id", {}, {}, {&scnr}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, cache, deadline); EXPECT_SCHEMA_EQ(output, R"([8])"); ddwaf_object_free(&output); @@ -475,7 +497,8 @@ TEST(TestExtractSchema, SchemaWithScannerArrayNoKey) extract_schema gen{"id", {}, {}, {&scnr}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, cache, deadline); EXPECT_SCHEMA_EQ(output, R"([[[8]],{"len":1}])"); ddwaf_object_free(&output); @@ -501,7 +524,8 @@ TEST(TestExtractSchema, SchemaWithScannerArrayWithKey) extract_schema gen{"id", {}, {}, {&scnr}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, cache, deadline); EXPECT_SCHEMA_EQ(output, R"([{"string":[[[8,{"category":"IP","type":"PII"}]],{"len":1}]}])"); ddwaf_object_free(&output); @@ -531,7 +555,8 @@ TEST(TestExtractSchema, SchemaWithScannerNestedArrayWithKey) extract_schema gen{"id", {}, {}, {&scnr}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &input}, cache, deadline); EXPECT_SCHEMA_EQ( output, R"([{"string":[[[[[8,{"category":"IP","type":"PII"}]],{"len":1}]],{"len":1}]}])"); diff --git a/tests/processor/fingerprint_test.cpp b/tests/processor/fingerprint_test.cpp index ad191d887..8483581d2 100644 --- a/tests/processor/fingerprint_test.cpp +++ b/tests/processor/fingerprint_test.cpp @@ -34,9 +34,10 @@ TEST(TestHttpEndpointFingerprint, Basic) http_endpoint_fingerprint gen{"id", {}, {}, false, true}; ddwaf::timer deadline{2s}; + processor_cache cache; auto [output, attr] = gen.eval_impl({{}, {}, false, "GET"}, {{}, {}, false, "/path/to/whatever?param=hello"}, - {{}, {}, false, &query}, {{}, {}, false, &body}, deadline); + {{}, {}, false, &query}, {{{}, {}, false, &body}}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -66,9 +67,10 @@ TEST(TestHttpEndpointFingerprint, EmptyQuery) http_endpoint_fingerprint gen{"id", {}, {}, false, true}; ddwaf::timer deadline{2s}; + processor_cache cache; auto [output, attr] = gen.eval_impl({{}, {}, false, "GET"}, {{}, {}, false, "/path/to/whatever?param=hello"}, - {{}, {}, false, &query}, {{}, {}, false, &body}, deadline); + {{}, {}, false, &query}, {{{}, {}, false, &body}}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -96,9 +98,10 @@ TEST(TestHttpEndpointFingerprint, EmptyBody) http_endpoint_fingerprint gen{"id", {}, {}, false, true}; ddwaf::timer deadline{2s}; + processor_cache cache; auto [output, attr] = gen.eval_impl({{}, {}, false, "GET"}, {{}, {}, false, "/path/to/whatever?param=hello"}, - {{}, {}, false, &query}, {{}, {}, false, &body}, deadline); + {{}, {}, false, &query}, {{{}, {}, false, &body}}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -121,8 +124,9 @@ TEST(TestHttpEndpointFingerprint, EmptyEverything) http_endpoint_fingerprint gen{"id", {}, {}, false, true}; ddwaf::timer deadline{2s}; + processor_cache cache; auto [output, attr] = gen.eval_impl({{}, {}, false, ""}, {{}, {}, false, ""}, - {{}, {}, false, &query}, {{}, {}, false, &body}, deadline); + {{}, {}, false, &query}, {{{}, {}, false, &body}}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -154,9 +158,10 @@ TEST(TestHttpEndpointFingerprint, KeyConsistency) http_endpoint_fingerprint gen{"id", {}, {}, false, true}; ddwaf::timer deadline{2s}; + processor_cache cache; auto [output, attr] = gen.eval_impl({{}, {}, false, "GET"}, {{}, {}, false, "/path/to/whatever?param=hello"}, - {{}, {}, false, &query}, {{}, {}, false, &body}, deadline); + {{}, {}, false, &query}, {{{}, {}, false, &body}}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -188,9 +193,10 @@ TEST(TestHttpEndpointFingerprint, InvalidQueryType) http_endpoint_fingerprint gen{"id", {}, {}, false, true}; ddwaf::timer deadline{2s}; + processor_cache cache; auto [output, attr] = gen.eval_impl({{}, {}, false, "GET"}, {{}, {}, false, "/path/to/whatever?param=hello"}, - {{}, {}, false, &query}, {{}, {}, false, &body}, deadline); + {{}, {}, false, &query}, {{{}, {}, false, &body}}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -222,9 +228,10 @@ TEST(TestHttpEndpointFingerprint, InvalidBodyType) http_endpoint_fingerprint gen{"id", {}, {}, false, true}; ddwaf::timer deadline{2s}; + processor_cache cache; auto [output, attr] = gen.eval_impl({{}, {}, false, "GET"}, {{}, {}, false, "/path/to/whatever?param=hello"}, - {{}, {}, false, &query}, {{}, {}, false, &body}, deadline); + {{}, {}, false, &query}, {{{}, {}, false, &body}}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -256,9 +263,10 @@ TEST(TestHttpEndpointFingerprint, InvalidQueryAndBodyType) http_endpoint_fingerprint gen{"id", {}, {}, false, true}; ddwaf::timer deadline{2s}; + processor_cache cache; auto [output, attr] = gen.eval_impl({{}, {}, false, "GET"}, {{}, {}, false, "/path/to/whatever?param=hello"}, - {{}, {}, false, &query}, {{}, {}, false, &body}, deadline); + {{}, {}, false, &query}, {{{}, {}, false, &body}}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -290,9 +298,10 @@ TEST(TestHttpEndpointFingerprint, UriRawConsistency) http_endpoint_fingerprint gen{"id", {}, {}, false, true}; { ddwaf::timer deadline{2s}; + processor_cache cache; auto [output, attr] = gen.eval_impl({{}, {}, false, "GET"}, {{}, {}, false, "/path/to/whatever?param=hello"}, - {{}, {}, false, &query}, {{}, {}, false, &body}, deadline); + {{}, {}, false, &query}, {{{}, {}, false, &body}}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -304,9 +313,10 @@ TEST(TestHttpEndpointFingerprint, UriRawConsistency) { ddwaf::timer deadline{2s}; + processor_cache cache; auto [output, attr] = gen.eval_impl({{}, {}, false, "GET"}, {{}, {}, false, "/path/to/whatever#fragment"}, - {{}, {}, false, &query}, {{}, {}, false, &body}, deadline); + {{}, {}, false, &query}, {{{}, {}, false, &body}}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -318,9 +328,10 @@ TEST(TestHttpEndpointFingerprint, UriRawConsistency) { ddwaf::timer deadline{2s}; + processor_cache cache; auto [output, attr] = gen.eval_impl({{}, {}, false, "GET"}, {{}, {}, false, "/path/to/whatever?param=hello#fragment"}, {{}, {}, false, &query}, - {{}, {}, false, &body}, deadline); + {{{}, {}, false, &body}}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -332,9 +343,10 @@ TEST(TestHttpEndpointFingerprint, UriRawConsistency) { ddwaf::timer deadline{2s}; + processor_cache cache; auto [output, attr] = gen.eval_impl({{}, {}, false, "GET"}, {{}, {}, false, "/path/to/whatever"}, - {{}, {}, false, &query}, {{}, {}, false, &body}, deadline); + {{}, {}, false, &query}, {{{}, {}, false, &body}}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -346,9 +358,10 @@ TEST(TestHttpEndpointFingerprint, UriRawConsistency) { ddwaf::timer deadline{2s}; + processor_cache cache; auto [output, attr] = gen.eval_impl({{}, {}, false, "GET"}, {{}, {}, false, "/PaTh/To/WhAtEVER"}, - {{}, {}, false, &query}, {{}, {}, false, &body}, deadline); + {{}, {}, false, &query}, {{{}, {}, false, &body}}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -362,6 +375,60 @@ TEST(TestHttpEndpointFingerprint, UriRawConsistency) ddwaf_object_free(&body); } +TEST(TestHttpEndpointFingerprint, Regeneration) +{ + ddwaf_object tmp; + + ddwaf_object query; + ddwaf_object_map(&query); + ddwaf_object_map_add(&query, "Key1", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&query, "KEY2", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&query, "key,3", ddwaf_object_invalid(&tmp)); + + http_endpoint_fingerprint gen{"id", {}, {}, false, true}; + processor_cache cache; + + { + ddwaf::timer deadline{2s}; + auto [output, attr] = + gen.eval_impl({{}, {}, false, "GET"}, {{}, {}, false, "/path/to/whatever?param=hello"}, + {{}, {}, false, &query}, std::nullopt, cache, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{output.stringValue, + static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "http-get-0ede9e60-0ac3796a-"); + + ddwaf_object_free(&output); + } + + { + ddwaf_object body; + ddwaf_object_map(&body); + ddwaf_object_map_add(&body, "KEY1", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&body, "KEY2", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&body, "KEY", ddwaf_object_invalid(&tmp)); + ddwaf_object_map_add(&body, "3", ddwaf_object_invalid(&tmp)); + + ddwaf::timer deadline{2s}; + auto [output, attr] = + gen.eval_impl({{}, {}, false, "GET"}, {{}, {}, false, "/path/to/whatever?param=hello"}, + {{}, {}, false, &query}, {{{}, {}, false, &body}}, cache, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{output.stringValue, + static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "http-get-0ede9e60-0ac3796a-9798c0e4"); + + ddwaf_object_free(&output); + ddwaf_object_free(&body); + } + + ddwaf_object_free(&query); +} + TEST(TestHttpHeaderFingerprint, AllKnownHeaders) { ddwaf_object tmp; @@ -382,7 +449,8 @@ TEST(TestHttpHeaderFingerprint, AllKnownHeaders) http_header_fingerprint gen{"id", {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -402,7 +470,8 @@ TEST(TestHttpHeaderFingerprint, NoHeaders) http_header_fingerprint gen{"id", {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -430,7 +499,8 @@ TEST(TestHttpHeaderFingerprint, SomeKnownHeaders) http_header_fingerprint gen{"id", {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -463,7 +533,8 @@ TEST(TestHttpHeaderFingerprint, UserAgent) http_header_fingerprint gen{"id", {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -509,7 +580,8 @@ TEST(TestHttpHeaderFingerprint, ExcludedUnknownHeaders) http_header_fingerprint gen{"id", {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -559,7 +631,8 @@ TEST(TestHttpHeaderFingerprint, UnknownHeaders) http_header_fingerprint gen{"id", {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -591,7 +664,8 @@ TEST(TestHttpNetworkFingerprint, AllXFFHeaders) http_network_fingerprint gen{"id", {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -610,7 +684,8 @@ TEST(TestHttpNetworkFingerprint, NoHeaders) http_network_fingerprint gen{"id", {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -643,7 +718,8 @@ TEST(TestHttpNetworkFingerprint, AllXFFHeadersMultipleChosenIPs) http_network_fingerprint gen{"id", {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -676,7 +752,8 @@ TEST(TestHttpNetworkFingerprint, AllXFFHeadersRandomChosenHeader) http_network_fingerprint gen{"id", {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -715,7 +792,8 @@ TEST(TestHttpNetworkFingerprint, HeaderPrecedence) auto match_frag = [&](ddwaf_object headers, const std::string &expected) { ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{}, {}, false, &headers}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -747,8 +825,9 @@ TEST(TestSessionFingerprint, UserOnly) session_fingerprint gen{"id", {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl( - {{}, {}, false, &cookies}, {{}, {}, false, {}}, {{}, {}, false, "admin"}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{{}, {}, false, &cookies}}, {{{}, {}, false, {}}}, + {{{}, {}, false, "admin"}}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -769,8 +848,9 @@ TEST(TestSessionFingerprint, SessionOnly) session_fingerprint gen{"id", {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl( - {{}, {}, false, &cookies}, {{}, {}, false, "ansd0182u2n"}, {{}, {}, false, {}}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{{}, {}, false, &cookies}}, + {{{}, {}, false, "ansd0182u2n"}}, {{{}, {}, false, {}}}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -800,8 +880,9 @@ TEST(TestSessionFingerprint, CookiesOnly) session_fingerprint gen{"id", {}, {}, false, true}; ddwaf::timer deadline{2s}; + processor_cache cache; auto [output, attr] = gen.eval_impl( - {{}, {}, false, &cookies}, {{}, {}, false, {}}, {{}, {}, false, {}}, deadline); + {{{}, {}, false, &cookies}}, {{{}, {}, false, {}}}, {{{}, {}, false, {}}}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -831,8 +912,9 @@ TEST(TestSessionFingerprint, UserCookieAndSession) session_fingerprint gen{"id", {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &cookies}, {{}, {}, false, "ansd0182u2n"}, - {{}, {}, false, "admin"}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{{}, {}, false, &cookies}}, + {{{}, {}, false, "ansd0182u2n"}}, {{{}, {}, false, "admin"}}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -862,8 +944,9 @@ TEST(TestSessionFingerprint, CookieKeysNormalization) session_fingerprint gen{"id", {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &cookies}, {{}, {}, false, "ansd0182u2n"}, - {{}, {}, false, "admin"}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{{}, {}, false, &cookies}}, + {{{}, {}, false, "ansd0182u2n"}}, {{{}, {}, false, "admin"}}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -893,8 +976,9 @@ TEST(TestSessionFingerprint, CookieValuesNormalization) session_fingerprint gen{"id", {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &cookies}, {{}, {}, false, "ansd0182u2n"}, - {{}, {}, false, "admin"}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{{}, {}, false, &cookies}}, + {{{}, {}, false, "ansd0182u2n"}}, {{{}, {}, false, "admin"}}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -924,8 +1008,9 @@ TEST(TestSessionFingerprint, CookieEmptyValues) session_fingerprint gen{"id", {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &cookies}, {{}, {}, false, "ansd0182u2n"}, - {{}, {}, false, "admin"}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{{}, {}, false, &cookies}}, + {{{}, {}, false, "ansd0182u2n"}}, {{{}, {}, false, "admin"}}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -955,8 +1040,9 @@ TEST(TestSessionFingerprint, CookieEmptyKeys) session_fingerprint gen{"id", {}, {}, false, true}; ddwaf::timer deadline{2s}; - auto [output, attr] = gen.eval_impl({{}, {}, false, &cookies}, {{}, {}, false, "ansd0182u2n"}, - {{}, {}, false, "admin"}, deadline); + processor_cache cache; + auto [output, attr] = gen.eval_impl({{{}, {}, false, &cookies}}, + {{{}, {}, false, "ansd0182u2n"}}, {{{}, {}, false, "admin"}}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -977,8 +1063,9 @@ TEST(TestSessionFingerprint, EmptyEverything) session_fingerprint gen{"id", {}, {}, false, true}; ddwaf::timer deadline{2s}; + processor_cache cache; auto [output, attr] = gen.eval_impl( - {{}, {}, false, &cookies}, {{}, {}, false, {}}, {{}, {}, false, {}}, deadline); + {{{}, {}, false, &cookies}}, {{{}, {}, false, {}}}, {{{}, {}, false, {}}}, cache, deadline); EXPECT_EQ(output.type, DDWAF_OBJ_STRING); EXPECT_EQ(attr, object_store::attribute::none); @@ -991,4 +1078,80 @@ TEST(TestSessionFingerprint, EmptyEverything) ddwaf_object_free(&output); } +TEST(TestSessionFingerprint, Regeneration) +{ + session_fingerprint gen{"id", {}, {}, false, true}; + processor_cache cache; + + { + ddwaf::timer deadline{2s}; + auto [output, attr] = + gen.eval_impl(std::nullopt, std::nullopt, std::nullopt, cache, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{output.stringValue, + static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "ssn----"); + ddwaf_object_free(&output); + } + + { + ddwaf_object tmp; + + ddwaf_object cookies; + ddwaf_object_map(&cookies); + ddwaf_object_map_add(&cookies, "name", ddwaf_object_string(&tmp, "albert,martinez")); + ddwaf_object_map_add(&cookies, "theme", ddwaf_object_string(&tmp, "dark")); + ddwaf_object_map_add(&cookies, "language", ddwaf_object_string(&tmp, "en-GB,en-US")); + ddwaf_object_map_add(&cookies, "tracking_id", ddwaf_object_string(&tmp, "xyzabc")); + ddwaf_object_map_add(&cookies, "gdpr_consent", ddwaf_object_string(&tmp, ",yes")); + ddwaf_object_map_add(&cookies, "session_id", ddwaf_object_string(&tmp, "ansd0182u2n,")); + ddwaf_object_map_add( + &cookies, "last_visit", ddwaf_object_string(&tmp, "2024-07-16T12:00:00Z")); + + ddwaf::timer deadline{2s}; + + auto [output, attr] = + gen.eval_impl({{{}, {}, false, &cookies}}, std::nullopt, std::nullopt, cache, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{output.stringValue, + static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "ssn--df6143bc-64f82cf7-"); + ddwaf_object_free(&output); + + ddwaf_object_free(&cookies); + } + + { + ddwaf::timer deadline{2s}; + + auto [output, attr] = gen.eval_impl( + std::nullopt, {{{}, {}, false, "ansd0182u2n"}}, std::nullopt, cache, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{output.stringValue, + static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "ssn--df6143bc-64f82cf7-269500d3"); + ddwaf_object_free(&output); + } + + { + ddwaf::timer deadline{2s}; + + auto [output, attr] = + gen.eval_impl(std::nullopt, std::nullopt, {{{}, {}, false, "user"}}, cache, deadline); + EXPECT_EQ(output.type, DDWAF_OBJ_STRING); + EXPECT_EQ(attr, object_store::attribute::none); + + std::string_view output_sv{output.stringValue, + static_cast(static_cast(output.nbEntries))}; + EXPECT_STRV(output_sv, "ssn-04f8996d-df6143bc-64f82cf7-269500d3"); + ddwaf_object_free(&output); + } +} + } // namespace diff --git a/tests/processor/processor_test.cpp b/tests/processor/processor_test.cpp index c7cab5ddc..92b262cc7 100644 --- a/tests/processor/processor_test.cpp +++ b/tests/processor/processor_test.cpp @@ -32,7 +32,7 @@ class processor : public ddwaf::structured_processor { {} MOCK_METHOD((std::pair), eval_impl, - (const unary_argument &, ddwaf::timer &), (const)); + (const unary_argument &, processor_cache &, ddwaf::timer &), (const)); }; } // namespace mock @@ -58,7 +58,7 @@ TEST(TestProcessor, SingleMappingOutputNoEvalUnconditional) mock::processor proc{"id", std::make_shared(), std::move(mappings), false, true}; - EXPECT_CALL(proc, eval_impl(_, _)) + EXPECT_CALL(proc, eval_impl(_, _, _)) .WillOnce(Return(std::pair{ output, object_store::attribute::none})); @@ -111,7 +111,7 @@ TEST(TestProcessor, MultiMappingOutputNoEvalUnconditional) mock::processor proc{"id", std::make_shared(), std::move(mappings), false, true}; EXPECT_STREQ(proc.get_id().c_str(), "id"); - EXPECT_CALL(proc, eval_impl(_, _)) + EXPECT_CALL(proc, eval_impl(_, _, _)) .WillOnce(Return(std::pair( first_output, object_store::attribute::none))) .WillOnce(Return(std::pair( @@ -173,7 +173,7 @@ TEST(TestProcessor, SingleMappingOutputNoEvalConditionalTrue) mock::processor proc{"id", builder.build(), std::move(mappings), false, true}; EXPECT_STREQ(proc.get_id().c_str(), "id"); - EXPECT_CALL(proc, eval_impl(_, _)) + EXPECT_CALL(proc, eval_impl(_, _, _)) .WillOnce(Return(std::pair{ output, object_store::attribute::none})); @@ -221,7 +221,7 @@ TEST(TestProcessor, SingleMappingOutputNoEvalConditionalCached) mock::processor proc{"id", builder.build(), std::move(mappings), false, true}; EXPECT_STREQ(proc.get_id().c_str(), "id"); - EXPECT_CALL(proc, eval_impl(_, _)) + EXPECT_CALL(proc, eval_impl(_, _, _)) .WillOnce(Return(std::pair{ output, object_store::attribute::none})); @@ -323,7 +323,7 @@ TEST(TestProcessor, SingleMappingNoOutputEvalUnconditional) mock::processor proc{"id", std::make_shared(), std::move(mappings), true, false}; EXPECT_STREQ(proc.get_id().c_str(), "id"); - EXPECT_CALL(proc, eval_impl(_, _)) + EXPECT_CALL(proc, eval_impl(_, _, _)) .WillOnce(Return(std::pair{ output, object_store::attribute::none})); @@ -376,7 +376,7 @@ TEST(TestProcessor, SingleMappingNoOutputEvalConditionalTrue) mock::processor proc{"id", builder.build(), std::move(mappings), true, false}; EXPECT_STREQ(proc.get_id().c_str(), "id"); - EXPECT_CALL(proc, eval_impl(_, _)) + EXPECT_CALL(proc, eval_impl(_, _, _)) .WillOnce(Return(std::pair{ output, object_store::attribute::none})); processor_cache cache; @@ -468,7 +468,7 @@ TEST(TestProcessor, MultiMappingNoOutputEvalUnconditional) mock::processor proc{"id", std::make_shared(), std::move(mappings), true, false}; EXPECT_STREQ(proc.get_id().c_str(), "id"); - EXPECT_CALL(proc, eval_impl(_, _)) + EXPECT_CALL(proc, eval_impl(_, _, _)) .WillOnce(Return(std::pair( first_output, object_store::attribute::none))) .WillOnce(Return(std::pair( @@ -518,7 +518,7 @@ TEST(TestProcessor, SingleMappingOutputEvalUnconditional) mock::processor proc{"id", std::make_shared(), std::move(mappings), true, true}; EXPECT_STREQ(proc.get_id().c_str(), "id"); - EXPECT_CALL(proc, eval_impl(_, _)) + EXPECT_CALL(proc, eval_impl(_, _, _)) .WillOnce(Return(std::pair{ output, object_store::attribute::none})); @@ -573,7 +573,7 @@ TEST(TestProcessor, OutputAlreadyAvailableInStore) mock::processor proc{"id", std::make_shared(), std::move(mappings), false, true}; EXPECT_STREQ(proc.get_id().c_str(), "id"); - EXPECT_CALL(proc, eval_impl(_, _)).Times(0); + EXPECT_CALL(proc, eval_impl(_, _, _)).Times(0); ddwaf_object output_map; ddwaf_object_map(&output_map); @@ -607,7 +607,7 @@ TEST(TestProcessor, OutputAlreadyGenerated) mock::processor proc{"id", std::make_shared(), std::move(mappings), false, true}; EXPECT_STREQ(proc.get_id().c_str(), "id"); - EXPECT_CALL(proc, eval_impl(_, _)).Times(1); + EXPECT_CALL(proc, eval_impl(_, _, _)).Times(1); ddwaf_object output_map; ddwaf_object_map(&output_map); @@ -643,7 +643,7 @@ TEST(TestProcessor, EvalAlreadyAvailableInStore) mock::processor proc{"id", std::make_shared(), std::move(mappings), true, false}; EXPECT_STREQ(proc.get_id().c_str(), "id"); - EXPECT_CALL(proc, eval_impl(_, _)).Times(0); + EXPECT_CALL(proc, eval_impl(_, _, _)).Times(0); processor_cache cache; timer deadline{2s}; @@ -671,7 +671,7 @@ TEST(TestProcessor, OutputWithoutDerivedMap) mock::processor proc{"id", std::make_shared(), std::move(mappings), false, true}; EXPECT_STREQ(proc.get_id().c_str(), "id"); - EXPECT_CALL(proc, eval_impl(_, _)).Times(0); + EXPECT_CALL(proc, eval_impl(_, _, _)).Times(0); processor_cache cache; timer deadline{2s}; @@ -702,7 +702,7 @@ TEST(TestProcessor, OutputEvalWithoutDerivedMap) mock::processor proc{"id", std::make_shared(), std::move(mappings), true, true}; EXPECT_STREQ(proc.get_id().c_str(), "id"); - EXPECT_CALL(proc, eval_impl(_, _)) + EXPECT_CALL(proc, eval_impl(_, _, _)) .WillOnce(Return(std::pair{ output, object_store::attribute::none})); @@ -736,7 +736,7 @@ TEST(TestProcessor, Timeout) mock::processor proc{"id", std::make_shared(), std::move(mappings), true, false}; EXPECT_STREQ(proc.get_id().c_str(), "id"); - EXPECT_CALL(proc, eval_impl(_, _)).Times(0); + EXPECT_CALL(proc, eval_impl(_, _, _)).Times(0); processor_cache cache; timer deadline{0s}; diff --git a/tests/processor/structured_processor_test.cpp b/tests/processor/structured_processor_test.cpp index 936fe7356..43715245a 100644 --- a/tests/processor/structured_processor_test.cpp +++ b/tests/processor/structured_processor_test.cpp @@ -34,7 +34,7 @@ class processor : public ddwaf::structured_processor { MOCK_METHOD((std::pair), eval_impl, (const unary_argument &unary, const optional_argument &optional, - const variadic_argument &variadic, ddwaf::timer &), + const variadic_argument &variadic, processor_cache &, ddwaf::timer &), (const)); }; @@ -67,7 +67,7 @@ TEST(TestStructuredProcessor, AllParametersAvailable) mock::processor proc{"id", std::make_shared(), std::move(mappings), false, true}; - EXPECT_CALL(proc, eval_impl(_, _, _, _)) + EXPECT_CALL(proc, eval_impl(_, _, _, _, _)) .WillOnce(Return(std::pair{ output, object_store::attribute::none})); @@ -116,7 +116,7 @@ TEST(TestStructuredProcessor, OptionalParametersNotAvailable) mock::processor proc{"id", std::make_shared(), std::move(mappings), false, true}; - EXPECT_CALL(proc, eval_impl(_, _, _, _)) + EXPECT_CALL(proc, eval_impl(_, _, _, _, _)) .WillOnce(Return(std::pair{ output, object_store::attribute::none})); @@ -162,7 +162,7 @@ TEST(TestStructuredProcessor, RequiredParameterNotAvailable) mock::processor proc{"id", std::make_shared(), std::move(mappings), false, true}; - EXPECT_CALL(proc, eval_impl(_, _, _, _)).Times(0); + EXPECT_CALL(proc, eval_impl(_, _, _, _, _)).Times(0); EXPECT_STREQ(proc.get_id().c_str(), "id"); @@ -201,7 +201,7 @@ TEST(TestStructuredProcessor, NoVariadocParametersAvailable) mock::processor proc{"id", std::make_shared(), std::move(mappings), false, true}; - EXPECT_CALL(proc, eval_impl(_, _, _, _)).Times(0); + EXPECT_CALL(proc, eval_impl(_, _, _, _, _)).Times(0); EXPECT_STREQ(proc.get_id().c_str(), "id"); diff --git a/tests/transformer/transformer_utils.hpp b/tests/transformer/transformer_utils.hpp index 031422262..059cd345b 100644 --- a/tests/transformer/transformer_utils.hpp +++ b/tests/transformer/transformer_utils.hpp @@ -38,13 +38,13 @@ template constexpr std::array literal_to_array(cons #define EXPECT_TRANSFORM(name, source, expected) \ { \ { \ - cow_string str({source, sizeof(source) - 1}); \ + cow_string str(std::string_view{source, sizeof(source) - 1}); \ EXPECT_TRUE(transformer::name::transform(str)); \ EXPECT_STREQ(str.data(), expected); \ } \ if constexpr (sizeof(source) > 1) { \ std::array copy{literal_to_array(source)}; \ - cow_string str({copy.data(), copy.size()}); \ + cow_string str(std::string_view{copy.data(), copy.size()}); \ EXPECT_TRUE(transformer::name::transform(str)) << "Non nul-terminated string"; \ EXPECT_STREQ(str.data(), expected) << "Non nul-terminated string"; \ } \ @@ -53,13 +53,13 @@ template constexpr std::array literal_to_array(cons #define EXPECT_NO_TRANSFORM(name, source) \ { \ { \ - cow_string str({source, sizeof(source) - 1}); \ + cow_string str(std::string_view{source, sizeof(source) - 1}); \ EXPECT_FALSE(transformer::name::transform(str)); \ EXPECT_FALSE(str.modified()); \ } \ if constexpr (sizeof(source) > 1) { \ std::array copy{literal_to_array(source)}; \ - cow_string str({copy.data(), copy.size()}); \ + cow_string str(std::string_view{copy.data(), copy.size()}); \ EXPECT_FALSE(transformer::name::transform(str)) << "Non nul-terminated string"; \ EXPECT_FALSE(str.modified()); \ } \ diff --git a/tools/waf_runner.cpp b/tools/waf_runner.cpp index 07686bdfd..c14136494 100644 --- a/tools/waf_runner.cpp +++ b/tools/waf_runner.cpp @@ -26,7 +26,8 @@ auto parse_args(int argc, char *argv[]) { const std::map> arg_mapping{ - {"-r", "--ruleset"}, {"-i", "--input"}, {"--ruleset", "--ruleset"}, {"--input", "--input"}}; + {"-r", "--ruleset"}, {"-i", "--input"}, {"--ruleset", "--ruleset"}, + {"--input", "--input"}, {"-v", "--verbose"}, {"--verbose", "--verbose"}}; std::unordered_map> args; auto last_arg = args.end(); @@ -53,10 +54,14 @@ const char *value_regex = R"((?i)(?:p(?:ass)?w(?:or)?d|pass(?:[_-]?phrase)?|secr int main(int argc, char *argv[]) { - ddwaf_set_log_cb(log_cb, DDWAF_LOG_TRACE); - auto args = parse_args(argc, argv); + if (args.contains("--verbose")) { + ddwaf_set_log_cb(log_cb, DDWAF_LOG_TRACE); + } else { + ddwaf_set_log_cb(log_cb, DDWAF_LOG_OFF); + } + const std::vector rulesets = args["--ruleset"]; const std::vector inputs = args["--input"]; if (rulesets.empty() || inputs.empty()) {