Skip to content

Commit

Permalink
Fix tests, add parser_v1 support again
Browse files Browse the repository at this point in the history
  • Loading branch information
Anilm3 committed Jan 16, 2024
1 parent 6d07990 commit e1a9342
Show file tree
Hide file tree
Showing 17 changed files with 1,135 additions and 420 deletions.
1 change: 1 addition & 0 deletions cmake/objects.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ set(LIBDDWAF_SOURCE
${libddwaf_SOURCE_DIR}/src/generator/extract_schema.cpp
${libddwaf_SOURCE_DIR}/src/parser/common.cpp
${libddwaf_SOURCE_DIR}/src/parser/parser.cpp
${libddwaf_SOURCE_DIR}/src/parser/parser_v1.cpp
${libddwaf_SOURCE_DIR}/src/parser/parser_v2.cpp
${libddwaf_SOURCE_DIR}/src/parser/rule_data_parser.cpp
${libddwaf_SOURCE_DIR}/src/condition/lfi_detector.cpp
Expand Down
31 changes: 16 additions & 15 deletions src/condition/matcher_proxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ const matcher::base *matcher_proxy::get_matcher(
return nullptr;
}

eval_result matcher_proxy::eval_impl(const argument_stack &stack, cache_type &cache,
eval_result matcher_proxy::eval(cache_type &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,
const object_limits &limits, ddwaf::timer &deadline) const
Expand All @@ -136,43 +136,44 @@ eval_result matcher_proxy::eval_impl(const argument_stack &stack, cache_type &ca
return {};
}

const auto &targets = stack.get<argument_stack::variadic>(0);
if (cache.targets.size() != targets.size()) {
cache.targets.assign(targets.size(), nullptr);
if (cache.targets.size() != targets_.size()) {
cache.targets.assign(targets_.size(), nullptr);
}

for (unsigned i = 0; i < targets.size(); ++i) {
for (unsigned i = 0; i < targets_.size(); ++i) {
if (deadline.expired()) {
throw ddwaf::timeout_exception();
}

const auto &target = targets[i];
if (target.object == cache.targets[i]) {
const auto &target = targets_[i];
auto [object, attr] = store.get_target(target.root);
if (object == nullptr || object == cache.targets[i]) {
continue;
}

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

std::optional<event::match> optional_match;
// TODO: iterators could be cached to avoid reinitialisation
if (target.source == data_source::keys) {
object::key_iterator it(target.object, target.key_path, objects_excluded, limits);
object::key_iterator it(object, target.key_path, objects_excluded, limits);
optional_match = eval_target(it, *matcher, target.transformers, limits, deadline);
} else {
object::value_iterator it(target.object, target.key_path, objects_excluded, limits);
object::value_iterator it(object, target.key_path, objects_excluded, limits);
optional_match = eval_target(it, *matcher, target.transformers, limits, deadline);
}

if (optional_match.has_value()) {
optional_match->address = target.address;
optional_match->ephemeral = target.ephemeral;
optional_match->address = target.name;
optional_match->ephemeral = ephemeral;
DDWAF_TRACE(
"Target {} matched parameter value {}", target.address, optional_match->resolved);
"Target {} matched parameter value {}", target.name, optional_match->resolved);

cache.match = std::move(optional_match);
return {true, target.ephemeral};
return {true, ephemeral};
}
}

Expand Down
41 changes: 27 additions & 14 deletions src/condition/matcher_proxy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,48 @@

namespace ddwaf::condition {

class matcher_proxy : public base_impl<matcher_proxy> {
class matcher_proxy : public base {
public:
matcher_proxy(std::unique_ptr<matcher::base> &&matcher, std::string data_id,
std::vector<argument_definition> args)
: base_impl<matcher_proxy>(std::move(args)), matcher_(std::move(matcher)),
data_id_(std::move(data_id))
{}
: matcher_(std::move(matcher)), data_id_(std::move(data_id))
{
if (args.size() > 1) {
throw std::invalid_argument("Matcher initialised with more than one argument");
}

protected:
eval_result eval_impl(const argument_stack &stack, cache_type &cache,
if (args.empty()) {
throw std::invalid_argument("Matcher initialised without arguments");
}

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

eval_result eval(cache_type &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,
const object_limits &limits, ddwaf::timer &deadline) const;
const object_limits &limits, ddwaf::timer &deadline) const override;

[[nodiscard]] const matcher::base *get_matcher(
const std::unordered_map<std::string, std::shared_ptr<matcher::base>> &dynamic_matchers)
const;
void get_addresses(std::unordered_map<target_index, std::string> &addresses) const override
{
for (const auto &target : targets_) { addresses.emplace(target.root, target.name); }
}

static const std::vector<argument_specification> &arguments_impl()
static const std::vector<argument_specification> &arguments()
{
static std::vector<argument_specification> args = {
{"inputs", object_type::any, true, false}};
return args;
};
}

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_;

friend class base_impl<matcher_proxy>;
std::vector<target_definition> targets_;
};

} // namespace ddwaf::condition
6 changes: 6 additions & 0 deletions src/parser/parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ using base_section_info = ddwaf::base_ruleset_info::base_section_info;
namespace ddwaf::parser {

unsigned parse_schema_version(parameter::map &ruleset);

namespace v1 {
void parse(
parameter::map &ruleset, base_ruleset_info &info, ddwaf::ruleset &rs, object_limits limits);
} // namespace v1

namespace v2 {

rule_spec_container parse_rules(parameter::vector &rule_array, base_section_info &info,
Expand Down
193 changes: 193 additions & 0 deletions src/parser/parser_v1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
// Unless explicitly stated otherwise all files in this repository are
// dual-licensed under the Apache-2.0 License or BSD-3-Clause License.
//
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2021 Datadog, Inc.

#include <set>
#include <string>
#include <unordered_map>
#include <vector>

#include "condition/matcher_proxy.hpp"
#include "exception.hpp"
#include "log.hpp"
#include "matcher/is_sqli.hpp"
#include "matcher/is_xss.hpp"
#include "matcher/phrase_match.hpp"
#include "matcher/regex_match.hpp"
#include "parameter.hpp"
#include "parser/common.hpp"
#include "parser/parser.hpp"
#include "rule.hpp"
#include "ruleset.hpp"
#include "ruleset_info.hpp"

namespace ddwaf::parser::v1 {

namespace {

std::shared_ptr<expression> parse_expression(parameter::vector &conditions_array,
const std::vector<transformer_id> &transformers, ddwaf::object_limits limits)
{
std::vector<std::unique_ptr<condition::base>> conditions;

for (const auto &cond_param : conditions_array) {
auto cond = static_cast<parameter::map>(cond_param);

auto matcher_name = at<std::string_view>(cond, "operation");
auto params = at<parameter::map>(cond, "parameters");

parameter::map options;
std::unique_ptr<matcher::base> matcher;
if (matcher_name == "phrase_match") {
auto list = at<parameter::vector>(params, "list");

std::vector<const char *> patterns;
std::vector<uint32_t> lengths;

patterns.reserve(list.size());
lengths.reserve(list.size());

for (auto &pattern : list) {
if (pattern.type != DDWAF_OBJ_STRING) {
throw ddwaf::parsing_error("phrase_match list item not a string");
}

patterns.push_back(pattern.stringValue);
lengths.push_back((uint32_t)pattern.nbEntries);
}

matcher = std::make_unique<matcher::phrase_match>(patterns, lengths);
} else if (matcher_name == "match_regex") {
auto regex = at<std::string>(params, "regex");
options = at<parameter::map>(params, "options", options);

auto case_sensitive = at<bool>(options, "case_sensitive", false);
auto min_length = at<int64_t>(options, "min_length", 0);
if (min_length < 0) {
throw ddwaf::parsing_error("min_length is a negative number");
}

matcher = std::make_unique<matcher::regex_match>(regex, min_length, case_sensitive);
} else if (matcher_name == "is_xss") {
matcher = std::make_unique<matcher::is_xss>();
} else if (matcher_name == "is_sqli") {
matcher = std::make_unique<matcher::is_sqli>();
} else {
throw ddwaf::parsing_error("unknown matcher: " + std::string(matcher_name));
}

std::vector<condition::argument_definition> definitions;
definitions.emplace_back();
condition::argument_definition &def = definitions.back();

auto inputs = at<parameter::vector>(params, "inputs");
for (const auto &input_param : inputs) {
auto input = static_cast<std::string>(input_param);
if (input.empty()) {
throw ddwaf::parsing_error("empty address");
}

std::string root;
std::vector<std::string> key_path;
const size_t pos = input.find(':', 0);
if (pos == std::string::npos || pos + 1 >= input.size()) {
root = input;
} else {
root = input.substr(0, pos);
key_path.emplace_back(input.substr(pos + 1, input.size()));
}

def.targets.emplace_back(condition::target_definition{root, get_target_index(root),
std::move(key_path), transformers, condition::data_source::values});
}

conditions.emplace_back(std::make_unique<condition::matcher_proxy>(
std::move(matcher), std::string{}, std::move(definitions)));
}

return std::make_shared<expression>(std::move(conditions), limits);
}

void parseRule(parameter::map &rule, base_section_info &info,
std::unordered_set<std::string_view> &rule_ids, ddwaf::ruleset &rs, ddwaf::object_limits limits)
{
auto id = at<std::string>(rule, "id");
if (rule_ids.find(id) != rule_ids.end()) {
DDWAF_WARN("duplicate rule {}", id);
info.add_failed(id, "duplicate rule");
return;
}

try {
std::vector<transformer_id> rule_transformers;
auto transformers = at<parameter::vector>(rule, "transformers", parameter::vector());
for (const auto &transformer_param : transformers) {
auto transformer = static_cast<std::string_view>(transformer_param);
auto id = transformer_from_string(transformer);
if (!id.has_value()) {
throw ddwaf::parsing_error("invalid transformer" + std::string(transformer));
}
rule_transformers.emplace_back(id.value());
}

auto conditions_array = at<parameter::vector>(rule, "conditions");
auto expression = parse_expression(conditions_array, rule_transformers, limits);

std::unordered_map<std::string, std::string> tags;
for (auto &[key, value] : at<parameter::map>(rule, "tags")) {
try {
tags.emplace(key, std::string(value));
} catch (const bad_cast &e) {
throw invalid_type(std::string(key), e);
}
}

if (tags.find("type") == tags.end()) {
throw ddwaf::parsing_error("missing key 'type'");
}

auto rule_ptr = std::make_shared<ddwaf::rule>(
std::string(id), at<std::string>(rule, "name"), std::move(tags), std::move(expression));

rule_ids.emplace(rule_ptr->get_id());
rs.insert_rule(rule_ptr);
info.add_loaded(rule_ptr->get_id());
} catch (const std::exception &e) {
DDWAF_WARN("failed to parse rule '{}': {}", id, e.what());
info.add_failed(id, e.what());
}
}

} // namespace

void parse(
parameter::map &ruleset, base_ruleset_info &info, ddwaf::ruleset &rs, object_limits limits)
{
auto rules_array = at<parameter::vector>(ruleset, "events");
rs.rules.reserve(rules_array.size());

auto &section = info.add_section("rules");

std::unordered_set<std::string_view> rule_ids;
for (unsigned i = 0; i < rules_array.size(); ++i) {
const auto &rule_param = rules_array[i];
try {
auto rule = static_cast<parameter::map>(rule_param);
parseRule(rule, section, rule_ids, rs, limits);
} catch (const std::exception &e) {
DDWAF_WARN("{}", e.what());
section.add_failed("index:" + to_string<std::string>(i), e.what());
}
}

if (rs.rules.empty()) {
throw ddwaf::parsing_error("no valid rules found");
}

DDWAF_DEBUG("Loaded %zu rules out of %zu available in the ruleset", rs.rules.size(),
rules_array.size());
}

} // namespace ddwaf::parser::v1
11 changes: 11 additions & 0 deletions src/waf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ waf::waf(ddwaf::parameter input, ddwaf::base_ruleset_info &info, ddwaf::object_l
}
}

// Prevent combining version 1 of the ruleset and the builder
if (version == 1) {
ddwaf::ruleset rs;
rs.free_fn = free_fn;
rs.event_obfuscator = event_obfuscator;
DDWAF_DEBUG("Parsing ruleset with schema version 1.x");
parser::v1::parse(input_map, info, rs, limits);
ruleset_ = std::make_shared<ddwaf::ruleset>(std::move(rs));
return;
}

if (version == 2) {
DDWAF_DEBUG("Parsing ruleset with schema version 2.x");
builder_ = std::make_shared<ruleset_builder>(limits, free_fn, std::move(event_obfuscator));
Expand Down
Loading

0 comments on commit e1a9342

Please sign in to comment.