From 9d7b9880e712d75eca91ccf5549f6f9b6e51715b Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Tue, 30 Sep 2025 18:07:24 +0100 Subject: [PATCH 1/2] Support parsing partial json in appsec --- .gitlab/build-appsec.sh | 8 +- .gitlab/generate-appsec.php | 6 +- appsec/CMakeLists.txt | 1 + appsec/src/extension/.clang-tidy | 4 +- appsec/src/extension/entity_body.c | 50 +-- appsec/src/extension/entity_body.stub.php | 4 +- appsec/src/extension/entity_body_arginfo.h | 21 +- .../src/extension/json_truncated_parser.cpp | 406 ++++++++++++++++++ appsec/src/extension/json_truncated_parser.h | 28 ++ appsec/src/extension/logging.h | 4 +- appsec/src/extension/php_compat.c | 150 +++++++ appsec/src/extension/php_compat.h | 34 +- appsec/src/helper/.clang-tidy | 2 +- appsec/src/helper/.gitignore | 1 - appsec/tests/extension/convert_json.phpt | 9 +- .../extension/convert_json_max_depth.phpt | 81 +++- .../convert_json_special_values.phpt | 158 +++++++ .../extension/convert_json_truncated.phpt | 148 +++++++ appsec/tests/extension/convert_xml_basic.phpt | 2 +- .../extension/convert_xml_external_dtd.phpt | 2 +- .../convert_xml_external_entity.phpt | 2 +- ...convert_xml_external_entity_no_double.phpt | 8 +- .../tests/extension/convert_xml_iso88591.phpt | 4 +- .../extension/convert_xml_max_depth.phpt | 2 +- .../extension/convert_xml_namespaces.phpt | 2 +- .../extension/convert_xml_truncated.phpt | 10 +- .../rshutdown_command_body_size_exceeded.phpt | 33 +- appsec/tests/integration/build.gradle | 3 +- appsec/valgrind.supp | 24 ++ 29 files changed, 1112 insertions(+), 95 deletions(-) create mode 100644 appsec/src/extension/json_truncated_parser.cpp create mode 100644 appsec/src/extension/json_truncated_parser.h delete mode 100644 appsec/src/helper/.gitignore create mode 100644 appsec/tests/extension/convert_json_special_values.phpt create mode 100644 appsec/tests/extension/convert_json_truncated.phpt diff --git a/.gitlab/build-appsec.sh b/.gitlab/build-appsec.sh index 1e9a3165e22..841e63ee3f1 100755 --- a/.gitlab/build-appsec.sh +++ b/.gitlab/build-appsec.sh @@ -13,14 +13,18 @@ suffix="${1:-}" echo "Build nts extension" switch-php "${PHP_VERSION}" mkdir -p appsec/build ; cd appsec/build -cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo -DDD_APPSEC_BUILD_HELPER=OFF -DDD_APPSEC_TESTING=OFF ; make -j $MAKE_JOBS +cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo -DDD_APPSEC_BUILD_HELPER=OFF \ + -DDD_APPSEC_TESTING=OFF -DDD_APPSEC_EXTENSION_STATIC_LIBSTDCXX=ON +make -j $MAKE_JOBS cp -v ddappsec.so "../../appsec_$(uname -m)/ddappsec-$PHP_API${suffix}.so" cd "../../" echo "Build zts extension" switch-php "${PHP_VERSION}-zts" mkdir -p appsec/build-zts ; cd appsec/build-zts -cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo -DDD_APPSEC_BUILD_HELPER=OFF -DDD_APPSEC_TESTING=OFF ; make -j $MAKE_JOBS +cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo -DDD_APPSEC_BUILD_HELPER=OFF \ + -DDD_APPSEC_TESTING=OFF -DDD_APPSEC_EXTENSION_STATIC_LIBSTDCXX=ON +make -j $MAKE_JOBS cp -v ddappsec.so "../../appsec_$(uname -m)/ddappsec-$PHP_API${suffix}-zts.so" cd "../../" diff --git a/.gitlab/generate-appsec.php b/.gitlab/generate-appsec.php index e0ca3922c29..360364449e0 100644 --- a/.gitlab/generate-appsec.php +++ b/.gitlab/generate-appsec.php @@ -87,10 +87,12 @@ script: - switch-php $SWITCH_PHP_VERSION - cd appsec/build + - if [[ "$SWITCH_PHP_VERSION" == *"asan"* ]]; then ASAN_FLAG=ON; else ASAN_FLAG=OFF; fi - "cmake .. -DCMAKE_BUILD_TYPE=Debug -DDD_APPSEC_BUILD_HELPER=OFF -DCMAKE_CXX_FLAGS='-stdlib=libc++' -DCMAKE_CXX_LINK_FLAGS='-stdlib=libc++' - -DDD_APPSEC_TESTING=ON -DBOOST_CACHE_PREFIX=$CI_PROJECT_DIR/boost-cache" - - make -j 4 xtest + -DDD_APPSEC_TESTING=ON -DBOOST_CACHE_PREFIX=$CI_PROJECT_DIR/boost-cache + -DENABLE_ASAN=$ASAN_FLAG" + - ASAN_OPTIONS=malloc_context_size=0 make -j 4 xtest "appsec integration tests": stage: test diff --git a/appsec/CMakeLists.txt b/appsec/CMakeLists.txt index 9a3bed6b4e8..a8c0645dd83 100644 --- a/appsec/CMakeLists.txt +++ b/appsec/CMakeLists.txt @@ -37,6 +37,7 @@ option(DD_APPSEC_BUILD_EXTENSION "Whether to builder the extension" ON) option(DD_APPSEC_ENABLE_COVERAGE "Whether to enable coverage calculation" OFF) option(DD_APPSEC_TESTING "Whether to enable testing" ON) option(DD_APPSEC_DDTRACE_ALT "Whether to build appsec with cmake" OFF) +option(DD_APPSEC_EXTENSION_STATIC_LIBSTDCXX "Whether to link the extension with -static-libstdc++ (not available on macOS)" OFF) add_subdirectory(third_party EXCLUDE_FROM_ALL) diff --git a/appsec/src/extension/.clang-tidy b/appsec/src/extension/.clang-tidy index 7507364f2e9..e5aeb8efaf9 100644 --- a/appsec/src/extension/.clang-tidy +++ b/appsec/src/extension/.clang-tidy @@ -1,3 +1,3 @@ ---- +Checks: '-fuchsia-trailing-return,-hicpp-vararg,-cppcoreguidelines-pro-type-vararg,-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,-cppcoreguidelines-pro-bounds-array-to-pointer-decay' + InheritParentConfig: true -Checks: '-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling' diff --git a/appsec/src/extension/entity_body.c b/appsec/src/extension/entity_body.c index cadde4ab051..4c27daf809f 100644 --- a/appsec/src/extension/entity_body.c +++ b/appsec/src/extension/entity_body.c @@ -9,6 +9,7 @@ #include "ddappsec.h" #include "php_compat.h" // NOLINT (must come before entity_body_arginfo.h) #include "entity_body_arginfo.h" +#include "json_truncated_parser.h" #include "logging.h" #include "php_objects.h" #include "string_helpers.h" @@ -175,23 +176,13 @@ zval dd_entity_body_convert( static zval _convert_json(char *nonnull entity, size_t entity_len) { - zval zv; - ZVAL_NULL(&zv); - if (!_json_decode_ex) { - return zv; - } - #define MAX_DEPTH 30 - _json_decode_ex( - &zv, entity, entity_len, PHP_JSON_OBJECT_AS_ARRAY, MAX_DEPTH); - if (Z_TYPE(zv) == IS_NULL) { - mlog(dd_log_info, "Failed to parse JSON response body"); - if (dd_log_level() >= dd_log_trace && entity_len < INT_MAX) { - mlog(dd_log_trace, "Contents were: %.*s", (int)entity_len, entity); - } - zval_ptr_dtor(&zv); + zval res = dd_parse_json_truncated(entity, entity_len, MAX_DEPTH); + if (Z_TYPE(res) == IS_UNDEF) { + // Failed to parse JSON + ZVAL_NULL(&res); } - return zv; + return res; } static bool _assume_utf8(const char *ct, size_t ct_len) @@ -235,28 +226,37 @@ static zval _convert_xml(const char *nonnull entity, size_t entity_len, return dd_parse_xml_truncated(entity, entity_len, MAX_XML_DEPTH); } -PHP_FUNCTION(datadog_appsec_testing_convert_json) +PHP_FUNCTION(datadog_appsec_convert_xml) { zend_string *entity; - ZEND_PARSE_PARAMETERS_START(1, 1) // NOLINT + zend_string *content_type; + ZEND_PARSE_PARAMETERS_START(2, 2) // NOLINT Z_PARAM_STR(entity) + Z_PARAM_STR(content_type) ZEND_PARSE_PARAMETERS_END(); - zval result = _convert_json(ZSTR_VAL(entity), ZSTR_LEN(entity)); + zval result = _convert_xml(ZSTR_VAL(entity), ZSTR_LEN(entity), + ZSTR_VAL(content_type), ZSTR_LEN(content_type)); + RETURN_ZVAL(&result, 0, 0); } -PHP_FUNCTION(datadog_appsec_testing_convert_xml) +PHP_FUNCTION(datadog_appsec_convert_json) { zend_string *entity; - zend_string *content_type; - ZEND_PARSE_PARAMETERS_START(2, 2) // NOLINT +#define MAX_DEPTH_DEFAULT 30 + zend_long max_depth = MAX_DEPTH_DEFAULT; + ZEND_PARSE_PARAMETERS_START(1, 2) // NOLINT Z_PARAM_STR(entity) - Z_PARAM_STR(content_type) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(max_depth) ZEND_PARSE_PARAMETERS_END(); - zval result = _convert_xml(ZSTR_VAL(entity), ZSTR_LEN(entity), - ZSTR_VAL(content_type), ZSTR_LEN(content_type)); - + zval result = dd_parse_json_truncated( + ZSTR_VAL(entity), ZSTR_LEN(entity), (int)max_depth); + if (Z_TYPE(result) == IS_UNDEF) { + // Failed to parse JSON + RETURN_NULL(); + } RETURN_ZVAL(&result, 0, 0); } diff --git a/appsec/src/extension/entity_body.stub.php b/appsec/src/extension/entity_body.stub.php index aa71ce99d0c..7b3dfb79903 100644 --- a/appsec/src/extension/entity_body.stub.php +++ b/appsec/src/extension/entity_body.stub.php @@ -4,7 +4,7 @@ * @generate-function-entries */ -namespace datadog\appsec\testing { - function convert_json(string $json): ?array {} +namespace datadog\appsec { function convert_xml(string $xml, string $contentType): ?array {} + function convert_json(string $json, int $maxDepth = 30) : ?mixed {} } diff --git a/appsec/src/extension/entity_body_arginfo.h b/appsec/src/extension/entity_body_arginfo.h index 7380ff90f11..9288eaae281 100644 --- a/appsec/src/extension/entity_body_arginfo.h +++ b/appsec/src/extension/entity_body_arginfo.h @@ -1,22 +1,23 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 7875ffe7095e579e7ff76ece953fbbc1c9ff12f6 */ + * Stub hash: dfa1f0081bab0c798625353df5966ae21e7c4b89 */ -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_datadog_appsec_testing_convert_json, 0, 1, IS_ARRAY, 1) - ZEND_ARG_TYPE_INFO(0, json, IS_STRING, 0) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_datadog_appsec_testing_convert_xml, 0, 2, IS_ARRAY, 1) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_datadog_appsec_convert_xml, 0, 2, IS_ARRAY, 1) ZEND_ARG_TYPE_INFO(0, xml, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, contentType, IS_STRING, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_datadog_appsec_convert_json, 0, 1, IS_MIXED, 1) + ZEND_ARG_TYPE_INFO(0, json, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, maxDepth, IS_LONG, 0, "30") +ZEND_END_ARG_INFO() + -ZEND_FUNCTION(datadog_appsec_testing_convert_json); -ZEND_FUNCTION(datadog_appsec_testing_convert_xml); +ZEND_FUNCTION(datadog_appsec_convert_xml); +ZEND_FUNCTION(datadog_appsec_convert_json); static const zend_function_entry ext_functions[] = { - ZEND_NS_FALIAS("datadog\\appsec\\testing", convert_json, datadog_appsec_testing_convert_json, arginfo_datadog_appsec_testing_convert_json) - ZEND_NS_FALIAS("datadog\\appsec\\testing", convert_xml, datadog_appsec_testing_convert_xml, arginfo_datadog_appsec_testing_convert_xml) + ZEND_NS_FALIAS("datadog\\appsec", convert_xml, datadog_appsec_convert_xml, arginfo_datadog_appsec_convert_xml) + ZEND_NS_FALIAS("datadog\\appsec", convert_json, datadog_appsec_convert_json, arginfo_datadog_appsec_convert_json) ZEND_FE_END }; diff --git a/appsec/src/extension/json_truncated_parser.cpp b/appsec/src/extension/json_truncated_parser.cpp new file mode 100644 index 00000000000..438ab51c3ac --- /dev/null +++ b/appsec/src/extension/json_truncated_parser.cpp @@ -0,0 +1,406 @@ +// 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 "json_truncated_parser.h" + +extern "C" { +#include "logging.h" +#include "php_compat.h" +#include +} +#include +#include +#include +#include +#include +#include +#include + +template class PhpAllocator { +public: + using value_type = T; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using propagate_on_container_move_assignment = std::true_type; + + PhpAllocator() noexcept = default; + + template + explicit PhpAllocator(const PhpAllocator & /* other*/) noexcept + {} + + auto allocate(size_type n) -> T * + { + if (n == 0) { + return nullptr; + } + // NOLINTNEXTLINE(bugprone-sizeof-expression) + return static_cast(safe_emalloc(n, sizeof(T), 0)); + } + + void deallocate(T *p, size_type /* n */) noexcept + { + if (p != nullptr) { + efree(p); + } + } +}; + +// Custom allocator for RapidJSON using PHP's emalloc/efree +class RapidJsonPhpAllocator { +public: + static constexpr bool kNeedFree = true; + + static auto Malloc(size_t size) -> void * + { + if (size == 0) { + return nullptr; + } + return emalloc(size); + } + + static auto Realloc( + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + void *originalPtr, size_t originalSize, size_t newSize) -> void * + { + (void)originalSize; + if (newSize == 0) { + if (originalPtr != nullptr) { + efree(originalPtr); + } + return nullptr; + } + return erealloc(originalPtr, newSize); + } + + static void Free(void *ptr) + { + if (ptr != nullptr) { + efree(ptr); + } + } +}; + +// Custom input stream for truncated JSON parsing, based on nginx-datadog +// implementation +class TruncatedJsonInputStream { +public: + using Ch = char; + + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + TruncatedJsonInputStream( + const char *data, std::size_t length, std::size_t size_limit = SIZE_MAX) + : data_{data}, length_{length}, size_limit_{size_limit} + {} + + [[nodiscard]] auto Peek() const -> Ch + { + return (pos_ < length_ && pos_ < size_limit_) ? data_[pos_] : '\0'; + } + + auto Take() -> Ch + { + return (pos_ < length_ && pos_ < size_limit_) ? data_[pos_++] : '\0'; + } + + [[nodiscard]] std::size_t Tell() const { return pos_; } + + // required by RapidJSON + auto PutBegin() -> Ch * { return nullptr; } // NOLINT + void Put(Ch) {} // NOLINT + void Flush() {} + auto PutEnd(Ch *) -> size_t { return 0; } // NOLINT + +private: + const char *data_; + std::size_t length_; + std::size_t pos_{0}; + std::size_t size_limit_; +}; + +// Handler to convert JSON events directly to zval, inspired by nginx-datadog's +// ToDdwafObjHandler +class ToZvalHandler { +public: + ToZvalHandler(const ToZvalHandler &) = delete; + ToZvalHandler(ToZvalHandler &&) = delete; + ToZvalHandler &operator=(const ToZvalHandler &) = delete; + ToZvalHandler &operator=(ToZvalHandler &&) = delete; + explicit ToZvalHandler(std::size_t max_depth) : max_depth_{max_depth} + { + ZVAL_UNDEF(&root_); + } + + ~ToZvalHandler() { zval_ptr_dtor(&root_); } + + auto Null() -> bool + { + zval val; + ZVAL_NULL(&val); + return AddValue(val); + } + + auto Bool(bool b) -> bool + { + zval val; + ZVAL_BOOL(&val, b); + return AddValue(val); + } + + auto Int(int i) -> bool + { + zval val; + ZVAL_LONG(&val, i); + return AddValue(val); + } + + auto Uint(unsigned u) -> bool + { + zval val; + ZVAL_LONG(&val, static_cast(u)); + return AddValue(val); + } + + auto Int64(int64_t i) -> bool + { + zval val; + ZVAL_LONG(&val, i); + return AddValue(val); + } + + auto Uint64(uint64_t u) -> bool + { + zval val; + if (u > std::numeric_limits::max()) { + ZVAL_DOUBLE(&val, static_cast(u)); + } else { + ZVAL_LONG(&val, static_cast(u)); + } + return AddValue(val); + } + + auto Double(double d) -> bool + { + zval val; + ZVAL_DOUBLE(&val, d); + return AddValue(val); + } + + // NOLINTNEXTLINE(readability-convert-member-functions-to-static,readability-named-parameter) + auto RawNumber( + const TruncatedJsonInputStream::Ch *, rapidjson::SizeType, bool) -> bool + { + assert("RawNumber should not be called (requires flags we are not " + "using)" == nullptr); + return false; + } + + auto String(const char *str, rapidjson::SizeType length, bool /* copy */) + -> bool + { + zval val; + ZVAL_STRINGL(&val, str, length); + return AddValue(val); + } + + auto StartObject() -> bool + { + zval obj; + array_init(&obj); + return AddValue(obj); + } + + auto Key(const char *str, rapidjson::SizeType length, bool /* copy */) + -> bool + { + pending_key_.reset(zend_string_init(str, length, false)); + return true; + } + + auto EndObject(rapidjson::SizeType /* memberCount */) -> bool + { + return end_container(); + } + + auto StartArray() -> bool + { + zval arr; + array_init(&arr); + return AddValue(arr); + } + + auto EndArray(rapidjson::SizeType /* elementCount */) -> bool + { + return end_container(); + } + + [[nodiscard]] auto HasResult() const -> bool + { + return Z_TYPE_P(&root_) != IS_UNDEF; + } + + auto GetResult() -> zval + { + zval result = root_; + ZVAL_UNDEF(&root_); + pending_key_.reset(); + value_stack_.clear(); + suppressed_depth_ = 0; + return result; + } + +private: + auto AddValue(zval val) -> bool + { + if (is_suppressing()) { + if (Z_TYPE_P(&val) == IS_ARRAY) { + suppressed_depth_++; + } + zval_ptr_dtor(&val); + return true; + } + + if (Z_TYPE_P(&root_) == IS_UNDEF) { + root_ = val; + if (Z_TYPE(val) == IS_ARRAY) { + if (stack_depth() == max_depth_) { + suppressed_depth_++; + } else { + value_stack_.push_back(&root_); + } + } + return true; + } + + if (value_stack_.empty()) { + return false; + } + + zval *container = value_stack_.back(); + + if (Z_TYPE_P(container) == IS_ARRAY) { + zval *new_val; + if (pending_key_) { + new_val = zend_symtable_update( + Z_ARRVAL_P(container), pending_key_.get(), &val); + discard_pending_key(); + } else { + new_val = + zend_hash_next_index_insert(Z_ARRVAL_P(container), &val); + } + + if (new_val == nullptr) { + return false; + } + + if (Z_TYPE_P(new_val) == IS_ARRAY) { + if (stack_depth() == max_depth_) { + suppressed_depth_++; + } else { + value_stack_.push_back(new_val); + } + } + } else { + return false; + } + + return true; + } + + auto end_container() -> bool + { + if (is_suppressing()) { + suppressed_depth_--; + return true; + } + + if (value_stack_.empty()) { + return false; + } + value_stack_.pop_back(); + return true; + } + + [[nodiscard]] auto stack_depth() const -> std::size_t + { + return value_stack_.size(); + } + + auto discard_pending_key() -> void { pending_key_.reset(); } + + [[nodiscard]] auto is_suppressing() const -> bool + { + return suppressed_depth_ > 0; + } + + zval root_{}; + std::unique_ptr pending_key_{ + nullptr, zend_string_release}; + std::size_t max_depth_; + std::size_t suppressed_depth_{0}; + std::vector> value_stack_; +}; + +extern "C" { + +auto dd_parse_json_truncated( + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + const char *json_data, std::size_t json_len, int max_depth) -> zval +{ + zval result; + ZVAL_UNDEF(&result); + + if ((json_data == nullptr) || json_len == 0) { + return result; + } + + // Use the same parsing flags as nginx-datadog for handling + // truncated/malformed JSON + static constexpr unsigned parse_flags = + rapidjson::kParseStopWhenDoneFlag | + rapidjson::kParseEscapedApostropheFlag | + rapidjson::kParseNanAndInfFlag | rapidjson::kParseTrailingCommasFlag | + rapidjson::kParseCommentsFlag | rapidjson::kParseIterativeFlag; + + TruncatedJsonInputStream stream(json_data, json_len); + ToZvalHandler handler(max_depth); + RapidJsonPhpAllocator allocator; + rapidjson::GenericReader, rapidjson::UTF8<>, + RapidJsonPhpAllocator> + reader(&allocator); + + rapidjson::ParseResult const parse_result = + reader.Parse(stream, handler); + + if (parse_result != rapidjson::kParseErrorNone) { + if (!handler.HasResult()) { + mlog_g(dd_log_debug, "Error parsing JSON (no data): %s", + rapidjson::GetParseError_En(parse_result.Code())); + return result; + } + + mlog_g(dd_log_debug, "Error parsing JSON (with partial data): %s", + rapidjson::GetParseError_En(parse_result.Code())); + } else { + mlog_g(dd_log_debug, "Successfully parsed JSON (full object)"); + } + + assert(handler.HasResult()); + + result = handler.GetResult(); + if (dd_log_level() >= dd_log_trace) { + zend_string *zv_str = zend_print_zval_r_to_str(&result, 0); + if (ZSTR_LEN(zv_str) < INT_MAX) { + mlog_g(dd_log_trace, "JSON result: %.*s", (int)ZSTR_LEN(zv_str), + ZSTR_VAL(zv_str)); + } + zend_string_release(zv_str); + } + + return result; +} + +} // extern "C" diff --git a/appsec/src/extension/json_truncated_parser.h b/appsec/src/extension/json_truncated_parser.h new file mode 100644 index 00000000000..379c8cb98a7 --- /dev/null +++ b/appsec/src/extension/json_truncated_parser.h @@ -0,0 +1,28 @@ +// 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 + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** + * Parse possibly truncated JSON data using RapidJSON with permissive flags. + * Based on nginx-datadog implementation for handling incomplete JSON. + * + * @param json_data Pointer to JSON string data + * @param json_len Length of JSON data + * @param max_depth Maximum recursion depth for nested structures + * @return zval containing parsed data, or NULL zval on failure + */ +zval dd_parse_json_truncated(const char* json_data, size_t json_len, int max_depth); + +#ifdef __cplusplus +} +#endif diff --git a/appsec/src/extension/logging.h b/appsec/src/extension/logging.h index 284d9030235..83ba1e39c6c 100644 --- a/appsec/src/extension/logging.h +++ b/appsec/src/extension/logging.h @@ -31,8 +31,8 @@ extern __thread char _dd_strerror_buf[STRERROR_R_BUF_SIZE]; static inline dd_log_level_t dd_log_level(void) { - return runtime_config_first_init ? get_DD_APPSEC_LOG_LEVEL() - : get_global_DD_APPSEC_LOG_LEVEL(); + return (dd_log_level_t)(runtime_config_first_init ? get_DD_APPSEC_LOG_LEVEL() + : get_global_DD_APPSEC_LOG_LEVEL()); } void dd_log_startup(void); diff --git a/appsec/src/extension/php_compat.c b/appsec/src/extension/php_compat.c index 608b16d097b..c30eb35c289 100644 --- a/appsec/src/extension/php_compat.c +++ b/appsec/src/extension/php_compat.c @@ -5,6 +5,115 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #include "php_compat.h" +#if PHP_VERSION_ID < 70100 +# define PRINT_ZVAL_INDENT 4 +static void _print_hash_compat( + smart_str *buf, HashTable *ht, int indent, zend_bool is_object) +{ + for (int i = 0; i < indent; i++) { smart_str_appendc(buf, ' '); } + smart_str_appends(buf, "(\n"); + indent += PRINT_ZVAL_INDENT; + + zend_ulong num_key; + zend_string *string_key; + zval *tmp; + ZEND_HASH_FOREACH_KEY_VAL_IND(ht, num_key, string_key, tmp) + { + for (int i = 0; i < indent; i++) { smart_str_appendc(buf, ' '); } + smart_str_appendc(buf, '['); + if (string_key) { + if (is_object) { + const char *prop_name, *class_name; + size_t prop_len; + int mangled = zend_unmangle_property_name_ex( + string_key, &class_name, &prop_name, &prop_len); + + smart_str_appendl(buf, prop_name, prop_len); + if (class_name && mangled == SUCCESS) { + if (class_name[0] == '*') { + smart_str_appends(buf, ":protected"); + } else { + smart_str_appends(buf, ":"); + smart_str_appends(buf, class_name); + smart_str_appends(buf, ":private"); + } + } + } else { + smart_str_append(buf, string_key); + } + } else { + smart_str_append_long(buf, num_key); + } + smart_str_appends(buf, "] => "); + zend_print_zval_r_to_buf_compat(buf, tmp, indent + PRINT_ZVAL_INDENT); + smart_str_appends(buf, "\n"); + } + ZEND_HASH_FOREACH_END(); + indent -= PRINT_ZVAL_INDENT; + for (int i = 0; i < indent; i++) { smart_str_appendc(buf, ' '); } + smart_str_appends(buf, ")\n"); +} +void zend_print_zval_r_to_buf_compat( + smart_str *buf, zval *expr, int indent) /* {{{ */ +{ + ZVAL_DEREF(expr); + switch (Z_TYPE_P(expr)) { + case IS_ARRAY: + smart_str_appends(buf, "Array\n"); + if (ZEND_HASH_APPLY_PROTECTION(Z_ARRVAL_P(expr)) && + ++Z_ARRVAL_P(expr)->u.v.nApplyCount > 1) { + smart_str_appends(buf, " *RECURSION*"); + Z_ARRVAL_P(expr)->u.v.nApplyCount--; + return; + } + _print_hash_compat(buf, Z_ARRVAL_P(expr), indent, 0); + if (ZEND_HASH_APPLY_PROTECTION(Z_ARRVAL_P(expr))) { + Z_ARRVAL_P(expr)->u.v.nApplyCount--; + } + break; + case IS_OBJECT: { + HashTable *properties; + int is_temp; + + zend_string *class_name = + Z_OBJ_HANDLER_P(expr, get_class_name)(Z_OBJ_P(expr)); + smart_str_appends(buf, ZSTR_VAL(class_name)); + zend_string_release(class_name); + + smart_str_appends(buf, " Object\n"); + if (Z_OBJ_APPLY_COUNT_P(expr) > 0) { + smart_str_appends(buf, " *RECURSION*"); + return; + } + if ((properties = Z_OBJDEBUG_P(expr, is_temp)) == NULL) { + break; + } + + Z_OBJ_INC_APPLY_COUNT_P(expr); + _print_hash_compat(buf, properties, indent, 1); + Z_OBJ_DEC_APPLY_COUNT_P(expr); + + if (is_temp) { + zend_hash_destroy(properties); + FREE_HASHTABLE(properties); + } + break; + } + case IS_LONG: + smart_str_append_long(buf, Z_LVAL_P(expr)); + break; + case IS_STRING: + smart_str_append(buf, Z_STR_P(expr)); + break; + default: { + zend_string *str = zval_get_string(expr); + smart_str_append(buf, str); + zend_string_release(str); + } break; + } +} +#endif // PHP_VERSION_ID < 70100 + #if PHP_VERSION_ID < 70300 static zend_string _zend_empty_string_st = { .gc.refcount = 1, @@ -41,3 +150,44 @@ const HashTable zend_empty_array = {.gc.refcount = 2, .nNextFreeElement = 0, .pDestructor = ZVAL_PTR_DTOR}; #endif + +#if PHP_VERSION_ID < 70400 +zend_bool try_convert_to_string(zval *op) +{ + + if (Z_TYPE_P(op) == IS_STRING) { + return 1; + } + + zend_object *old_exception = EG(exception); + EG(exception) = NULL; + + zend_string *str = NULL; + bool bailout = false; + zend_try { str = _zval_get_string_func(op); } + zend_catch + { + bailout = true; + str = NULL; + } + zend_end_try(); + + if (UNEXPECTED(bailout || EG(exception))) { + if (str) { + zend_string_release(str); + } + if (!EG(exception) && old_exception) { + EG(exception) = old_exception; + } + return false; + } + + if (old_exception) { + EG(exception) = old_exception; + } + + zval_ptr_dtor(op); + ZVAL_STR(op, str); + return true; +} +#endif diff --git a/appsec/src/extension/php_compat.h b/appsec/src/extension/php_compat.h index c8574d39745..5798bed928b 100644 --- a/appsec/src/extension/php_compat.h +++ b/appsec/src/extension/php_compat.h @@ -19,15 +19,35 @@ #define MAY_BE_NULL 0 #define MAY_BE_STRING 0 #define MAY_BE_ARRAY 0 + +# include +void zend_print_zval_r_to_buf_compat(smart_str *buf, zval *expr, int indent); +static inline zend_string *zend_print_zval_r_to_str(zval *expr, int indent) +{ + smart_str buf = {0}; + zend_print_zval_r_to_buf_compat(&buf, expr, indent); + smart_str_0(&buf); + return buf.s; +} #endif #if PHP_VERSION_ID < 70200 # undef ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX -# define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX( \ - name, return_reference, required_num_args, type, allow_null) \ - static const zend_internal_arg_info name[] = { \ - {(const char *)(zend_uintptr_t)(required_num_args), NULL, type, \ - return_reference, allow_null, 0}, +# undef ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO + +# define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(name, return_reference, required_num_args, type, allow_null) \ + static const zend_internal_arg_info name[] = { \ + { (const char*)(zend_uintptr_t)(required_num_args), NULL, type, return_reference, allow_null, 0 }, + +# define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO(name, type, allow_null) \ + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(name, 0, -1, type, allow_null) + +# define ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(name, return_reference, required_num_args, class_name, allow_null) \ + static const zend_internal_arg_info name[] = { \ + { (const char*)(zend_uintptr_t)(required_num_args), #class_name, IS_OBJECT, return_reference, allow_null, 0 }, + +# define ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO(name, class_name, allow_null) \ + ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(name, 0, -1, class_name, allow_null) // persistent must be true iif we're on the startup phase static zend_always_inline zend_string *zend_string_init_interned( @@ -70,7 +90,7 @@ static zend_always_inline void _gc_try_delref(zend_refcounted_h *_rc) # define GC_TRY_DELREF(p) _gc_try_delref(&(p)->gc) zend_bool zend_ini_parse_bool(zend_string *str); -# define zend_string_efree zend_string_free +# define zend_string_efree zend_string_free static inline HashTable *zend_new_array(uint32_t nSize) { HashTable *ht = (HashTable *)emalloc(sizeof(HashTable)); @@ -80,6 +100,7 @@ static inline HashTable *zend_new_array(uint32_t nSize) { #endif #if PHP_VERSION_ID < 70400 +zend_bool try_convert_to_string(zval *op); # define tsrm_env_lock() # define tsrm_env_unlock() #endif @@ -87,6 +108,7 @@ static inline HashTable *zend_new_array(uint32_t nSize) { #if PHP_VERSION_ID < 80000 #define ZEND_ARG_TYPE_MASK(pass_by_ref, name, type_mask, default_value) ZEND_ARG_INFO_WITH_DEFAULT_VALUE(pass_by_ref, name, default_value) #define ZEND_ARG_INFO_WITH_DEFAULT_VALUE(pass_by_ref, name, default_value) ZEND_ARG_INFO(pass_by_ref, name) +#define ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(pass_by_ref, name, type_hint, allow_null, default_value) ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) #define IS_MIXED 0 #endif diff --git a/appsec/src/helper/.clang-tidy b/appsec/src/helper/.clang-tidy index de8680d4208..56f0b950c90 100644 --- a/appsec/src/helper/.clang-tidy +++ b/appsec/src/helper/.clang-tidy @@ -1,4 +1,4 @@ -Checks: 'readability-identifier-naming' +Checks: 'readability-identifier-naming,-bugprone-lambda-function-name' CheckOptions: - key: readability-identifier-naming.StructCase diff --git a/appsec/src/helper/.gitignore b/appsec/src/helper/.gitignore deleted file mode 100644 index 9134aa0b521..00000000000 --- a/appsec/src/helper/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/version.hpp diff --git a/appsec/tests/extension/convert_json.phpt b/appsec/tests/extension/convert_json.phpt index a86e0225171..6e84c58961b 100644 --- a/appsec/tests/extension/convert_json.phpt +++ b/appsec/tests/extension/convert_json.phpt @@ -5,13 +5,13 @@ _convert_json function $j = '{"a":[],"b":{"0": 1},"c":{"d":"e"}}'; -$result = datadog\appsec\testing\convert_json($j); +$result = datadog\appsec\convert_json($j); echo(json_encode($result, JSON_PRETTY_PRINT)), "\n"; -$result = datadog\appsec\testing\convert_json('[1,2]'); +$result = datadog\appsec\convert_json('[1,2]'); echo(json_encode($result, JSON_PRETTY_PRINT)), "\n"; -var_dump(\datadog\appsec\testing\convert_json('{')); +var_dump(\datadog\appsec\convert_json('{')); --EXPECT-- { "a": [], @@ -26,4 +26,5 @@ var_dump(\datadog\appsec\testing\convert_json('{')); 1, 2 ] -NULL +array(0) { +} diff --git a/appsec/tests/extension/convert_json_max_depth.phpt b/appsec/tests/extension/convert_json_max_depth.phpt index e172d5f0ee4..b6d93f39b02 100644 --- a/appsec/tests/extension/convert_json_max_depth.phpt +++ b/appsec/tests/extension/convert_json_max_depth.phpt @@ -4,33 +4,84 @@ _convert_json function (exceed max depth) before + [1] => Array + ( + ) + + [4] => after +) +Original (depth=4): +string(39) "["before",{"2":{"3":{"4":[]}}},"after"]" +After transformation (depth=4, max_depth=2): +Array +( + [0] => before + [1] => Array + ( + [2] => Array + ( + ) + + ) + + [4] => after +) +Original (depth=3): +string(33) "["before",{"2":{"3":[]}},"after"]" +After transformation (depth=3, max_depth=3): +Array +( + [0] => before + [1] => Array + ( + [2] => Array + ( + [3] => Array + ( + ) + + ) + + ) + + [2] => after +) diff --git a/appsec/tests/extension/convert_json_special_values.phpt b/appsec/tests/extension/convert_json_special_values.phpt new file mode 100644 index 00000000000..d4a6e766f5d --- /dev/null +++ b/appsec/tests/extension/convert_json_special_values.phpt @@ -0,0 +1,158 @@ +--TEST-- +convert_json function - special values +--FILE-- + + bool(true) + ["isFalse"]=> + bool(false) +} + +Test 2: Null value: +Original: +null +Result: +NULL + +Test 3: Numbers: +Original: +{"int": 42, "float": 3.14, "negative": -10, "zero": 0} +Result: +array(4) { + ["int"]=> + int(42) + ["float"]=> + float(3.14) + ["negative"]=> + int(-10) + ["zero"]=> + int(0) +} + +Test 4: Empty structures: +Original: +{"empty_obj": {}, "empty_arr": []} +Result: +array(2) { + ["empty_obj"]=> + array(0) { + } + ["empty_arr"]=> + array(0) { + } +} + +Test 5: Escaped characters: +Original: +{"unicode": "\u0041\u0042", "simple": "test"} +Result: +array(2) { + ["unicode"]=> + string(2) "AB" + ["simple"]=> + string(4) "test" +} + +Test 6: Trailing comma: +Original: +{"a": 1, "b": 2,} +Result: +array(2) { + ["a"]=> + int(1) + ["b"]=> + int(2) +} + +Test 7: Comments: +Original: +{"a": 1, /* comment */ "b": 2} +Result: +array(2) { + ["a"]=> + int(1) + ["b"]=> + int(2) +} + +Test 8: NaN and Infinity: +Original: +{"nan": NaN, "inf": Infinity, "negInf": -Infinity} +Result: +array(3) { + ["nan"]=> + float(NAN) + ["inf"]=> + float(INF) + ["negInf"]=> + float(-INF) +} + +Test 9: Empty input: +Original: + +Result: +NULL + +Test 10: Single string value: +Original: +"just a string" +Result: +string(13) "just a string" + +Test 11: Single number: +Original: +42 +Result: +int(42) + +Test 12: Large integer: +Original: +{"bignum": 9223372036854775807} +Result: +array(1) { + ["bignum"]=> + int(9223372036854775807) +} diff --git a/appsec/tests/extension/convert_json_truncated.phpt b/appsec/tests/extension/convert_json_truncated.phpt new file mode 100644 index 00000000000..79eb1149e55 --- /dev/null +++ b/appsec/tests/extension/convert_json_truncated.phpt @@ -0,0 +1,148 @@ +--TEST-- +convert_json function - truncated JSON parsing +--FILE-- + value + [number] => 42 +) +Test 2: Truncated array: +Original: +[1, 2, 3, 4 +Array +( + [0] => 1 + [1] => 2 + [2] => 3 + [3] => 4 +) +Test 3: Nested truncated object: +Original: +{"outer": {"inner": {"deep": "value"}, "other": 123 +Array +( + [outer] => Array + ( + [inner] => Array + ( + [deep] => value + ) + + [other] => 123 + ) + +) +Test 4: String truncation: +Original: +{"message": "This is a long messa +Array +( +) +Test 5: Number truncation: +Original: +{"pi": 3.14159 +Array +( + [pi] => 3.14159 +) +Test 6: Mixed array truncation: +Original: +["string", 123, true, null, {"nested": "obj"} +Array +( + [0] => string + [1] => 123 + [2] => 1 + [3] => + [4] => Array + ( + [nested] => obj + ) + +) +Test 7: Deep nesting truncation: +Original: +{"a": {"b": {"c": {"d": "value" +Array +( + [a] => Array + ( + [b] => Array + ( + [c] => Array + ( + [d] => value + ) + + ) + + ) + +) +Test 8: Array of objects truncation: +Original: +[{"id": 1, "name": "first"}, {"id": 2, "name": "sec +Array +( + [0] => Array + ( + [id] => 1 + [name] => first + ) + + [1] => Array + ( + [id] => 2 + ) + +) +Test 9: Truncated after key: +Original: +{"key1": "value1", "key2": +Array +( + [key1] => value1 +) +Test 10: Truncated with trailing comma: +Original: +{"a": 1, "b": 2, +Array +( + [a] => 1 + [b] => 2 +) diff --git a/appsec/tests/extension/convert_xml_basic.phpt b/appsec/tests/extension/convert_xml_basic.phpt index 5a64fcded9c..33b08360847 100644 --- a/appsec/tests/extension/convert_xml_basic.phpt +++ b/appsec/tests/extension/convert_xml_basic.phpt @@ -21,7 +21,7 @@ XML; $content_type = "application/xml"; -$result = datadog\appsec\testing\convert_xml($entity, $content_type); +$result = datadog\appsec\convert_xml($entity, $content_type); if (!$result) { print_r(libxml_get_errors()); } diff --git a/appsec/tests/extension/convert_xml_external_dtd.phpt b/appsec/tests/extension/convert_xml_external_dtd.phpt index d8365bacd0a..938c701d852 100644 --- a/appsec/tests/extension/convert_xml_external_dtd.phpt +++ b/appsec/tests/extension/convert_xml_external_dtd.phpt @@ -14,7 +14,7 @@ $content_type = "application/xml"; // External DTD is blocked for security. The &test; entity which would be // defined in that DTD cannot be resolved, so it appears literally. -$result = datadog\appsec\testing\convert_xml($entity, $content_type); +$result = datadog\appsec\convert_xml($entity, $content_type); echo(json_encode($result, JSON_PRETTY_PRINT)); --EXPECT-- diff --git a/appsec/tests/extension/convert_xml_external_entity.phpt b/appsec/tests/extension/convert_xml_external_entity.phpt index cb385d8817c..f281769b9f2 100644 --- a/appsec/tests/extension/convert_xml_external_entity.phpt +++ b/appsec/tests/extension/convert_xml_external_entity.phpt @@ -22,7 +22,7 @@ XML; $content_type = "application/xml"; -$result = datadog\appsec\testing\convert_xml($entity, $content_type); +$result = datadog\appsec\convert_xml($entity, $content_type); echo(json_encode($result, JSON_PRETTY_PRINT)); --EXPECT-- diff --git a/appsec/tests/extension/convert_xml_external_entity_no_double.phpt b/appsec/tests/extension/convert_xml_external_entity_no_double.phpt index eabc7812af0..518e0e588a8 100644 --- a/appsec/tests/extension/convert_xml_external_entity_no_double.phpt +++ b/appsec/tests/extension/convert_xml_external_entity_no_double.phpt @@ -15,7 +15,7 @@ $xml1 = <<&ext; XML; -$result1 = datadog\appsec\testing\convert_xml($xml1, "application/xml"); +$result1 = datadog\appsec\convert_xml($xml1, "application/xml"); echo "Test 1 - SYSTEM entity:\n"; echo json_encode($result1, JSON_PRETTY_PRINT) . "\n\n"; @@ -39,7 +39,7 @@ $xml2 = <<&e1;&e2; XML; -$result2 = datadog\appsec\testing\convert_xml($xml2, "application/xml"); +$result2 = datadog\appsec\convert_xml($xml2, "application/xml"); echo "Test 2 - Multiple SYSTEM entities:\n"; echo json_encode($result2, JSON_PRETTY_PRINT) . "\n\n"; @@ -63,7 +63,7 @@ $xml3 = <<before&external;after XML; -$result3 = datadog\appsec\testing\convert_xml($xml3, "application/xml"); +$result3 = datadog\appsec\convert_xml($xml3, "application/xml"); echo "Test 3 - External entity with surrounding text:\n"; echo json_encode($result3, JSON_PRETTY_PRINT) . "\n\n"; @@ -86,7 +86,7 @@ $xml4 = <<&ext; XML; -$result4 = datadog\appsec\testing\convert_xml($xml4, "application/xml"); +$result4 = datadog\appsec\convert_xml($xml4, "application/xml"); echo "Test 4 - With parameter entity declaration:\n"; echo json_encode($result4, JSON_PRETTY_PRINT) . "\n\n"; diff --git a/appsec/tests/extension/convert_xml_iso88591.phpt b/appsec/tests/extension/convert_xml_iso88591.phpt index 2e15a7f3af3..ff7060633ec 100644 --- a/appsec/tests/extension/convert_xml_iso88591.phpt +++ b/appsec/tests/extension/convert_xml_iso88591.phpt @@ -12,9 +12,9 @@ XML; $content_type = "application/xml;charset=iso-8859-1"; -$result = datadog\appsec\testing\convert_xml($entity, $content_type); +$result = datadog\appsec\convert_xml($entity, $content_type); var_dump($result); --EXPECTF-- -Notice: datadog\appsec\testing\convert_xml(): [ddappsec] Only UTF-8 is supported for XML parsing in %s on line %d +Notice: datadog\appsec\convert_xml(): [ddappsec] Only UTF-8 is supported for XML parsing in %s on line %d NULL diff --git a/appsec/tests/extension/convert_xml_max_depth.phpt b/appsec/tests/extension/convert_xml_max_depth.phpt index 0532e53c884..2680662b8bc 100644 --- a/appsec/tests/extension/convert_xml_max_depth.phpt +++ b/appsec/tests/extension/convert_xml_max_depth.phpt @@ -15,7 +15,7 @@ for ($i = $depth; $i >= 1; $i--) { $content_type = "application/xml"; -$result = datadog\appsec\testing\convert_xml($xml, $content_type); +$result = datadog\appsec\convert_xml($xml, $content_type); echo(json_encode($result, JSON_PRETTY_PRINT)); --EXPECTF-- diff --git a/appsec/tests/extension/convert_xml_namespaces.phpt b/appsec/tests/extension/convert_xml_namespaces.phpt index 1ed620f2891..d4246f4aac3 100644 --- a/appsec/tests/extension/convert_xml_namespaces.phpt +++ b/appsec/tests/extension/convert_xml_namespaces.phpt @@ -11,7 +11,7 @@ XML; $content_type = "application/xml"; -$result = datadog\appsec\testing\convert_xml($entity, $content_type); +$result = datadog\appsec\convert_xml($entity, $content_type); echo(json_encode($result, JSON_PRETTY_PRINT)); --EXPECTF-- { diff --git a/appsec/tests/extension/convert_xml_truncated.phpt b/appsec/tests/extension/convert_xml_truncated.phpt index 64b973e954d..5f93d866786 100644 --- a/appsec/tests/extension/convert_xml_truncated.phpt +++ b/appsec/tests/extension/convert_xml_truncated.phpt @@ -7,31 +7,31 @@ gracefully and return whatever structure was successfully parsed. contenttrun'; -$result1 = datadog\appsec\testing\convert_xml($xml1, "text/xml"); +$result1 = datadog\appsec\convert_xml($xml1, "text/xml"); echo "Test 1 (truncated mid-element):\n"; echo json_encode($result1, JSON_PRETTY_PRINT) . "\n\n"; // Test 2: Truncated with unclosed tags $xml2 = 'text'; -$result2 = datadog\appsec\testing\convert_xml($xml2, "text/xml"); +$result2 = datadog\appsec\convert_xml($xml2, "text/xml"); echo "Test 2 (unclosed tags):\n"; echo json_encode($result2, JSON_PRETTY_PRINT) . "\n\n"; // Test 3: Truncated in attribute $xml3 = 'get_commands(); // ignore +var_dump($res_rinit); var_dump(rshutdown()); $c = $helper->get_commands(); -print_r(array_keys($c[0][1][0])); +print_r($c[0][1][0]); ?> --EXPECT-- -{"a": [1,2,"3"]} +{"a": [1,2,"3","4"]} bool(true) bool(true) Array ( - [0] => server.response.status - [1] => server.response.headers.no_cookies + [server.response.status] => 403 + [server.response.headers.no_cookies] => Array + ( + [content-type] => Array + ( + [0] => application/json + ) + + ) + + [server.response.body] => Array + ( + [a] => Array + ( + [0] => 1 + [1] => 2 + [2] => 3 + ) + + ) + ) diff --git a/appsec/tests/integration/build.gradle b/appsec/tests/integration/build.gradle index 10be6deffd5..8ad9de29015 100644 --- a/appsec/tests/integration/build.gradle +++ b/appsec/tests/integration/build.gradle @@ -361,7 +361,8 @@ def buildAppSecTask = { String version, String variant -> -DCMAKE_INSTALL_PREFIX=/appsec \\ -DDD_APPSEC_ENABLE_PATCHELF_LIBC=ON \\ -DGIT_COMMIT=${libddwafCommit()} \\ - -DDD_APPSEC_TESTING=ON /project/appsec + -DDD_APPSEC_TESTING=ON \\ + -DBOOST_CACHE_PREFIX=/root/.boost /project/appsec make -j extension ddappsec-helper && \\ touch ddappsec.so libddappsec-helper.so """ diff --git a/appsec/valgrind.supp b/appsec/valgrind.supp index fd3acbca757..2cb6cac199b 100644 --- a/appsec/valgrind.supp +++ b/appsec/valgrind.supp @@ -239,3 +239,27 @@ fun:zend_wrong_parameter_class_error fun:zend_wrong_parameter_error } +{ + + Memcheck:Cond + fun:_ZNK3re210SparseSetTIvE8containsEi + ... +} +{ + + Memcheck:Cond + fun:_ZNK3re211SparseArrayIiE9has_indexEi + ... +} +{ + + Memcheck:Value8 + fun:_ZNK3re211SparseArrayIiE9has_indexEi + ... +} +{ + + Memcheck:Value8 + fun:_ZNK3re210SparseSetTIvE8containsEi + ... +} From 1622f8dc16da6d862eb6830696889e09543f977a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Andr=C3=A9=20dos=20Santos=20Lopes?= Date: Wed, 25 Feb 2026 21:39:05 +0100 Subject: [PATCH 2/2] add docker based clang tools Also try to find homebrew clang tools or clang-tidy/format with -17 as a suffix. --- appsec/cmake/clang-format.cmake | 33 +++- appsec/cmake/clang-tidy.cmake | 65 ++++++-- appsec/cmake/clang-tools/Dockerfile | 94 ++++++++++++ appsec/cmake/clang-tools/README.md | 145 ++++++++++++++++++ appsec/cmake/clang-tools/build_local.sh | 3 + appsec/cmake/clang-tools/clang-format | 20 +++ appsec/cmake/clang-tools/clang-tidy | 20 +++ appsec/cmake/clang-tools/common.sh | 31 ++++ .../src/extension/json_truncated_parser.cpp | 5 +- appsec/src/helper/network/.clang-tidy | 2 + appsec/third_party/CMakeLists.txt | 2 +- 11 files changed, 397 insertions(+), 23 deletions(-) create mode 100644 appsec/cmake/clang-tools/Dockerfile create mode 100644 appsec/cmake/clang-tools/README.md create mode 100755 appsec/cmake/clang-tools/build_local.sh create mode 100755 appsec/cmake/clang-tools/clang-format create mode 100755 appsec/cmake/clang-tools/clang-tidy create mode 100755 appsec/cmake/clang-tools/common.sh create mode 100644 appsec/src/helper/network/.clang-tidy diff --git a/appsec/cmake/clang-format.cmake b/appsec/cmake/clang-format.cmake index a467a23cf10..16514051782 100644 --- a/appsec/cmake/clang-format.cmake +++ b/appsec/cmake/clang-format.cmake @@ -1,7 +1,32 @@ -find_program(CLANG_FORMAT clang-format) -if(CLANG_FORMAT STREQUAL CLANG_FORMAT-NOTFOUND) - message(STATUS "Cannot find clang-format, either set CLANG_FORMAT or make it discoverable") - return() +set(_LLVM17_FORMAT /opt/homebrew/opt/llvm@17/bin/clang-format) +if(EXISTS ${_LLVM17_FORMAT}) + set(CLANG_FORMAT ${_LLVM17_FORMAT}) + message(STATUS "Using Homebrew LLVM 17 clang-format: ${CLANG_FORMAT}") +else() + find_program(_CF_VERSIONED clang-format-17) + if(NOT _CF_VERSIONED STREQUAL _CF_VERSIONED-NOTFOUND) + set(CLANG_FORMAT ${_CF_VERSIONED}) + else() + find_program(_CF_UNVERSIONED clang-format) + if(NOT _CF_UNVERSIONED STREQUAL _CF_UNVERSIONED-NOTFOUND) + execute_process( + COMMAND ${_CF_UNVERSIONED} --version + OUTPUT_VARIABLE _CF_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET) + if(_CF_VERSION MATCHES " 17\\.") + set(CLANG_FORMAT ${_CF_UNVERSIONED}) + endif() + endif() + endif() + if(NOT CLANG_FORMAT) + set(CLANG_FORMAT ${CMAKE_CURRENT_LIST_DIR}/clang-tools/clang-format) + if(NOT EXISTS ${CLANG_FORMAT}) + message(STATUS "Cannot find clang-format version 17, either set CLANG_FORMAT or make it discoverable") + return() + endif() + message(STATUS "Using Docker-based clang-format wrapper: ${CLANG_FORMAT}") + endif() endif() set(FILE_LIST "") diff --git a/appsec/cmake/clang-tidy.cmake b/appsec/cmake/clang-tidy.cmake index 071c225576c..f10567020d5 100644 --- a/appsec/cmake/clang-tidy.cmake +++ b/appsec/cmake/clang-tidy.cmake @@ -1,7 +1,47 @@ -find_program(CLANG_TIDY run-clang-tidy) -if(CLANG_TIDY STREQUAL CLANG_TIDY-NOTFOUND) - message(STATUS "Cannot find clang-tidy, either set CLANG_TIDY or make it discoverable") - return() +# Prefer a locally installed LLVM 17 run-clang-tidy (e.g. via brew install llvm@17) +# over the Docker-based wrapper, since native execution avoids SDK incompatibilities. +set(_LLVM17_BIN /opt/homebrew/opt/llvm@17/bin) +set(_LLVM17_TIDY ${_LLVM17_BIN}/run-clang-tidy) +set(CLANG_TIDY_BINARY_OPT "") +if(EXISTS ${_LLVM17_TIDY}) + set(CLANG_TIDY ${_LLVM17_TIDY}) + set(CLANG_TIDY_BINARY_OPT -clang-tidy-binary ${_LLVM17_BIN}/clang-tidy) + message(STATUS "Using Homebrew LLVM 17 run-clang-tidy: ${CLANG_TIDY}") +else() + find_program(_RCT_VERSIONED run-clang-tidy-17) + if(NOT _RCT_VERSIONED STREQUAL _RCT_VERSIONED-NOTFOUND) + set(CLANG_TIDY ${_RCT_VERSIONED}) + find_program(_CT_VERSIONED clang-tidy-17) + if(NOT _CT_VERSIONED STREQUAL _CT_VERSIONED-NOTFOUND) + set(CLANG_TIDY_BINARY_OPT -clang-tidy-binary ${_CT_VERSIONED}) + endif() + else() + find_program(_RCT_UNVERSIONED run-clang-tidy) + if(NOT _RCT_UNVERSIONED STREQUAL _RCT_UNVERSIONED-NOTFOUND) + # Verify version via co-located clang-tidy + get_filename_component(_RCT_DIR ${_RCT_UNVERSIONED} DIRECTORY) + find_program(_CT_COLOCATED clang-tidy HINTS ${_RCT_DIR} NO_DEFAULT_PATH) + if(NOT _CT_COLOCATED STREQUAL _CT_COLOCATED-NOTFOUND) + execute_process( + COMMAND ${_CT_COLOCATED} --version + OUTPUT_VARIABLE _CT_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET) + if(_CT_VERSION MATCHES " 17\\.") + set(CLANG_TIDY ${_RCT_UNVERSIONED}) + set(CLANG_TIDY_BINARY_OPT -clang-tidy-binary ${_CT_COLOCATED}) + endif() + endif() + endif() + endif() + if(NOT CLANG_TIDY) + set(CLANG_TIDY ${CMAKE_CURRENT_LIST_DIR}/clang-tools/run-clang-tidy) + if(NOT EXISTS ${CLANG_TIDY}) + message(STATUS "Cannot find clang-tidy version 17, either set CLANG_TIDY or make it discoverable") + return() + endif() + message(STATUS "Using Docker-based run-clang-tidy wrapper: ${CLANG_TIDY}") + endif() endif() set(FILE_LIST "") @@ -20,27 +60,20 @@ if(DD_APPSEC_BUILD_EXTENSION) append_target_sources(extension) endif() -execute_process ( - COMMAND bash -c "${CLANG_TIDY} --help | grep -qs 'use-color'" - RESULT_VARIABLE USE_COLOR -) - -set(COLOR_OPT "") -if (USE_COLOR EQUAL 0) - set(COLOR_OPT -use-color) -endif() - set(TIDY_DEPS "") if(DD_APPSEC_BUILD_EXTENSION AND TARGET libxml2_build) list(APPEND TIDY_DEPS libxml2_build) endif() +if(TARGET boost_build) + list(APPEND TIDY_DEPS boost_build) +endif() add_custom_target(tidy - COMMAND ${CLANG_TIDY} ${COLOR_OPT} -p ${CMAKE_BINARY_DIR} ${FILE_LIST} + COMMAND ${CLANG_TIDY} ${CLANG_TIDY_BINARY_OPT} -use-color -p ${CMAKE_BINARY_DIR} ${FILE_LIST} WORKING_DIRECTORY ${CMAKE_BINARY_DIR} DEPENDS ${TIDY_DEPS}) add_custom_target(tidy_fix - COMMAND ${CLANG_TIDY} ${COLOR_OPT} -fix -p ${CMAKE_BINARY_DIR} ${FILE_LIST} + COMMAND ${CLANG_TIDY} ${CLANG_TIDY_BINARY_OPT} -use-color -fix -p ${CMAKE_BINARY_DIR} ${FILE_LIST} WORKING_DIRECTORY ${CMAKE_BINARY_DIR} DEPENDS ${TIDY_DEPS}) diff --git a/appsec/cmake/clang-tools/Dockerfile b/appsec/cmake/clang-tools/Dockerfile new file mode 100644 index 00000000000..aa839fe3084 --- /dev/null +++ b/appsec/cmake/clang-tools/Dockerfile @@ -0,0 +1,94 @@ +# Minimal Docker image with clang-format and clang-tidy built from LLVM source +# Uses static linking for smallest possible image size +# Based on Alpine Linux 3.21 + +FROM alpine:3.21 AS builder + +RUN apk add --no-cache \ + build-base \ + cmake \ + ninja \ + python3 \ + git \ + linux-headers \ + wget \ + clang \ + clang-dev + +# Download and extract LLVM source +ARG LLVM_VERSION=17.0.6 +WORKDIR /src +RUN wget -q https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/llvm-project-${LLVM_VERSION}.src.tar.xz && \ + tar -xf llvm-project-${LLVM_VERSION}.src.tar.xz && \ + mv llvm-project-${LLVM_VERSION}.src llvm-project && \ + rm llvm-project-${LLVM_VERSION}.src.tar.xz + +# Configure LLVM build with minimal size optimizations +WORKDIR /src/llvm-project/build +RUN cmake -G Ninja ../llvm \ + -DCMAKE_BUILD_TYPE=MinSizeRel \ + -DCMAKE_INSTALL_PREFIX=/usr/local \ + -DCMAKE_C_COMPILER=clang \ + -DCMAKE_CXX_COMPILER=clang++ \ + -DCMAKE_CXX_STANDARD=17 \ + -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra" \ + -DLLVM_TARGETS_TO_BUILD="" \ + -DLLVM_INCLUDE_TESTS=OFF \ + -DLLVM_INCLUDE_EXAMPLES=OFF \ + -DLLVM_INCLUDE_BENCHMARKS=OFF \ + -DLLVM_INCLUDE_DOCS=OFF \ + -DLLVM_ENABLE_BINDINGS=OFF \ + -DLLVM_ENABLE_OCAMLDOC=OFF \ + -DLLVM_ENABLE_Z3_SOLVER=OFF \ + -DLLVM_ENABLE_LIBXML2=OFF \ + -DLLVM_ENABLE_ZLIB=OFF \ + -DLLVM_ENABLE_ZSTD=OFF \ + -DLLVM_ENABLE_TERMINFO=OFF \ + -DLLVM_BUILD_STATIC=ON \ + -DLLVM_LINK_LLVM_DYLIB=OFF \ + -DLLVM_BUILD_LLVM_DYLIB=OFF \ + -DBUILD_SHARED_LIBS=OFF \ + -DLLVM_STATIC_LINK_CXX_STDLIB=ON \ + -DCMAKE_EXE_LINKER_FLAGS="-static" \ + -DCLANG_ENABLE_STATIC_ANALYZER=OFF \ + -DCLANG_ENABLE_ARCMT=OFF \ + -DCLANG_BUILD_EXAMPLES=OFF + +# Build only the required tools +RUN ninja clang-format clang-tidy clang-apply-replacements + +# Install binaries +RUN ninja install-clang-format install-clang-tidy install-clang-apply-replacements install-clang-resource-headers + +# Copy run-clang-tidy helper script +RUN cp /src/llvm-project/clang-tools-extra/clang-tidy/tool/run-clang-tidy.py /usr/local/bin/run-clang-tidy && \ + chmod +x /usr/local/bin/run-clang-tidy + +# Strip binaries to reduce size +RUN strip /usr/local/bin/clang-format \ + /usr/local/bin/clang-tidy \ + /usr/local/bin/clang-apply-replacements + +# Final minimal runtime image +FROM alpine:3.21 + +# Install only Python runtime for run-clang-tidy script +RUN apk add --no-cache python3 + +# Copy static binaries from builder +COPY --from=builder /usr/local/bin/clang-format /usr/local/bin/ +COPY --from=builder /usr/local/bin/clang-tidy /usr/local/bin/ +COPY --from=builder /usr/local/bin/clang-apply-replacements /usr/local/bin/ +COPY --from=builder /usr/local/bin/run-clang-tidy /usr/local/bin/ + +# Copy clang resource headers so clang-tidy uses its own headers +COPY --from=builder /usr/local/lib/clang/ /usr/local/lib/clang/ + +# Verify installations +RUN clang-format --version && \ + clang-tidy --version && \ + run-clang-tidy --help > /dev/null + +WORKDIR /workspace + +CMD ["/bin/sh"] diff --git a/appsec/cmake/clang-tools/README.md b/appsec/cmake/clang-tools/README.md new file mode 100644 index 00000000000..0c7b1071de5 --- /dev/null +++ b/appsec/cmake/clang-tools/README.md @@ -0,0 +1,145 @@ +# Clang Tools Docker Wrappers + +This directory contains transparent wrapper scripts for `clang-format`, `clang-tidy`, and `run-clang-tidy` that execute in Docker while providing seamless filesystem access. + +## Overview + +The wrapper scripts behave identically to native binaries but run in a minimal Alpine Linux container with LLVM 17.0.6 tools. This ensures consistent tooling across different development environments without requiring local installation. + +## Building the Docker Image + +```bash +./build_local.sh +``` + +This builds the `datadog/dd-appsec-php-ci:clang-tools` image (~108MB) with statically-linked binaries. + +## Usage + +### Adding to PATH + +Add this directory to your PATH to use the wrappers system-wide: + +```bash +export PATH="/path/to/dd-trace-php/appsec/cmake/clang-tools:$PATH" +``` + +### clang-format + +```bash +# Format code from stdin +echo "int main(){return 0;}" | ./clang-format + +# Format a file +./clang-format myfile.cpp + +# Format in-place +./clang-format -i myfile.cpp + +# Check formatting +./clang-format --dry-run --Werror myfile.cpp +``` + +### clang-tidy + +```bash +# Run clang-tidy on a file +./clang-tidy myfile.cpp -- -Iinclude + +# With compilation database +./clang-tidy -p build myfile.cpp +``` + +### run-clang-tidy + +```bash +# Run on all files in compilation database +./run-clang-tidy -p build + +# Apply fixes automatically +./run-clang-tidy -p build -fix + +# Run on specific files matching regex +./run-clang-tidy -p build '.*\.cpp$' +``` + +## How It Works + +### Filesystem Sharing + +- Mounts the **git repository root** at `/workspace` in the container +- Preserves the current working directory relative to the git root +- Automatically translates absolute paths to container paths +- Runs with your user ID to maintain file ownership + +### Path Translation + +The wrappers intelligently handle paths: +- **Relative paths**: Pass through unchanged +- **Absolute paths within git repo**: Translated to `/workspace/...` +- **Absolute paths outside git repo**: Converted to relative when possible +- **Flags and options**: Pass through unchanged + +### stdin/stdout Support + +- Detects piped input and enables interactive mode (`-i` flag) when needed +- Preserves stdin for formatting code from pipes +- stdout and stderr pass through transparently + +## Example Workflows + +### IDE Integration + +Configure your IDE to use these wrappers as the clang-format/clang-tidy executables. They'll work transparently with features like "format on save" or inline diagnostics. + +### CI/CD + +```bash +# Check all C++ files are formatted +find . -name "*.cpp" -o -name "*.h" | xargs ./clang-format --dry-run --Werror + +# Run clang-tidy with fixes +./run-clang-tidy -p build -fix -quiet +``` + +### Git Pre-commit Hook + +```bash +#!/bin/bash +# Format staged C++ files +git diff --cached --name-only --diff-filter=ACM | \ + grep -E '\.(cpp|h)$' | \ + xargs ./clang-format -i +``` + +## Technical Details + +**Docker Image**: `datadog/dd-appsec-php-ci:clang-tools` +- Base: Alpine Linux 3.21 +- LLVM Version: 17.0.6 +- Size: ~108MB (statically-linked binaries) +- Tools: clang-format, clang-tidy, clang-apply-replacements, run-clang-tidy + +**User Mapping**: Runs as `-u $(id -u):$(id -g)` to preserve file ownership + +**Volume Mount**: `-v $GIT_ROOT:/workspace` for full repository access + +## Troubleshooting + +### "Docker image not found" + +Build the image first: +```bash +./build_local.sh +``` + +### "Permission denied" + +Ensure scripts are executable: +```bash +chmod +x clang-format clang-tidy run-clang-tidy +``` + +### Paths not resolving correctly + +The scripts automatically find the git repository root. If you're outside a git repo, they fall back to `$PWD`. diff --git a/appsec/cmake/clang-tools/build_local.sh b/appsec/cmake/clang-tools/build_local.sh new file mode 100755 index 00000000000..e972163df59 --- /dev/null +++ b/appsec/cmake/clang-tools/build_local.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +docker buildx build -t datadog/dd-appsec-php-ci:clang-tools . diff --git a/appsec/cmake/clang-tools/clang-format b/appsec/cmake/clang-tools/clang-format new file mode 100755 index 00000000000..754585c0d9b --- /dev/null +++ b/appsec/cmake/clang-tools/clang-format @@ -0,0 +1,20 @@ +#!/bin/bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/common.sh" + +if [ -t 0 ]; then + docker run --rm \ + -v "$GIT_ROOT:/workspace" \ + -w "/workspace/$REL_WORK_DIR" \ + -u "$(id -u):$(id -g)" \ + "$DOCKER_IMAGE" \ + clang-format ${ARGS[@]+"${ARGS[@]}"} +else + docker run --rm -i \ + -v "$GIT_ROOT:/workspace" \ + -w "/workspace/$REL_WORK_DIR" \ + -u "$(id -u):$(id -g)" \ + "$DOCKER_IMAGE" \ + clang-format ${ARGS[@]+"${ARGS[@]}"} +fi diff --git a/appsec/cmake/clang-tools/clang-tidy b/appsec/cmake/clang-tools/clang-tidy new file mode 100755 index 00000000000..b4b8fab0782 --- /dev/null +++ b/appsec/cmake/clang-tools/clang-tidy @@ -0,0 +1,20 @@ +#!/bin/bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/common.sh" + +if [ -t 0 ]; then + docker run --rm \ + -v "$GIT_ROOT:/workspace" \ + -w "/workspace/$REL_WORK_DIR" \ + -u "$(id -u):$(id -g)" \ + "$DOCKER_IMAGE" \ + clang-tidy ${ARGS[@]+"${ARGS[@]}"} +else + docker run --rm -i \ + -v "$GIT_ROOT:/workspace" \ + -w "/workspace/$REL_WORK_DIR" \ + -u "$(id -u):$(id -g)" \ + "$DOCKER_IMAGE" \ + clang-tidy ${ARGS[@]+"${ARGS[@]}"} +fi diff --git a/appsec/cmake/clang-tools/common.sh b/appsec/cmake/clang-tools/common.sh new file mode 100755 index 00000000000..86a1ed0bed0 --- /dev/null +++ b/appsec/cmake/clang-tools/common.sh @@ -0,0 +1,31 @@ +#!/bin/bash +set -euo pipefail + +DOCKER_IMAGE="datadog/dd-appsec-php-ci:clang-tools" + +GIT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || echo "$PWD") + +WORK_DIR=$(pwd) +REL_WORK_DIR="${WORK_DIR#$GIT_ROOT}" +REL_WORK_DIR="${REL_WORK_DIR#/}" +if [ -z "$REL_WORK_DIR" ]; then + REL_WORK_DIR="." +fi + +ARGS=() +for arg in "$@"; do + if [[ "$arg" == "$GIT_ROOT"* ]]; then + REL_PATH="${arg#$GIT_ROOT}" + REL_PATH="${REL_PATH#/}" + ARGS+=("/workspace/$REL_PATH") + elif [[ "$arg" == /* ]] && [[ -e "$arg" ]]; then + if command -v realpath &> /dev/null; then + REAL_PATH=$(realpath --relative-to="$WORK_DIR" "$arg" 2>/dev/null || echo "$arg") + ARGS+=("$REAL_PATH") + else + ARGS+=("$arg") + fi + else + ARGS+=("$arg") + fi +done diff --git a/appsec/src/extension/json_truncated_parser.cpp b/appsec/src/extension/json_truncated_parser.cpp index 438ab51c3ac..a01331a367c 100644 --- a/appsec/src/extension/json_truncated_parser.cpp +++ b/appsec/src/extension/json_truncated_parser.cpp @@ -90,8 +90,8 @@ class TruncatedJsonInputStream { public: using Ch = char; - // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) TruncatedJsonInputStream( + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) const char *data, std::size_t length, std::size_t size_limit = SIZE_MAX) : data_{data}, length_{length}, size_limit_{size_limit} {} @@ -189,8 +189,9 @@ class ToZvalHandler { return AddValue(val); } - // NOLINTNEXTLINE(readability-convert-member-functions-to-static,readability-named-parameter) + // NOLINTNEXTLINE(readability-convert-member-functions-to-static) auto RawNumber( + // NOLINTNEXTLINE(readability-named-parameter) const TruncatedJsonInputStream::Ch *, rapidjson::SizeType, bool) -> bool { assert("RawNumber should not be called (requires flags we are not " diff --git a/appsec/src/helper/network/.clang-tidy b/appsec/src/helper/network/.clang-tidy new file mode 100644 index 00000000000..ff5d51e01d9 --- /dev/null +++ b/appsec/src/helper/network/.clang-tidy @@ -0,0 +1,2 @@ +Checks: '-clang-analyzer-cplusplus.NewDelete,-clang-analyzer-unix.Malloc' +InheritParentConfig: true diff --git a/appsec/third_party/CMakeLists.txt b/appsec/third_party/CMakeLists.txt index d4efc69a81a..f09cfab64cf 100644 --- a/appsec/third_party/CMakeLists.txt +++ b/appsec/third_party/CMakeLists.txt @@ -35,7 +35,7 @@ if(DD_APPSEC_BUILD_HELPER) file(GLOB_RECURSE MSGPACK_C_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/msgpack-c/src/*.c) add_library(msgpack_c STATIC ${MSGPACK_C_SOURCES}) set_target_properties(msgpack_c PROPERTIES POSITION_INDEPENDENT_CODE 1) - target_include_directories(msgpack_c PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/msgpack-c/include/) + target_include_directories(msgpack_c SYSTEM PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/msgpack-c/include/) target_compile_definitions(msgpack_c INTERFACE MSGPACK_CXX17=ON) file(GLOB_RECURSE CPPBASE64_C_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cpp-base64/base64.cpp)