From 4cbca117a3f2ef2d6695504970378ec4c483d19f Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 5 Feb 2018 17:27:45 +0100 Subject: Compatibility layer for pcre2 This should close #129--- src/config.m4 | 1 + src/php_snuffleupagus.h | 13 +------- src/sp_config.c | 12 ++----- src/sp_config.h | 14 ++++---- src/sp_config_keywords.c | 2 +- src/sp_cookie_encryption.c | 2 +- src/sp_disabled_functions.c | 12 +++---- src/sp_pcre_compat.c | 50 +++++++++++++++++++++++++++++ src/sp_pcre_compat.h | 34 ++++++++++++++++++++ src/sp_utils.c | 33 ++++--------------- src/sp_utils.h | 9 +++--- src/sp_var_parser.c | 16 ++++----- src/tests/broken_conf_config_regexp.phpt | 4 +-- src/tests/broken_conf_shown_in_phpinfo.phpt | 4 +-- src/tests/broken_regexp.phpt | 2 +- 15 files changed, 125 insertions(+), 83 deletions(-) create mode 100644 src/sp_pcre_compat.c create mode 100644 src/sp_pcre_compat.h (limited to 'src') diff --git a/src/config.m4 b/src/config.m4 index 3d4c1ba..8d5278e 100644 --- a/src/config.m4 +++ b/src/config.m4 @@ -6,6 +6,7 @@ sources="$sources sp_unserialize.c sp_utils.c sp_disable_xxe.c sp_list.c" sources="$sources sp_disabled_functions.c sp_execute.c sp_upload_validation.c" sources="$sources sp_cookie_encryption.c sp_network_utils.c tweetnacl.c" sources="$sources sp_config_keywords.c sp_var_parser.c sp_var_value.c sp_tree.c" +sources="$sources sp_pcre_compat.c" PHP_ARG_ENABLE(snuffleupagus, whether to enable snuffleupagus support, [ --enable-snuffleupagus Enable snuffleupagus support]) diff --git a/src/php_snuffleupagus.h b/src/php_snuffleupagus.h index 95f271d..52bcc93 100644 --- a/src/php_snuffleupagus.h +++ b/src/php_snuffleupagus.h @@ -24,6 +24,7 @@ #include "zend_string.h" #include "zend_extensions.h" +#include "sp_pcre_compat.h" #include "sp_list.h" #include "sp_tree.h" #include "sp_var_parser.h" @@ -71,18 +72,6 @@ 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); PHP_FUNCTION(eval_blacklist_callback); diff --git a/src/sp_config.c b/src/sp_config.c index 7a38a45..1236ebd 100644 --- a/src/sp_config.c +++ b/src/sp_config.c @@ -140,15 +140,9 @@ int parse_regexp(char *restrict line, char *restrict keyword, void *retval) { char *value = get_param(&consumed, line, SP_TYPE_STR, keyword); if (value) { - const char *pcre_error; - int pcre_error_offset; - 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 on line %zu.", value, - pcre_error, sp_line_no); - } else { - *(pcre **)retval = compiled_re; + sp_pcre *compiled_re = sp_pcre_compile(value); + if (NULL != compiled_re) { + *(sp_pcre **)retval = compiled_re; return consumed; } } diff --git a/src/sp_config.h b/src/sp_config.h index 25963f1..75ee83d 100644 --- a/src/sp_config.h +++ b/src/sp_config.h @@ -59,7 +59,7 @@ typedef struct { enum samesite_type { strict = 1, lax = 2 } samesite; bool encrypt; char *name; - pcre *name_r; + sp_pcre *name_r; bool simulation; } sp_cookie; @@ -72,29 +72,29 @@ typedef struct { char *textual_representation; char *filename; - pcre *r_filename; + sp_pcre *r_filename; char *function; - pcre *r_function; + sp_pcre *r_function; sp_list_node *functions_list; char *hash; int simulation; sp_tree *param; - pcre *r_param; + sp_pcre *r_param; sp_php_type param_type; int pos; unsigned int line; char *ret; - pcre *r_ret; + sp_pcre *r_ret; sp_php_type ret_type; - pcre *value_r; + sp_pcre *value_r; char *value; - pcre *r_key; + sp_pcre *r_key; char *key; char *dump; diff --git a/src/sp_config_keywords.c b/src/sp_config_keywords.c index 93c3da9..5df3d97 100644 --- a/src/sp_config_keywords.c +++ b/src/sp_config_keywords.c @@ -25,7 +25,7 @@ static int get_construct_type(sp_disabled_function const *const df) { } } else { if (true == - is_regexp_matching(df->r_function, CONSTRUCTS_TYPES[i].keys[j])) { + sp_is_regexp_matching(df->r_function, CONSTRUCTS_TYPES[i].keys[j])) { return CONSTRUCTS_TYPES[i].type; } } diff --git a/src/sp_cookie_encryption.c b/src/sp_cookie_encryption.c index 29e96b1..09cf884 100644 --- a/src/sp_cookie_encryption.c +++ b/src/sp_cookie_encryption.c @@ -167,7 +167,7 @@ static zend_string *encrypt_data(char *data, unsigned long long data_len) { } PHP_FUNCTION(sp_setcookie) { - zval params[7] = {0}; + zval params[7] = {{{0}}}; zend_string *name = NULL, *value = NULL, *path = NULL, *domain = NULL, *samesite = NULL; zend_long expires = 0; diff --git a/src/sp_disabled_functions.c b/src/sp_disabled_functions.c index 8396cae..f8c21d2 100644 --- a/src/sp_disabled_functions.c +++ b/src/sp_disabled_functions.c @@ -155,7 +155,7 @@ static bool is_param_matching(zend_execute_data* execute_data, } const bool pcre_matching = config_node->r_param && - (true == is_regexp_matching(config_node->r_param, *arg_name)); + (true == sp_is_regexp_matching(config_node->r_param, *arg_name)); /* This is the parameter name we're looking for. */ if (true == pcre_matching || config_node->pos != -1) { @@ -259,7 +259,7 @@ bool should_disable(zend_execute_data* execute_data, const char* builtin_name, } } else if (config_node->r_function) { if (false == - is_regexp_matching(config_node->r_function, complete_path_function)) { + sp_is_regexp_matching(config_node->r_function, complete_path_function)) { goto next; } } @@ -276,7 +276,7 @@ bool should_disable(zend_execute_data* execute_data, const char* builtin_name, } } else if (config_node->r_filename) { if (false == - is_regexp_matching(config_node->r_filename, current_filename)) { + sp_is_regexp_matching(config_node->r_filename, current_filename)) { goto next; } } @@ -366,7 +366,7 @@ bool should_drop_on_ret(zval* return_value, } } else if (config_node->r_function) { if (false == - is_regexp_matching(config_node->r_function, complete_path_function)) { + sp_is_regexp_matching(config_node->r_function, complete_path_function)) { goto next; } } @@ -377,7 +377,7 @@ bool should_drop_on_ret(zval* return_value, } } else if (config_node->r_filename) { if (false == - is_regexp_matching(config_node->r_filename, current_filename)) { + sp_is_regexp_matching(config_node->r_filename, current_filename)) { goto next; } } @@ -440,7 +440,7 @@ ZEND_FUNCTION(check_disabled_function) { static int hook_functions(const sp_list_node* config) { while (config && config->data) { const char* function_name = ((sp_disabled_function*)config->data)->function; - const pcre* function_name_regexp = + const sp_pcre* function_name_regexp = ((sp_disabled_function*)config->data)->r_function; if (NULL != function_name) { // hook function by name diff --git a/src/sp_pcre_compat.c b/src/sp_pcre_compat.c new file mode 100644 index 0000000..42a11cb --- /dev/null +++ b/src/sp_pcre_compat.c @@ -0,0 +1,50 @@ +#include "php_snuffleupagus.h" + +#include "sp_pcre_compat.h" + +sp_pcre* sp_pcre_compile(const char *const pattern) { + sp_pcre* ret = NULL; + const char *pcre_error = NULL; +#ifdef SP_HAS_PCRE2 + int errornumber; + PCRE2_SIZE erroroffset; + ret = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, PCRE2_CASELESS, &errornumber, &erroroffset, NULL); +#else + int erroroffset; + ret = pcre_compile(pattern, PCRE_CASELESS, &pcre_error, &erroroffset, NULL); +#endif + + if (NULL == ret) { + sp_log_err("config", "Failed to compile '%s': %s on line %zu.", pattern, + pcre_error, sp_line_no); + } + return ret; +} + +bool sp_is_regexp_matching_len(const sp_pcre* regexp, const char* str, size_t len) { + int ret = 0; + + assert(NULL != regexp); + assert(NULL != str); + +#ifdef SP_HAS_PCRE2 + pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(regexp, NULL); + ret = pcre2_match(regexp, (PCRE2_SPTR)str, len, 0, 0, match_data, NULL); +#else + int vec[30]; + ret = pcre_exec(regexp, NULL, str, len, 0, 0, vec, + sizeof(vec) / sizeof(int)); +#endif + + if (ret < 0) { +#ifdef SP_HAS_PCRE2 + if (ret != PCRE2_ERROR_NOMATCH) { +#else + if (ret != PCRE_ERROR_NOMATCH) { +#endif + sp_log_err("regexp", "Something went wrong with a regexp (%d).", ret); + } + return false; + } + return true; +} diff --git a/src/sp_pcre_compat.h b/src/sp_pcre_compat.h new file mode 100644 index 0000000..a9eb253 --- /dev/null +++ b/src/sp_pcre_compat.h @@ -0,0 +1,34 @@ +#ifndef SP_PCRE_COMPAT_H +#define SP_PCRE_COMPAT_H + +#include +#include + +#undef pcre_exec +#undef pcre_compile + +/* We're not supporting pcre2 when it's not bundled with php7, + * yet. Pull-requests are welcome. */ +#if HAVE_BUNDLED_PCRE + #if PHP_VERSION_ID >= 70300 + #define SP_HAS_PCRE2 + #include "ext/pcre/pcre2lib/pcre2.h" + #else + #include "ext/pcre/pcrelib/pcre.h" + #endif +#else + #include "pcre.h" +#endif + +#ifdef SP_HAS_PCRE2 + #define sp_pcre pcre2_code +#else + #define sp_pcre pcre +#endif + +sp_pcre* sp_pcre_compile(const char* str); +#define sp_is_regexp_matching(regexp, str) \ + sp_is_regexp_matching_len(regexp, str, strlen(str)) +bool sp_is_regexp_matching_len(const sp_pcre* regexp, const char* str, size_t len); + +#endif // SP_PCRE_COMPAT_H diff --git a/src/sp_utils.c b/src/sp_utils.c index 0625a2f..2979d98 100644 --- a/src/sp_utils.c +++ b/src/sp_utils.c @@ -32,25 +32,6 @@ void sp_log_msg(char const* feature, char const* level, const char* fmt, ...) { client_ip ? client_ip : "0.0.0.0", feature, level, msg); } -zend_always_inline int is_regexp_matching(const pcre* regexp, const char* str) { - int vec[30]; - int ret = 0; - - assert(NULL != regexp); - assert(NULL != str); - - ret = sp_pcre_exec(regexp, NULL, str, strlen(str), 0, 0, vec, - sizeof(vec) / sizeof(int)); - - if (ret < 0) { - if (ret != PCRE_ERROR_NOMATCH) { - sp_log_err("regexp", "Something went wrong with a regexp (%d).", ret); - } - return false; - } - return true; -} - int compute_hash(const char* const filename, char* file_hash) { unsigned char buf[1024]; unsigned char digest[SHA256_SIZE]; @@ -192,13 +173,13 @@ char* sp_convert_to_string(zval* zv) { return estrdup(""); } -bool sp_match_value(const char* value, const char* to_match, const pcre* rx) { +bool sp_match_value(const char* value, const char* to_match, const sp_pcre* rx) { if (to_match) { if (0 == strcmp(to_match, value)) { return true; } } else if (rx) { - return is_regexp_matching(rx, value); + return sp_is_regexp_matching(rx, value); } else { return true; } @@ -274,7 +255,7 @@ void sp_log_disable_ret(const char* restrict path, } } -bool sp_match_array_key(const zval* zv, const char* to_match, const pcre* rx) { +bool sp_match_array_key(const zval* zv, const char* to_match, const sp_pcre* rx) { zend_string* key; zend_ulong idx; @@ -298,7 +279,7 @@ bool sp_match_array_key(const zval* zv, const char* to_match, const pcre* rx) { } bool sp_match_array_value(const zval* arr, const char* to_match, - const pcre* rx) { + const sp_pcre* rx) { zval* value; ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(arr), value) { @@ -368,7 +349,7 @@ int hook_function(const char* original_name, HashTable* hook_table, return SUCCESS; } -int hook_regexp(const pcre* regexp, HashTable* hook_table, +int hook_regexp(const sp_pcre* regexp, HashTable* hook_table, void (*new_function)(INTERNAL_FUNCTION_PARAMETERS), bool hook_execution_table) { zend_string* key; @@ -377,9 +358,7 @@ int hook_regexp(const pcre* regexp, HashTable* hook_table, ZEND_HASH_FOREACH_STR_KEY(ht, key) { if (key) { - int vec[30]; - int ret = sp_pcre_exec(regexp, NULL, key->val, key->len, 0, 0, vec, - sizeof(vec) / sizeof(int)); + int ret = sp_is_regexp_matching_len(regexp, key->val, key->len); 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 ada7cf6..10a6daa 100644 --- a/src/sp_utils.h +++ b/src/sp_utils.h @@ -46,17 +46,16 @@ void sp_log_msg(char const *feature, char const *level, const char *fmt, ...); int compute_hash(const char *const filename, char *file_hash); char *sp_convert_to_string(zval *); -bool sp_match_value(const char *, const char *, const pcre *); -bool sp_match_array_key(const zval *, const char *, const pcre *); -bool sp_match_array_value(const zval *, const char *, const pcre *); +bool sp_match_value(const char *, const char *, const sp_pcre *); +bool sp_match_array_key(const zval *, const char *, const sp_pcre *); +bool sp_match_array_value(const zval *, const char *, const sp_pcre *); void sp_log_disable(const char *restrict, const char *restrict, const char *restrict, const sp_disabled_function *); void sp_log_disable_ret(const char *restrict, const char *restrict, const sp_disabled_function *); -int is_regexp_matching(const pcre *, const char *); int hook_function(const char *, HashTable *, void (*)(INTERNAL_FUNCTION_PARAMETERS), bool); -int hook_regexp(const pcre *, HashTable *, +int hook_regexp(const sp_pcre *, HashTable *, void (*)(INTERNAL_FUNCTION_PARAMETERS), bool); bool check_is_in_eval_whitelist(const char * const function_name); diff --git a/src/sp_var_parser.c b/src/sp_var_parser.c index d0ae67c..330fa54 100644 --- a/src/sp_var_parser.c +++ b/src/sp_var_parser.c @@ -20,26 +20,22 @@ static sp_list_node *parse_str_tokens(const char *str, const sp_conf_token token } static bool is_var_name_valid(const char *name) { - static pcre *regexp_const = NULL; - static pcre *regexp_var = NULL; - const char *pcre_error; - int pcre_error_offset; + static sp_pcre *regexp_const = NULL; + static sp_pcre *regexp_var = NULL; if (!name) { return false; } if (NULL == regexp_var || NULL == regexp_const) { - regexp_var = sp_pcre_compile(REGEXP_VAR, PCRE_CASELESS, &pcre_error, - &pcre_error_offset, NULL); - regexp_const = sp_pcre_compile(REGEXP_CONST, PCRE_CASELESS, &pcre_error, - &pcre_error_offset, NULL); + regexp_var = sp_pcre_compile(REGEXP_VAR); + regexp_const = sp_pcre_compile(REGEXP_CONST); } if (NULL == regexp_var || NULL == regexp_const) { sp_log_err("config", "Could not compile regexp."); return false; } - if (0 > sp_pcre_exec(regexp_var, NULL, name, strlen(name), 0, 0, NULL, 0) && - 0 > sp_pcre_exec(regexp_const, NULL, name, strlen(name), 0, 0, NULL, 0)) { + if ((false == sp_is_regexp_matching(regexp_var, name)) && + (false == sp_is_regexp_matching(regexp_const, name))) { return false; } return true; diff --git a/src/tests/broken_conf_config_regexp.phpt b/src/tests/broken_conf_config_regexp.phpt index d2a379e..e49788c 100644 --- a/src/tests/broken_conf_config_regexp.phpt +++ b/src/tests/broken_conf_config_regexp.phpt @@ -5,6 +5,6 @@ Broken configuration --INI-- sp.configuration_file={PWD}/config/broken_config_regexp.ini --FILE-- ---EXPECT-- -[snuffleupagus][0.0.0.0][config][error] Failed to compile '*.': nothing to repeat on line 1. +--EXPECTF-- +[snuffleupagus][0.0.0.0][config][error] Failed to compile '*.': %aon line 1. [snuffleupagus][0.0.0.0][config][error] '.filename_r()' is expecting a valid regexp, and not '"*."' on line 1. diff --git a/src/tests/broken_conf_shown_in_phpinfo.phpt b/src/tests/broken_conf_shown_in_phpinfo.phpt index 61e9512..5053ee0 100644 --- a/src/tests/broken_conf_shown_in_phpinfo.phpt +++ b/src/tests/broken_conf_shown_in_phpinfo.phpt @@ -16,7 +16,7 @@ if (strstr($info, 'Valid config => no') !== FALSE) { echo "lose"; } ?> ---EXPECT-- -[snuffleupagus][0.0.0.0][config][error] Failed to compile '*.': nothing to repeat on line 1. +--EXPECTF-- +[snuffleupagus][0.0.0.0][config][error] Failed to compile '*.': %aon line 1. [snuffleupagus][0.0.0.0][config][error] '.filename_r()' is expecting a valid regexp, and not '"*."' on line 1. win diff --git a/src/tests/broken_regexp.phpt b/src/tests/broken_regexp.phpt index 3f027f1..7c7ac9c 100644 --- a/src/tests/broken_regexp.phpt +++ b/src/tests/broken_regexp.phpt @@ -6,5 +6,5 @@ Broken regexp sp.configuration_file={PWD}/config/broken_regexp.ini --FILE-- --EXPECTF-- -[snuffleupagus][0.0.0.0][config][error] Failed to compile '^$[': missing terminating ] for character class on line 1. +[snuffleupagus][0.0.0.0][config][error] Failed to compile '^$[': %aon line 1. [snuffleupagus][0.0.0.0][config][error] '.value_r()' is expecting a valid regexp, and not '"^$["' on line 1. -- cgit v1.3