Skip to content

Commit

Permalink
Negated scalar condition for matchers
Browse files Browse the repository at this point in the history
  • Loading branch information
Anilm3 committed Aug 21, 2024
1 parent ba427a7 commit 08826d4
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 29 deletions.
101 changes: 81 additions & 20 deletions src/condition/scalar_condition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ namespace ddwaf {

namespace {

template <typename Iterator>
std::optional<condition_match> eval_object(Iterator &it, std::string_view address, bool ephemeral,
template <typename ResultType, typename Iterator>
ResultType eval_object(Iterator &it, std::string_view address, bool ephemeral,
const matcher::base &matcher, const std::span<const transformer_id> &transformers,
const object_limits &limits)
{
Expand Down Expand Up @@ -60,8 +60,12 @@ std::optional<condition_match> eval_object(Iterator &it, std::string_view addres

DDWAF_TRACE("Target {} matched parameter value {}", address, highlight);

return {{{{"input"sv, object_to_string(dst), address, it.get_current_path()}},
{std::move(highlight)}, matcher.name(), matcher.to_string(), ephemeral}};
if constexpr (std::is_same_v<ResultType, bool>) {
return true;
} else {
return {{{{"input"sv, object_to_string(dst), address, it.get_current_path()}},
{std::move(highlight)}, matcher.name(), matcher.to_string(), ephemeral}};
}
}
}
}
Expand All @@ -73,12 +77,16 @@ std::optional<condition_match> eval_object(Iterator &it, std::string_view addres

DDWAF_TRACE("Target {} matched parameter value {}", address, highlight);

return {{{{"input"sv, object_to_string(src), address, it.get_current_path()}},
{std::move(highlight)}, matcher.name(), matcher.to_string(), ephemeral}};
if constexpr (std::is_same_v<ResultType, bool>) {
return true;
} else {
return {{{{"input"sv, object_to_string(src), address, it.get_current_path()}},
{std::move(highlight)}, matcher.name(), matcher.to_string(), ephemeral}};
}
}

template <typename Iterator>
std::optional<condition_match> eval_target(Iterator &it, std::string_view address, bool ephemeral,
template <typename ResultType, typename Iterator>
ResultType eval_target(Iterator &it, std::string_view address, bool ephemeral,
const matcher::base &matcher, const std::span<const transformer_id> &transformers,
const object_limits &limits, ddwaf::timer &deadline)
{
Expand All @@ -91,8 +99,8 @@ std::optional<condition_match> eval_target(Iterator &it, std::string_view addres
continue;
}

auto match = eval_object(it, address, ephemeral, matcher, transformers, limits);
if (match.has_value()) {
auto match = eval_object<ResultType>(it, address, ephemeral, matcher, transformers, limits);
if (match) {
// If this target matched, we can stop processing
return match;
}
Expand All @@ -101,29 +109,30 @@ std::optional<condition_match> eval_target(Iterator &it, std::string_view addres
return {};
}

} // namespace

const matcher::base *scalar_condition::get_matcher(
const std::unordered_map<std::string, std::shared_ptr<matcher::base>> &dynamic_matchers) const
const matcher::base *get_matcher(const std::unique_ptr<matcher::base> &matcher,
const std::string &data_id,
const std::unordered_map<std::string, std::shared_ptr<matcher::base>> &dynamic_matchers)
{
if (matcher_ || data_id_.empty()) {
return matcher_.get();
if (matcher || data_id.empty()) {
return matcher.get();
}

auto it = dynamic_matchers.find(data_id_);
auto it = dynamic_matchers.find(data_id);
if (it != dynamic_matchers.end()) {
return it->second.get();
}

return nullptr;
}

} // namespace

eval_result scalar_condition::eval(condition_cache &cache, const object_store &store,
const exclusion::object_set_ref &objects_excluded,
const std::unordered_map<std::string, std::shared_ptr<matcher::base>> &dynamic_matchers,
ddwaf::timer &deadline) const
{
const auto *matcher = get_matcher(dynamic_matchers);
const auto *matcher = get_matcher(matcher_, data_id_, dynamic_matchers);
if (matcher == nullptr) {
return {};
}
Expand Down Expand Up @@ -152,11 +161,11 @@ eval_result scalar_condition::eval(condition_cache &cache, const object_store &s
// TODO: iterators could be cached to avoid reinitialisation
if (target.source == data_source::keys) {
object::key_iterator it(object, target.key_path, objects_excluded, limits_);
match = eval_target(
match = eval_target<std::optional<condition_match>>(
it, target.name, ephemeral, *matcher, target.transformers, limits_, deadline);
} else {
object::value_iterator it(object, target.key_path, objects_excluded, limits_);
match = eval_target(
match = eval_target<std::optional<condition_match>>(
it, target.name, ephemeral, *matcher, target.transformers, limits_, deadline);
}

Expand All @@ -169,4 +178,56 @@ eval_result scalar_condition::eval(condition_cache &cache, const object_store &s
return {false, false};
}

eval_result scalar_negated_condition::eval(condition_cache &cache, const object_store &store,
const exclusion::object_set_ref &objects_excluded,
const std::unordered_map<std::string, std::shared_ptr<matcher::base>> &dynamic_matchers,
ddwaf::timer &deadline) const
{
if (deadline.expired()) {
throw ddwaf::timeout_exception();
}

const auto *matcher = get_matcher(matcher_, data_id_, dynamic_matchers);
if (matcher == nullptr) {
return {};
}

if (cache.targets.size() != 1) {
cache.targets.assign(1, nullptr);
}

// This type of scalar condition only accepts a single target
const auto &target = targets_[0];

auto [object, attr] = store.get_target(target.index);
if (object == nullptr || object == cache.targets[0]) {
return {};
}

const bool ephemeral = (attr == object_store::attribute::ephemeral);
if (!ephemeral) {
cache.targets[0] = object;
}

bool match = false;
if (target.source == data_source::keys) {
object::key_iterator it(object, target.key_path, objects_excluded, limits_);
match = eval_target<bool>(
it, target.name, ephemeral, *matcher, target.transformers, limits_, deadline);
} else {
object::value_iterator it(object, target.key_path, objects_excluded, limits_);
match = eval_target<bool>(
it, target.name, ephemeral, *matcher, target.transformers, limits_, deadline);
}

if (!match) {
cache.match = {{{{"input"sv, object_to_string(*object), target.name,
{target.key_path.begin(), target.key_path.end()}}},
{}, matcher_name_, matcher->to_string(), ephemeral}};
return {true, ephemeral};
}

return {false, false};
}

} // namespace ddwaf
46 changes: 42 additions & 4 deletions src/condition/scalar_condition.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,55 @@ class scalar_condition : public base_condition {

static constexpr auto arguments()
{
return std::array<parameter_specification, 1>{{{"inputs", true, false}}};
return std::array<parameter_specification, 1>{{{"inputs", /*variadic*/ true, false}}};
}

protected:
[[nodiscard]] const matcher::base *get_matcher(
const std::unordered_map<std::string, std::shared_ptr<matcher::base>> &dynamic_matchers)
const;
std::unique_ptr<matcher::base> matcher_;
std::string data_id_;
std::vector<condition_target> targets_;
const object_limits limits_;
};

class scalar_negated_condition : public base_condition {
public:
scalar_negated_condition(std::unique_ptr<matcher::base> &&matcher, std::string data_id,
std::vector<condition_parameter> args, std::string matcher_name,
const object_limits &limits = {})
: matcher_(std::move(matcher)), data_id_(std::move(data_id)),
matcher_name_(std::move(matcher_name)), limits_(limits)
{
if (args.size() > 1) {
throw std::invalid_argument("Matcher initialised with more than one argument");
}

if (args.empty()) {
throw std::invalid_argument("Matcher initialised without arguments");
}

targets_ = std::move(args[0].targets);
}

eval_result eval(condition_cache &cache, const object_store &store,
const exclusion::object_set_ref &objects_excluded,
const std::unordered_map<std::string, std::shared_ptr<matcher::base>> &dynamic_matchers,
ddwaf::timer &deadline) const override;

void get_addresses(std::unordered_map<target_index, std::string> &addresses) const override
{
for (const auto &target : targets_) { addresses.emplace(target.index, target.name); }
}

static constexpr auto arguments()
{
return std::array<parameter_specification, 1>{{{"inputs", /*variadic*/ false, false}}};
}

protected:
std::unique_ptr<matcher::base> matcher_;
std::string data_id_;
std::vector<condition_target> targets_;
std::string matcher_name_;
const object_limits limits_;
};

Expand Down
23 changes: 18 additions & 5 deletions src/parser/expression_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,17 +141,30 @@ std::shared_ptr<expression> parse_expression(const parameter::vector &conditions
conditions.emplace_back(
std::make_unique<exists_negated_condition>(std::move(arguments), limits));
} else {
auto raw_operator_name = operator_name;
auto negated = operator_name.starts_with('!');
if (negated) {
operator_name = operator_name.substr(1);
}

auto [data_id, matcher] = parse_matcher(operator_name, params);

if (!matcher && !data_id.empty()) {
data_ids_to_type.emplace(data_id, operator_name);
}

auto arguments =
parse_arguments<scalar_condition>(params, source, transformers, addresses, limits);

conditions.emplace_back(std::make_unique<scalar_condition>(
std::move(matcher), data_id, std::move(arguments), limits));
if (!negated) {
auto arguments = parse_arguments<scalar_condition>(
params, source, transformers, addresses, limits);
conditions.emplace_back(std::make_unique<scalar_condition>(
std::move(matcher), data_id, std::move(arguments), limits));
} else {
auto arguments = parse_arguments<scalar_negated_condition>(
params, source, transformers, addresses, limits);
conditions.emplace_back(
std::make_unique<scalar_negated_condition>(std::move(matcher), data_id,
std::move(arguments), std::string{raw_operator_name}, limits));
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
name: "negated ip_match operator, match IPv4 CIDR",
runs: [
{
persistent-input: {
rule3-input: 192.187.25.1
},
rules: [
{
3: [
{
address: rule3-input,
value: 192.187.25.1
}
]
}
],
code: match
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
name: "negated ip_match operator, match IPv6 CIDR",
runs: [
{
persistent-input: {
rule3-input: "abce::1234:0:ab11:0"
},
rules: [
{
3: [
{
address: rule3-input,
value: "abce::1234:0:ab11:0"
}
]
}
],
code: match
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
name: "negated ip_match operator, no match on IPv4 CIDR",
runs: [
{
persistent-input: {
rule3-input: 192.188.25.1
},
code: ok
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
name: "negated ip_match operator, no match on IPv6 CIDR",
runs: [
{
persistent-input: {
rule3-input: "abcd::1234:0:ab11:0"
},
code: ok
}
]
}
13 changes: 13 additions & 0 deletions validator/tests/rules/operators/ip_match/ruleset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,16 @@ rules:
list:
- "192.188.0.0/16"
- "abcd::1234:0:0:0/96"
- id: "3"
name: rule3-ip-match-negated-with-cidr
tags:
type: flow3
category: category
conditions:
- operator: "!ip_match"
parameters:
inputs:
- address: rule3-input
list:
- "192.188.0.0/16"
- "abcd::1234:0:0:0/96"

0 comments on commit 08826d4

Please sign in to comment.