From a48856822c0853508c2d096cf6e28f2fa1612571 Mon Sep 17 00:00:00 2001 From: Eduardo Arias Date: Tue, 23 Apr 2024 11:46:29 -0300 Subject: [PATCH 01/20] Updated included headers to support compilation on Windows (using Visual C++) - most of posix related functions and constants in unistd.h can be found in io.h in Visual C++ - introduced src/compat/msvc.h to adjust for compiler differences (and avoid updating code with #ifdef blocks for Windows support) - removed some included headers that are not needed (both on Unix and Windows builds) --- examples/multiprocess_c/multi.c | 4 ++++ .../using_bodies_in_chunks/simple_request.cc | 4 ++++ .../transformations/html_entity_decode.cc | 4 ++++ src/actions/transformations/lower_case.cc | 1 + src/actions/transformations/upper_case.cc | 1 + src/audit_log/writer/parallel.cc | 5 +++++ src/audit_log/writer/writer.h | 2 ++ .../backend/in_memory-per_process.h | 1 + src/collection/backend/lmdb.cc | 4 ++++ src/compat/msvc.h | 22 +++++++++++++++++++ src/debug_log/debug_log.cc | 4 ---- src/operators/inspect_file.cc | 4 ++++ src/operators/rbl.cc | 5 +++++ src/operators/rbl.h | 4 ++++ src/parser/seclang-scanner.cc | 4 ++++ src/rules_set_properties.cc | 4 ++++ src/transaction.cc | 3 +++ src/utils/geo_lookup.cc | 5 ++++- src/utils/ip_tree.cc | 4 ++++ src/utils/msc_tree.cc | 6 +++++ src/utils/string.cc | 5 ----- src/utils/system.h | 2 -- src/variables/time.cc | 4 ++++ src/variables/time_day.cc | 4 ++++ src/variables/time_hour.cc | 4 ++++ src/variables/time_min.cc | 4 ++++ src/variables/time_mon.cc | 4 ++++ src/variables/time_sec.cc | 4 ++++ src/variables/time_wday.cc | 4 ++++ src/variables/time_year.cc | 4 ++++ test/fuzzer/afl_fuzzer.cc | 4 ++++ test/regression/regression.cc | 4 ++++ tools/rules-check/rules-check.cc | 4 ++++ 33 files changed, 130 insertions(+), 12 deletions(-) create mode 100644 src/compat/msvc.h diff --git a/examples/multiprocess_c/multi.c b/examples/multiprocess_c/multi.c index 6c2ae52182..2481db4e56 100644 --- a/examples/multiprocess_c/multi.c +++ b/examples/multiprocess_c/multi.c @@ -19,7 +19,11 @@ #include #include #include +#ifndef WIN32 #include +#else +#include +#endif #include #include #include diff --git a/examples/using_bodies_in_chunks/simple_request.cc b/examples/using_bodies_in_chunks/simple_request.cc index ec8795fe8f..783e639b2d 100644 --- a/examples/using_bodies_in_chunks/simple_request.cc +++ b/examples/using_bodies_in_chunks/simple_request.cc @@ -13,7 +13,11 @@ * */ +#ifndef WIN32 #include +#else +#include +#endif #include #include diff --git a/src/actions/transformations/html_entity_decode.cc b/src/actions/transformations/html_entity_decode.cc index b9268df55c..6a68324564 100644 --- a/src/actions/transformations/html_entity_decode.cc +++ b/src/actions/transformations/html_entity_decode.cc @@ -27,6 +27,10 @@ #include "modsecurity/transaction.h" #include "src/actions/transformations/transformation.h" +#ifdef WIN32 +#include "src/compat/msvc.h" +#endif + namespace modsecurity { namespace actions { diff --git a/src/actions/transformations/lower_case.cc b/src/actions/transformations/lower_case.cc index d00ab40cb6..7bca947972 100644 --- a/src/actions/transformations/lower_case.cc +++ b/src/actions/transformations/lower_case.cc @@ -17,6 +17,7 @@ #include #include +#include #include "modsecurity/transaction.h" #include "src/actions/transformations/transformation.h" diff --git a/src/actions/transformations/upper_case.cc b/src/actions/transformations/upper_case.cc index 118696ad43..998ec725ea 100644 --- a/src/actions/transformations/upper_case.cc +++ b/src/actions/transformations/upper_case.cc @@ -17,6 +17,7 @@ #include #include +#include #include "modsecurity/transaction.h" #include "src/actions/transformations/transformation.h" diff --git a/src/audit_log/writer/parallel.cc b/src/audit_log/writer/parallel.cc index e42d7871cb..d23e7958ae 100644 --- a/src/audit_log/writer/parallel.cc +++ b/src/audit_log/writer/parallel.cc @@ -21,7 +21,12 @@ #include #include #include +#ifndef WIN32 #include +#else +#include +#include "src/compat/msvc.h" +#endif #include #include diff --git a/src/audit_log/writer/writer.h b/src/audit_log/writer/writer.h index 093f271c17..56dada0622 100644 --- a/src/audit_log/writer/writer.h +++ b/src/audit_log/writer/writer.h @@ -18,8 +18,10 @@ #include +#ifndef WIN32 #include #include +#endif #include #include diff --git a/src/collection/backend/in_memory-per_process.h b/src/collection/backend/in_memory-per_process.h index 43b8de24dc..3cacdca0d2 100644 --- a/src/collection/backend/in_memory-per_process.h +++ b/src/collection/backend/in_memory-per_process.h @@ -12,6 +12,7 @@ * directly using the email address security@modsecurity.org. * */ +#include #ifdef __cplusplus diff --git a/src/collection/backend/lmdb.cc b/src/collection/backend/lmdb.cc index 040ebdff10..85c68e3cdd 100644 --- a/src/collection/backend/lmdb.cc +++ b/src/collection/backend/lmdb.cc @@ -18,7 +18,11 @@ #include "src/collection/backend/collection_data.h" #include +#ifndef WIN32 #include +#else +#include +#endif #include #include diff --git a/src/compat/msvc.h b/src/compat/msvc.h new file mode 100644 index 0000000000..ce9a14c0a4 --- /dev/null +++ b/src/compat/msvc.h @@ -0,0 +1,22 @@ +#ifndef __COMPAT_MSVC +#define __COMPAT_MSVC + +#include + +#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG) +#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +#endif + +#define strcasecmp _stricmp +#define strncasecmp _strnicmp +#define strtok_r strtok_s +#define popen _popen +#define pclose _pclose + +inline tm* localtime_r(const time_t* tin, tm* tout) { + if (!localtime_s(tout, tin)) return tout; + + return nullptr; +} + +#endif diff --git a/src/debug_log/debug_log.cc b/src/debug_log/debug_log.cc index 54d86bda0c..e883177ca4 100644 --- a/src/debug_log/debug_log.cc +++ b/src/debug_log/debug_log.cc @@ -15,10 +15,6 @@ #include "modsecurity/debug_log.h" -#include - -#include - #include "src/debug_log/debug_log_writer.h" #include "src/debug_log_writer_agent.h" diff --git a/src/operators/inspect_file.cc b/src/operators/inspect_file.cc index 9ce43af0d8..72052a32da 100644 --- a/src/operators/inspect_file.cc +++ b/src/operators/inspect_file.cc @@ -23,6 +23,10 @@ #include "src/operators/operator.h" #include "src/utils/system.h" +#ifdef WIN32 +#include "src/compat/msvc.h" +#endif + namespace modsecurity { namespace operators { diff --git a/src/operators/rbl.cc b/src/operators/rbl.cc index ffdb17a288..eb7c3f0b44 100644 --- a/src/operators/rbl.cc +++ b/src/operators/rbl.cc @@ -16,10 +16,15 @@ #include "src/operators/rbl.h" #include +#ifndef WIN32 #include #include #include #include +#else +#include +#include +#endif #include diff --git a/src/operators/rbl.h b/src/operators/rbl.h index fb57d2d960..7f70b5997a 100644 --- a/src/operators/rbl.h +++ b/src/operators/rbl.h @@ -17,10 +17,14 @@ #define SRC_OPERATORS_RBL_H_ #include +#ifndef WIN32 #include #include #include #include +#else +#include +#endif #include #include diff --git a/src/parser/seclang-scanner.cc b/src/parser/seclang-scanner.cc index c0d4d7661a..92afab762b 100644 --- a/src/parser/seclang-scanner.cc +++ b/src/parser/seclang-scanner.cc @@ -4952,7 +4952,11 @@ static std::stack YY_PREVIOUS_STATE; * The user has a chance to override it with an option. */ /* %if-c-only */ +#ifndef WIN32 #include +#else +#include +#endif /* %endif */ /* %if-c++-only */ /* %endif */ diff --git a/src/rules_set_properties.cc b/src/rules_set_properties.cc index 075e3e8770..80078b3dd2 100644 --- a/src/rules_set_properties.cc +++ b/src/rules_set_properties.cc @@ -19,6 +19,10 @@ #include "src/utils/string.h" #include "src/variables/variable.h" +#ifdef WIN32 +#include "src/compat/msvc.h" +#endif + namespace modsecurity { diff --git a/src/transaction.cc b/src/transaction.cc index cf52288d9b..a076212571 100644 --- a/src/transaction.cc +++ b/src/transaction.cc @@ -53,6 +53,9 @@ #include "src/actions/disruptive/allow.h" #include "src/variables/remote_user.h" +#ifdef WIN32 +#include "src/compat/msvc.h" +#endif using modsecurity::actions::Action; diff --git a/src/utils/geo_lookup.cc b/src/utils/geo_lookup.cc index 33a80be5cb..e76e728dc9 100644 --- a/src/utils/geo_lookup.cc +++ b/src/utils/geo_lookup.cc @@ -13,10 +13,13 @@ * */ +#ifndef WIN32 #include -#include #include #include +#else +#include +#endif #include #include diff --git a/src/utils/ip_tree.cc b/src/utils/ip_tree.cc index 969213a55f..c4c38f1cb5 100644 --- a/src/utils/ip_tree.cc +++ b/src/utils/ip_tree.cc @@ -15,10 +15,14 @@ #include "src/utils/ip_tree.h" +#ifndef WIN32 #include #include #include #include +#else +#include +#endif #include #include diff --git a/src/utils/msc_tree.cc b/src/utils/msc_tree.cc index 8e36f274ef..c2850750ec 100644 --- a/src/utils/msc_tree.cc +++ b/src/utils/msc_tree.cc @@ -15,9 +15,15 @@ #include #include #include +#ifndef WIN32 #include #include #include +#else +#include "src/compat/msvc.h" +#include +#include +#endif #include "src/utils/msc_tree.h" diff --git a/src/utils/string.cc b/src/utils/string.cc index 750d2bd1c8..8d9e08ff48 100644 --- a/src/utils/string.cc +++ b/src/utils/string.cc @@ -17,11 +17,6 @@ #include #include #include -#ifdef __OpenBSD__ -#include -#else -#include -#endif #include #include diff --git a/src/utils/system.h b/src/utils/system.h index b3033b44e9..d6b0adf631 100644 --- a/src/utils/system.h +++ b/src/utils/system.h @@ -13,8 +13,6 @@ * */ -#include -#include #include #include diff --git a/src/variables/time.cc b/src/variables/time.cc index ae071b496f..7d481ee6b0 100644 --- a/src/variables/time.cc +++ b/src/variables/time.cc @@ -30,6 +30,10 @@ #include "modsecurity/transaction.h" +#ifdef WIN32 +#include "src/compat/msvc.h" +#endif + namespace modsecurity { namespace variables { diff --git a/src/variables/time_day.cc b/src/variables/time_day.cc index f74a8409be..f094d4c922 100644 --- a/src/variables/time_day.cc +++ b/src/variables/time_day.cc @@ -30,6 +30,10 @@ #include "modsecurity/transaction.h" +#ifdef WIN32 +#include "src/compat/msvc.h" +#endif + namespace modsecurity { namespace variables { diff --git a/src/variables/time_hour.cc b/src/variables/time_hour.cc index 0f2a421931..ab809ead00 100644 --- a/src/variables/time_hour.cc +++ b/src/variables/time_hour.cc @@ -30,6 +30,10 @@ #include "modsecurity/transaction.h" +#ifdef WIN32 +#include "src/compat/msvc.h" +#endif + namespace modsecurity { namespace variables { diff --git a/src/variables/time_min.cc b/src/variables/time_min.cc index 526d8e6f61..9d03be4de1 100644 --- a/src/variables/time_min.cc +++ b/src/variables/time_min.cc @@ -30,6 +30,10 @@ #include "modsecurity/transaction.h" +#ifdef WIN32 +#include "src/compat/msvc.h" +#endif + namespace modsecurity { namespace variables { diff --git a/src/variables/time_mon.cc b/src/variables/time_mon.cc index 53ba291904..17e74f3114 100644 --- a/src/variables/time_mon.cc +++ b/src/variables/time_mon.cc @@ -30,6 +30,10 @@ #include "modsecurity/transaction.h" +#ifdef WIN32 +#include "src/compat/msvc.h" +#endif + namespace modsecurity { namespace variables { diff --git a/src/variables/time_sec.cc b/src/variables/time_sec.cc index 20d3be7a93..5e39af7fe1 100644 --- a/src/variables/time_sec.cc +++ b/src/variables/time_sec.cc @@ -30,6 +30,10 @@ #include "modsecurity/transaction.h" +#ifdef WIN32 +#include "src/compat/msvc.h" +#endif + namespace modsecurity { namespace variables { diff --git a/src/variables/time_wday.cc b/src/variables/time_wday.cc index ff9825c189..fd6a027846 100644 --- a/src/variables/time_wday.cc +++ b/src/variables/time_wday.cc @@ -30,6 +30,10 @@ #include "modsecurity/transaction.h" +#ifdef WIN32 +#include "src/compat/msvc.h" +#endif + namespace modsecurity { namespace variables { diff --git a/src/variables/time_year.cc b/src/variables/time_year.cc index a465612543..f68e8cd622 100644 --- a/src/variables/time_year.cc +++ b/src/variables/time_year.cc @@ -30,6 +30,10 @@ #include "modsecurity/transaction.h" +#ifdef WIN32 +#include "src/compat/msvc.h" +#endif + namespace modsecurity { namespace variables { diff --git a/test/fuzzer/afl_fuzzer.cc b/test/fuzzer/afl_fuzzer.cc index 6b19a18f09..21bfa1bfd7 100644 --- a/test/fuzzer/afl_fuzzer.cc +++ b/test/fuzzer/afl_fuzzer.cc @@ -114,7 +114,11 @@ using namespace modsecurity; #include #include +#ifndef WIN32 #include +#else +#include +#endif #include #include diff --git a/test/regression/regression.cc b/test/regression/regression.cc index 6343c9e1c3..a5c4832dea 100644 --- a/test/regression/regression.cc +++ b/test/regression/regression.cc @@ -15,7 +15,11 @@ #include +#ifndef WIN32 #include +#else +#include +#endif #include #include diff --git a/tools/rules-check/rules-check.cc b/tools/rules-check/rules-check.cc index f59439ee9f..46c8e1a87b 100644 --- a/tools/rules-check/rules-check.cc +++ b/tools/rules-check/rules-check.cc @@ -15,7 +15,11 @@ #include #include +#ifndef WIN32 #include +#else +#include +#endif #include #include From 942c8ba606bc2f1cd9e578dced3c282552a4d038 Mon Sep 17 00:00:00 2001 From: Eduardo Arias Date: Tue, 23 Apr 2024 12:23:53 -0300 Subject: [PATCH 02/20] Replaced usage of usleep (not available in Visual C++) with C++11's std::this_thread::sleep_for & std::chrono::microseconds. - disabled build error from warning C4716 because process_request does not return a value and Visual C++ doesn't support [[noreturn]] --- .../reading_logs_via_rule_message.h | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/examples/reading_logs_via_rule_message/reading_logs_via_rule_message.h b/examples/reading_logs_via_rule_message/reading_logs_via_rule_message.h index 9d80d9b1d2..6b2800482c 100644 --- a/examples/reading_logs_via_rule_message/reading_logs_via_rule_message.h +++ b/examples/reading_logs_via_rule_message/reading_logs_via_rule_message.h @@ -13,10 +13,11 @@ * */ -#include - #include #include +#include +#include +#include #define NUM_THREADS 100 @@ -72,6 +73,11 @@ struct data_ms { modsecurity::RulesSet *rules; }; +#if defined _MSC_VER +#pragma warning(push) +#pragma warning(disable:4716) // avoid error C4716: 'process_request': must return a value, as MSVC C++ compiler doesn't support [[noreturn]] +#pragma warning(disable:4715) // avoid warning c4715: 'process_request' : not all control paths return a value +#endif [[noreturn]] static void *process_request(void *data) { struct data_ms *a = (struct data_ms *)data; @@ -85,7 +91,7 @@ struct data_ms { modsecTransaction->processConnection(ip, 12345, "127.0.0.1", 80); modsecTransaction->processURI(request_uri, "GET", "1.1"); - usleep(10); + std::this_thread::sleep_for(std::chrono::microseconds(10)); modsecTransaction->addRequestHeader("Host", "net.tutsplus.com"); modsecTransaction->processRequestHeaders(); @@ -105,6 +111,9 @@ struct data_ms { pthread_exit(nullptr); } +#if defined _MSC_VER +#pragma warning(pop) +#endif class ReadingLogsViaRuleMessage { public: @@ -151,7 +160,7 @@ class ReadingLogsViaRuleMessage { reinterpret_cast(&dms)); } - usleep(10000); + std::this_thread::sleep_for(std::chrono::microseconds(10000)); for (i=0; i < NUM_THREADS; i++) { pthread_join(threads[i], &status); From abbd7b2f4255d61aaf562600344256855c43fdbd Mon Sep 17 00:00:00 2001 From: Eduardo Arias Date: Tue, 23 Apr 2024 12:40:47 -0300 Subject: [PATCH 03/20] Replaced usage of apr_snprintf with snprintf (already in Windows exclusive code block) - updated included headers to support compilation on Windows (using Visual C++) --- src/unique_id.cc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/unique_id.cc b/src/unique_id.cc index a78ebd751b..c6234112cc 100644 --- a/src/unique_id.cc +++ b/src/unique_id.cc @@ -17,6 +17,7 @@ #include "src/config.h" #ifdef WIN32 +#include "src/compat/msvc.h" #include #include #endif @@ -48,7 +49,11 @@ #endif #include +#ifndef WIN32 #include +#else +#include +#endif #include #include "src/utils/sha1.h" @@ -207,7 +212,7 @@ std::string UniqueId::ethernetMacAddress() { pAdapter = pAdapterInfo; while (pAdapter && !mac[0] && !mac[1] && !mac[2]) { if (pAdapter->AddressLength > 4) { - apr_snprintf(mac, MAC_ADDRESS_SIZE, "%02x:%02x:%02x:%02x:%02x:%02x", + snprintf(mac, MAC_ADDRESS_SIZE, "%02x:%02x:%02x:%02x:%02x:%02x", (unsigned char)pAdapter->Address[0], (unsigned char)pAdapter->Address[1], (unsigned char)pAdapter->Address[2], From 35949179a40d32d46fac681d6c5adfdcb7e3a59d Mon Sep 17 00:00:00 2001 From: Eduardo Arias Date: Tue, 23 Apr 2024 12:43:22 -0300 Subject: [PATCH 04/20] setenv is not available in Windows build, replaced with _putenv_s --- src/actions/set_env.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/actions/set_env.cc b/src/actions/set_env.cc index e1c3afdba3..2929065313 100644 --- a/src/actions/set_env.cc +++ b/src/actions/set_env.cc @@ -37,7 +37,11 @@ bool SetENV::evaluate(RuleWithActions *rule, Transaction *t) { auto pair = utils::string::ssplit_pair(colNameExpanded, '='); ms_dbg_a(t, 8, "Setting environment variable: " + pair.first + " to " + pair.second); +#ifndef WIN32 setenv(pair.first.c_str(), pair.second.c_str(), /*overwrite*/ 1); +#else + _putenv_s(pair.first.c_str(), pair.second.c_str()); +#endif return true; } From 373633ffe2ae754973ce8b34373b9ee16bd359b8 Mon Sep 17 00:00:00 2001 From: Eduardo Arias Date: Wed, 24 Apr 2024 11:01:32 -0300 Subject: [PATCH 05/20] mkstemp is not available in Windows build, replaced with _mktemp_s plus _open. - Updated included headers to support compilation on Windows (using Visual C++) - Minor change to use C++ default (zero) initialization instead of calling memset. --- src/request_body_processor/multipart.cc | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/request_body_processor/multipart.cc b/src/request_body_processor/multipart.cc index 691a6fd3fb..d9ed6c6d15 100644 --- a/src/request_body_processor/multipart.cc +++ b/src/request_body_processor/multipart.cc @@ -21,7 +21,12 @@ #include #include #include +#ifndef WIN32 #include +#else +#include +#include "src/compat/msvc.h" +#endif #include #include #include @@ -61,12 +66,11 @@ MultipartPartTmpFile::~MultipartPartTmpFile() { void MultipartPartTmpFile::Open() { struct tm timeinfo; - char tstr[300]; time_t tt = time(NULL); localtime_r(&tt, &timeinfo); - memset(tstr, '\0', 300); + char tstr[300] {}; strftime(tstr, 299, "/%Y%m%d-%H%M%S", &timeinfo); std::string path = m_transaction->m_rules->m_uploadDirectory.m_value; @@ -74,14 +78,23 @@ void MultipartPartTmpFile::Open() { path += "-file-XXXXXX"; char* tmp = strdup(path.c_str()); +#ifndef WIN32 m_tmp_file_fd = mkstemp(tmp); +#else + _mktemp_s(tmp, path.length()+1); + m_tmp_file_fd = _open(tmp, _O_CREAT | _O_EXCL | _O_RDWR); +#endif m_tmp_file_name.assign(tmp); free(tmp); ms_dbg_a(m_transaction, 4, "MultipartPartTmpFile: Create filename= " + m_tmp_file_name); int mode = m_transaction->m_rules->m_uploadFileMode.m_value; if ((m_tmp_file_fd != -1) && (mode != 0)) { +#ifndef WIN32 if (fchmod(m_tmp_file_fd, mode) == -1) { +#else + if (_chmod(m_tmp_file_name.c_str(), mode) == -1) { +#endif m_tmp_file_fd = -1; } } From 91a736692a6d2c921d569fd0775501bf649cfb2d Mon Sep 17 00:00:00 2001 From: Eduardo Arias Date: Tue, 23 Apr 2024 12:57:28 -0300 Subject: [PATCH 06/20] Minor changes to debug_log_writer - Removed unused m_first data member. - Explicitly delete copy constructor and assignment operator. - Removed unused included headers. --- src/debug_log/debug_log_writer.cc | 12 ------------ src/debug_log/debug_log_writer.h | 16 +++------------- 2 files changed, 3 insertions(+), 25 deletions(-) diff --git a/src/debug_log/debug_log_writer.cc b/src/debug_log/debug_log_writer.cc index 86facfed3b..b7bb1ff265 100644 --- a/src/debug_log/debug_log_writer.cc +++ b/src/debug_log/debug_log_writer.cc @@ -15,18 +15,6 @@ #include "src/debug_log/debug_log_writer.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - #include "src/utils/shared_files.h" namespace modsecurity { diff --git a/src/debug_log/debug_log_writer.h b/src/debug_log/debug_log_writer.h index 1f4f43df53..698ff35874 100644 --- a/src/debug_log/debug_log_writer.h +++ b/src/debug_log/debug_log_writer.h @@ -13,15 +13,7 @@ * */ -#include -#include -#include -#include - -#include -#include #include -#include #ifndef SRC_DEBUG_LOG_DEBUG_LOG_WRITER_H_ @@ -45,7 +37,7 @@ class DebugLogWriter { static int open(const std::string& m_fileName, std::string *error); private: - DebugLogWriter() : m_first(NULL) { } + DebugLogWriter() { } ~DebugLogWriter() { } // C++ 03 @@ -53,10 +45,8 @@ class DebugLogWriter { // Dont forget to declare these two. You want to make sure they // are unacceptable otherwise you may accidentally get copies of // your singleton appearing. - DebugLogWriter(DebugLogWriter const&); - void operator=(DebugLogWriter const&); - - struct debug_log_file_handler *m_first; + DebugLogWriter(DebugLogWriter const&) = delete; + void operator=(DebugLogWriter const&) = delete; }; From ebf1f8fd28251fd6bd205e510421c46d2eee9269 Mon Sep 17 00:00:00 2001 From: Eduardo Arias Date: Tue, 23 Apr 2024 13:00:19 -0300 Subject: [PATCH 07/20] On Windows use the operating system's native CA store for certificate verification of https requests. - Updated included headers to support compilation on Windows (using Visual C++) --- src/utils/https_client.cc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/utils/https_client.cc b/src/utils/https_client.cc index 9ee84b1540..66e818d4c3 100644 --- a/src/utils/https_client.cc +++ b/src/utils/https_client.cc @@ -20,10 +20,14 @@ #include #endif +#ifndef WIN32 #include #include #include #include +#else +#include +#endif #include #include @@ -94,6 +98,11 @@ bool HttpsClient::download(const std::string &uri) { curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1); +#ifdef WIN32 + /* use the operating system's native CA store for certificate verification.*/ + curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, (long)CURLSSLOPT_NATIVE_CA); +#endif + /* send all data to this function */ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &HttpsClient::handle); From 10c6ee726f63cddbd71cd9c071876a7b1fbbfb07 Mon Sep 17 00:00:00 2001 From: Eduardo Arias Date: Tue, 23 Apr 2024 14:55:39 -0300 Subject: [PATCH 08/20] Added support for expandEnv, createDir & cpu_seconds on Windows - expandEnv on Windows uses POCO C++ Libraries implementation of Glob - Paths of matched files are adjusted to preserve UNIX path separators for consistency with the rest of the code. - Minor change to code shared with other platforms that removes allocation of std::ifstream on the heap to check whether the file can be opened, which can be done with local stack variable that closes the file when out of scope. - createDir uses _mkdir on Windows, which doesn't support configuring the new directory's mode. - added public domain implementation of clock_gettime for clock_id CLOCK_PROCESS_CPUTIME_ID from mingw-w64's winpthreads to support cpu_seconds on Windows. - Updated included headers to support compilation on Windows (using Visual C++) --- src/utils/system.cc | 62 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/src/utils/system.cc b/src/utils/system.cc index 69b904042b..ef4e506031 100644 --- a/src/utils/system.cc +++ b/src/utils/system.cc @@ -19,6 +19,9 @@ #include #ifdef __OpenBSD__ #include +#elif defined(WIN32) +#include "Poco/Glob.h" +#include #else #include #endif @@ -31,6 +34,7 @@ #include #if defined _MSC_VER +#include "src/compat/msvc.h" #include #elif defined __GNUC__ #include @@ -40,6 +44,36 @@ #include "src/utils/system.h" #include "src/config.h" +#ifdef WIN32 + +// Public domain code from mingw-w64's winpthreads +// https://sourceforge.net/p/mingw-w64/code/HEAD/tree/trunk/mingw-w64-libraries/winpthreads/src/clock.c +// + +#define CLOCK_PROCESS_CPUTIME_ID 2 +#define POW10_7 10000000 + +// NOTE: includes only CLOCK_PROCESS_CPUTIME_ID implementation, ignores clock_id argument +static int clock_gettime(int clock_id, struct timespec *tp) +{ + unsigned __int64 t; + LARGE_INTEGER pf, pc; + union { + unsigned __int64 u64; + FILETIME ft; + } ct, et, kt, ut; + + if(0 == GetProcessTimes(GetCurrentProcess(), &ct.ft, &et.ft, &kt.ft, &ut.ft)) + return -1; + t = kt.u64 + ut.u64; + tp->tv_sec = t / POW10_7; + tp->tv_nsec = ((int) (t % POW10_7)) * 100; + + return 0; +} + +#endif + namespace modsecurity { namespace utils { @@ -122,8 +156,16 @@ std::string get_path(const std::string& file) { std::list expandEnv(const std::string& var, int flags) { std::list vars; - -#ifdef __OpenBSD__ +#ifdef WIN32 + // NOTE: align scopes with if & if in other versions + { + { + std::set files; + Poco::Glob::glob(var, files); + for(auto file : files) { + std::replace(file.begin(), file.end(), '\\', '/'); // preserve unix-like paths + const char* exp[] = { file.c_str() }; +#elif defined(__OpenBSD__) glob_t p; if (glob(var.c_str(), flags, NULL, &p) == false) { if (p.gl_pathc) { @@ -135,15 +177,13 @@ std::list expandEnv(const std::string& var, int flags) { if (p.we_wordc) { for (char** exp = p.we_wordv; *exp; ++exp) { #endif - std::ifstream *iss = new std::ifstream(exp[0], std::ios::in); - if (iss->is_open()) { - iss->close(); + auto iss = std::ifstream(exp[0], std::ios::in); + if (iss.is_open()) vars.push_back(exp[0]); - } - delete iss; } } -#ifdef __OpenBSD__ +#ifdef WIN32 +#elif defined(__OpenBSD__) globfree(&p); #else wordfree(&p); @@ -153,7 +193,13 @@ std::list expandEnv(const std::string& var, int flags) { } bool createDir(const std::string& dir, int mode, std::string *error) { +#ifndef WIN32 int ret = mkdir(dir.data(), mode); +#else + if (dir == ".") + return true; + int ret = _mkdir(dir.c_str()); +#endif if (ret != 0 && errno != EEXIST) { error->assign("Not able to create directory: " + dir + ": " \ + strerror(errno) + "."); From fef419f9869bd5b160683011f284fde585e6667f Mon Sep 17 00:00:00 2001 From: Eduardo Arias Date: Tue, 23 Apr 2024 16:22:58 -0300 Subject: [PATCH 09/20] Minor changes related to std::shared_ptr usage in RuleWithActions - RuleWithActions::evaluate(Transaction *transaction) - Removed temporary rm local variable used to immediately create std::shared_ptr. - Leverage std::make_shared & auto to simplify code. --- src/rule_with_actions.cc | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/rule_with_actions.cc b/src/rule_with_actions.cc index df323c4bbe..5ae7d407e7 100644 --- a/src/rule_with_actions.cc +++ b/src/rule_with_actions.cc @@ -172,9 +172,7 @@ RuleWithActions::~RuleWithActions() { bool RuleWithActions::evaluate(Transaction *transaction) { - RuleMessage rm(this, transaction); - std::shared_ptr rm2 = std::make_shared(&rm); - return evaluate(transaction, rm2); + return evaluate(transaction, std::make_shared(this, transaction)); } @@ -330,7 +328,7 @@ inline void RuleWithActions::executeTransformation( std::string newValue = a->evaluate(*oldValue, trans); if (newValue != *oldValue) { - std::shared_ptr u(new std::string(newValue)); + auto u = std::make_shared(newValue); if (m_containsMultiMatchAction) { ret->push_back(std::make_pair(u, a->m_name)); (*nth)++; @@ -355,14 +353,13 @@ void RuleWithActions::executeTransformations( int none = 0; int transformations = 0; std::string path(""); - std::shared_ptr value = - std::shared_ptr(new std::string(in)); + auto value = std::make_shared(in); if (m_containsMultiMatchAction == true) { /* keep the original value */ ret.push_back(std::make_pair( - std::shared_ptr(new std::string(*value)), - std::shared_ptr(new std::string(path)))); + std::make_shared(*value), + std::make_shared(path))); } for (Action *a : m_transformations) { @@ -436,8 +433,8 @@ void RuleWithActions::executeTransformations( if (!m_containsMultiMatchAction) { ret.push_back(std::make_pair( - std::shared_ptr(new std::string(*value)), - std::shared_ptr(new std::string(path)))); + std::make_shared(*value), + std::make_shared(path))); } } From 7bff2f77aa4bbbb267de5667db0cf4de8cd7467b Mon Sep 17 00:00:00 2001 From: Eduardo Arias Date: Tue, 23 Apr 2024 16:35:37 -0300 Subject: [PATCH 10/20] Updated references to coreruleset repository - For OWASP v2 rules, switch to a v2 tag for the paths referenced in the rest of the script to apply. --- test/benchmark/download-owasp-v2-rules.sh | 5 ++++- test/benchmark/download-owasp-v3-rules.sh | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/test/benchmark/download-owasp-v2-rules.sh b/test/benchmark/download-owasp-v2-rules.sh index facc48d3e0..dd1623e72e 100755 --- a/test/benchmark/download-owasp-v2-rules.sh +++ b/test/benchmark/download-owasp-v2-rules.sh @@ -2,7 +2,10 @@ # # -git clone https://github.com/SpiderLabs/owasp-modsecurity-crs.git owasp-v2 +git clone https://github.com/coreruleset/coreruleset.git owasp-v2 +cd owasp-v2 +git checkout 2.2.9 -b tag2.2.9 +cd - echo 'Include "owasp-v2/base_rules/*.conf"' >> basic_rules.conf echo 'Include "owasp-v2/optional_rules/*.conf"' >> basic_rules.conf diff --git a/test/benchmark/download-owasp-v3-rules.sh b/test/benchmark/download-owasp-v3-rules.sh index c3bc0dfa88..d0d9f80941 100755 --- a/test/benchmark/download-owasp-v3-rules.sh +++ b/test/benchmark/download-owasp-v3-rules.sh @@ -1,7 +1,7 @@ #!/bin/bash -git clone https://github.com/SpiderLabs/owasp-modsecurity-crs.git owasp-v3 +git clone https://github.com/coreruleset/coreruleset.git owasp-v3 cd owasp-v3 git checkout v3.0.2 -b tag3.0.2 cd - From 50c35345ed941209331aaa2fe785df53053609c1 Mon Sep 17 00:00:00 2001 From: Eduardo Arias Date: Tue, 23 Apr 2024 17:10:47 -0300 Subject: [PATCH 11/20] Fixed use after free in ModSecurity::processContentOffset - Use after free issue detected with Address Sanitizer while running the reading_logs_with_offset example. - Keeps reference to last element in vars vector with vars.back(). Then it removes the element from vars calling vars.pop_back() which invalidates the reference, but it's accessed later in the function. --- src/modsecurity.cc | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/modsecurity.cc b/src/modsecurity.cc index 4b48b7995f..7319bb9699 100644 --- a/src/modsecurity.cc +++ b/src/modsecurity.cc @@ -258,14 +258,11 @@ int ModSecurity::processContentOffset(const char *content, size_t len, strlen("highlight")); yajl_gen_array_open(g); - while (vars.size() > 3) { - std::string value; + for(auto [it, pending] = std::tuple{vars.rbegin(), vars.size()}; pending > 3; pending -= 3) { yajl_gen_map_open(g); - vars.pop_back(); - const std::string &startingAt = vars.back().str(); - vars.pop_back(); - const std::string &size = vars.back().str(); - vars.pop_back(); + it++; + const std::string &startingAt = it->str(); it++; + const std::string &size = it->str(); it++; yajl_gen_string(g, reinterpret_cast("startingAt"), strlen("startingAt")); @@ -284,7 +281,7 @@ int ModSecurity::processContentOffset(const char *content, size_t len, return -1; } - value = std::string(content, stoi(startingAt), stoi(size)); + const auto value = std::string(content, stoi(startingAt), stoi(size)); if (varValue.size() > 0) { varValue.append(" " + value); } else { @@ -340,16 +337,13 @@ int ModSecurity::processContentOffset(const char *content, size_t len, yajl_gen_map_open(g); - while (ops.size() > 3) { - std::string value; + for(auto [it, pending] = std::tuple{ops.rbegin(), ops.size()}; pending > 3; pending -= 3) { yajl_gen_string(g, reinterpret_cast("highlight"), strlen("highlight")); yajl_gen_map_open(g); - ops.pop_back(); - std::string startingAt = ops.back().str(); - ops.pop_back(); - std::string size = ops.back().str(); - ops.pop_back(); + it++; + const std::string &startingAt = it->str(); it++; + const std::string &size = ops.back().str(); it++; yajl_gen_string(g, reinterpret_cast("startingAt"), strlen("startingAt")); @@ -371,7 +365,7 @@ int ModSecurity::processContentOffset(const char *content, size_t len, reinterpret_cast("value"), strlen("value")); - value = std::string(varValue, stoi(startingAt), stoi(size)); + const auto value = std::string(varValue, stoi(startingAt), stoi(size)); yajl_gen_string(g, reinterpret_cast(value.c_str()), From 50e78331b1b79d907fd77204e9b3a8bb26a8dab6 Mon Sep 17 00:00:00 2001 From: Eduardo Arias Date: Tue, 23 Apr 2024 17:40:24 -0300 Subject: [PATCH 12/20] Updated Env::evaluate to support case-insensitive environment variable names in Windows - Env::evaluate - Environment variable names in Windows are case-insensitive, so in the Windows build we use strcasecmp to ignore case when matching variables in transaction->m_variableEnvs. - If the variable is found, we use the expected variable name to create the VariableValue instance, as further rule processing will look for the variable using case-sensitive comparisons. - This code is not limited to Windows to avoid another #ifdef block because for other platforms, because the env variable names are case-sensitive the value from either x.first and m_name will be the same. - In Windows build, avoid redefining environ, already defined by including stdlib.h. --- src/variables/env.cc | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/variables/env.cc b/src/variables/env.cc index dfb2c3f03d..bf40954a86 100644 --- a/src/variables/env.cc +++ b/src/variables/env.cc @@ -25,9 +25,15 @@ #include #include +#ifdef WIN32 +#include "src/compat/msvc.h" +#endif + #include "modsecurity/transaction.h" +#ifndef WIN32 extern char **environ; +#endif namespace modsecurity { namespace variables { @@ -47,12 +53,20 @@ void Env::evaluate(Transaction *transaction, transaction->m_variableEnvs.insert(a); } + const auto hasName = m_name.length() > 0; for (auto& x : transaction->m_variableEnvs) { - if (x.first != m_name && m_name.length() > 0) { +#ifndef WIN32 + if (hasName && x.first != m_name) { +#else + if (hasName && strcasecmp(x.first.c_str(), m_name.c_str()) != 0) { +#endif continue; } - if (!m_keyExclusion.toOmit(x.first)) { - l->push_back(new VariableValue(&m_collectionName, &x.first, + // (Windows) we need to keep the case from the rule in case that from + // the environment differs. + const auto &key = hasName ? m_name : x.first; + if (!m_keyExclusion.toOmit(key)) { + l->push_back(new VariableValue(&m_collectionName, &key, &x.second)); } } From d7c49ed590a32208f07cd2894ca5a39f70444194 Mon Sep 17 00:00:00 2001 From: Eduardo Arias Date: Tue, 23 Apr 2024 17:52:25 -0300 Subject: [PATCH 13/20] Added support to lock files on Windows and major rewrite to reintroduce reference counting and remove unused code. - In Windows build, replaced usage of fcntl with cmd F_SETLKW with Win32 APIs to do file locking (LockFileEx & UnlockFileEx). - Reintroduced the reference counting initially present in the class which is necessary to correctly handle merging of rules. This allows for correctly closing the file and removing the associated entry from m_handlers when the file is no longer used. - The need for reference counting can be seen in the example simple_example_using_c, where rules are initially loaded locally and then further rules are loaded remotely. This will initially open a shared file for a log, then in order to merge rules, the shared file is opened again for the new configuration. Then, the previous configuration closes the shared file on destruction. That is, two consecutive opens are done on a shared file, which is followed by a close. If the shared file is not reference counted, the shared file will be closed while there is still a reference active. The current version works because closing of the file has been disabled after reference counting was removed. - Replaced `std::vector` data structure with `std::unordered_map` to improve lookup/update times, and simplify code. - Removed unused code - Shared memory to store msc_file_handler structure - Initially SharedFiles used shared memory to store information about each shared file, including its file pointer and a mutex to synchronize access to the file on write. See code at commit 01c13da, in particular, usage of lock & fp fields in the msc_file_handler_t structure. - At that time, msc_file_handler_t included reference counting too with the using_it field, which was incremented when a file was opened and decremented on close. If the reference count reached zero, the shared file would be closed, the lock destroyed and the file handler entry removed from m_handlers. - Reference counting was removed in commit 7f9cd76, which introduced the following issues in SharedFiles::close: - No longer closes the file pointer. - The file pointer appears to be reset when a.second = 0, but this is a local copy of the data pair obtained from m_handlers, so this is essentially a nop (updating a local variable that is not referenced later in the function). - NOTE: The file pointer was moved out of the shared memory in this commit too, and stored alongside the msc_file_handler_t instance in the m_handlers entry associated to the shared file. - The lock is no longer destroyed. - The shared memory is marked to be destroyed in the call to: shmctl(a.first->shm_id_structure, IPC_RMID, NULL); - The shared file entry is not removed from m_handlers, so: - the file pointer is still valid, which is how writing to the file continues to work, - the reference to the shared memory is also present and will be marked to be destroyed whenever close is called again on the shared file. - File locking using the mutex in msc_file_handler_t was replaced in commit 3d20304 with usage of fcntl with cmd F_SETLKW. - At this time, it appears that the shared memory is no longer used, as the file pointer and locking no longer depend on it. - MODSEC_USE_GENERAL_LOCK - This code is introduced commit 7f9cd76 and is enabled if MODSEC_USE_GENERAL_LOCK` is defined. - The define is commented out in the source code since the original commit and is not present in the build configuration either. - In commit ff9152e, in the SharedFiles constructor, the initialization of the local variable toBeCreated is removed. This means that in this version, if MODSEC_USE_GENERAL_LOCK is enabled, execution of the code that checks on toBeCreated is undefined. - Then, in commit 9b40a04, the variable toBeCreated is initialized again, but is now set to false, which means that if MODSEC_USE_GENERAL_LOCK is enabled, the shared memory and lock it uses will *not* be initialized and thus doesn't work (execution of the current version will result in trying to acquire a lock that will be null). - I conclude that the feature is not used and can be removed. - Additionally, if it were working, I think the lock should be used in SharedFiles::write as well, which is a reader of the underlying data structures protected by this lock when they're modified in SharedFiles::open & SharedFiles::close. --- src/utils/shared_files.cc | 227 ++++++++------------------------------ src/utils/shared_files.h | 123 ++++----------------- 2 files changed, 66 insertions(+), 284 deletions(-) diff --git a/src/utils/shared_files.cc b/src/utils/shared_files.cc index 73216bd002..df18022f7c 100644 --- a/src/utils/shared_files.cc +++ b/src/utils/shared_files.cc @@ -15,236 +15,103 @@ #include "src/utils/shared_files.h" -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include +#ifdef WIN32 +#include +#include +#endif + namespace modsecurity { namespace utils { -std::pair SharedFiles::find_handler( - const std::string &fileName) { - for (const auto &i : m_handlers) { - if (i.first == fileName) { - return i.second; - } - } - return std::pair(NULL, NULL); -} - - -std::pair SharedFiles::add_new_handler( +SharedFiles::handlers_map::iterator SharedFiles::add_new_handler( const std::string &fileName, std::string *error) { - int shm_id; - int ret; - key_t mem_key_structure; - msc_file_handler_t *new_debug_log = NULL; - struct shmid_ds shared_mem_info; - FILE *fp; - bool toBeCreated = true; - - fp = fopen(fileName.c_str(), "a"); + FILE *fp = fopen(fileName.c_str(), "a"); if (fp == 0) { error->assign("Failed to open file: " + fileName); - goto err_fh; - } - - mem_key_structure = ftok(fileName.c_str(), 1); - if (mem_key_structure < 0) { - error->assign("Failed to select key for the shared memory (1): "); - error->append(strerror(errno)); - goto err_mem_key; - } - - shm_id = shmget(mem_key_structure, sizeof (msc_file_handler_t) \ - + fileName.size() + 1, IPC_CREAT | IPC_EXCL | 0666); - if (shm_id < 0) { - shm_id = shmget(mem_key_structure, sizeof (msc_file_handler_t) - + fileName.size() + 1, IPC_CREAT | 0666); - toBeCreated = false; - if (shm_id < 0) { - error->assign("Failed to allocate shared memory (1): "); - error->append(strerror(errno)); - goto err_shmget1; - } + return m_handlers.end(); } - ret = shmctl(shm_id, IPC_STAT, &shared_mem_info); - if (ret < 0) { - error->assign("Failed to get information on shared memory (1): "); - error->append(strerror(errno)); - goto err_shmctl1; - } - - new_debug_log = reinterpret_cast( - shmat(shm_id, NULL, 0)); - if ((reinterpret_cast(new_debug_log)[0]) == -1) { - error->assign("Failed to attach shared memory (1): "); - error->append(strerror(errno)); - goto err_shmat1; - } - - if (toBeCreated == false && shared_mem_info.shm_nattch == 0) { - toBeCreated = true; - } - - if (toBeCreated) { - memset(new_debug_log, '\0', sizeof(msc_file_handler_t)); - new_debug_log->shm_id_structure = shm_id; - memcpy(new_debug_log->file_name, fileName.c_str(), fileName.size()); - new_debug_log->file_name[fileName.size()] = '\0'; - } - m_handlers.push_back(std::make_pair(fileName, - std::make_pair(new_debug_log, fp))); - - return std::make_pair(new_debug_log, fp); -err_shmat1: - shmdt(new_debug_log); -err_shmctl1: -err_shmget1: -err_mem_key: - fclose(fp); -err_fh: - return std::pair(NULL, NULL); + return m_handlers.insert({ fileName, {fp, 0} }).first; } bool SharedFiles::open(const std::string& fileName, std::string *error) { - std::pair a; - bool ret = true; - - #if MODSEC_USE_GENERAL_LOCK - pthread_mutex_lock(m_generalLock); -#endif - - a = find_handler(fileName); - if (a.first == NULL) { - a = add_new_handler(fileName, error); - if (error->size() > 0) { - ret = false; - goto out; - } + auto it = m_handlers.find(fileName); + if (it == m_handlers.end()) { + it = add_new_handler(fileName, error); + if (error->size() > 0) + return false; } - if (a.first == NULL) { + + if (it == m_handlers.end()) { error->assign("Not able to open: " + fileName); - ret = false; - goto out; + return false; } -out: -#if MODSEC_USE_GENERAL_LOCK - pthread_mutex_unlock(m_generalLock); -#endif + it->second.cnt++; - return ret; + return true; } void SharedFiles::close(const std::string& fileName) { - std::pair a; - /* int ret; */ - /* int shm_id; */ - /* struct shmid_ds shared_mem_info; */ - /* int j = 0; */ - -#if MODSEC_USE_GENERAL_LOCK - pthread_mutex_lock(m_generalLock); -#endif - - if (fileName.empty()) { - goto out; - } - - a = find_handler(fileName); - if (a.first == NULL || a.second == NULL) { - goto out; - } - - /* fclose(a.second); */ - a.second = 0; - - /* - * Delete the file structure will be welcomed, but we cannot delay - * while the process is being killed. - * - for (std::pair> i : m_handlers) { - if (i.first == fileName) { - j++; - } - } + if (fileName.empty()) + return; - m_handlers.erase(m_handlers.begin()+j); - */ + auto it = m_handlers.find(fileName); + if (it == m_handlers.end()) + return; - /* hmdt(a.second); */ - shmctl(a.first->shm_id_structure, IPC_RMID, NULL); + it->second.cnt--; + if (it->second.cnt == 0) + { + fclose(it->second.fp); - /* - * - * We could check to see how many process attached to the shared memory - * we have, prior to the deletion of the shared memory. - * - ret = shmctl(a.first->shm_id_structure, IPC_STAT, &shared_mem_info); - if (ret < 0) { - goto out; + m_handlers.erase(it); } - ret = shared_mem_info.shm_nattch; - shm_id = a.first->shm_id_structure; - */ - -out: -#if MODSEC_USE_GENERAL_LOCK - pthread_mutex_unlock(m_generalLock); -#endif - return; } bool SharedFiles::write(const std::string& fileName, const std::string &msg, std::string *error) { - std::pair a; - std::string lmsg = msg; - size_t wrote; - struct flock lock{}; bool ret = true; - a = find_handler(fileName); - if (a.first == NULL) { + auto it = m_handlers.find(fileName); + if (it == m_handlers.end()) { error->assign("file is not open: " + fileName); return false; } //Exclusively lock whole file +#ifndef WIN32 + struct flock lock {}; lock.l_start = lock.l_len = lock.l_whence = 0; lock.l_type = F_WRLCK; - fcntl(fileno(a.second), F_SETLKW, &lock); + fcntl(fileno(it->second.fp), F_SETLKW, &lock); +#else + auto handle = reinterpret_cast(_get_osfhandle(fileno(it->second.fp))); + OVERLAPPED overlapped = { 0 }; + ::LockFileEx(handle, LOCKFILE_EXCLUSIVE_LOCK, 0, MAXDWORD, MAXDWORD, &overlapped); +#endif - wrote = fwrite(lmsg.c_str(), 1, lmsg.size(), a.second); + auto wrote = fwrite(msg.c_str(), 1, msg.size(), it->second.fp); if (wrote < msg.size()) { error->assign("failed to write: " + fileName); ret = false; } - fflush(a.second); + fflush(it->second.fp); //Remove exclusive lock +#ifndef WIN32 lock.l_type = F_UNLCK; - fcntl(fileno(a.second), F_SETLKW, &lock); + fcntl(fileno(it->second.fp), F_SETLKW, &lock); +#else + overlapped = { 0 }; + ::UnlockFileEx(handle, 0, MAXDWORD, MAXDWORD, &overlapped); +#endif return ret; } diff --git a/src/utils/shared_files.h b/src/utils/shared_files.h index d0d8ef9922..4953eeff4a 100644 --- a/src/utils/shared_files.h +++ b/src/utils/shared_files.h @@ -17,45 +17,18 @@ #define SRC_UTILS_SHARED_FILES_H_ -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "modsecurity/transaction.h" -#include "modsecurity/audit_log.h" +#include +#include -/** - * Not using this critical section yet. - * - */ -/* #define MODSEC_USE_GENERAL_LOCK */ namespace modsecurity { namespace utils { -typedef struct msc_file_handler { - int shm_id_structure; - char file_name[]; -} msc_file_handler_t; - - class SharedFiles { - public: +public: bool open(const std::string& fileName, std::string *error); void close(const std::string& fileName); bool write(const std::string& fileName, const std::string &msg, @@ -66,86 +39,28 @@ class SharedFiles { return instance; } - protected: - std::pair find_handler( - const std::string &fileName); - std::pair add_new_handler( - const std::string &fileName, std::string *error); - - private: - SharedFiles() -#ifdef MODSEC_USE_GENERAL_LOCK - : m_generalLock(NULL), - m_memKeyStructure(0) -#endif - { -#ifdef MODSEC_USE_GENERAL_LOCK - int shm_id; - bool toBeCreated(false); - bool err = false; - - m_memKeyStructure = ftok(".", 1); // cppcheck-suppress useInitializationList - if (m_memKeyStructure < 0) { - err = true; - goto err_mem_key; - } - - shm_id = shmget(m_memKeyStructure, sizeof(pthread_mutex_t), - IPC_CREAT | IPC_EXCL | 0666); - if (shm_id < 0) { - shm_id = shmget(m_memKeyStructure, sizeof(pthread_mutex_t), - IPC_CREAT | 0666); - toBeCreated = false; - if (shm_id < 0) { - err = true; - goto err_shmget1; - } - } - - m_generalLock = reinterpret_cast( - shmat(shm_id, NULL, 0)); - if ((reinterpret_cast(m_generalLock)[0]) == -1) { - err = true; - goto err_shmat1; - } - - if (toBeCreated) { - memset(m_generalLock, '\0', sizeof(pthread_mutex_t)); - pthread_mutex_init(m_generalLock, NULL); - pthread_mutex_unlock(m_generalLock); - } - - if (err) { -err_mem_key: - std::cerr << strerror(errno) << std::endl; -err_shmget1: - std::cerr << "err_shmget1" << std::endl; -err_shmat1: - std::cerr << "err_shmat1" << std::endl; - } -#endif - } - ~SharedFiles() { -#if MODSEC_USE_GENERAL_LOCK - shmdt(m_generalLock); - shmctl(m_memKeyStructure, IPC_RMID, NULL); -#endif - } +private: + SharedFiles() = default; + ~SharedFiles() = default; // C++ 03 // ======== // Dont forget to declare these two. You want to make sure they // are unacceptable otherwise you may accidentally get copies of // your singleton appearing. - SharedFiles(SharedFiles const&); - void operator=(SharedFiles const&); - - std::vector>> m_handlers; -#if MODSEC_USE_GENERAL_LOCK - pthread_mutex_t *m_generalLock; - key_t m_memKeyStructure; -#endif + SharedFiles(SharedFiles const&) = delete; + void operator=(SharedFiles const&) = delete; + + struct handler_info { + FILE* fp; + unsigned int cnt; + }; + + using handlers_map = std::unordered_map; + handlers_map m_handlers; + + handlers_map::iterator add_new_handler( + const std::string &fileName, std::string *error); }; From faae58eed79d2096210e0e3812f2c10cb21974e9 Mon Sep 17 00:00:00 2001 From: Eduardo Arias Date: Wed, 24 Apr 2024 10:51:07 -0300 Subject: [PATCH 14/20] Added Windows build scripts using Build Tools for Visual Studio 2022 (MSVC compiler & CMake) and Conan package manager - Included Dockerfile to automate the setup process of prerequisites and build of libModSecurity binaries. --- build/win32/CMakeLists.txt | 196 +++++++++++++++++++++++ build/win32/ConfigureChecks.cmake | 18 +++ build/win32/README.md | 111 +++++++++++++ build/win32/conanfile.txt | 15 ++ build/win32/config.h.cmake | 92 +++++++++++ build/win32/docker/Dockerfile | 86 ++++++++++ build/win32/docker/InstallBuildTools.cmd | 17 ++ build/win32/docker/git.inf | 20 +++ vcbuild.bat | 28 ++++ 9 files changed, 583 insertions(+) create mode 100644 build/win32/CMakeLists.txt create mode 100644 build/win32/ConfigureChecks.cmake create mode 100644 build/win32/README.md create mode 100644 build/win32/conanfile.txt create mode 100644 build/win32/config.h.cmake create mode 100644 build/win32/docker/Dockerfile create mode 100644 build/win32/docker/InstallBuildTools.cmd create mode 100644 build/win32/docker/git.inf create mode 100644 vcbuild.bat diff --git a/build/win32/CMakeLists.txt b/build/win32/CMakeLists.txt new file mode 100644 index 0000000000..f7bad3baae --- /dev/null +++ b/build/win32/CMakeLists.txt @@ -0,0 +1,196 @@ +cmake_minimum_required(VERSION 3.15) + +set(BASE_DIR ${CMAKE_CURRENT_LIST_DIR}/../..) + +set(USE_ASAN OFF CACHE BOOL "Build with Address Sanitizer") + +# common compiler settings + +# NOTE: MBEDTLS_CONFIG_FILE is not only required to compile the mbedtls subset in others, but also +# when their headers are included while compiling libModSecurity +add_compile_definitions(WIN32 _CRT_SECURE_NO_WARNINGS MBEDTLS_CONFIG_FILE="mbed-tls-config.h") + +# set standards conformance preprocessor & compiler to align with cross-compiled codebase +# NOTE: otherwise visual c++'s default compiler/preprocessor behaviour generates C4067 warnings +# (unexpected tokens following preprocessor directive - expected a newline) +add_compile_options(/Zc:preprocessor /permissive-) + +if(USE_ASAN) + add_compile_options(/fsanitize=address) + add_link_options(/INFERASANLIBS /INCREMENTAL:no) +endif() + +# libinjection + +project(libinjection C) + +add_library(libinjection STATIC ${BASE_DIR}/others/libinjection/src/libinjection_sqli.c ${BASE_DIR}/others/libinjection/src/libinjection_xss.c ${BASE_DIR}/others/libinjection/src/libinjection_html5.c) + +# mbedtls + +project(mbedtls C) + +add_library(mbedtls STATIC ${BASE_DIR}/others/mbedtls/base64.c ${BASE_DIR}/others/mbedtls/sha1.c ${BASE_DIR}/others/mbedtls/md5.c) + +target_include_directories(mbedtls PRIVATE ${BASE_DIR}/others) + +# +# libModSecurity +# + +project(libModSecurity + VERSION + 3.0.12 + LANGUAGES + CXX +) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED On) +set(CMAKE_CXX_EXTENSIONS Off) + +set(PACKAGE_BUGREPORT "security@modsecurity.org") +set(PACKAGE_NAME "modsecurity") +set(PACKAGE_VERSION "${PROJECT_VERSION}") +set(PACKAGE_STRING "${PACKAGE_NAME} ${PACKAGE_VERSION}") +set(PACKAGE_TARNAME "${PACKAGE_NAME}") + +set(HAVE_GEOIP 0) # should always be zero, no conan package available +set(HAVE_LMDB 1) +set(HAVE_LUA 1) +set(HAVE_LIBXML2 1) +set(HAVE_MAXMIND 1) +set(HAVE_SSDEEP 0) # should always be zero, no conan package available +set(HAVE_YAJL 1) # should always be one, mandatory dependency +set(HAVE_CURL 1) + +include(${CMAKE_CURRENT_LIST_DIR}/ConfigureChecks.cmake) + +configure_file(config.h.cmake ${BASE_DIR}/src/config.h) + +find_package(PCRE2 REQUIRED) +find_package(PThreads4W REQUIRED) +find_package(Poco REQUIRED) +find_package(dirent REQUIRED) # used only by tests (check dirent::dirent refernces) + +macro(include_package package flag) + if(${flag}) + find_package(${package} REQUIRED) + endif() +endmacro() + +include_package(yajl HAVE_YAJL) +include_package(libxml2 HAVE_LIBXML2) +include_package(lua HAVE_LUA) +include_package(CURL HAVE_CURL) +include_package(lmdb HAVE_LMDB) +include_package(maxminddb HAVE_MAXMIND) + +# library +# + +# NOTE: required to generate libModSecurity's import library (libModSecurity.lib), used by tests to link with shared library +set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) + +file(GLOB_RECURSE libModSecuritySources ${BASE_DIR}/src/*.cc) + +add_library(libModSecurity SHARED ${libModSecuritySources}) + +target_compile_definitions(libModSecurity PRIVATE WITH_PCRE2) +target_include_directories(libModSecurity PRIVATE ${BASE_DIR} ${BASE_DIR}/headers ${BASE_DIR}/others) +target_link_libraries(libModSecurity PRIVATE pcre2::pcre2 pthreads4w::pthreads4w libinjection mbedtls Poco::Poco Iphlpapi.lib) + +macro(add_package_dependency project compile_definition link_library flag) + if(${flag}) + target_compile_definitions(${project} PRIVATE ${compile_definition}) + target_link_libraries(${project} PRIVATE ${link_library}) + endif() +endmacro() + +add_package_dependency(libModSecurity WITH_YAJL yajl::yajl HAVE_YAJL) +add_package_dependency(libModSecurity WITH_LIBXML2 LibXml2::LibXml2 HAVE_LIBXML2) +add_package_dependency(libModSecurity WITH_LUA lua::lua HAVE_LUA) +if(HAVE_LUA) + target_compile_definitions(libModSecurity PRIVATE WITH_LUA_5_4) +endif() +add_package_dependency(libModSecurity MSC_WITH_CURL CURL::libcurl HAVE_CURL) +add_package_dependency(libModSecurity WITH_LMDB lmdb::lmdb HAVE_LMDB) +add_package_dependency(libModSecurity WITH_MAXMIND maxminddb::maxminddb HAVE_MAXMIND) + +# tests +# + +project(libModSecurityTests) + +function(setTestTargetProperties executable) + target_compile_definitions(${executable} PRIVATE WITH_PCRE2) + target_include_directories(${executable} PRIVATE ${BASE_DIR} ${BASE_DIR}/headers) + target_link_libraries(${executable} PRIVATE libModSecurity pcre2::pcre2 dirent::dirent) + add_package_dependency(${executable} WITH_YAJL yajl::yajl HAVE_YAJL) +endfunction() + +# unit tests +file(GLOB unitTestSources ${BASE_DIR}/test/unit/*.cc) +add_executable(unit_tests ${unitTestSources}) +setTestTargetProperties(unit_tests) +target_compile_options(unit_tests PRIVATE /wd4805) + +# regression tests +file(GLOB regressionTestsSources ${BASE_DIR}/test/regression/*.cc) +add_executable(regression_tests ${regressionTestsSources}) +setTestTargetProperties(regression_tests) + +macro(add_regression_test_capability compile_definition flag) + if(${flag}) + target_compile_definitions(regression_tests PRIVATE ${compile_definition}) + endif() +endmacro() + +add_regression_test_capability(WITH_LUA HAVE_LUA) +add_regression_test_capability(WITH_CURL HAVE_CURL) +add_regression_test_capability(WITH_LMDB HAVE_LMDB) +add_regression_test_capability(WITH_MAXMIND HAVE_MAXMIND) + +# benchmark +add_executable(benchmark ${BASE_DIR}/test/benchmark/benchmark.cc) +setTestTargetProperties(benchmark) + +# rules_optimization +add_executable(rules_optimization ${BASE_DIR}/test/optimization/optimization.cc) +setTestTargetProperties(rules_optimization) + + +# examples +# + +project(libModSecurityExamples) + +function(setExampleTargetProperties executable) + target_include_directories(${executable} PRIVATE ${BASE_DIR} ${BASE_DIR}/headers) + target_link_libraries(${executable} PRIVATE libModSecurity) +endfunction() + +# simple_example_using_c +add_executable(simple_example_using_c ${BASE_DIR}/examples/simple_example_using_c/test.c) +setExampleTargetProperties(simple_example_using_c) + +# using_bodies_in_chunks +add_executable(using_bodies_in_chunks ${BASE_DIR}/examples/using_bodies_in_chunks/simple_request.cc) +setExampleTargetProperties(using_bodies_in_chunks) + +# reading_logs_via_rule_message +add_executable(reading_logs_via_rule_message ${BASE_DIR}/examples/reading_logs_via_rule_message/simple_request.cc) +setExampleTargetProperties(reading_logs_via_rule_message) +target_link_libraries(reading_logs_via_rule_message PRIVATE libModSecurity pthreads4w::pthreads4w) + +# reading_logs_with_offset +add_executable(reading_logs_with_offset ${BASE_DIR}/examples/reading_logs_with_offset/read.cc) +setExampleTargetProperties(reading_logs_with_offset) + +# tools +# + +# rules_check +add_executable(rules_check ${BASE_DIR}/tools/rules-check/rules-check.cc) +target_include_directories(rules_check PRIVATE ${BASE_DIR} ${BASE_DIR}/headers) +target_link_libraries(rules_check PRIVATE libModSecurity) diff --git a/build/win32/ConfigureChecks.cmake b/build/win32/ConfigureChecks.cmake new file mode 100644 index 0000000000..6322b69652 --- /dev/null +++ b/build/win32/ConfigureChecks.cmake @@ -0,0 +1,18 @@ +include(CheckIncludeFile) +include(CheckIncludeFiles) + +check_include_file("dlfcn.h" HAVE_DLFCN_H) +check_include_file("inttypes.h" HAVE_INTTYPES_H) +check_include_file("stdint.h" HAVE_STDINT_H) +check_include_file("stdio.h" HAVE_STDIO_H) +check_include_file("stdlib.h" HAVE_STDLIB_H) +check_include_file("string" HAVE_STRING) +check_include_file("strings.h" HAVE_STRINGS_H) +check_include_file("string.h" HAVE_STRING_H) +check_include_file("sys/stat.h" HAVE_SYS_STAT_H) +check_include_file("sys/types.h" HAVE_SYS_TYPES_H) +check_include_file("sys/utsname.h" HAVE_SYS_UTSNAME_H) +check_include_file("unistd.h" HAVE_UNISTD_H) + +#/* Define to 1 if you have the ANSI C header files. */ +check_include_files("stdlib.h;stdarg.h;string.h;float.h" STDC_HEADERS) diff --git a/build/win32/README.md b/build/win32/README.md new file mode 100644 index 0000000000..7b70cfbdf8 --- /dev/null +++ b/build/win32/README.md @@ -0,0 +1,111 @@ +# libModSecurity Windows build information + +The Windows build of libModSecurity uses Build Tools for Visual Studio 2022 (for Visual C++ & CMake) and Conan package manager. + +## Contents + +- [Prerequisites](#prerequisites) +- [Build](#build) + - [Optional features](#optional-features) + - [Address Sanitizer](#address-sanitizer) + - [Docker container](#docker-container) + +## Prerequisites + + * [Build Tools for Visual Studio 2022](https://aka.ms/vs/17/release/vs_buildtools.exe) + * Install *Desktop development with C++* workload, which includes: + * MSVC C++ compiler + * Windows SDK + * CMake + * Address Sanitizer + * [Conan package manager 2.2.2](https://github.com/conan-io/conan/releases/download/2.2.2/conan-2.2.2-windows-x86_64-installer.exe) + * Install and then setup the default Conan profile to use the MSVC C++ compiler: + 1. Open a command-prompt and set the MSVC C++ compiler environment by executing: `C:\BuildTools\VC\Auxiliary\Build\vcvars64.bat` + 2. Execute: `conan profile detect --force` + * [Git for Windows 2.44.0](https://github.com/git-for-windows/git/releases/download/v2.44.0.windows.1/Git-2.44.0-64-bit.exe) + * To clone the libModSecurity repository. + * NOTE: Make sure to initialize and update submodules (to get `libinjection` and regression tests) + * `git submodule init` + * `git submodule update` + +## Build + +Install the prerequisites listsed in the previous section, checkout libModSecurity and from the directory where it's located execute: + +``` +vcbuild.bat [build_configuration] [arch] [USE_ASAN] +``` + +where `[build_configuration]` can be: `Release` (default), `RelWithDebInfo`, `MinSizeRel` or `Debug`, and `[arch]` can be: `x86_64` (default) or `x86`. + +Built files will be located in the directory: `build\win32\build\[build_configuration]` and include: + + * `libModSecurity.dll` + * Executable files for test projects + * `unit_tests.exe` + * `regression_tests.exe` + * `benchmark.exe` + * `rules_optimization.exe` + * Executable files for examples + * `simple_example_using_c.exe` + * `using_bodies_in_chunks.exe` + * `reading_logs_via_rule_message.exe` + * `reading_logs_with_offset.exe` + * Executable files for tools + * `rules_check.exe` + +NOTE: When building a different configuration, it's recommended to reset: + + * the build directory: `build\win32\build` + * previously built conan packages executing the command: + * `conan remove * -c` + +### Optional features + +By default the following all the following features are enabled by including the associated third-party library through a Conan package: + + * libxml2 2.12.6 for XML processing support + * libcurl 8.6.0 to support http requests from rules + * libmaxminddb 1.9.1 to support reading MaxMind DB files. + * LUA 5.4.6 to enable rules to run scripts in this language for extensibility + * lmdb 0.9.31 in-memory database + +Each of these can be turned off by updating the associated `HAVE_xxx` variable (setting it to zero) in the beginning of the libModSecurity section of `CMakeLists.txt`. + +### Address Sanitizer + +[AddressSanitizer](https://github.com/google/sanitizers/wiki/AddressSanitizer) (aka ASan) is a memory error detector for C/C++. + +To generate a build with *Address Sanitizer*, add the `USE_ASAN` optional third argument to `vcbuild.bat`. For example: + * `vcbuild.bat Debug x86_64 USE_ASAN` + +NOTE: `USE_ASAN` does not work with `Release` & `MinSizeRel` configurations because they do not include debug info (it is only compatible with `Debug` & `RelWithDebInfo` builds). + + * References + * [AddressSanitizer | Microsoft Learn](https://learn.microsoft.com/en-us/cpp/sanitizers/asan?view=msvc-170) + * [AddressSanitizer for Windows: x64 and Debug Build Support - C++ Team Blog (microsoft.com)](https://devblogs.microsoft.com/cppblog/asan-for-windows-x64-and-debug-build-support/) + * [AddressSanitizer language, build, and debugging reference | Microsoft Learn](https://learn.microsoft.com/en-us/cpp/sanitizers/asan-building?view=msvc-170) + +### Docker container + +A `Dockerfile` configuration file is provided in the `docker` subdir that creates a Windows container image which installs the [prerequisites](#prerequisites) and builds libModSecurity and other binaries. + +NOTE: Windows containers are supported in Docker Desktop for Windows, using the *Switch to Windows containers...* option on the context menu of the system tray icon. + +To build the docker image, execute the following command (from the `build\win32\docker` directory): + + * `docker build -t libmodsecurity:latest -m 4GB .` + * Build type, architecture and build with Address Sanitizer can be configured through build arguments (`BUILD_TYPE`, `ARCH` & `USE_ASAN` respectively). For example, to generate a debug build, add the following argument: + * `--build-arg BUILD_TYPE=Debug` + +Once the image is generated, the library and associated binaries (tests & examples) are located in the `C:\src\ModSecurity\build\win32\build\[build_type]` directory. + +To extract the library (`libModSecurity.dll`) from the image, you can execute the following commands: + + * `docker container create --name [container_name] libmodsecurity` + * `docker cp [container_name]:C:\src\ModSecurity\build\win32\build\[build_type]\libModSecurity.dll .` + * NOTE: If you leave out the `libModSecurity.dll` filename out, you can copy all the built binaries (including examples & tests). + +Additionally, the image can be used interactively for additional development work by executing: + + * `docker run -it libmodsecurity` diff --git a/build/win32/conanfile.txt b/build/win32/conanfile.txt new file mode 100644 index 0000000000..b1a6982156 --- /dev/null +++ b/build/win32/conanfile.txt @@ -0,0 +1,15 @@ +[requires] +yajl/2.1.0 +pcre2/10.42 +pthreads4w/3.0.0 +libxml2/2.12.6 +lua/5.4.6 +libcurl/8.6.0 +lmdb/0.9.31 +libmaxminddb/1.9.1 +dirent/1.24 +poco/1.13.3 + +[generators] +CMakeDeps +CMakeToolchain diff --git a/build/win32/config.h.cmake b/build/win32/config.h.cmake new file mode 100644 index 0000000000..2f6a73085e --- /dev/null +++ b/build/win32/config.h.cmake @@ -0,0 +1,92 @@ +/* config.h.cmake. Based upon generated config.h.in. */ + +#ifndef MODSECURITY_CONFIG_H +#define MODSECURITY_CONFIG_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_DLFCN_H + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_INTTYPES_H + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_IOSTREAM + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STDINT_H + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STDIO_H + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STDLIB_H + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STRING + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STRINGS_H + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STRING_H + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_STAT_H + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_TYPES_H + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_UTSNAME_H + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_UNISTD_H + +/* Define if GeoIP is available */ +#cmakedefine HAVE_GEOIP + +/* Define if LMDB is available */ +#cmakedefine HAVE_LMDB + +/* Define if LUA is available */ +#cmakedefine HAVE_LUA + +/* Define if MaxMind is available */ +#cmakedefine HAVE_MAXMIND + +/* Define if SSDEEP is available */ +#cmakedefine HAVE_SSDEEP + +/* Define if YAJL is available */ +#cmakedefine HAVE_YAJL + +/* Define if libcurl is available */ +#cmakedefine HAVE_CURL + +/* Name of package */ +#define PACKAGE "@PACKAGE_NAME@" + +/* Define to the address where bug reports for this package should be sent. */ +#cmakedefine PACKAGE_BUGREPORT "@PACKAGE_BUGREPORT@" + +/* Define to the full name of this package. */ +#cmakedefine PACKAGE_NAME "@PACKAGE_NAME@" + +/* Define to the full name and version of this package. */ +#cmakedefine PACKAGE_STRING "@PACKAGE_STRING@" + +/* Define to the one symbol short name of this package. */ +#cmakedefine PACKAGE_TARNAME "@PACKAGE_TARNAME@" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "" + +/* Define to the version of this package. */ +#cmakedefine PACKAGE_VERSION "@PACKAGE_VERSION@" + +/* Define to 1 if you have the ANSI C header files. */ +#ifndef STDC_HEADERS +#cmakedefine STDC_HEADERS +#endif + +#endif // ndef MODSECURITY_CONFIG_H \ No newline at end of file diff --git a/build/win32/docker/Dockerfile b/build/win32/docker/Dockerfile new file mode 100644 index 0000000000..4967226e00 --- /dev/null +++ b/build/win32/docker/Dockerfile @@ -0,0 +1,86 @@ +# escape=` + +ARG FROM_IMAGE=mcr.microsoft.com/windows/servercore:ltsc2022 +FROM ${FROM_IMAGE} + +# reset the shell. +SHELL ["cmd", "/S", "/C"] + +# set up environment to collect install errors. +COPY InstallBuildTools.cmd C:\TEMP\ +ADD https://aka.ms/vscollect.exe C:\TEMP\collect.exe + +# download channel for fixed install. +ARG CHANNEL_URL=https://aka.ms/vs/17/release/channel +ADD ${CHANNEL_URL} C:\TEMP\VisualStudio.chman + +# download and install Build Tools for Visual Studio 2022 for native desktop workload. +ADD https://aka.ms/vs/17/release/vs_buildtools.exe C:\TEMP\vs_buildtools.exe +RUN C:\TEMP\InstallBuildTools.cmd C:\TEMP\vs_buildtools.exe --quiet --wait --norestart --nocache ` + --channelUri C:\TEMP\VisualStudio.chman ` + --installChannelUri C:\TEMP\VisualStudio.chman ` + --add Microsoft.VisualStudio.Workload.VCTools ` + --includeRecommended ` + --installPath C:\BuildTools + +# download & install GIT +ARG GIT_VERSION=2.44.0 +ARG GIT_BINARY=Git-${GIT_VERSION}-64-bit.exe +ARG GIT_URL=https://github.com/git-for-windows/git/releases/download/v${GIT_VERSION}.windows.1/${GIT_BINARY} + +COPY git.inf C:\TEMP\ +ARG INSTALLER=C:\TEMP\${GIT_BINARY} +ADD ${GIT_URL} ${INSTALLER} +RUN %INSTALLER% /SP- /VERYSILENT /SUPPRESSMSGBOXES /NOCANCEL ` + /NORESTART /CLOSEAPPLICATIONS /RESTARTAPPLICATIONS /LOADINF=git.inf + +# download & setup conan +ARG CONAN_VERSION=2.2.2 +ARG CONAN_BINARY=conan-${CONAN_VERSION}-windows-x86_64-installer.exe +ARG CONAN_URL=https://github.com/conan-io/conan/releases/download/${CONAN_VERSION}/${CONAN_BINARY} + +ARG INSTALLER=C:\TEMP\${CONAN_BINARY} +ADD ${CONAN_URL} ${INSTALLER} +RUN %INSTALLER% /SP- /VERYSILENT /SUPPRESSMSGBOXES + +# setup conan profile +RUN C:\BuildTools\VC\Auxiliary\Build\vcvars64.bat && conan profile detect --force + +# download libModSecurity +# + +# create src dir +ARG SRC_DIR=C:\src + +WORKDIR C:\ +RUN cmd.exe /C md %SRC_DIR% + +# libModSecurity +WORKDIR C:\src + +ARG MOD_SECURITY_TAG=v3/master +RUN git clone -c advice.detachedHead=false --depth 1 --branch %MOD_SECURITY_TAG% https://github.com/owasp-modsecurity/ModSecurity.git + +ARG MOD_SECURITY_DIR=${SRC_DIR}\ModSecurity +WORKDIR ${MOD_SECURITY_DIR} + +# fetch submodules (bindings/python, others/libinjection, test/test-cases/secrules-language-tests) +RUN git submodule init +RUN git submodule update + +# build libraries +# + +ARG BUILD_TYPE=Release +ARG ARCH=x86_64 +ARG USE_ASAN= + +RUN C:\BuildTools\VC\Auxiliary\Build\vcvars64.bat && vcbuild.bat %BUILD_TYPE% %ARCH% %USE_ASAN% + +# setup container's entrypoint +# + +WORKDIR C:\ + +# Use developer command prompt and start PowerShell if no other command specified. +ENTRYPOINT ["C:\\BuildTools\\VC\\Auxiliary\\Build\\vcvars64.bat", "&&", "powershell.exe", "-NoLogo", "-ExecutionPolicy", "Bypass"] diff --git a/build/win32/docker/InstallBuildTools.cmd b/build/win32/docker/InstallBuildTools.cmd new file mode 100644 index 0000000000..a0c07c7815 --- /dev/null +++ b/build/win32/docker/InstallBuildTools.cmd @@ -0,0 +1,17 @@ +@rem Copyright (C) Microsoft Corporation. All rights reserved. +@rem Licensed under the MIT license. See LICENSE.txt in the project root for license information. + +@if not defined _echo echo off +setlocal enabledelayedexpansion + +call %* +if "%ERRORLEVEL%"=="3010" ( + exit /b 0 +) else ( + if not "%ERRORLEVEL%"=="0" ( + set ERR=%ERRORLEVEL% + call C:\TEMP\collect.exe -zip:C:\vslogs.zip + + exit /b !ERR! + ) +) diff --git a/build/win32/docker/git.inf b/build/win32/docker/git.inf new file mode 100644 index 0000000000..49781dd9a4 --- /dev/null +++ b/build/win32/docker/git.inf @@ -0,0 +1,20 @@ +[Setup] +Lang=default +Dir=C:\Program Files\Git +Group=Git +NoIcons=0 +SetupType=default +Components=ext,ext\shellhere,ext\guihere,gitlfs,assoc,autoupdate +Tasks= +EditorOption=VIM +CustomEditorPath= +PathOption=Cmd +SSHOption=OpenSSH +TortoiseOption=false +CURLOption=WinSSL +CRLFOption=LFOnly +BashTerminalOption=ConHost +PerformanceTweaksFSCache=Enabled +UseCredentialManager=Enabled +EnableSymlinks=Disabled +EnableBuiltinInteractiveAdd=Disabled \ No newline at end of file diff --git a/vcbuild.bat b/vcbuild.bat new file mode 100644 index 0000000000..0789c9f81f --- /dev/null +++ b/vcbuild.bat @@ -0,0 +1,28 @@ +@rem For Windows build information, see build\win32\README.md + +@echo off +pushd %CD% + +if not "%1"=="" (set build_type=%1) else (set build_type=Release) +echo Build type: %build_type% + +if not "%2"=="" (set arch=%2) else (set arch=x86_64) +echo Arch: %arch% + +if "%3"=="USE_ASAN" ( + echo Address Sanitizer: Enabled + set CI_ASAN=-c tools.build:cxxflags="[""/fsanitize=address""]" + set ASAN_FLAG=ON +) else ( + echo Address Sanitizer: Disabled + set CI_ASAN= + set ASAN_FLAG=OFF +) + +cd build\win32 +conan install . -s compiler.cppstd=17 %CI_ASAN% --output-folder=build --build=missing --settings=build_type=%build_type% --settings=arch=%arch% +cd build +cmake .. -G "Visual Studio 17 2022" -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake -DUSE_ASAN=%ASAN_FLAG% +cmake --build . --config %build_type% + +popd From 411bbb2d36ad286483a4741b6ad38aadb66c83fe Mon Sep 17 00:00:00 2001 From: Eduardo Arias Date: Fri, 26 Apr 2024 10:28:39 -0300 Subject: [PATCH 15/20] Updated case of winsock header files - Address SonarCloud cpp:S3806 issues ("#include" paths should be portable) - This is not an actual issue in this case, because WinSock2.h and WS2tcpip.h are Windows only. --- src/operators/rbl.cc | 4 ++-- src/operators/rbl.h | 2 +- src/unique_id.cc | 2 +- src/utils/geo_lookup.cc | 2 +- src/utils/https_client.cc | 2 +- src/utils/ip_tree.cc | 2 +- src/utils/msc_tree.cc | 4 ++-- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/operators/rbl.cc b/src/operators/rbl.cc index eb7c3f0b44..f1561e7f08 100644 --- a/src/operators/rbl.cc +++ b/src/operators/rbl.cc @@ -22,8 +22,8 @@ #include #include #else -#include -#include +#include +#include #endif #include diff --git a/src/operators/rbl.h b/src/operators/rbl.h index 7f70b5997a..30fcaa3e37 100644 --- a/src/operators/rbl.h +++ b/src/operators/rbl.h @@ -23,7 +23,7 @@ #include #include #else -#include +#include #endif #include diff --git a/src/unique_id.cc b/src/unique_id.cc index c6234112cc..3106776e3a 100644 --- a/src/unique_id.cc +++ b/src/unique_id.cc @@ -18,7 +18,7 @@ #ifdef WIN32 #include "src/compat/msvc.h" -#include +#include #include #endif diff --git a/src/utils/geo_lookup.cc b/src/utils/geo_lookup.cc index e76e728dc9..4e06a1ec52 100644 --- a/src/utils/geo_lookup.cc +++ b/src/utils/geo_lookup.cc @@ -18,7 +18,7 @@ #include #include #else -#include +#include #endif #include diff --git a/src/utils/https_client.cc b/src/utils/https_client.cc index 66e818d4c3..f413e8ca07 100644 --- a/src/utils/https_client.cc +++ b/src/utils/https_client.cc @@ -26,7 +26,7 @@ #include #include #else -#include +#include #endif #include diff --git a/src/utils/ip_tree.cc b/src/utils/ip_tree.cc index c4c38f1cb5..124bc47f30 100644 --- a/src/utils/ip_tree.cc +++ b/src/utils/ip_tree.cc @@ -21,7 +21,7 @@ #include #include #else -#include +#include #endif #include diff --git a/src/utils/msc_tree.cc b/src/utils/msc_tree.cc index c2850750ec..27c7461c81 100644 --- a/src/utils/msc_tree.cc +++ b/src/utils/msc_tree.cc @@ -21,8 +21,8 @@ #include #else #include "src/compat/msvc.h" -#include -#include +#include +#include #endif #include "src/utils/msc_tree.h" From b69405a372dbcdf843b47c4901bca903f2ce2362 Mon Sep 17 00:00:00 2001 From: Eduardo Arias Date: Fri, 26 Apr 2024 10:35:01 -0300 Subject: [PATCH 16/20] Use default keyword to implement constructor/destructor - Addresses SonarCloud cpp:S3490 issue (Special member function should not be defined unless a non standard behavior is required) --- src/debug_log/debug_log_writer.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/debug_log/debug_log_writer.h b/src/debug_log/debug_log_writer.h index 698ff35874..46b641d686 100644 --- a/src/debug_log/debug_log_writer.h +++ b/src/debug_log/debug_log_writer.h @@ -37,8 +37,8 @@ class DebugLogWriter { static int open(const std::string& m_fileName, std::string *error); private: - DebugLogWriter() { } - ~DebugLogWriter() { } + DebugLogWriter() = default; + ~DebugLogWriter() = default; // C++ 03 // ======== From a8e132f3a18e24ba5f11d394d50cf65e2f88201d Mon Sep 17 00:00:00 2001 From: Eduardo Arias Date: Fri, 26 Apr 2024 10:50:24 -0300 Subject: [PATCH 17/20] Replaced the use of "new" in find_resource - Addresses SonarCloud issue cpp:S5025 (Memory should not be managed manually) - This function was not changed for the Windows port, but a similar change to the one suggested was done in expandEnv in the same file. - The first stream is not destructed at the exact same point it was in the previous code (but rather when the second stream replaces it on assignment to the same variable). An arbitrary scope could have been introduced to destruct the object at the same place, but it doesn't seem to be necessary and would make the code a bit strange. --- src/utils/system.cc | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/utils/system.cc b/src/utils/system.cc index ef4e506031..f48afb7d6b 100644 --- a/src/utils/system.cc +++ b/src/utils/system.cc @@ -98,19 +98,15 @@ double cpu_seconds(void) { std::string find_resource(const std::string& resource, const std::string& config, std::string *err) { - std::ifstream *iss; err->assign("Looking at: "); // Trying absolute or relative to the current dir. - iss = new std::ifstream(resource, std::ios::in); - if (iss->is_open()) { - iss->close(); - delete iss; + auto iss = std::ifstream(resource, std::ios::in); + if (iss.is_open()) { return resource; } else { err->append("'" + resource + "', "); } - delete iss; // What about `*' ? if (utils::expandEnv(resource, 0).size() > 0) { @@ -121,15 +117,12 @@ std::string find_resource(const std::string& resource, // Trying the same path of the configuration file. std::string f = get_path(config) + "/" + resource; - iss = new std::ifstream(f, std::ios::in); - if (iss->is_open()) { - iss->close(); - delete iss; + iss = std::ifstream(f, std::ios::in); + if (iss.is_open()) { return f; } else { err->append("'" + f + "', "); } - delete iss; // What about `*' ? if (utils::expandEnv(f, 0).size() > 0) { From e6e2989bd597a7bb454e975d371db46591a77b77 Mon Sep 17 00:00:00 2001 From: Eduardo Arias Date: Sat, 11 May 2024 14:52:32 +0000 Subject: [PATCH 18/20] Configure test fixture using CTest for Windows build - Added new test/test_suite.in with list of regression and unit tests previously in Makefile.am, to be shared between Unix and Windows builds. - Updated regression.cc & unit.cc to return the number of failed tests to indicate to CTest that the test failed. Similarly, a crash or unhandled exception terminates the process with a non-zero exit code. - This change doesn't affect running the tests with autotest in Unix builds because this processes test output from custom-test-driver & test-suite.sh, and ignores the exit code of the test runner. - Removed comment in test/test-cases/regression-offset-variable.json as this is not supported by JSON and prevents strict parsers to read and process the file. - Minor change in regression.cc's clearAuditLog to replace std::ifstream with std::ofstream as the mode to open the flag applies to an output stream. - Minor change in unit.cc to simplify code that deletes tests. - Minor changes to test/custom-test-driver to correct usage information. --- Makefile.am | 257 +----------------- build/win32/CMakeLists.txt | 33 +++ build/win32/docker/Dockerfile | 29 ++ test/custom-test-driver | 6 +- test/regression/regression.cc | 7 +- .../regression/offset-variable.json | 2 - test/test-suite.in | 255 +++++++++++++++++ test/test-suite.sh | 6 +- test/unit/unit.cc | 25 +- 9 files changed, 338 insertions(+), 282 deletions(-) create mode 100644 test/test-suite.in diff --git a/Makefile.am b/Makefile.am index 501fcfdf4a..5983c7c47e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -89,263 +89,8 @@ LOG_DRIVER = env $(SHELL) $(top_srcdir)/test/custom-test-driver AM_TESTS_ENVIRONMENT=AUTOMAKE_TESTS=true; export AUTOMAKE_TESTS; LOG_COMPILER=test/test-suite.sh -# for i in `find test/test-cases -iname *.json`; do echo TESTS+=$i; done TESTS= -TESTS+=test/test-cases/regression/action-allow.json -TESTS+=test/test-cases/regression/action-block.json -TESTS+=test/test-cases/regression/action-ctl_request_body_access.json -TESTS+=test/test-cases/regression/action-ctl_request_body_processor.json -TESTS+=test/test-cases/regression/action-ctl_request_body_processor_urlencoded.json -TESTS+=test/test-cases/regression/action-ctl_rule_engine.json -TESTS+=test/test-cases/regression/action-ctl_audit_engine.json -TESTS+=test/test-cases/regression/action-ctl_rule_remove_by_id.json -TESTS+=test/test-cases/regression/action-ctl_rule_remove_by_tag.json -TESTS+=test/test-cases/regression/action-ctl_rule_remove_target_by_id.json -TESTS+=test/test-cases/regression/action-ctl_rule_remove_target_by_tag.json -TESTS+=test/test-cases/regression/action-disruptive.json -TESTS+=test/test-cases/regression/action-exec.json -TESTS+=test/test-cases/regression/action-expirevar.json -TESTS+=test/test-cases/regression/action-id.json -TESTS+=test/test-cases/regression/action-initcol.json -TESTS+=test/test-cases/regression/action-msg.json -TESTS+=test/test-cases/regression/action-setenv.json -TESTS+=test/test-cases/regression/action-setrsc.json -TESTS+=test/test-cases/regression/action-setsid.json -TESTS+=test/test-cases/regression/action-setuid.json -TESTS+=test/test-cases/regression/actions.json -TESTS+=test/test-cases/regression/action-skip.json -TESTS+=test/test-cases/regression/action-tag.json -TESTS+=test/test-cases/regression/action-tnf-base64.json -TESTS+=test/test-cases/regression/action-xmlns.json -TESTS+=test/test-cases/regression/auditlog.json -TESTS+=test/test-cases/regression/collection-case-insensitive.json -TESTS+=test/test-cases/regression/collection-lua.json -TESTS+=test/test-cases/regression/collection-regular_expression_selection.json -TESTS+=test/test-cases/regression/collection-resource.json -TESTS+=test/test-cases/regression/collection-tx.json -TESTS+=test/test-cases/regression/collection-tx-with-macro.json -TESTS+=test/test-cases/regression/config-body_limits.json -TESTS+=test/test-cases/regression/config-calling_phases_by_name.json -TESTS+=test/test-cases/regression/config-include-bad.json -TESTS+=test/test-cases/regression/config-include.json -TESTS+=test/test-cases/regression/config-remove_by_id.json -TESTS+=test/test-cases/regression/config-remove_by_msg.json -TESTS+=test/test-cases/regression/config-remove_by_tag.json -TESTS+=test/test-cases/regression/config-response_type.json -TESTS+=test/test-cases/regression/config-secdefaultaction.json -TESTS+=test/test-cases/regression/config-secremoterules.json -TESTS+=test/test-cases/regression/config-update-action-by-id.json -TESTS+=test/test-cases/regression/config-update-target-by-id.json -TESTS+=test/test-cases/regression/config-update-target-by-msg.json -TESTS+=test/test-cases/regression/config-update-target-by-tag.json -TESTS+=test/test-cases/regression/config-xml_external_entity.json -TESTS+=test/test-cases/regression/debug_log.json -TESTS+=test/test-cases/regression/directive-sec_rule_script.json -TESTS+=test/test-cases/regression/issue-1152.json -TESTS+=test/test-cases/regression/issue-1528.json -TESTS+=test/test-cases/regression/issue-1565.json -TESTS+=test/test-cases/regression/issue-1576.json -TESTS+=test/test-cases/regression/issue-1591.json -TESTS+=test/test-cases/regression/issue-1725.json -TESTS+=test/test-cases/regression/issue-1743.json -TESTS+=test/test-cases/regression/issue-1785.json -TESTS+=test/test-cases/regression/issue-1812.json -TESTS+=test/test-cases/regression/issue-1831.json -TESTS+=test/test-cases/regression/issue-1844.json -TESTS+=test/test-cases/regression/issue-1850.json -TESTS+=test/test-cases/regression/issue-1941.json -TESTS+=test/test-cases/regression/issue-1943.json -TESTS+=test/test-cases/regression/issue-1956.json -TESTS+=test/test-cases/regression/issue-1960.json -TESTS+=test/test-cases/regression/issue-2099.json -TESTS+=test/test-cases/regression/issue-2000.json -TESTS+=test/test-cases/regression/issue-2111.json -TESTS+=test/test-cases/regression/issue-2196.json -TESTS+=test/test-cases/regression/issue-2423-msg-in-chain.json -TESTS+=test/test-cases/regression/issue-2427.json -TESTS+=test/test-cases/regression/issue-2296.json -TESTS+=test/test-cases/regression/issue-394.json -TESTS+=test/test-cases/regression/issue-849.json -TESTS+=test/test-cases/regression/issue-960.json -TESTS+=test/test-cases/regression/misc.json -TESTS+=test/test-cases/regression/misc-variable-under-quotes.json -TESTS+=test/test-cases/regression/offset-variable.json -TESTS+=test/test-cases/regression/operator-detectsqli.json -TESTS+=test/test-cases/regression/operator-detectxss.json -TESTS+=test/test-cases/regression/operator-fuzzyhash.json -TESTS+=test/test-cases/regression/operator-inpectFile.json -TESTS+=test/test-cases/regression/operator-ipMatchFromFile.json -TESTS+=test/test-cases/regression/operator-pm.json -TESTS+=test/test-cases/regression/operator-rx.json -TESTS+=test/test-cases/regression/operator-rxGlobal.json -TESTS+=test/test-cases/regression/operator-UnconditionalMatch.json -TESTS+=test/test-cases/regression/operator-validate-byte-range.json -TESTS+=test/test-cases/regression/operator-verifycc.json -TESTS+=test/test-cases/regression/operator-verifycpf.json -TESTS+=test/test-cases/regression/operator-verifyssn.json -TESTS+=test/test-cases/regression/operator-verifysvnr.json -TESTS+=test/test-cases/regression/request-body-parser-json.json -TESTS+=test/test-cases/regression/request-body-parser-multipart-crlf.json -TESTS+=test/test-cases/regression/request-body-parser-multipart.json -TESTS+=test/test-cases/regression/request-body-parser-xml.json -TESTS+=test/test-cases/regression/request-body-parser-xml-validade-dtd.json -TESTS+=test/test-cases/regression/rule-920120.json -TESTS+=test/test-cases/regression/rule-920200.json -TESTS+=test/test-cases/regression/rule-920274.json -TESTS+=test/test-cases/regression/secaction.json -TESTS+=test/test-cases/regression/secargumentslimit.json -TESTS+=test/test-cases/regression/sec_component_signature.json -TESTS+=test/test-cases/regression/secmarker.json -TESTS+=test/test-cases/regression/secruleengine.json -TESTS+=test/test-cases/regression/transformation-none.json -TESTS+=test/test-cases/regression/transformations.json -TESTS+=test/test-cases/regression/variable-ARGS_COMBINED_SIZE.json -TESTS+=test/test-cases/regression/variable-ARGS_GET.json -TESTS+=test/test-cases/regression/variable-ARGS_GET_NAMES.json -TESTS+=test/test-cases/regression/variable-ARGS.json -TESTS+=test/test-cases/regression/variable-ARGS_NAMES.json -TESTS+=test/test-cases/regression/variable-ARGS_POST.json -TESTS+=test/test-cases/regression/variable-ARGS_POST_NAMES.json -TESTS+=test/test-cases/regression/variable-AUTH_TYPE.json -TESTS+=test/test-cases/regression/variable-DURATION.json -TESTS+=test/test-cases/regression/variable-ENV.json -TESTS+=test/test-cases/regression/variable-FILES_COMBINED_SIZE.json -TESTS+=test/test-cases/regression/variable-FILES.json -TESTS+=test/test-cases/regression/variable-FILES_NAMES.json -TESTS+=test/test-cases/regression/variable-FILES_SIZES.json -TESTS+=test/test-cases/regression/variable-FULL_REQUEST.json -TESTS+=test/test-cases/regression/variable-FULL_REQUEST_LENGTH.json -TESTS+=test/test-cases/regression/variable-GEO.json -TESTS+=test/test-cases/regression/variable-HIGHEST_SEVERITY.json -TESTS+=test/test-cases/regression/variable-INBOUND_DATA_ERROR.json -TESTS+=test/test-cases/regression/variable-MATCHED_VAR.json -TESTS+=test/test-cases/regression/variable-MATCHED_VAR_NAME.json -TESTS+=test/test-cases/regression/variable-MATCHED_VARS.json -TESTS+=test/test-cases/regression/variable-MATCHED_VARS_NAMES.json -TESTS+=test/test-cases/regression/variable-MODSEC_BUILD.json -TESTS+=test/test-cases/regression/variable-MULTIPART_CRLF_LF_LINES.json -TESTS+=test/test-cases/regression/variable-MULTIPART_FILENAME.json -TESTS+=test/test-cases/regression/variable-MULTIPART_INVALID_HEADER_FOLDING.json -TESTS+=test/test-cases/regression/variable-MULTIPART_NAME.json -TESTS+=test/test-cases/regression/variable-MULTIPART_PART_HEADERS.json -TESTS+=test/test-cases/regression/variable-MULTIPART_STRICT_ERROR.json -TESTS+=test/test-cases/regression/variable-MULTIPART_UNMATCHED_BOUNDARY.json -TESTS+=test/test-cases/regression/variable-OUTBOUND_DATA_ERROR.json -TESTS+=test/test-cases/regression/variable-PATH_INFO.json -TESTS+=test/test-cases/regression/variable-QUERY_STRING.json -TESTS+=test/test-cases/regression/variable-REMOTE_ADDR.json -TESTS+=test/test-cases/regression/variable-REMOTE_HOST.json -TESTS+=test/test-cases/regression/variable-REMOTE_PORT.json -TESTS+=test/test-cases/regression/variable-REMOTE_USER.json -TESTS+=test/test-cases/regression/variable-REQBODY_PROCESSOR_ERROR.json -TESTS+=test/test-cases/regression/variable-REQBODY_PROCESSOR.json -TESTS+=test/test-cases/regression/variable-REQUEST_BASENAME.json -TESTS+=test/test-cases/regression/variable-REQUEST_BODY.json -TESTS+=test/test-cases/regression/variable-REQUEST_BODY_LENGTH.json -TESTS+=test/test-cases/regression/variable-REQUEST_COOKIES.json -TESTS+=test/test-cases/regression/variable-REQUEST_COOKIES_NAMES.json -TESTS+=test/test-cases/regression/variable-REQUEST_FILENAME.json -TESTS+=test/test-cases/regression/variable-REQUEST_HEADERS.json -TESTS+=test/test-cases/regression/variable-REQUEST_HEADERS_NAMES.json -TESTS+=test/test-cases/regression/variable-REQUEST_LINE.json -TESTS+=test/test-cases/regression/variable-REQUEST_METHOD.json -TESTS+=test/test-cases/regression/variable-REQUEST_PROTOCOL.json -TESTS+=test/test-cases/regression/variable-REQUEST_URI.json -TESTS+=test/test-cases/regression/variable-REQUEST_URI_RAW.json -TESTS+=test/test-cases/regression/variable-RESPONSE_BODY.json -TESTS+=test/test-cases/regression/variable-RESPONSE_CONTENT_LENGTH.json -TESTS+=test/test-cases/regression/variable-RESPONSE_CONTENT_TYPE.json -TESTS+=test/test-cases/regression/variable-RESPONSE_HEADERS.json -TESTS+=test/test-cases/regression/variable-RESPONSE_HEADERS_NAMES.json -TESTS+=test/test-cases/regression/variable-RESPONSE_PROTOCOL.json -TESTS+=test/test-cases/regression/variable-RULE.json -TESTS+=test/test-cases/regression/variable-SERVER_ADDR.json -TESTS+=test/test-cases/regression/variable-SERVER_NAME.json -TESTS+=test/test-cases/regression/variable-SERVER_PORT.json -TESTS+=test/test-cases/regression/variable-SESSIONID.json -TESTS+=test/test-cases/regression/variable-STATUS.json -TESTS+=test/test-cases/regression/variable-TIME_DAY.json -TESTS+=test/test-cases/regression/variable-TIME_EPOCH.json -TESTS+=test/test-cases/regression/variable-TIME_HOUR.json -TESTS+=test/test-cases/regression/variable-TIME.json -TESTS+=test/test-cases/regression/variable-TIME_MIN.json -TESTS+=test/test-cases/regression/variable-TIME_MON.json -TESTS+=test/test-cases/regression/variable-TIME_SEC.json -TESTS+=test/test-cases/regression/variable-TIME_WDAY.json -TESTS+=test/test-cases/regression/variable-TIME_YEAR.json -TESTS+=test/test-cases/regression/variable-TX.json -TESTS+=test/test-cases/regression/variable-UNIQUE_ID.json -TESTS+=test/test-cases/regression/variable-URLENCODED_ERROR.json -TESTS+=test/test-cases/regression/variable-USERID.json -TESTS+=test/test-cases/regression/variable-variation-count.json -TESTS+=test/test-cases/regression/variable-variation-exclusion.json -TESTS+=test/test-cases/regression/variable-WEBAPPID.json -TESTS+=test/test-cases/regression/variable-WEBSERVER_ERROR_LOG.json -TESTS+=test/test-cases/regression/variable-XML.json -TESTS+=test/test-cases/secrules-language-tests/operators/beginsWith.json -TESTS+=test/test-cases/secrules-language-tests/operators/contains.json -TESTS+=test/test-cases/secrules-language-tests/operators/containsWord.json -TESTS+=test/test-cases/secrules-language-tests/operators/detectSQLi.json -TESTS+=test/test-cases/secrules-language-tests/operators/detectXSS.json -TESTS+=test/test-cases/secrules-language-tests/operators/endsWith.json -TESTS+=test/test-cases/secrules-language-tests/operators/eq.json -TESTS+=test/test-cases/secrules-language-tests/operators/ge.json -TESTS+=test/test-cases/secrules-language-tests/operators/geoLookup.json -TESTS+=test/test-cases/secrules-language-tests/operators/gt.json -TESTS+=test/test-cases/secrules-language-tests/operators/ipMatch.json -TESTS+=test/test-cases/secrules-language-tests/operators/le.json -TESTS+=test/test-cases/secrules-language-tests/operators/lt.json -TESTS+=test/test-cases/secrules-language-tests/operators/noMatch.json -TESTS+=test/test-cases/secrules-language-tests/operators/pmFromFile.json -TESTS+=test/test-cases/secrules-language-tests/operators/pm.json -TESTS+=test/test-cases/secrules-language-tests/operators/rx.json -TESTS+=test/test-cases/secrules-language-tests/operators/rxGlobal.json -TESTS+=test/test-cases/secrules-language-tests/operators/streq.json -TESTS+=test/test-cases/secrules-language-tests/operators/strmatch.json -TESTS+=test/test-cases/secrules-language-tests/operators/unconditionalMatch.json -TESTS+=test/test-cases/secrules-language-tests/operators/validateByteRange.json -TESTS+=test/test-cases/secrules-language-tests/operators/validateUrlEncoding.json -TESTS+=test/test-cases/secrules-language-tests/operators/validateUtf8Encoding.json -TESTS+=test/test-cases/secrules-language-tests/operators/verifyCC.json -TESTS+=test/test-cases/secrules-language-tests/operators/verifycpf.json -TESTS+=test/test-cases/secrules-language-tests/operators/verifyssn.json -TESTS+=test/test-cases/secrules-language-tests/operators/verifysvnr.json -TESTS+=test/test-cases/secrules-language-tests/operators/within.json -TESTS+=test/test-cases/secrules-language-tests/transformations/base64DecodeExt.json -TESTS+=test/test-cases/secrules-language-tests/transformations/base64Decode.json -TESTS+=test/test-cases/secrules-language-tests/transformations/base64Encode.json -TESTS+=test/test-cases/secrules-language-tests/transformations/cmdLine.json -TESTS+=test/test-cases/secrules-language-tests/transformations/compressWhitespace.json -TESTS+=test/test-cases/secrules-language-tests/transformations/cssDecode.json -TESTS+=test/test-cases/secrules-language-tests/transformations/escapeSeqDecode.json -TESTS+=test/test-cases/secrules-language-tests/transformations/hexDecode.json -TESTS+=test/test-cases/secrules-language-tests/transformations/hexEncode.json -TESTS+=test/test-cases/secrules-language-tests/transformations/htmlEntityDecode.json -TESTS+=test/test-cases/secrules-language-tests/transformations/jsDecode.json -TESTS+=test/test-cases/secrules-language-tests/transformations/length.json -TESTS+=test/test-cases/secrules-language-tests/transformations/lowercase.json -TESTS+=test/test-cases/secrules-language-tests/transformations/md5.json -TESTS+=test/test-cases/secrules-language-tests/transformations/normalisePath.json -TESTS+=test/test-cases/secrules-language-tests/transformations/normalisePathWin.json -TESTS+=test/test-cases/secrules-language-tests/transformations/parityEven7bit.json -TESTS+=test/test-cases/secrules-language-tests/transformations/parityOdd7bit.json -TESTS+=test/test-cases/secrules-language-tests/transformations/parityZero7bit.json -TESTS+=test/test-cases/secrules-language-tests/transformations/removeCommentsChar.json -TESTS+=test/test-cases/secrules-language-tests/transformations/removeComments.json -TESTS+=test/test-cases/secrules-language-tests/transformations/removeNulls.json -TESTS+=test/test-cases/secrules-language-tests/transformations/removeWhitespace.json -TESTS+=test/test-cases/secrules-language-tests/transformations/replaceComments.json -TESTS+=test/test-cases/secrules-language-tests/transformations/replaceNulls.json -TESTS+=test/test-cases/secrules-language-tests/transformations/sha1.json -TESTS+=test/test-cases/secrules-language-tests/transformations/sqlHexDecode.json -TESTS+=test/test-cases/secrules-language-tests/transformations/trim.json -TESTS+=test/test-cases/secrules-language-tests/transformations/trimLeft.json -TESTS+=test/test-cases/secrules-language-tests/transformations/trimRight.json -TESTS+=test/test-cases/secrules-language-tests/transformations/urlDecode.json -TESTS+=test/test-cases/secrules-language-tests/transformations/urlDecodeUni.json -TESTS+=test/test-cases/secrules-language-tests/transformations/urlEncode.json -TESTS+=test/test-cases/secrules-language-tests/transformations/utf8toUnicode.json - +include test/test-suite.in pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = modsecurity.pc diff --git a/build/win32/CMakeLists.txt b/build/win32/CMakeLists.txt index f7bad3baae..9b52926c19 100644 --- a/build/win32/CMakeLists.txt +++ b/build/win32/CMakeLists.txt @@ -151,6 +151,39 @@ add_regression_test_capability(WITH_CURL HAVE_CURL) add_regression_test_capability(WITH_LMDB HAVE_LMDB) add_regression_test_capability(WITH_MAXMIND HAVE_MAXMIND) +enable_testing() + +file(READ ${BASE_DIR}/test/test-suite.in TEST_FILES_RAW) +string(REPLACE "\n" ";" TEST_FILES ${TEST_FILES_RAW}) + +foreach(TEST_FILE ${TEST_FILES}) + # ignore comment lines + string(FIND ${TEST_FILE} "#" is_comment) + if(NOT is_comment EQUAL 0) + string(FIND ${TEST_FILE} "TESTS+=" is_valid_prefix) + if(NOT is_valid_prefix EQUAL 0) + message(FATAL_ERROR "Invalid prefix in line: ${TEST_FILE}") + endif() + + # remove 'TESTS+=' prefix and 'test/' too because tests are launched + # from that directory + string(SUBSTRING ${TEST_FILE} 12 -1 TEST_FILE) + + # test name + get_filename_component(TEST_NAME ${TEST_FILE} NAME_WE) + + # determine test runner based on test path prefix + string(FIND ${TEST_FILE} "test-cases/regression/" is_regression_test) + if(is_regression_test EQUAL 0) + set(TEST_RUNNER "regression_tests") + else() + set(TEST_RUNNER "unit_tests") + endif() + + add_test(NAME ${TEST_NAME} COMMAND ${TEST_RUNNER} ${TEST_FILE} WORKING_DIRECTORY ${BASE_DIR}/test) + endif() +endforeach() + # benchmark add_executable(benchmark ${BASE_DIR}/test/benchmark/benchmark.cc) setTestTargetProperties(benchmark) diff --git a/build/win32/docker/Dockerfile b/build/win32/docker/Dockerfile index 4967226e00..1dc6048275 100644 --- a/build/win32/docker/Dockerfile +++ b/build/win32/docker/Dockerfile @@ -77,6 +77,35 @@ ARG USE_ASAN= RUN C:\BuildTools\VC\Auxiliary\Build\vcvars64.bat && vcbuild.bat %BUILD_TYPE% %ARCH% %USE_ASAN% +# test suite +# + +# setup test environment +RUN cmd.exe /C md \tmp +RUN cmd.exe /C md \bin +RUN cmd.exe /C copy "C:\Program Files\GIT\usr\bin" \bin > NUL +RUN cmd.exe /C copy "C:\Program Files\GIT\usr\bin\echo.exe" \bin\echo > NUL + +# disable tests that don't work on windows +ARG JQ_VERSION=1.7.1 +ARG JQ_BINARY=jq-windows-amd64.exe +ARG JQ_URL=https://github.com/jqlang/jq/releases/download/jq-${JQ_VERSION}/${JQ_BINARY} + +ARG JQ_BIN=C:\TEMP\jq.exe +ADD ${JQ_URL} ${JQ_BIN} + +WORKDIR ${MOD_SECURITY_DIR}\test\test-cases\regression + +RUN %JQ_BIN% "map(if .title == \"Test match variable (1/n)\" then .enabled = 0 else . end)" issue-2423-msg-in-chain.json > tmp.json && move /Y tmp.json issue-2423-msg-in-chain.json +RUN %JQ_BIN% "map(if .title == \"Test match variable (2/n)\" then .enabled = 0 else . end)" issue-2423-msg-in-chain.json > tmp.json && move /Y tmp.json issue-2423-msg-in-chain.json +RUN %JQ_BIN% "map(if .title == \"Test match variable (3/n)\" then .enabled = 0 else . end)" issue-2423-msg-in-chain.json > tmp.json && move /Y tmp.json issue-2423-msg-in-chain.json +RUN %JQ_BIN% "map(if .title == \"Variable offset - FILES_NAMES\" then .enabled = 0 else . end)" offset-variable.json > tmp.json && move /Y tmp.json offset-variable.json + +# run tests +WORKDIR ${MOD_SECURITY_DIR}\build\win32\build + +RUN C:\BuildTools\VC\Auxiliary\Build\vcvars64.bat && ctest -C %BUILD_TYPE% --output-on-failure + # setup container's entrypoint # diff --git a/test/custom-test-driver b/test/custom-test-driver index aa490ee690..d9b0d0ff6f 100755 --- a/test/custom-test-driver +++ b/test/custom-test-driver @@ -42,9 +42,9 @@ print_usage () { cat <print() << std::endl; } + const int skp = std::count_if(results.cbegin(), results.cend(), [](const auto &i) + { return i->skipped; }); + const int failed = results.size() - skp; + if (!test.m_automake_output) { std::cout << std::endl; @@ -202,13 +206,7 @@ int main(int argc, char **argv) { if (results.size() == 0) { std::cout << KGRN << "All tests passed" << RESET << std::endl; } else { - int skp = 0; - for (const auto &i : results) { - if (i->skipped == true) { - skp++; - } - } - std::cout << KRED << results.size()-skp << " failed."; + std::cout << KRED << failed << " failed."; std::cout << RESET << std::endl; if (skp > 0) { std::cout << " " << std::to_string(skp) << " "; @@ -217,13 +215,12 @@ int main(int argc, char **argv) { } } - for (std::pair *> a : test) { - std::vector *vec = a.second; - for (int i = 0; i < vec->size(); i++) { - delete vec->at(i); - } + for (auto a : test) { + auto *vec = a.second; + for(auto *t : *vec) + delete t; delete vec; } -} - + return failed; +} From 6bf78f2560fc60e47f0753b4db2aa8727d22c798 Mon Sep 17 00:00:00 2001 From: Eduardo Arias Date: Mon, 13 May 2024 13:37:24 -0700 Subject: [PATCH 19/20] Added GitHub workflow to build libModSecurity on Windows. --- .github/workflows/ci.yml | 46 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3350743b16..110bfa693f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,3 +72,49 @@ jobs: run: make -j `sysctl -n hw.logicalcpu` - name: check run: make check + + build-windows: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-2022] + platform: [x86_64] + configuration: [Release] + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install Conan + run: | + pip3 install conan --upgrade + conan profile detect + - uses: ammaraskar/msvc-problem-matcher@master + - name: Build ${{ matrix.configuration }} ${{ matrix.platform }} + shell: cmd + run: vcbuild.bat ${{ matrix.configuration }} ${{ matrix.platform }} + - name: Set up test environment + working-directory: build\win32\build\${{ matrix.configuration }} + env: + BASE_DIR: ..\..\..\.. + shell: cmd + run: | + copy unit_tests.exe %BASE_DIR%\test + copy regression_tests.exe %BASE_DIR%\test + copy libModSecurity.dll %BASE_DIR%\test + copy %BASE_DIR%\unicode.mapping %BASE_DIR%\test + md \tmp + md \bin + copy "C:\Program Files\Git\usr\bin\echo.exe" \bin + copy "C:\Program Files\Git\usr\bin\echo.exe" \bin\echo + - name: Disable tests that don't work on Windows + working-directory: test\test-cases\regression + shell: cmd + run: | + jq "map(if .title == \"Test match variable (1/n)\" then .enabled = 0 else . end)" issue-2423-msg-in-chain.json > tmp.json && move /Y tmp.json issue-2423-msg-in-chain.json + jq "map(if .title == \"Test match variable (2/n)\" then .enabled = 0 else . end)" issue-2423-msg-in-chain.json > tmp.json && move /Y tmp.json issue-2423-msg-in-chain.json + jq "map(if .title == \"Test match variable (3/n)\" then .enabled = 0 else . end)" issue-2423-msg-in-chain.json > tmp.json && move /Y tmp.json issue-2423-msg-in-chain.json + jq "map(if .title == \"Variable offset - FILES_NAMES\" then .enabled = 0 else . end)" offset-variable.json > tmp.json && move /Y tmp.json offset-variable.json + - name: Run tests + working-directory: build\win32\build + run: | + ctest -C ${{ matrix.configuration }} --output-on-failure From 1b2de5a5d3cf8231977cb2c87bced2fee7f37ede Mon Sep 17 00:00:00 2001 From: Eduardo Arias Date: Tue, 14 May 2024 13:42:20 -0700 Subject: [PATCH 20/20] Add support to turn 3rd party dependencies off - By default, all the 3rd party dependencies are enabled. - A dependency can be turned off by adding the "-DWITHOUT_xxx=ON" to the call of vcbuild.bat - List of 3rd party dependencies and associated option to turn them off: - LMDB: WITHOUT_LMDB - LUA: WITHOUT_LUA - LibXML2: WITHOUT_LIBXML2 - MaxMind: WITHOUT_MAXMIND - cURL: WITHOUT_CURL --- .github/workflows/ci.yml | 10 ++++++++-- build/win32/CMakeLists.txt | 31 +++++++++++++++++++++++-------- vcbuild.bat | 2 +- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 110bfa693f..34f345d696 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,6 +80,12 @@ jobs: os: [windows-2022] platform: [x86_64] configuration: [Release] + configure: + - {label: "full", opt: "" } + - {label: "wo lmdb", opt: "-DWITHOUT_LMDB=ON" } + - {label: "wo lua", opt: "-DWITHOUT_LUA=ON" } + - {label: "wo maxmind", opt: "-DWITHOUT_MAXMIND=ON" } + - {label: "wo curl", opt: "-DWITHOUT_CURL=ON" } steps: - uses: actions/checkout@v4 with: @@ -89,9 +95,9 @@ jobs: pip3 install conan --upgrade conan profile detect - uses: ammaraskar/msvc-problem-matcher@master - - name: Build ${{ matrix.configuration }} ${{ matrix.platform }} + - name: Build ${{ matrix.configuration }} ${{ matrix.platform }} ${{ matrix.configure.label }} shell: cmd - run: vcbuild.bat ${{ matrix.configuration }} ${{ matrix.platform }} + run: vcbuild.bat ${{ matrix.configuration }} ${{ matrix.platform }} NO_ASAN "${{ matrix.configure.opt }}" - name: Set up test environment working-directory: build\win32\build\${{ matrix.configuration }} env: diff --git a/build/win32/CMakeLists.txt b/build/win32/CMakeLists.txt index 9b52926c19..20cba66ccb 100644 --- a/build/win32/CMakeLists.txt +++ b/build/win32/CMakeLists.txt @@ -1,8 +1,14 @@ -cmake_minimum_required(VERSION 3.15) +cmake_minimum_required(VERSION 3.24) set(BASE_DIR ${CMAKE_CURRENT_LIST_DIR}/../..) -set(USE_ASAN OFF CACHE BOOL "Build with Address Sanitizer") +option(WITHOUT_LMDB "Include LMDB support" OFF) +option(WITHOUT_LUA "Include LUA support" OFF) +option(WITHOUT_LIBXML2 "Include LibXML2 support" OFF) +option(WITHOUT_MAXMIND "Include MaxMind support" OFF) +option(WITHOUT_CURL "Include CURL support" OFF) + +option(USE_ASAN "Build with Address Sanitizer" OFF) # common compiler settings @@ -55,14 +61,23 @@ set(PACKAGE_VERSION "${PROJECT_VERSION}") set(PACKAGE_STRING "${PACKAGE_NAME} ${PACKAGE_VERSION}") set(PACKAGE_TARNAME "${PACKAGE_NAME}") +set(HAVE_YAJL 1) # should always be one, mandatory dependency set(HAVE_GEOIP 0) # should always be zero, no conan package available -set(HAVE_LMDB 1) -set(HAVE_LUA 1) -set(HAVE_LIBXML2 1) -set(HAVE_MAXMIND 1) set(HAVE_SSDEEP 0) # should always be zero, no conan package available -set(HAVE_YAJL 1) # should always be one, mandatory dependency -set(HAVE_CURL 1) + +macro(enable_feature flag option) + if(${option}) + set(${flag} 0) + else() + set(${flag} 1) + endif() +endmacro() + +enable_feature(HAVE_LMDB ${WITHOUT_LMDB}) +enable_feature(HAVE_LUA ${WITHOUT_LUA}) +enable_feature(HAVE_LIBXML2 ${WITHOUT_LIBXML2}) +enable_feature(HAVE_MAXMIND ${WITHOUT_MAXMIND}) +enable_feature(HAVE_CURL ${WITHOUT_CURL}) include(${CMAKE_CURRENT_LIST_DIR}/ConfigureChecks.cmake) diff --git a/vcbuild.bat b/vcbuild.bat index 0789c9f81f..b24572abae 100644 --- a/vcbuild.bat +++ b/vcbuild.bat @@ -22,7 +22,7 @@ if "%3"=="USE_ASAN" ( cd build\win32 conan install . -s compiler.cppstd=17 %CI_ASAN% --output-folder=build --build=missing --settings=build_type=%build_type% --settings=arch=%arch% cd build -cmake .. -G "Visual Studio 17 2022" -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake -DUSE_ASAN=%ASAN_FLAG% +cmake --fresh .. -G "Visual Studio 17 2022" -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake -DUSE_ASAN=%ASAN_FLAG% %4 %5 %6 %7 %8 %9 cmake --build . --config %build_type% popd