From 6487590b4fd55dddd59b43f1fcf2ebd8d56f20ac Mon Sep 17 00:00:00 2001 From: jvoisin Date: Wed, 20 Sep 2017 10:51:22 +0200 Subject: Add travis --- .travis.yml | 23 +++++++++++++ Makefile | 2 +- src/config.m4 | 4 +++ src/php_snuffleupagus.h | 14 ++++++++ src/sp_config.c | 2 +- src/sp_cookie_encryption.c | 4 +-- src/sp_disabled_functions.c | 5 ++- src/sp_execute.c | 6 ++-- src/sp_unserialize.c | 6 ++-- src/sp_upload_validation.c | 2 +- src/sp_utils.c | 55 +++++++++++++++++------------- src/sp_utils.h | 12 +++---- src/tests/config/dump_request.ini | 2 +- src/tests/disable_xxe_dom.phpt | 1 - src/tests/disable_xxe_dom_disabled.phpt | 1 - src/tests/disable_xxe_simplexml.phpt | 1 - src/tests/disable_xxe_simplexml_oop.phpt | 1 - src/tests/disable_xxe_xml_parse.phpt | 1 - src/tests/disabled_functions_ret3.phpt | 1 + src/tests/disabled_functions_ret_type.phpt | 8 ++--- src/tests/disabled_option.phpt | 14 +++++--- src/tests/dump_request.phpt | 10 +++--- src/tests/dump_request_too_big.phpt | 8 ++--- 23 files changed, 119 insertions(+), 64 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..057c080 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,23 @@ +language: php + +php: + - '7.0' + - '7.1' + +env: + - CC=gcc + - CC=clang + +addons: + apt: + packages: + - gdb + - gcc + - lcov + - valgrind + - libpcre3 + - libpcre3-dev + +script: + - php-config --configure-options + - make debug diff --git a/Makefile b/Makefile index 869f9fb..040809e 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ clean: debug: cd src; phpize - export CFLAGS="-Wall -Wextra -g3 -ggdb -Wno-unused-function"; cd src; ./configure --enable-snuffleupagus --enable-debug + export CFLAGS="-Wall -Wextra -g3 -ggdb -O1 -g -Wno-unused-function"; cd src; ./configure --enable-snuffleupagus --enable-debug make -C src TEST_PHP_ARGS='-q' REPORT_EXIT_STATUS=1 make -C src test diff --git a/src/config.m4 b/src/config.m4 index aba355c..84ca2f4 100644 --- a/src/config.m4 +++ b/src/config.m4 @@ -20,10 +20,14 @@ CFLAGS="$CFLAGS -lpcre" CFLAGS="$CFLAGS -D_DEFAULT_SOURCE=1 -std=c99" CFLAGS="$CFLAGS -Wall -Wextra -Wno-unused-parameter" +LFLAGS="$LFLAGS -lpcre" + if test "$PHP_DEBUG" = "yes"; then AC_DEFINE(SP_DEBUG, 1, [Wether you want to enable debug messages]) fi +AC_CHECK_LIB(pcre, pcre_compile, AC_DEFINE(HAVE_PCRE, 1, [have pcre])) + if test "$PHP_SNUFFLEUPAGUS" != "no"; then if test "$PHP_COVERAGE" != "no"; then CFLAGS="$CFLAGS --coverage -fprofile-arcs -ftest-coverage" diff --git a/src/php_snuffleupagus.h b/src/php_snuffleupagus.h index e7a3d59..9f85cc1 100644 --- a/src/php_snuffleupagus.h +++ b/src/php_snuffleupagus.h @@ -16,6 +16,7 @@ #include "SAPI.h" #include "ext/standard/info.h" +#include "ext/pcre/php_pcre.h" #include "php.h" #include "php_ini.h" #include "zend_hash.h" @@ -37,6 +38,7 @@ #include "sp_upload_validation.h" #include "sp_utils.h" + extern zend_module_entry snuffleupagus_module_entry; #define phpext_snuffleupagus_ptr &snuffleupagus_module_entry @@ -64,6 +66,18 @@ ZEND_END_MODULE_GLOBALS(snuffleupagus) ZEND_TSRMLS_CACHE_EXTERN() #endif +#if HAVE_BUNDLED_PCRE + #include "ext/pcre/pcrelib/pcre.h" + #undef pcre_exec + #undef pcre_compile + #define sp_pcre_exec pcre_exec + #define sp_pcre_compile pcre_compile +#else + #include "pcre.h" + #define sp_pcre_exec pcre_exec + #define sp_pcre_compile pcre_compile +#endif + PHP_FUNCTION(check_disabled_function); static inline void sp_terminate() { zend_bailout(); } diff --git a/src/sp_config.c b/src/sp_config.c index f73347d..37a34b9 100644 --- a/src/sp_config.c +++ b/src/sp_config.c @@ -145,7 +145,7 @@ int parse_regexp(char *restrict line, char *restrict keyword, void *retval) { if (value) { const char *pcre_error; int pcre_error_offset; - pcre *compiled_re = pcre_compile(value, PCRE_CASELESS, &pcre_error, + pcre *compiled_re = sp_pcre_compile(value, PCRE_CASELESS, &pcre_error, &pcre_error_offset, NULL); if (NULL == compiled_re) { sp_log_err("config", "Failed to compile '%s': %s.", value, pcre_error); diff --git a/src/sp_cookie_encryption.c b/src/sp_cookie_encryption.c index 5248486..ad8438a 100644 --- a/src/sp_cookie_encryption.c +++ b/src/sp_cookie_encryption.c @@ -63,7 +63,7 @@ int decrypt_cookie(zval *pDest, int num_args, va_list args, if (value_len < crypto_secretbox_NONCEBYTES + crypto_secretbox_ZEROBYTES) { - sp_log_msg("cookie_encryption", LOG_DROP, + sp_log_msg("cookie_encryption", SP_LOG_DROP, "Buffer underflow tentative detected in cookie encryption handling."); return ZEND_HASH_APPLY_REMOVE; } @@ -77,7 +77,7 @@ int decrypt_cookie(zval *pDest, int num_args, va_list args, (unsigned char *)ZSTR_VAL(debase64), key); if (ret == -1) { - sp_log_msg("cookie_encryption", LOG_DROP, + sp_log_msg("cookie_encryption", SP_LOG_DROP, "Something went wrong with the decryption of %s.", ZSTR_VAL(hash_key->key)); return ZEND_HASH_APPLY_REMOVE; diff --git a/src/sp_disabled_functions.c b/src/sp_disabled_functions.c index 55d782b..b3e5cbc 100644 --- a/src/sp_disabled_functions.c +++ b/src/sp_disabled_functions.c @@ -271,7 +271,7 @@ static bool should_drop_on_ret(zval* return_value, } } - ret_value_str = sp_convert_to_string(return_value); // FIXME memleak + ret_value_str = sp_convert_to_string(return_value); bool match_type = (config_node->ret_type) && (config_node->ret_type == Z_TYPE_P(return_value)); @@ -282,15 +282,18 @@ static bool should_drop_on_ret(zval* return_value, if (true == match_type || match_value) { if (true == config_node->allow) { efree(complete_path_function); + efree(ret_value_str); return false; } sp_log_disable_ret(complete_path_function, ret_value_str, config_node); if (false == config_node->simulation) { efree(complete_path_function); + efree(ret_value_str); return true; } } next: + efree(ret_value_str); config = config->next; } efree(complete_path_function); diff --git a/src/sp_execute.c b/src/sp_execute.c index faf126c..7d62e88 100644 --- a/src/sp_execute.c +++ b/src/sp_execute.c @@ -13,10 +13,10 @@ static int (*orig_zend_stream_open)(const char *filename, ZEND_COLD static inline void terminate_if_writable(const char *filename) { if (0 == access(filename, W_OK)) { if (true == SNUFFLEUPAGUS_G(config).config_readonly_exec->simulation) { - sp_log_msg("readonly_exec", LOG_NOTICE, + sp_log_msg("readonly_exec", SP_LOG_NOTICE, "Attempted execution of a writable file (%s).", filename); } else { - sp_log_msg("readonly_exec", LOG_DROP, + sp_log_msg("readonly_exec", SP_LOG_DROP, "Attempted execution of a writable file (%s).", filename); sp_terminate(); } @@ -37,7 +37,7 @@ static void check_inclusion_regexp(const char * const filename) { while (config) { pcre *config_node = (pcre*)(config->data); if (false == is_regexp_matching(config_node, filename)) { - sp_log_msg("include", LOG_DROP, "Inclusion of a forbidden file (%s).", filename); + sp_log_msg("include", SP_LOG_DROP, "Inclusion of a forbidden file (%s).", filename); sp_terminate(); } config = config->next; diff --git a/src/sp_unserialize.c b/src/sp_unserialize.c index b5b67b4..c8503de 100644 --- a/src/sp_unserialize.c +++ b/src/sp_unserialize.c @@ -57,7 +57,7 @@ PHP_FUNCTION(sp_unserialize) { /* 64 is the length of HMAC-256 */ if (buf_len < 64) { - sp_log_msg("unserialize", LOG_DROP, "The serialized object is too small."); + sp_log_msg("unserialize", SP_LOG_DROP, "The serialized object is too small."); RETURN_FALSE; } @@ -88,13 +88,13 @@ PHP_FUNCTION(sp_unserialize) { } } else { if ( true == SNUFFLEUPAGUS_G(config).config_unserialize->simulation) { - sp_log_msg("unserialize", LOG_NOTICE, "Invalid HMAC for %s", serialized_str); + sp_log_msg("unserialize", SP_LOG_NOTICE, "Invalid HMAC for %s", serialized_str); if ((orig_handler = zend_hash_str_find_ptr(SNUFFLEUPAGUS_G(sp_internal_functions_hook), "unserialize", 11))) { orig_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU); } } else { - sp_log_msg("unserialize", LOG_DROP, "Invalid HMAC for %s", serialized_str); + sp_log_msg("unserialize", SP_LOG_DROP, "Invalid HMAC for %s", serialized_str); } } efree(serialized_str); diff --git a/src/sp_upload_validation.c b/src/sp_upload_validation.c index bbd7eae..6655e11 100644 --- a/src/sp_upload_validation.c +++ b/src/sp_upload_validation.c @@ -79,7 +79,7 @@ int sp_rfc1867_callback(unsigned int event, void *event_data, void **extra) { if (WEXITSTATUS(waitstatus) != 0) { // Nope char *uri = sp_getenv("REQUEST_URI"); int sim = SNUFFLEUPAGUS_G(config).config_upload_validation->simulation; - sp_log_msg("upload_valiation", sim?LOG_NOTICE:LOG_DROP, + sp_log_msg("upload_valiation", sim?SP_LOG_NOTICE:SP_LOG_DROP, "The upload of %s on %s was rejected.", filename, uri?uri:"?"); if (!SNUFFLEUPAGUS_G(config).config_upload_validation->simulation) { zend_bailout(); diff --git a/src/sp_utils.c b/src/sp_utils.c index 087f431..56512df 100644 --- a/src/sp_utils.c +++ b/src/sp_utils.c @@ -201,7 +201,7 @@ bool sp_match_value(const char* value, const char* to_match, const pcre* rx) { } } else if (rx) { int substrvec[30]; - int ret = pcre_exec(rx, NULL, value, strlen(value), 0, 0, substrvec, 30); + int ret = sp_pcre_exec(rx, NULL, value, strlen(value), 0, 0, substrvec, 30); if (ret < 0) { if (ret != PCRE_ERROR_NOMATCH) { @@ -223,14 +223,14 @@ void sp_log_disable(const char* restrict path, const char* restrict arg_name, const int sim = config_node->simulation; if (arg_name) { if (alias) { - sp_log_msg("disabled_function", sim?LOG_NOTICE:LOG_DROP, + sp_log_msg("disabled_function", sim?SP_LOG_NOTICE:SP_LOG_DROP, "The call to the function '%s' in %s:%d has been disabled, " "because its argument '%s' content (%s) matched the rule '%s'.", path, zend_get_executed_filename(TSRMLS_C), zend_get_executed_lineno(TSRMLS_C), arg_name, arg_value?arg_value:"?", alias); } else { - sp_log_msg("disabled_function", sim?LOG_NOTICE:LOG_DROP, + sp_log_msg("disabled_function", sim?SP_LOG_NOTICE:SP_LOG_DROP, "The call to the function '%s' in %s:%d has been disabled, " "because its argument '%s' content (%s) matched a rule.", path, zend_get_executed_filename(TSRMLS_C), @@ -239,13 +239,13 @@ void sp_log_disable(const char* restrict path, const char* restrict arg_name, } } else { if (alias) { - sp_log_msg("disabled_function", sim?LOG_NOTICE:LOG_DROP, + sp_log_msg("disabled_function", sim?SP_LOG_NOTICE:SP_LOG_DROP, "The call to the function '%s' in %s:%d has been disabled, " "because of the the rule '%s'.",path, zend_get_executed_filename(TSRMLS_C), zend_get_executed_lineno(TSRMLS_C), alias); } else { - sp_log_msg("disabled_function", sim?LOG_NOTICE:LOG_DROP, + sp_log_msg("disabled_function", sim?SP_LOG_NOTICE:SP_LOG_DROP, "The call to the function '%s' in %s:%d has been disabled.", path, zend_get_executed_filename(TSRMLS_C), zend_get_executed_lineno(TSRMLS_C)); @@ -263,13 +263,13 @@ void sp_log_disable_ret(const char* restrict path, const char* alias = config_node->alias; const int sim = config_node->simulation; if (alias) { - sp_log_msg("disabled_function", sim?LOG_NOTICE:LOG_DROP, + sp_log_msg("disabled_function", sim?SP_LOG_NOTICE:SP_LOG_DROP, "The execution has been aborted in %s:%d, " "because the function '%s' returned '%s', which matched the rule '%s'.", zend_get_executed_filename(TSRMLS_C), zend_get_executed_lineno(TSRMLS_C), path, ret_value?ret_value:"?", alias); } else { - sp_log_msg("disabled_function", sim?LOG_NOTICE:LOG_DROP, + sp_log_msg("disabled_function", sim?SP_LOG_NOTICE:SP_LOG_DROP, "The execution has been aborted in %s:%d, " "because the return value (%s) of the function '%s' matched a rule.", zend_get_executed_filename(TSRMLS_C), @@ -349,7 +349,7 @@ zend_always_inline char* sp_getenv(char* var) { zend_always_inline int is_regexp_matching(const pcre* regexp, const char* str) { int vec[30]; - int ret = pcre_exec(regexp, NULL, str, strlen(str), 0, 0, vec, sizeof(vec)); + int ret = sp_pcre_exec(regexp, NULL, str, strlen(str), 0, 0, vec, sizeof(vec)); if (ret < 0) { if (ret != PCRE_ERROR_NOMATCH) { sp_log_err("regexp", "Something went wrong with a regexp."); @@ -367,22 +367,14 @@ int hook_function(const char* original_name, HashTable* hook_table, /* The `mb` module likes to hook functions, like strlen->mb_strlen, * so we have to hook both of them. */ - if (0 == strncmp(original_name, "mb_", 3)) { - CG(compiler_options) |= ZEND_COMPILE_NO_BUILTIN_STRLEN; - if (zend_hash_str_find(ht, - VAR_AND_LEN(original_name + 3))) { - hook_function(original_name + 3, hook_table, new_function, hook_execution_table); - } - } else { // TODO this can be moved somewhere else to gain some marginal perfs - CG(compiler_options) |= ZEND_COMPILE_NO_BUILTIN_STRLEN; - char* mb_name = pecalloc(strlen(original_name) + 3 + 1, 1, 0); - memcpy(mb_name, "mb_", 3); - memcpy(mb_name + 3, VAR_AND_LEN(original_name)); - if (zend_hash_str_find(CG(function_table), VAR_AND_LEN(mb_name))) { - hook_function(mb_name, hook_table, new_function, hook_execution_table); + + if ((func = zend_hash_str_find_ptr(ht, + VAR_AND_LEN(original_name)))) { + if (func->handler == new_function) { + return SUCCESS; } } - + if ((func = zend_hash_str_find_ptr(CG(function_table), VAR_AND_LEN(original_name)))) { if (func->handler == new_function) { @@ -397,6 +389,23 @@ int hook_function(const char* original_name, HashTable* hook_table, func->handler = new_function; } } + + if (0 == strncmp(original_name, "mb_", 3)) { + CG(compiler_options) |= ZEND_COMPILE_NO_BUILTIN_STRLEN; + if (zend_hash_str_find(ht, + VAR_AND_LEN(original_name + 3))) { + hook_function(original_name + 3, hook_table, new_function, hook_execution_table); + } + } else { // TODO this can be moved somewhere else to gain some marginal perfs + CG(compiler_options) |= ZEND_COMPILE_NO_BUILTIN_STRLEN; + char* mb_name = pecalloc(strlen(original_name) + 3 + 1, 1, 0); + memcpy(mb_name, "mb_", 3); + memcpy(mb_name + 3, VAR_AND_LEN(original_name)); + if (zend_hash_str_find(CG(function_table), VAR_AND_LEN(mb_name))) { + hook_function(mb_name, hook_table, new_function, hook_execution_table); + } + } + return SUCCESS; } @@ -409,7 +418,7 @@ int hook_regexp(const pcre* regexp, HashTable* hook_table, ZEND_HASH_FOREACH_STR_KEY(ht, key) { if (key) { int vec[30]; - int ret = pcre_exec(regexp, NULL, key->val, key->len, 0, 0, vec, 30); + int ret = sp_pcre_exec(regexp, NULL, key->val, key->len, 0, 0, vec, 30); if (ret < 0) { /* Error or no match*/ if (PCRE_ERROR_NOMATCH != ret) { sp_log_err("pcre", "Runtime error with pcre, error code: %d", ret); diff --git a/src/sp_utils.h b/src/sp_utils.h index 37dd2c0..3b14205 100644 --- a/src/sp_utils.h +++ b/src/sp_utils.h @@ -35,14 +35,14 @@ #define HOOK_FUNCTION_BY_REGEXP(regexp, hook_table, new_function, execution) \ hook_regexp(regexp, SNUFFLEUPAGUS_G(hook_table), new_function, execution) -#define LOG_NOTICE "notice" -#define LOG_DROP "drop" -#define LOG_DEBUG "debug" -#define LOG_ERROR "error" +#define SP_LOG_NOTICE "notice" +#define SP_LOG_DROP "drop" +#define SP_LOG_DEBUG "debug" +#define SP_LOG_ERROR "error" -#define sp_log_err(feature, ...) sp_log_msg(feature, LOG_ERROR, __VA_ARGS__) +#define sp_log_err(feature, ...) sp_log_msg(feature, SP_LOG_ERROR, __VA_ARGS__) #ifdef SP_DEBUG - #define sp_log_debug(...) sp_log_msg("DEBUG", LOG_DEBUG, __VA_ARGS__) + #define sp_log_debug(...) sp_log_msg("DEBUG", SP_LOG_DEBUG, __VA_ARGS__) #else #define sp_log_debug(...) #endif diff --git a/src/tests/config/dump_request.ini b/src/tests/config/dump_request.ini index 8c595f9..00ee7b8 100644 --- a/src/tests/config/dump_request.ini +++ b/src/tests/config/dump_request.ini @@ -1 +1 @@ -sp.disable_functions.function("system").drop().dump("./dump_results/"); +sp.disable_functions.function("system").drop().dump("/tmp/dump_results/"); diff --git a/src/tests/disable_xxe_dom.phpt b/src/tests/disable_xxe_dom.phpt index 47f3db3..864b2a1 100644 --- a/src/tests/disable_xxe_dom.phpt +++ b/src/tests/disable_xxe_dom.phpt @@ -6,7 +6,6 @@ Disable XXE if (!extension_loaded("dom")) die "skip"; ?> --INI-- -extension=`php-config --extension-dir`/dom.so sp.configuration_file={PWD}/config/disable_xxe.ini --FILE-- --INI-- -extension=`php-config --extension-dir`/dom.so sp.configuration_file={PWD}/config/disable_xxe_disable.ini --FILE-- --INI-- -extension=`php-config --extension-dir`/simplexml.so sp.configuration_file={PWD}/config/disable_xxe.ini --FILE-- --INI-- -extension=`php-config --extension-dir`/simplexml.so sp.configuration_file={PWD}/config/disable_xxe.ini --FILE-- --INI-- -extension=`php-config --extension-dir`/xml.so sp.configuration_file={PWD}/config/disable_xxe.ini --FILE-- --INI-- sp.configuration_file={PWD}/config/disabled_functions_ret.ini +memory_limit=-1 --FILE-- --INI-- sp.configuration_file={PWD}/config/disabled_functions_ret_type.ini --FILE-- --EXPECTF-- -0 +int(0) 1337 -[snuffleupagus][0.0.0.0][disabled_function][drop] The execution has been aborted in %a/tests/disabled_functions_ret_type.php:%d, because the function 'strpos' returned 'FALSE', which matched the rule 'Return value is FALSE'. +[snuffleupagus][0.0.0.0][disabled_function][drop] The execution has been aborted in %a/disabled_functions_ret_type.php:%d, because the function 'strpos' returned 'FALSE', which matched the rule 'Return value is FALSE'. diff --git a/src/tests/disabled_option.phpt b/src/tests/disabled_option.phpt index 8bc7e39..70e1382 100644 --- a/src/tests/disabled_option.phpt +++ b/src/tests/disabled_option.phpt @@ -7,10 +7,16 @@ sp.configuration_file={PWD}/config/config_rand_harden_disabled.ini --FILE-- --EXPECT-- -84 -84 +win diff --git a/src/tests/dump_request.phpt b/src/tests/dump_request.phpt index a752def..5fa43c4 100644 --- a/src/tests/dump_request.phpt +++ b/src/tests/dump_request.phpt @@ -6,10 +6,10 @@ if (!extension_loaded("snuffleupagus")) { print "skip"; } -foreach (glob("./tests/dump_results/*.dump") as $dump) { - unlink($dump); +foreach (glob("/tmp/dump_results/*.dump") as $dump) { + @unlink($dump); } -rmdir("./tests/dump_results/"); +@rmdir("/tmp/dump_results/"); ?> --POST-- post_a=data_post_a&post_b=data_post_b @@ -21,10 +21,10 @@ cookie_a=data_cookie_a&cookie_b=data_cookie_b sp.configuration_file={PWD}/config/dump_request.ini --FILE-- --POST-- post_a=data_post_a&post_b=data_post_b&post_c=c @@ -27,7 +27,7 @@ sp.configuration_file={PWD}/config/dump_request.ini