Skip to content

Commit

Permalink
Module-based rule evaluation precedence (#353)
Browse files Browse the repository at this point in the history
  • Loading branch information
Anilm3 authored Dec 2, 2024
1 parent eb80490 commit 94a594d
Show file tree
Hide file tree
Showing 117 changed files with 5,248 additions and 1,563 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ perf/test_files/breakdown.numbers
.vscode
*.swp
*.swo
*.swn
3 changes: 2 additions & 1 deletion cmake/objects.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ set(LIBDDWAF_SOURCE
${libddwaf_SOURCE_DIR}/src/event.cpp
${libddwaf_SOURCE_DIR}/src/object.cpp
${libddwaf_SOURCE_DIR}/src/object_store.cpp
${libddwaf_SOURCE_DIR}/src/collection.cpp
${libddwaf_SOURCE_DIR}/src/module.cpp
${libddwaf_SOURCE_DIR}/src/expression.cpp
${libddwaf_SOURCE_DIR}/src/ruleset_info.cpp
${libddwaf_SOURCE_DIR}/src/ip_utils.cpp
Expand All @@ -22,6 +22,7 @@ set(LIBDDWAF_SOURCE
${libddwaf_SOURCE_DIR}/src/sha256.cpp
${libddwaf_SOURCE_DIR}/src/uuid.cpp
${libddwaf_SOURCE_DIR}/src/action_mapper.cpp
${libddwaf_SOURCE_DIR}/src/builder/module_builder.cpp
${libddwaf_SOURCE_DIR}/src/builder/processor_builder.cpp
${libddwaf_SOURCE_DIR}/src/tokenizer/sql_base.cpp
${libddwaf_SOURCE_DIR}/src/tokenizer/pgsql.cpp
Expand Down
75 changes: 75 additions & 0 deletions src/builder/module_builder.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Unless explicitly stated otherwise all files in this repository are
// dual-licensed under the Apache-2.0 License or BSD-3-Clause License.
//
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2021 Datadog, Inc.

#include <algorithm>
#include <array>
#include <cstddef>
#include <memory>
#include <string_view>
#include <utility>
#include <vector>

#include "builder/module_builder.hpp"
#include "module.hpp"
#include "module_category.hpp"
#include "rule.hpp"

namespace ddwaf {

rule_module rule_module_builder::build()
{
const auto &sort_fn = [this](const auto *left, const auto *right) {
const auto lverdict = left->get_verdict();
const auto rverdict = right->get_verdict();
const auto lsource = left->get_source();
const auto rsource = right->get_source();
return lverdict > rverdict ||
(lverdict == rverdict &&
(source_precedence_fn_(lsource, rsource) ||
(lsource == rsource && grouping_key_fn_(left) < grouping_key_fn_(right))));
};

// Sort first
std::sort(rules_.begin(), rules_.end(), sort_fn);

// Generate collections by grouping based on the grouping key
std::string_view prev_key;
for (std::size_t i = 0; i < rules_.size(); ++i) {
const auto *rule = rules_[i];
auto cur_key = grouping_key_fn_(rule);
if (cur_key != prev_key) {
if (!collections_.empty()) {
collections_.back().end = i;
}
using rule_collection = rule_module::rule_collection;
collections_.emplace_back(rule_collection{cur_key, rule->get_verdict(), i, i + 1});
prev_key = cur_key;
}
}

if (!collections_.empty()) {
collections_.back().end = rules_.size();
}

return rule_module{std::move(rules_), std::move(collections_), policy_};
}

std::array<rule_module, rule_module_count> rule_module_set_builder::build(
const std::vector<std::shared_ptr<core_rule>> &rules)
{
std::array<rule_module, rule_module_count> all_modules;

for (const auto &rule : rules) {
auto &builder = builders_[static_cast<std::size_t>(rule->get_module())];
builder.insert(rule.get());
}

for (std::size_t i = 0; i < builders_.size(); ++i) { all_modules[i] = builders_[i].build(); }

return all_modules;
}

} // namespace ddwaf
131 changes: 131 additions & 0 deletions src/builder/module_builder.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// 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 "module.hpp"
#include "module_category.hpp"
#include "rule.hpp"

namespace ddwaf {

// Helpers
inline bool user_rule_precedence(
const core_rule::source_type left, const core_rule::source_type right)
{
return left > right;
}

inline bool base_rule_precedence(
const core_rule::source_type left, const core_rule::source_type right)
{
return left < right;
}

[[gnu::const]] inline std::string_view type_grouping_key(const core_rule *rule)
{
return rule->get_type();
}
[[gnu::const]] constexpr std::string_view null_grouping_key(const core_rule * /*rule*/)
{
return {};
}

// The module builder can be used to build a single module
class rule_module_builder {
public:
using source_type = core_rule::source_type;
using source_precedence_fn_type = bool (*)(source_type left, source_type right);
using grouping_key_fn_type = std::string_view (*)(const core_rule *rule);
using expiration_policy = rule_module::expiration_policy;

rule_module_builder(source_precedence_fn_type source_precedence,
grouping_key_fn_type grouping_key, expiration_policy policy = expiration_policy::expiring)
: source_precedence_fn_(source_precedence), grouping_key_fn_(grouping_key), policy_(policy)
{}
~rule_module_builder() = default;
rule_module_builder(rule_module_builder &&) = delete;
rule_module_builder(const rule_module_builder &) = delete;
rule_module_builder &operator=(rule_module_builder &&) = delete;
rule_module_builder &operator=(const rule_module_builder &) = delete;

void insert(core_rule *rule) { rules_.emplace_back(rule); }

rule_module build();

protected:
source_precedence_fn_type source_precedence_fn_;
grouping_key_fn_type grouping_key_fn_;
std::vector<core_rule *> rules_;
std::vector<rule_module::rule_collection> collections_;
expiration_policy policy_;
};

class rule_module_set_builder {
public:
rule_module_set_builder() = default;
~rule_module_set_builder() = default;
rule_module_set_builder(rule_module_set_builder &&) = delete;
rule_module_set_builder(const rule_module_set_builder &) = delete;
rule_module_set_builder &operator=(rule_module_set_builder &&) = delete;
rule_module_set_builder &operator=(const rule_module_set_builder &) = delete;

std::array<rule_module, rule_module_count> build(
const std::vector<std::shared_ptr<core_rule>> &rules);

protected:
/* Rules Ordering
* ---------------------------
* network_acl: (ignore timeout)
* - Collection: [order by: verdict (block then monitor), source (base then user)]
*
* authentication_acl: (ignore timeout)
* - Collection: [order by: verdict, source (base then user)]
*
* custom_acl:
* - Collection: [order by: verdict, source (user then base)]
*
* configuration:
* - Collection: [order by: verdict, source (user then base)]
*
* business_logic:
* - Collection: [order by: verdict, source (user then base)]
*
* rasp:
* - Collection: [order by: verdict, source (base then user)]
*
* waf:
* - Collection: [verdict=block, source=user, type=<type 1>]
* - Collection: [verdict=block, source=user, type=...]
* - Collection: [verdict=block, source=base, type=<type 1>]
* - Collection: [verdict=block, source=base, type=...]
* - Collection: [verdict=monitor, source=user, type=<type 1>]
* - Collection: [verdict=monitor, source=user, type=...]
* - Collection: [verdict=monitor, source=base, type=<type 1>]
* - Collection: [verdict=monitor, source=base, type=...]
*
* Note: Non expiring modules should always be placed before expiring modules.
*/

std::array<rule_module_builder, rule_module_count> builders_{{
// Network-ACL
{base_rule_precedence, null_grouping_key, rule_module::expiration_policy::non_expiring},
// Authentication-ACL
{base_rule_precedence, null_grouping_key, rule_module::expiration_policy::non_expiring},
// Custom-ACL
{user_rule_precedence, null_grouping_key, rule_module::expiration_policy::expiring},
// Configuration
{user_rule_precedence, null_grouping_key, rule_module::expiration_policy::expiring},
// Business logic
{user_rule_precedence, null_grouping_key, rule_module::expiration_policy::expiring},
// RASP
{base_rule_precedence, null_grouping_key, rule_module::expiration_policy::expiring},
// WAF
{user_rule_precedence, type_grouping_key, rule_module::expiration_policy::expiring},
}};
};

} // namespace ddwaf
50 changes: 40 additions & 10 deletions src/clock.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
#include <atomic>
#include <chrono>

#if defined __has_builtin
# if __has_builtin(__builtin_add_overflow)
# define HAS_BUILTIN_ADD_OVERFLOW
# endif
#endif

namespace ddwaf {
#ifndef __linux__
using monotonic_clock = std::chrono::steady_clock;
Expand All @@ -28,14 +34,14 @@ struct monotonic_clock {
};
#endif // __linux__

class timer {
// Syscall period refers to the number of calls to expired() before
// clock_gettime is called. This approach is only feasible because the
// WAF calls expired() quite often, otherwise another solution would be
// required to minimise syscalls.
template <std::size_t SyscallPeriod = 16> class base_timer {
public:
// Syscall period refers to the number of calls to expired() before
// clock_gettime is called. This approach is only feasible because the
// WAF calls expired() quite often, otherwise another solution would be
// required to minimise syscalls.
explicit timer(std::chrono::microseconds exp, uint32_t syscall_period = default_syscall_period)
: start_(monotonic_clock::now()), end_(start_ + exp), syscall_period_(syscall_period)
explicit base_timer(std::chrono::nanoseconds exp)
: start_(monotonic_clock::now()), end_(add_saturated(start_, exp))
{}

bool expired()
Expand All @@ -44,7 +50,7 @@ class timer {
if (end_ <= monotonic_clock::now()) {
expired_ = true;
} else {
calls_ = syscall_period_;
calls_ = SyscallPeriod;
}
}
return expired_;
Expand All @@ -58,12 +64,36 @@ class timer {
}

protected:
constexpr static uint32_t default_syscall_period{16};
static monotonic_clock::time_point add_saturated(
monotonic_clock::time_point augend, std::chrono::nanoseconds addend)
{
#ifdef HAS_BUILTIN_ADD_OVERFLOW
using duration = monotonic_clock::duration;

duration::rep augend_count = augend.time_since_epoch().count();
duration::rep addend_count = addend.count();
duration::rep result;

if (__builtin_add_overflow(augend_count, addend_count, &result)) {
return monotonic_clock::time_point::max();
}

return monotonic_clock::time_point(duration(result));
#else
return (addend > (monotonic_clock::time_point::max() - augend))
? monotonic_clock::time_point::max()
: augend + addend;
#endif
}

monotonic_clock::time_point start_;
monotonic_clock::time_point end_;
const uint32_t syscall_period_;
uint32_t calls_{1};
bool expired_{false};
};

using timer = base_timer<16>;

inline timer endless_timer() { return timer{std::chrono::nanoseconds::max()}; }

} // namespace ddwaf
Loading

0 comments on commit 94a594d

Please sign in to comment.