Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support min_version and max_version on evaluation primitives and RASP operator versioning #343

Merged
merged 12 commits into from
Oct 11, 2024
1 change: 1 addition & 0 deletions src/condition/lfi_detector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace ddwaf {

class lfi_detector : public base_impl<lfi_detector> {
public:
static constexpr unsigned version = 1;
static constexpr std::array<std::string_view, 2> param_names{"resource", "params"};

explicit lfi_detector(std::vector<condition_parameter> args, const object_limits &limits = {})
Expand Down
1 change: 1 addition & 0 deletions src/condition/shi_detector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ namespace ddwaf {

class shi_detector : public base_impl<shi_detector> {
public:
static constexpr unsigned version = 1;
static constexpr std::array<std::string_view, 2> param_names{"resource", "params"};

explicit shi_detector(std::vector<condition_parameter> args, const object_limits &limits = {});
Expand Down
1 change: 1 addition & 0 deletions src/condition/sqli_detector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ namespace ddwaf {

class sqli_detector : public base_impl<sqli_detector> {
public:
static constexpr unsigned version = 2;
Anilm3 marked this conversation as resolved.
Show resolved Hide resolved
static constexpr std::array<std::string_view, 3> param_names{"resource", "params", "db_type"};

explicit sqli_detector(std::vector<condition_parameter> args, const object_limits &limits = {})
Expand Down
1 change: 1 addition & 0 deletions src/condition/ssrf_detector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ namespace ddwaf {

class ssrf_detector : public base_impl<ssrf_detector> {
public:
static constexpr unsigned version = 1;
static constexpr std::array<std::string_view, 2> param_names{"resource", "params"};

explicit ssrf_detector(std::vector<condition_parameter> args, const object_limits &limits = {});
Expand Down
11 changes: 11 additions & 0 deletions src/exception.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
#include <string>
#include <utility>

#include "fmt/format.h"

namespace ddwaf {

class exception : public std::exception {
Expand All @@ -27,6 +29,15 @@ class unsupported_version : public exception {
unsupported_version() : exception(std::string()){};
};

class unsupported_operator_version : public exception {
public:
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
unsupported_operator_version(std::string_view name, unsigned expected, unsigned current)
: exception(ddwaf::fmt::format(
"unsupported operator version {}@{}, current {}@{}", name, expected, name, current))
{}
};

class parsing_error : public exception {
public:
explicit parsing_error(const std::string &what) : exception(what) {}
Expand Down
2 changes: 1 addition & 1 deletion src/interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ void ddwaf_context_destroy(ddwaf_context context)
}
}

const char *ddwaf_get_version() { return LIBDDWAF_VERSION; }
const char *ddwaf_get_version() { return ddwaf::current_version.cstring(); }

bool ddwaf_set_log_cb(ddwaf_log_cb cb, DDWAF_LOG_LEVEL min_level)
{
Expand Down
10 changes: 10 additions & 0 deletions src/parameter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "ddwaf.h"
#include "exception.hpp"
#include "parameter.hpp"
#include "semver.hpp"
#include "utils.hpp"

namespace {
Expand Down Expand Up @@ -306,4 +307,13 @@ parameter::operator std::unordered_map<std::string, std::string>() const
return data;
}

parameter::operator semantic_version() const
{
if (type != DDWAF_OBJ_STRING || stringValue == nullptr) {
throw bad_cast("string", strtype(type));
}

return semantic_version{{stringValue, static_cast<size_t>(nbEntries)}};
}

} // namespace ddwaf
6 changes: 6 additions & 0 deletions src/parameter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#include "ddwaf.h"
#include "exception.hpp"
#include "semver.hpp"

namespace ddwaf {

Expand Down Expand Up @@ -47,6 +48,7 @@ class parameter : public ddwaf_object {
explicit operator std::vector<std::string>() const;
explicit operator std::vector<std::string_view>() const;
explicit operator std::unordered_map<std::string, std::string>() const;
explicit operator semantic_version() const;

~parameter() = default;
};
Expand Down Expand Up @@ -87,4 +89,8 @@ template <> struct parameter_traits<std::unordered_map<std::string, std::string>
static const char *name() { return "std::unordered_map<std::string, std::string>"; }
};

template <> struct parameter_traits<semantic_version> {
static const char *name() { return "semantic_version"; }
};

} // namespace ddwaf
15 changes: 15 additions & 0 deletions src/parser/exclusion_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
#include "parser/common.hpp"
#include "parser/parser.hpp"
#include "parser/specification.hpp"
#include "semver.hpp"
#include "utils.hpp"
#include "version.hpp"

namespace ddwaf::parser::v2 {

Expand Down Expand Up @@ -124,6 +126,16 @@ filter_spec_container parse_filters(parameter::vector &filter_array, base_sectio
continue;
}

// Check version compatibility and fail without diagnostic
auto min_version{at<semantic_version>(node, "min_version", semantic_version::min())};
auto max_version{at<semantic_version>(node, "max_version", semantic_version::max())};
if (min_version > current_version || max_version < current_version) {
DDWAF_DEBUG("Skipping filter '{}': version required between [{}, {}], current {}",
id, min_version, max_version, current_version);
info.add_skipped(id);
continue;
}

if (node.find("inputs") != node.end()) {
auto filter = parse_input_filter(node, addresses, filter_data_ids, limits);
filters.ids.emplace(id);
Expand All @@ -137,6 +149,9 @@ filter_spec_container parse_filters(parameter::vector &filter_array, base_sectio

info.add_loaded(id);
add_addresses_to_info(addresses, info);
} catch (const unsupported_operator_version &e) {
DDWAF_WARN("Skipping filter '{}': {}", id, e.what());
info.add_skipped(id);
Anilm3 marked this conversation as resolved.
Show resolved Hide resolved
} catch (const std::exception &e) {
if (id.empty()) {
id = index_to_id(i);
Expand Down
52 changes: 38 additions & 14 deletions src/parser/expression_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,10 @@ std::vector<condition_parameter> parse_arguments(const parameter::map &params, d
}

template <typename T, typename... Matchers>
auto build_condition(auto operator_name, auto &params, auto &data_ids_to_type, auto source,
auto &transformers, auto &addresses, auto &limits)
auto build_condition(std::string_view operator_name, const parameter::map &params,
std::unordered_map<std::string, std::string> &data_ids_to_type, data_source source,
const std::vector<transformer_id> &transformers, address_container &addresses,
const object_limits &limits)
{
auto [data_id, matcher] = parse_matcher<Matchers...>(operator_name, params);

Expand All @@ -124,6 +126,20 @@ auto build_condition(auto operator_name, auto &params, auto &data_ids_to_type, a
return std::make_unique<T>(std::move(matcher), data_id, std::move(arguments), limits);
}

template <typename Condition>
auto build_versioned_condition(std::string_view operator_name, unsigned version,
const parameter::map &params, data_source source,
const std::vector<transformer_id> &transformers, address_container &addresses,
const object_limits &limits)
{
if (version > Condition::version) {
throw unsupported_operator_version(operator_name, version, Condition::version);
}

auto arguments = parse_arguments<Condition>(params, source, transformers, addresses, limits);
return std::make_unique<Condition>(std::move(arguments), limits);
}

} // namespace

std::shared_ptr<expression> parse_expression(const parameter::vector &conditions_array,
Expand All @@ -138,22 +154,30 @@ std::shared_ptr<expression> parse_expression(const parameter::vector &conditions
auto operator_name = at<std::string_view>(root, "operator");
auto params = at<parameter::map>(root, "parameters");

// Exploit Prevention Operators may have a single-digit version
unsigned version = 0;
auto version_idx = operator_name.find("@v");
if (version_idx != std::string_view::npos) {
auto version_str = operator_name.substr(version_idx + 2);
auto [res, value] = from_string<unsigned>(version_str);
if (res) {
version = value;
}
operator_name = operator_name.substr(0, version_idx);
}

if (operator_name == "lfi_detector") {
auto arguments =
parse_arguments<lfi_detector>(params, source, transformers, addresses, limits);
conditions.emplace_back(std::make_unique<lfi_detector>(std::move(arguments), limits));
conditions.emplace_back(build_versioned_condition<lfi_detector>(
operator_name, version, params, source, transformers, addresses, limits));
} else if (operator_name == "ssrf_detector") {
auto arguments =
parse_arguments<ssrf_detector>(params, source, transformers, addresses, limits);
conditions.emplace_back(std::make_unique<ssrf_detector>(std::move(arguments), limits));
conditions.emplace_back(build_versioned_condition<ssrf_detector>(
operator_name, version, params, source, transformers, addresses, limits));
} else if (operator_name == "sqli_detector") {
auto arguments =
parse_arguments<sqli_detector>(params, source, transformers, addresses, limits);
conditions.emplace_back(std::make_unique<sqli_detector>(std::move(arguments), limits));
conditions.emplace_back(build_versioned_condition<sqli_detector>(
operator_name, version, params, source, transformers, addresses, limits));
} else if (operator_name == "shi_detector") {
auto arguments =
parse_arguments<shi_detector>(params, source, transformers, addresses, limits);
conditions.emplace_back(std::make_unique<shi_detector>(std::move(arguments), limits));
conditions.emplace_back(build_versioned_condition<shi_detector>(
operator_name, version, params, source, transformers, addresses, limits));
} else if (operator_name == "exists") {
auto arguments =
parse_arguments<exists_condition>(params, source, transformers, addresses, limits);
Expand Down
17 changes: 16 additions & 1 deletion src/parser/processor_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
#include "processor/base.hpp"
#include "processor/extract_schema.hpp"
#include "processor/fingerprint.hpp"
#include "semver.hpp"
#include "utils.hpp"
#include "version.hpp"

namespace ddwaf::parser::v2 {

Expand Down Expand Up @@ -81,6 +83,17 @@ processor_container parse_processors(
continue;
}

// Check version compatibility and fail without diagnostic
auto min_version{at<semantic_version>(node, "min_version", semantic_version::min())};
auto max_version{at<semantic_version>(node, "max_version", semantic_version::max())};
if (min_version > current_version || max_version < current_version) {
DDWAF_DEBUG(
"Skipping processor '{}': version required between [{}, {}], current {}", id,
min_version, max_version, current_version);
info.add_skipped(id);
continue;
}

processor_type type;
auto generator_id = at<std::string>(node, "generator");
if (generator_id == "extract_schema") {
Expand Down Expand Up @@ -152,7 +165,9 @@ processor_container parse_processors(
processors.post.emplace_back(processor_builder{type, std::move(id), std::move(expr),
std::move(mappings), std::move(scanners), eval, output});
}

} catch (const unsupported_operator_version &e) {
DDWAF_WARN("Skipping processor '{}': {}", id, e.what());
info.add_skipped(id);
} catch (const std::exception &e) {
if (id.empty()) {
id = index_to_id(i);
Expand Down
21 changes: 18 additions & 3 deletions src/parser/rule_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
#include "parser/parser.hpp"
#include "parser/specification.hpp"
#include "rule.hpp"
#include "semver.hpp"
#include "transformer/base.hpp"
#include "utils.hpp"
#include "version.hpp"

namespace ddwaf::parser::v2 {

Expand Down Expand Up @@ -71,24 +73,37 @@ rule_spec_container parse_rules(parameter::vector &rule_array, base_section_info
rule_spec_container rules;
for (unsigned i = 0; i < rule_array.size(); ++i) {
const auto &rule_param = rule_array[i];
auto rule_map = static_cast<parameter::map>(rule_param);
auto node = static_cast<parameter::map>(rule_param);
std::string id;
try {
address_container addresses;

id = at<std::string>(rule_map, "id");
id = at<std::string>(node, "id");
if (rules.find(id) != rules.end()) {
DDWAF_WARN("Duplicate rule {}", id);
info.add_failed(id, "duplicate rule");
continue;
}

auto rule = parse_rule(rule_map, rule_data_ids, limits, source, addresses);
// Check version compatibility and fail without diagnostic
auto min_version{at<semantic_version>(node, "min_version", semantic_version::min())};
auto max_version{at<semantic_version>(node, "max_version", semantic_version::max())};
if (min_version > current_version || max_version < current_version) {
DDWAF_DEBUG("Skipping rule '{}': version required between [{}, {}], current {}", id,
min_version, max_version, current_version);
info.add_skipped(id);
continue;
}

auto rule = parse_rule(node, rule_data_ids, limits, source, addresses);
DDWAF_DEBUG("Parsed rule {}", id);
info.add_loaded(id);
add_addresses_to_info(addresses, info);

rules.emplace(std::move(id), std::move(rule));
} catch (const unsupported_operator_version &e) {
DDWAF_WARN("Skipping rule '{}': {}", id, e.what());
info.add_skipped(id);
} catch (const std::exception &e) {
if (id.empty()) {
id = index_to_id(i);
Expand Down
12 changes: 12 additions & 0 deletions src/parser/scanner_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
#include "parser/matcher_parser.hpp"
#include "parser/parser.hpp"
#include "scanner.hpp"
#include "semver.hpp"
#include "version.hpp"

namespace ddwaf::parser::v2 {

Expand Down Expand Up @@ -55,6 +57,16 @@ indexer<const scanner> parse_scanners(parameter::vector &scanner_array, base_sec
continue;
}

// Check version compatibility and fail without diagnostic
auto min_version{at<semantic_version>(node, "min_version", semantic_version::min())};
auto max_version{at<semantic_version>(node, "max_version", semantic_version::max())};
if (min_version > current_version || max_version < current_version) {
DDWAF_DEBUG("Skipping scanner '{}': version required between [{}, {}], current {}",
id, min_version, max_version, current_version);
info.add_skipped(id);
continue;
}

std::unordered_map<std::string, std::string> tags;
for (auto &[key, value] : at<parameter::map>(node, "tags")) {
try {
Expand Down
7 changes: 7 additions & 0 deletions src/ruleset_info.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ void ruleset_info::section_info::add_failed(std::string_view id, std::string_vie
ddwaf_object_array_add(&failed_, &id_str);
}

void ruleset_info::section_info::add_skipped(std::string_view id)
{
ddwaf_object id_str;
ddwaf_object_stringl(&id_str, id.data(), id.size());
ddwaf_object_array_add(&skipped_, &id_str);
}

void ruleset_info::section_info::add_required_address(std::string_view address)
{
if (!required_addresses_set_.contains(address)) {
Expand Down
Loading
Loading