From 7963580d72a358975133f86f01de2d2eab08ba38 Mon Sep 17 00:00:00 2001 From: xXx-caillou-xXx Date: Fri, 13 Jul 2018 10:36:50 +0200 Subject: Massively optimize how rules are handled This commit does a lot of things: - Use hashtables instead of lists to store the rules - Rules that can be applied at launch time won't be tried at runtime - Improve feedback when writing nonsensical rules - Make intensive use of `zend_string` instead of `char*`--- Makefile | 4 +- config/default.rules | 50 ++++---- src/snuffleupagus.c | 53 +++++--- src/sp_config.c | 36 +++--- src/sp_config.h | 58 ++++----- src/sp_config_keywords.c | 199 ++++++++++++++--------------- src/sp_config_utils.c | 15 ++- src/sp_config_utils.h | 2 +- src/sp_cookie_encryption.c | 16 +-- src/sp_crypt.c | 29 +++-- src/sp_crypt.h | 4 +- src/sp_disabled_functions.c | 304 ++++++++++++++++++++++++++------------------ src/sp_disabled_functions.h | 9 +- src/sp_execute.c | 142 ++++++++++++++------- src/sp_execute.h | 2 +- src/sp_pcre_compat.h | 2 + src/sp_session.c | 4 +- src/sp_sloppy.c | 9 +- src/sp_unserialize.c | 12 +- src/sp_upload_validation.c | 26 ++-- src/sp_utils.c | 147 ++++++++++++--------- src/sp_utils.h | 16 +-- src/sp_var_value.c | 7 +- 23 files changed, 660 insertions(+), 486 deletions(-) diff --git a/Makefile b/Makefile index 8470588..b642b26 100644 --- a/Makefile +++ b/Makefile @@ -45,9 +45,9 @@ joomla: fi cd joomla-cms; composer install >/dev/null 2>/dev/null echo "\nWith snuffleupagus:" - cd joomla-cms; time libraries/vendor/phpunit/phpunit/phpunit -d "extension=./src/modules/snuffleupagus.so" -d "sp.configuration_file=config/default.rules" --no-coverage >/dev/null + cd joomla-cms; time php -d "extension=../src/modules/snuffleupagus.so" -d "sp.configuration_file=../config/default.rules" libraries/vendor/phpunit/phpunit/phpunit --no-coverage >/dev/null echo "\nWithout snuffleupagus:" - cd joomla-cms; time libraries/vendor/phpunit/phpunit/phpunit --no-coverage >/dev/null + cd joomla-cms; time php libraries/vendor/phpunit/phpunit/phpunit --no-coverage >/dev/null packages: debian ## produce packages diff --git a/config/default.rules b/config/default.rules index fb53708..4e6a27f 100644 --- a/config/default.rules +++ b/config/default.rules @@ -35,35 +35,37 @@ sp.disable_function.function("ini_get").param("var_name").value_r("(?:allow_url_ sp.disable_function.function("function_exists").param("function_name").value_r("(?:eval|exec|system)").drop(); sp.disable_function.function("is_callable").param("var").value_r("(?:eval|exec|system)").drop(); +# Commenting sqli related stuff to improve performance. +# TODO figure out why these functions can't be hooked at startup # Ghetto sqli hardening -sp.disable_function.function("mysql_query").param("query").value_r("/\\*").drop(); -sp.disable_function.function("mysql_query").param("query").value_r("--").drop(); -sp.disable_function.function("mysql_query").param("query").value_r("#").drop(); -sp.disable_function.function("mysql_query").param("query").value_r(";.*;").drop(); -sp.disable_function.function("mysql_query").param("query").value_r("benchmark").drop(); -sp.disable_function.function("mysql_query").param("query").value_r("sleep").drop(); -sp.disable_function.function("mysql_query").param("query").value_r("information_schema").drop(); +# sp.disable_function.function("mysql_query").param("query").value_r("/\\*").drop(); +# sp.disable_function.function("mysql_query").param("query").value_r("--").drop(); +# sp.disable_function.function("mysql_query").param("query").value_r("#").drop(); +# sp.disable_function.function("mysql_query").param("query").value_r(";.*;").drop(); +# sp.disable_function.function("mysql_query").param("query").value_r("benchmark").drop(); +# sp.disable_function.function("mysql_query").param("query").value_r("sleep").drop(); +# sp.disable_function.function("mysql_query").param("query").value_r("information_schema").drop(); -sp.disable_function.function("mysqli_query").param("query").value_r("/\\*").drop(); -sp.disable_function.function("mysqli_query").param("query").value_r("--").drop(); -sp.disable_function.function("mysqli_query").param("query").value_r("#").drop(); -sp.disable_function.function("mysqli_query").param("query").value_r(";.*;").drop(); -sp.disable_function.function("mysqli_query").param("query").value_r("benchmark").drop(); -sp.disable_function.function("mysqli_query").param("query").value_r("sleep").drop(); -sp.disable_function.function("mysqli_query").param("query").value_r("information_schema").drop(); +# sp.disable_function.function("mysqli_query").param("query").value_r("/\\*").drop(); +# sp.disable_function.function("mysqli_query").param("query").value_r("--").drop(); +# sp.disable_function.function("mysqli_query").param("query").value_r("#").drop(); +# sp.disable_function.function("mysqli_query").param("query").value_r(";.*;").drop(); +# sp.disable_function.function("mysqli_query").param("query").value_r("benchmark").drop(); +# sp.disable_function.function("mysqli_query").param("query").value_r("sleep").drop(); +# sp.disable_function.function("mysqli_query").param("query").value_r("information_schema").drop(); -sp.disable_function.function("PDO::query").param("query").value_r("/\\*").drop(); -sp.disable_function.function("PDO::query").param("query").value_r("--").drop(); -sp.disable_function.function("PDO::query").param("query").value_r("#").drop(); -sp.disable_function.function("PDO::query").param("query").value_r(";.*;").drop(); -sp.disable_function.function("PDO::query").param("query").value_r("benchmark\\s*\\(").drop(); -sp.disable_function.function("PDO::query").param("query").value_r("sleep\\s*\\(").drop(); -sp.disable_function.function("PDO::query").param("query").value_r("information_schema").drop(); +# sp.disable_function.function("PDO::query").param("query").value_r("/\\*").drop(); +# sp.disable_function.function("PDO::query").param("query").value_r("--").drop(); +# sp.disable_function.function("PDO::query").param("query").value_r("#").drop(); +# sp.disable_function.function("PDO::query").param("query").value_r(";.*;").drop(); +# sp.disable_function.function("PDO::query").param("query").value_r("benchmark\\s*\\(").drop(); +# sp.disable_function.function("PDO::query").param("query").value_r("sleep\\s*\\(").drop(); +# sp.disable_function.function("PDO::query").param("query").value_r("information_schema").drop(); # Ghetto sqli detection -sp.disable_function.function("mysql_query").ret("FALSE").drop(); -sp.disable_function.function("mysqli_query").ret("FALSE").drop(); -sp.disable_function.function("PDO::query").ret("FALSE").drop(); +# sp.disable_function.function("mysql_query").ret("FALSE").drop(); +# sp.disable_function.function("mysqli_query").ret("FALSE").drop(); +# sp.disable_function.function("PDO::query").ret("FALSE").drop(); #File upload sp.disable_function.function("move_uploaded_file").param("destination").value_r("\\.ph").drop(); diff --git a/src/snuffleupagus.c b/src/snuffleupagus.c index 08b2083..edca185 100644 --- a/src/snuffleupagus.c +++ b/src/snuffleupagus.c @@ -39,6 +39,14 @@ PHP_INI_ENTRY("sp.configuration_file", "", PHP_INI_SYSTEM, OnUpdateConfiguration) PHP_INI_END() +void free_disabled_functions_hashtable(HashTable *ht) { + void* ptr = NULL; + ZEND_HASH_FOREACH_PTR(ht, ptr) { + sp_list_free(ptr); + } + ZEND_HASH_FOREACH_END(); +} + ZEND_DLEXPORT zend_extension zend_extension_entry = { PHP_SNUFFLEUPAGUS_EXTNAME, PHP_SNUFFLEUPAGUS_VERSION, @@ -69,6 +77,10 @@ PHP_GINIT_FUNCTION(snuffleupagus) { SP_INIT_HT(snuffleupagus_globals->disabled_functions_hook); SP_INIT_HT(snuffleupagus_globals->sp_internal_functions_hook); SP_INIT_HT(snuffleupagus_globals->sp_eval_blacklist_functions_hook); + SP_INIT_HT(snuffleupagus_globals->config.config_disabled_functions); + SP_INIT_HT(snuffleupagus_globals->config.config_disabled_functions_hooked); + SP_INIT_HT(snuffleupagus_globals->config.config_disabled_functions_ret); + SP_INIT_HT(snuffleupagus_globals->config.config_disabled_functions_ret_hooked); SP_INIT(snuffleupagus_globals->config.config_unserialize); SP_INIT(snuffleupagus_globals->config.config_random); @@ -79,20 +91,15 @@ PHP_GINIT_FUNCTION(snuffleupagus) { SP_INIT(snuffleupagus_globals->config.config_snuffleupagus); SP_INIT(snuffleupagus_globals->config.config_disable_xxe); SP_INIT(snuffleupagus_globals->config.config_upload_validation); - SP_INIT(snuffleupagus_globals->config.config_disabled_functions); - SP_INIT(snuffleupagus_globals->config.config_disabled_functions_ret); + SP_INIT(snuffleupagus_globals->config.config_disabled_functions_reg); + SP_INIT(snuffleupagus_globals->config.config_disabled_functions_reg_ret); SP_INIT(snuffleupagus_globals->config.config_cookie); SP_INIT(snuffleupagus_globals->config.config_session); - SP_INIT(snuffleupagus_globals->config.config_disabled_constructs); SP_INIT(snuffleupagus_globals->config.config_eval); - snuffleupagus_globals->config.config_disabled_constructs->construct_include = - NULL; - snuffleupagus_globals->config.config_disabled_constructs->construct_eval = - NULL; - snuffleupagus_globals->config.config_disabled_functions->disabled_functions = - NULL; - snuffleupagus_globals->config.config_disabled_functions_ret + snuffleupagus_globals->config.config_disabled_functions_reg + ->disabled_functions = NULL; + snuffleupagus_globals->config.config_disabled_functions_reg_ret ->disabled_functions = NULL; snuffleupagus_globals->config.config_cookie->cookies = NULL; snuffleupagus_globals->config.config_eval->blacklist = NULL; @@ -109,12 +116,21 @@ PHP_MINIT_FUNCTION(snuffleupagus) { } PHP_MSHUTDOWN_FUNCTION(snuffleupagus) { + free_disabled_functions_hashtable(SNUFFLEUPAGUS_G(config).config_disabled_functions); + free_disabled_functions_hashtable(SNUFFLEUPAGUS_G(config).config_disabled_functions_hooked); + free_disabled_functions_hashtable(SNUFFLEUPAGUS_G(config).config_disabled_functions_ret); + free_disabled_functions_hashtable(SNUFFLEUPAGUS_G(config).config_disabled_functions_ret_hooked); + #define FREE_HT(F) \ zend_hash_destroy(SNUFFLEUPAGUS_G(F)); \ pefree(SNUFFLEUPAGUS_G(F), 1); FREE_HT(disabled_functions_hook); FREE_HT(sp_eval_blacklist_functions_hook); + FREE_HT(config.config_disabled_functions); + FREE_HT(config.config_disabled_functions_hooked); + FREE_HT(config.config_disabled_functions_ret); + FREE_HT(config.config_disabled_functions_ret_hooked); #undef FREE_HT @@ -135,19 +151,16 @@ PHP_MSHUTDOWN_FUNCTION(snuffleupagus) { sp_list_free(_n); \ } while (0) - FREE_LST_DISABLE(config.config_disabled_functions->disabled_functions); - FREE_LST_DISABLE(config.config_disabled_functions_ret->disabled_functions); - FREE_LST_DISABLE(config.config_disabled_constructs->construct_include); - FREE_LST_DISABLE(config.config_disabled_constructs->construct_eval); + FREE_LST_DISABLE(config.config_disabled_functions_reg->disabled_functions); + FREE_LST_DISABLE(config.config_disabled_functions_reg_ret->disabled_functions); sp_list_free(SNUFFLEUPAGUS_G(config).config_cookie->cookies); sp_list_free(SNUFFLEUPAGUS_G(config).config_eval->blacklist); sp_list_free(SNUFFLEUPAGUS_G(config).config_eval->whitelist); #undef FREE_LST_DISABLE - pefree(SNUFFLEUPAGUS_G(config.config_disabled_functions), 1); - pefree(SNUFFLEUPAGUS_G(config.config_disabled_functions_ret), 1); - pefree(SNUFFLEUPAGUS_G(config.config_disabled_constructs), 1); + pefree(SNUFFLEUPAGUS_G(config.config_disabled_functions_reg), 1); + pefree(SNUFFLEUPAGUS_G(config.config_disabled_functions_reg_ret), 1); pefree(SNUFFLEUPAGUS_G(config.config_cookie), 1); UNREGISTER_INI_ENTRIES(); @@ -249,6 +262,12 @@ static PHP_INI_MH(OnUpdateConfiguration) { CG(compiler_options) |= ZEND_COMPILE_HANDLE_OP_ARRAY; } + SNUFFLEUPAGUS_G(config).hook_execute = + SNUFFLEUPAGUS_G(config).config_disabled_functions_reg->disabled_functions || + SNUFFLEUPAGUS_G(config).config_disabled_functions_reg_ret->disabled_functions || + zend_hash_num_elements(SNUFFLEUPAGUS_G(config).config_disabled_functions) || + zend_hash_num_elements(SNUFFLEUPAGUS_G(config).config_disabled_functions_ret); + return SUCCESS; } diff --git a/src/sp_config.c b/src/sp_config.c index eb5b324..c652984 100644 --- a/src/sp_config.c +++ b/src/sp_config.c @@ -63,29 +63,29 @@ int parse_empty(char *restrict line, char *restrict keyword, void *retval) { int parse_php_type(char *restrict line, char *restrict keyword, void *retval) { size_t consumed = 0; - char *value = get_param(&consumed, line, SP_TYPE_STR, keyword); + zend_string *value = get_param(&consumed, line, SP_TYPE_STR, keyword); if (value) { - if (0 == strcasecmp("undef", value)) { + if (zend_string_equals_literal_ci(value, "undef")) { *(sp_php_type *)retval = SP_PHP_TYPE_UNDEF; - } else if (0 == strcasecmp("null", value)) { + } else if (zend_string_equals_literal_ci(value, "null")) { *(sp_php_type *)retval = SP_PHP_TYPE_NULL; - } else if (0 == strcasecmp("true", value)) { + } else if (zend_string_equals_literal_ci(value, "true")) { *(sp_php_type *)retval = SP_PHP_TYPE_TRUE; - } else if (0 == strcasecmp("false", value)) { + } else if (zend_string_equals_literal_ci(value, "false")) { *(sp_php_type *)retval = SP_PHP_TYPE_FALSE; - } else if (0 == strcasecmp("long", value)) { + } else if (zend_string_equals_literal_ci(value, "long")) { *(sp_php_type *)retval = SP_PHP_TYPE_LONG; - } else if (0 == strcasecmp("double", value)) { + } else if (zend_string_equals_literal_ci(value, "double")) { *(sp_php_type *)retval = SP_PHP_TYPE_DOUBLE; - } else if (0 == strcasecmp("string", value)) { + } else if (zend_string_equals_literal_ci(value, "string")) { *(sp_php_type *)retval = SP_PHP_TYPE_STRING; - } else if (0 == strcasecmp("array", value)) { + } else if (zend_string_equals_literal_ci(value, "array")) { *(sp_php_type *)retval = SP_PHP_TYPE_ARRAY; - } else if (0 == strcasecmp("object", value)) { + } else if (zend_string_equals_literal_ci(value, "object")) { *(sp_php_type *)retval = SP_PHP_TYPE_OBJECT; - } else if (0 == strcasecmp("resource", value)) { + } else if (zend_string_equals_literal_ci(value, "resource")) { *(sp_php_type *)retval = SP_PHP_TYPE_RESOURCE; - } else if (0 == strcasecmp("reference", value)) { + } else if (zend_string_equals_literal_ci(value, "reference")) { *(sp_php_type *)retval = SP_PHP_TYPE_REFERENCE; } else { pefree(value, 1); @@ -105,12 +105,12 @@ int parse_php_type(char *restrict line, char *restrict keyword, void *retval) { } int parse_str(char *restrict line, char *restrict keyword, void *retval) { - char *value = NULL; + zend_string *value = NULL; size_t consumed = 0; value = get_param(&consumed, line, SP_TYPE_STR, keyword); if (value) { - *(char **)retval = value; + *(zend_string **)retval = value; return consumed; } return -1; @@ -118,11 +118,11 @@ int parse_str(char *restrict line, char *restrict keyword, void *retval) { int parse_cidr(char *restrict line, char *restrict keyword, void *retval) { size_t consumed = 0; - char *value = get_param(&consumed, line, SP_TYPE_STR, keyword); + zend_string *value = get_param(&consumed, line, SP_TYPE_STR, keyword); sp_cidr *cidr = pecalloc(sizeof(sp_cidr), 1, 1); if (value) { - if (-1 == get_ip_and_cidr(value, cidr)) { + if (-1 == get_ip_and_cidr(ZSTR_VAL(value), cidr)) { return -1; } *(sp_cidr **)retval = cidr; @@ -139,10 +139,10 @@ int parse_regexp(char *restrict line, char *restrict keyword, void *retval) { * (http://www.pcre.org/original/doc/html/pcre_study.html) * maybe not: http://sljit.sourceforge.net/pcre.html*/ size_t consumed = 0; - char *value = get_param(&consumed, line, SP_TYPE_STR, keyword); + zend_string *value = get_param(&consumed, line, SP_TYPE_STR, keyword); if (value) { - sp_pcre *compiled_re = sp_pcre_compile(value); + sp_pcre *compiled_re = sp_pcre_compile(ZSTR_VAL(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 979feda..cfc3c8f 100644 --- a/src/sp_config.h +++ b/src/sp_config.h @@ -38,15 +38,15 @@ typedef struct { } sp_cidr; typedef struct { - char *encryption_key; - char *cookies_env_var; + zend_string *encryption_key; + zend_string *cookies_env_var; } sp_config_global; typedef struct { bool enable; bool simulation; - char *dump; - char *textual_representation; + zend_string *dump; + zend_string *textual_representation; } sp_config_readonly_exec; typedef struct { bool enable; } sp_config_global_strict; @@ -62,7 +62,7 @@ typedef struct { bool enable; } sp_config_disable_xxe; typedef struct { enum samesite_type { strict = 1, lax = 2 } samesite; bool encrypt; - char *name; + zend_string *name; sp_pcre *name_r; bool simulation; } sp_cookie; @@ -75,21 +75,21 @@ typedef struct { typedef struct { bool enable; bool simulation; - char *dump; - char *textual_representation; + zend_string *dump; + zend_string *textual_representation; } sp_config_unserialize; typedef struct { - char *textual_representation; + zend_string *textual_representation; - char *filename; + zend_string *filename; sp_pcre *r_filename; - char *function; + zend_string *function; sp_pcre *r_function; sp_list_node *functions_list; - char *hash; + zend_string *hash; int simulation; sp_tree *param; @@ -98,18 +98,18 @@ typedef struct { int pos; unsigned int line; - char *ret; sp_pcre *r_ret; + zend_string *ret; sp_php_type ret_type; - sp_pcre *value_r; - char *value; + sp_pcre *r_value; + zend_string *value; sp_pcre *r_key; - char *key; + zend_string *key; - char *dump; - char *alias; + zend_string *dump; + zend_string *alias; bool param_is_array; bool var_is_array; sp_list_node *param_array_keys; @@ -126,8 +126,8 @@ typedef struct { sp_list_node *blacklist; sp_list_node *whitelist; bool simulation; - char *dump; - char *textual_representation; + zend_string *dump; + zend_string *textual_representation; } sp_config_eval; typedef struct { @@ -139,14 +139,7 @@ typedef struct { } sp_config_cookie; typedef struct { - sp_list_node - *construct_include; // list of rules for `(include|require)_(once)?` - sp_list_node *construct_eval; - sp_list_node *construct_echo; -} sp_config_disabled_constructs; - -typedef struct { - char *script; + zend_string *script; bool simulation; bool enable; } sp_config_upload_validation; @@ -155,8 +148,6 @@ typedef struct { sp_config_random *config_random; sp_config_sloppy *config_sloppy; sp_config_unserialize *config_unserialize; - sp_config_disabled_functions *config_disabled_functions; - sp_config_disabled_functions *config_disabled_functions_ret; sp_config_readonly_exec *config_readonly_exec; sp_config_upload_validation *config_upload_validation; sp_config_cookie *config_cookie; @@ -164,9 +155,16 @@ typedef struct { sp_config_auto_cookie_secure *config_auto_cookie_secure; sp_config_global_strict *config_global_strict; sp_config_disable_xxe *config_disable_xxe; - sp_config_disabled_constructs *config_disabled_constructs; sp_config_eval *config_eval; sp_config_session *config_session; + bool hook_execute; + + HashTable *config_disabled_functions; + HashTable *config_disabled_functions_hooked; + HashTable *config_disabled_functions_ret; + HashTable *config_disabled_functions_ret_hooked; + sp_config_disabled_functions *config_disabled_functions_reg; + sp_config_disabled_functions *config_disabled_functions_reg_ret; } sp_config; typedef struct { diff --git a/src/sp_config_keywords.c b/src/sp_config_keywords.c index 2a570cd..81f43f7 100644 --- a/src/sp_config_keywords.c +++ b/src/sp_config_keywords.c @@ -1,39 +1,8 @@ #include "php_snuffleupagus.h" +#include "zend_types.h" ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus) -static const struct { - unsigned int type; - char *keys[5]; // Update this value if necessary -} CONSTRUCTS_TYPES[] = { - {.type = ZEND_INCLUDE_OR_EVAL, - .keys = {"include", "include_once", "require", "require_once", NULL}}, - {.type = ZEND_ECHO, .keys = {"echo", NULL}}, - {.type = ZEND_NEW, .keys = {"new", NULL}}, - {.type = ZEND_EXIT, .keys = {"exit", NULL}}, - {.type = ZEND_STRLEN, .keys = {"strlen", NULL}}, - {.type = ZEND_EVAL_CODE, .keys = {"eval", NULL}}, - {.type = 0, .keys = {NULL}}}; - -static int get_construct_type(sp_disabled_function const *const df) { - for (size_t i = 0; 0 != CONSTRUCTS_TYPES[i].type; i++) { - for (size_t j = 0; NULL != CONSTRUCTS_TYPES[i].keys[j]; j++) { - assert(df->function || df->r_function); - if (df->function) { - if (0 == strcmp(df->function, CONSTRUCTS_TYPES[i].keys[j])) { - return CONSTRUCTS_TYPES[i].type; - } - } else { - if (true == sp_is_regexp_matching(df->r_function, - CONSTRUCTS_TYPES[i].keys[j])) { - return CONSTRUCTS_TYPES[i].type; - } - } - } - } - return -1; -} - static int parse_enable(char *line, bool *restrict retval, bool *restrict simulation) { bool enable = false, disable = false; @@ -137,7 +106,7 @@ int parse_unserialize(char *line) { {parse_str, SP_TOKEN_DUMP, &(unserialize->dump)}, {0}}; - unserialize->textual_representation = estrdup(line); + unserialize->textual_representation = zend_string_init(line, strlen(line), 1); int ret = parse_keywords(sp_config_funcs, line); if (0 != ret) { @@ -167,7 +136,8 @@ int parse_readonly_exec(char *line) { {parse_str, SP_TOKEN_DUMP, &(readonly_exec->dump)}, {0}}; - readonly_exec->textual_representation = estrdup(line); + readonly_exec->textual_representation = + zend_string_init(line, strlen(line), 1); int ret = parse_keywords(sp_config_funcs, line); if (0 != ret) { @@ -196,8 +166,8 @@ int parse_global(char *line) { } static int parse_eval_filter_conf(char *line, sp_list_node **list) { - char *token; - char *rest; + char *token, *tmp; + zend_string *rest = NULL; sp_config_eval *eval = SNUFFLEUPAGUS_G(config).config_eval; sp_config_functions sp_config_funcs[] = { @@ -207,15 +177,19 @@ static int parse_eval_filter_conf(char *line, sp_list_node **list) { {parse_str, SP_TOKEN_DUMP, &(SNUFFLEUPAGUS_G(config).config_eval->dump)}, {0}}; - eval->textual_representation = estrdup(line); + eval->textual_representation = zend_string_init(line, strlen(line), 1); int ret = parse_keywords(sp_config_funcs, line); if (0 != ret) { return ret; } - while ((token = strtok_r(rest, ",", &rest))) { - *list = sp_list_insert(*list, token); + tmp = ZSTR_VAL(rest); + while ((token = strtok_r(tmp, ",", &tmp))) { + *list = sp_list_insert(*list, zend_string_init(token, strlen(token), 1)); + } + if (rest != NULL) { + pefree(rest, 1); } return SUCCESS; } @@ -232,7 +206,7 @@ int parse_eval_whitelist(char *line) { int parse_cookie(char *line) { int ret = 0; - char *samesite = NULL; + zend_string *samesite = NULL; sp_cookie *cookie = pecalloc(sizeof(sp_cookie), 1, 1); sp_config_functions sp_config_funcs_cookie_encryption[] = { @@ -274,7 +248,7 @@ int parse_cookie(char *line) { sp_line_no); return -1; } - if ((!cookie->name || '\0' == cookie->name[0]) && !cookie->name_r) { + if ((!cookie->name || 0 == ZSTR_LEN(cookie->name)) && !cookie->name_r) { sp_log_err("config", "You must specify a cookie name/regexp on line " "%zu.", @@ -289,16 +263,17 @@ int parse_cookie(char *line) { return -1; } if (samesite) { - if (0 == strcasecmp(samesite, SP_TOKEN_SAMESITE_LAX)) { + if (zend_string_equals_literal_ci(samesite, SP_TOKEN_SAMESITE_LAX)) { cookie->samesite = lax; - } else if (0 == strcasecmp(samesite, SP_TOKEN_SAMESITE_STRICT)) { + } else if (zend_string_equals_literal_ci(samesite, + SP_TOKEN_SAMESITE_STRICT)) { cookie->samesite = strict; } else { sp_log_err( "config", "%s is an invalid value to samesite (expected %s or %s) on line " "%zu.", - samesite, SP_TOKEN_SAMESITE_LAX, SP_TOKEN_SAMESITE_STRICT, + ZSTR_VAL(samesite), SP_TOKEN_SAMESITE_LAX, SP_TOKEN_SAMESITE_STRICT, sp_line_no); return -1; } @@ -308,11 +283,22 @@ int parse_cookie(char *line) { return SUCCESS; } +int add_df_to_hashtable(HashTable *ht, sp_disabled_function *df) { + zval *list = zend_hash_find(ht, df->function); + + if (NULL == list) { + zend_hash_add_ptr(ht, df->function, sp_list_insert(NULL, df)); + } else { + Z_PTR_P(list) = sp_list_insert(Z_PTR_P(list), df); + } + return SUCCESS; +} + int parse_disabled_functions(char *line) { int ret = 0; bool enable = true, disable = false, allow = false, drop = false; - char *pos = NULL, *var = NULL, *param = NULL; - char *line_number = NULL; + zend_string *pos = NULL, *var = NULL, *param = NULL; + zend_string *line_number = NULL; sp_disabled_function *df = pecalloc(sizeof(*df), 1, 1); df->pos = -1; @@ -330,7 +316,7 @@ int parse_disabled_functions(char *line) { {parse_empty, SP_TOKEN_DROP, &(drop)}, {parse_str, SP_TOKEN_HASH, &(df->hash)}, {parse_str, SP_TOKEN_PARAM, &(param)}, - {parse_regexp, SP_TOKEN_VALUE_REGEXP, &(df->value_r)}, + {parse_regexp, SP_TOKEN_VALUE_REGEXP, &(df->r_value)}, {parse_str, SP_TOKEN_VALUE, &(df->value)}, {parse_str, SP_TOKEN_KEY, &(df->key)}, {parse_regexp, SP_TOKEN_KEY_REGEXP, &(df->r_key)}, @@ -360,7 +346,7 @@ int parse_disabled_functions(char *line) { return 1; \ } - MUTUALLY_EXCLUSIVE(df->value, df->value_r, "value", "regexp"); + MUTUALLY_EXCLUSIVE(df->value, df->r_value, "value", "regexp"); MUTUALLY_EXCLUSIVE(df->r_function, df->function, "r_function", "function"); MUTUALLY_EXCLUSIVE(df->filename, df->r_filename, "r_filename", "filename"); MUTUALLY_EXCLUSIVE(df->ret, df->r_ret, "r_ret", "ret"); @@ -375,25 +361,38 @@ int parse_disabled_functions(char *line) { "'.r_param', '.param' and '.pos' are mutually exclusive on line %zu.", line, sp_line_no); return -1; - } else if ((df->r_key || df->key) && (df->value_r || df->value)) { + } else if ((df->r_key || df->key) && (df->r_value || df->value)) { sp_log_err("config", "Invalid configuration line: 'sp.disabled_functions%s':" "`key` and `value` are mutually exclusive on line %zu.", line, sp_line_no); return -1; - } else if ((df->r_ret || df->ret) && (df->r_param || param)) { + } else if ((df->r_ret || df->ret || df->ret_type) && (df->r_param || param)) { sp_log_err("config", "Invalid configuration line: 'sp.disabled_functions%s':" "`ret` and `param` are mutually exclusive on line %zu.", line, sp_line_no); return -1; + } else if ((df->r_ret || df->ret || df->ret_type) && (var)) { + sp_log_err("config", + "Invalid configuration line: 'sp.disabled_functions%s':" + "`ret` and `var` are mutually exclusive on line %zu.", + line, sp_line_no); + return -1; + } else if ((df->r_ret || df->ret || df->ret_type) && + (df->value || df->r_value)) { + sp_log_err("config", + "Invalid configuration line: 'sp.disabled_functions%s':" + "`ret` and `value` are mutually exclusive on line %zu.", + line, sp_line_no); + return -1; } else if (!(df->r_function || df->function)) { sp_log_err("config", "Invalid configuration line: 'sp.disabled_functions%s':" " must take a function name on line %zu.", line, sp_line_no); return -1; - } else if (df->filename && *df->filename != '/') { + } else if (df->filename && *ZSTR_VAL(df->filename) != '/') { sp_log_err("config", "Invalid configuration line: 'sp.disabled_functions%s':" "'.filename' must be an absolute path on line %zu.", @@ -410,10 +409,10 @@ int parse_disabled_functions(char *line) { if (pos) { errno = 0; char *endptr; - df->pos = (int)strtol(pos, &endptr, 10); - if (errno != 0 || endptr == pos) { + df->pos = (int)strtol(ZSTR_VAL(pos), &endptr, 10); + if (errno != 0 || endptr == ZSTR_VAL(pos)) { sp_log_err("config", "Failed to parse arg '%s' of `pos` on line %zu.", - pos, sp_line_no); + ZSTR_VAL(pos), sp_line_no); return -1; } } @@ -421,46 +420,46 @@ int parse_disabled_functions(char *line) { if (line_number) { errno = 0; char *endptr; - df->line = (unsigned int)strtoul(line_number, &endptr, 10); - if (errno != 0 || endptr == line_number) { + df->line = (unsigned int)strtoul(ZSTR_VAL(line_number), &endptr, 10); + if (errno != 0 || endptr == ZSTR_VAL(line_number)) { sp_log_err("config", "Failed to parse arg '%s' of `line` on line %zu.", - line_number, sp_line_no); + ZSTR_VAL(line_number), sp_line_no); return -1; } } df->allow = allow; - df->textual_representation = estrdup(line); + df->textual_representation = zend_string_init(line, strlen(line), 1); if (df->function) { - df->functions_list = parse_functions_list(df->function); + df->functions_list = parse_functions_list(ZSTR_VAL(df->function)); } if (param) { - if (strlen(param) > 0 && param[0] != '$') { + if (ZSTR_LEN(param) > 0 && ZSTR_VAL(param)[0] != '$') { /* This is an ugly hack. We're prefixing with a `$` because otherwise * the parser treats this as a constant. * FIXME: Remove this, and improve our (weird) parser. */ - char *new = pecalloc(strlen(param) + 2, 1, 1); + char *new = pecalloc(ZSTR_LEN(param) + 2, 1, 1); new[0] = '$'; - memcpy(new + 1, param, strlen(param)); + memcpy(new + 1, ZSTR_VAL(param), ZSTR_LEN(param)); df->param = sp_parse_var(new); free(new); } else { - df->param = sp_parse_var(param); + df->param = sp_parse_var(ZSTR_VAL(param)); } if (!df->param) { - sp_log_err("config", "Invalid value '%s' for `param` on line %zu.", param, - sp_line_no); + sp_log_err("config", "Invalid value '%s' for `param` on line %zu.", + ZSTR_VAL(param), sp_line_no); return -1; } } if (var) { - if (*var) { - df->var = sp_parse_var(var); + if (ZSTR_LEN(var)) { + df->var = sp_parse_var(ZSTR_VAL(var)); if (!df->var) { - sp_log_err("config", "Invalid value '%s' for `var` on line %zu.", var, - sp_line_no); + sp_log_err("config", "Invalid value '%s' for `var` on line %zu.", + ZSTR_VAL(var), sp_line_no); return -1; } } else { @@ -469,38 +468,33 @@ int parse_disabled_functions(char *line) { } } - switch (get_construct_type(df)) { - case ZEND_INCLUDE_OR_EVAL: - SNUFFLEUPAGUS_G(config) - .config_disabled_constructs->construct_include = sp_list_insert( - SNUFFLEUPAGUS_G(config).config_disabled_constructs->construct_include, - df); - return ret; - case ZEND_EVAL_CODE: - SNUFFLEUPAGUS_G(config) - .config_disabled_constructs->construct_eval = sp_list_insert( - SNUFFLEUPAGUS_G(config).config_disabled_constructs->construct_eval, - df); - return ret; - case ZEND_ECHO: - default: - break; - } - if (true == disable) { return ret; } - if (df->ret || df->r_ret || df->ret_type) { - SNUFFLEUPAGUS_G(config).config_disabled_functions_ret->disabled_functions = - sp_list_insert(SNUFFLEUPAGUS_G(config) - .config_disabled_functions_ret->disabled_functions, - df); + if (df->function && !df->functions_list) { + if (df->ret || df->r_ret || df->ret_type) { + add_df_to_hashtable(SNUFFLEUPAGUS_G(config).config_disabled_functions_ret, + df); + } else { + add_df_to_hashtable(SNUFFLEUPAGUS_G(config).config_disabled_functions, + df); + } } else { - SNUFFLEUPAGUS_G(config) - .config_disabled_functions->disabled_functions = sp_list_insert( - SNUFFLEUPAGUS_G(config).config_disabled_functions->disabled_functions, - df); + if (df->ret || df->r_ret || df->ret_type) { + SNUFFLEUPAGUS_G(config) + .config_disabled_functions_reg_ret->disabled_functions = + sp_list_insert( + SNUFFLEUPAGUS_G(config) + .config_disabled_functions_reg_ret->disabled_functions, + df); + } else { + SNUFFLEUPAGUS_G(config) + .config_disabled_functions_reg->disabled_functions = + sp_list_insert(SNUFFLEUPAGUS_G(config) + .config_disabled_functions_reg->disabled_functions, + df); + } } return ret; } @@ -529,20 +523,21 @@ int parse_upload_validation(char *line) { } SNUFFLEUPAGUS_G(config).config_upload_validation->enable = enable; - char const *script = SNUFFLEUPAGUS_G(config).config_upload_validation->script; + zend_string const *script = + SNUFFLEUPAGUS_G(config).config_upload_validation->script; if (!script) { sp_log_err("config", "The `script` directive is mandatory in '%s' on line %zu.", line, sp_line_no); return -1; - } else if (-1 == access(script, F_OK)) { - sp_log_err("config", "The `script` (%s) doesn't exist on line %zu.", script, - sp_line_no); + } else if (-1 == access(ZSTR_VAL(script), F_OK)) { + sp_log_err("config", "The `script` (%s) doesn't exist on line %zu.", + ZSTR_VAL(script), sp_line_no); return -1; - } else if (-1 == access(script, X_OK)) { + } else if (-1 == access(ZSTR_VAL(script), X_OK)) { sp_log_err("config", "The `script` (%s) isn't executable on line %zu.", - script, sp_line_no); + ZSTR_VAL(script), sp_line_no); return -1; } diff --git a/src/sp_config_utils.c b/src/sp_config_utils.c index 130f07e..d72561b 100644 --- a/src/sp_config_utils.c +++ b/src/sp_config_utils.c @@ -32,18 +32,18 @@ int parse_keywords(sp_config_functions *funcs, char *line) { return 0; } -char *get_param(size_t *consumed, char *restrict line, sp_type type, - const char *restrict keyword) { +zend_string *get_param(size_t *consumed, char *restrict line, sp_type type, + const char *restrict keyword) { enum { IN_ESCAPE, NONE } state = NONE; char *original_line = line; size_t j = 0; - char *ret = NULL; + zend_string *ret = NULL; if (NULL == line || '\0' == *line) { goto err; } - ret = pecalloc(sizeof(char), strlen(original_line) + 1, 1); + ret = zend_string_alloc(strlen(line) + 1, 1); /* The first char of a string is always '"', since they MUST be quoted. */ if ('"' == *line) { @@ -65,6 +65,11 @@ char *get_param(size_t *consumed, char *restrict line, sp_type type, 2. the SP_TOKEN_END_PARAM */ *consumed = i + 2; + // Make sure that the string we return is the right size, + // as it can be smaller than strlen(line) + ret = zend_string_truncate(ret, j, 1); + // truncate does not add a \0 + ZSTR_VAL(ret)[ZSTR_LEN(ret)] = 0; return ret; } else if (state == IN_ESCAPE) { break; // we're on an escped double quote @@ -82,7 +87,7 @@ char *get_param(size_t *consumed, char *restrict line, sp_type type, if (state == IN_ESCAPE) { state = NONE; } - ret[j++] = line[i]; + ZSTR_VAL(ret)[j++] = line[i]; } err: if (0 == j) { diff --git a/src/sp_config_utils.h b/src/sp_config_utils.h index 9fef996..a63cadc 100644 --- a/src/sp_config_utils.h +++ b/src/sp_config_utils.h @@ -2,7 +2,7 @@ #define SP_CONFIG_UTILS int parse_keywords(sp_config_functions *, char *); -char *get_param(size_t *, char *restrict, sp_type, const char *restrict); +zend_string *get_param(size_t *, char *restrict, sp_type, const char *restrict); int array_to_list(char **, sp_list_node **); sp_list_node *parse_functions_list(char *value); diff --git a/src/sp_cookie_encryption.c b/src/sp_cookie_encryption.c index 6cb1ff7..3f58fb0 100644 --- a/src/sp_cookie_encryption.c +++ b/src/sp_cookie_encryption.c @@ -4,8 +4,8 @@ ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus) -static inline const sp_cookie *sp_lookup_cookie_config(const char *key) { - sp_list_node *it = SNUFFLEUPAGUS_G(config).config_cookie->cookies; +static inline const sp_cookie *sp_lookup_cookie_config(const zend_string *key) { + const sp_list_node *it = SNUFFLEUPAGUS_G(config).config_cookie->cookies; while (it) { const sp_cookie *config = it->data; @@ -20,7 +20,7 @@ static inline const sp_cookie *sp_lookup_cookie_config(const char *key) { /* called at RINIT time with each cookie, eventually decrypt said cookie */ int decrypt_cookie(zval *pDest, int num_args, va_list args, zend_hash_key *hash_key) { - const sp_cookie *cookie = sp_lookup_cookie_config(ZSTR_VAL(hash_key->key)); + const sp_cookie *cookie = sp_lookup_cookie_config(hash_key->key); /* If the cookie isn't in the conf, it shouldn't be encrypted. */ if (!cookie || !cookie->encrypt) { @@ -35,9 +35,9 @@ int decrypt_cookie(zval *pDest, int num_args, va_list args, return decrypt_zval(pDest, cookie->simulation, hash_key); } -static zend_string *encrypt_data(char *data, unsigned long long data_len) { - zend_string *z = encrypt_zval(data, data_len); - sp_log_debug("cookie_encryption", "Cookie value:%s:", z->val); +static zend_string *encrypt_data(zend_string *data) { + zend_string *z = encrypt_zval(data); + sp_log_debug("cookie_encryption", "Cookie value:%s:", ZSTR_VAL(z)); return z; } @@ -77,7 +77,7 @@ PHP_FUNCTION(sp_setcookie) { } /* lookup existing configuration for said cookie */ - cookie_node = sp_lookup_cookie_config(ZSTR_VAL(name)); + cookie_node = sp_lookup_cookie_config(name); /* If the cookie's value is encrypted, it won't be usable by * javascript anyway. @@ -88,7 +88,7 @@ PHP_FUNCTION(sp_setcookie) { /* Shall we encrypt the cookie's value? */ if (cookie_node && cookie_node->encrypt && value) { - zend_string *encrypted_data = encrypt_data(value->val, value->len); + zend_string *encrypted_data = encrypt_data(value); ZVAL_STR_COPY(¶ms[1], encrypted_data); zend_string_release(encrypted_data); } else if (value) { diff --git a/src/sp_crypt.c b/src/sp_crypt.c index 6a46d06..d3588b4 100644 --- a/src/sp_crypt.c +++ b/src/sp_crypt.c @@ -7,10 +7,13 @@ ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus) void generate_key(unsigned char *key) { PHP_SHA256_CTX ctx; const char *user_agent = getenv("HTTP_USER_AGENT"); - const char *env_var = - getenv(SNUFFLEUPAGUS_G(config).config_snuffleupagus->cookies_env_var); - const char *encryption_key = + const zend_string *env_var_zend = + SNUFFLEUPAGUS_G(config).config_snuffleupagus->cookies_env_var; + const zend_string *encryption_key_zend = SNUFFLEUPAGUS_G(config).config_snuffleupagus->encryption_key; + const char *env_var = (env_var_zend ? getenv(ZSTR_VAL(env_var_zend)) : NULL); + const char *encryption_key = + (encryption_key_zend ? ZSTR_VAL(encryption_key_zend) : NULL); assert(32 == crypto_secretbox_KEYBYTES); // 32 is the size of a SHA256. assert(encryption_key); // Encryption key can't be NULL @@ -24,10 +27,12 @@ void generate_key(unsigned char *key) { if (env_var) { PHP_SHA256Update(&ctx, (unsigned char *)env_var, strlen(env_var)); } else { - sp_log_err("cookie_encryption", - "The environment variable '%s'" - "is empty, cookies are weakly encrypted.", - SNUFFLEUPAGUS_G(config).config_snuffleupagus->cookies_env_var); + sp_log_err( + "cookie_encryption", + "The environment variable '%s'" + "is empty, cookies are weakly encrypted.", + ZSTR_VAL( + SNUFFLEUPAGUS_G(config).config_snuffleupagus->cookies_env_var)); } if (encryption_key) { @@ -119,8 +124,9 @@ int decrypt_zval(zval *pDest, bool simulation, zend_hash_key *hash_key) { ** form `base64(nonce | encrypted_data)` (with `|` being the concatenation ** operation). */ -zend_string *encrypt_zval(char *data, unsigned long long data_len) { - const size_t encrypted_msg_len = crypto_secretbox_ZEROBYTES + data_len + 1; +zend_string *encrypt_zval(zend_string *data) { + const size_t encrypted_msg_len = + crypto_secretbox_ZEROBYTES + ZSTR_LEN(data) + 1; // FIXME : We know that this len is too long const size_t emsg_and_nonce_len = encrypted_msg_len + crypto_secretbox_NONCEBYTES; @@ -137,7 +143,8 @@ zend_string *encrypt_zval(char *data, unsigned long long data_len) { /* tweetnacl's API requires the message to be padded with crypto_secretbox_ZEROBYTES zeroes. */ - memcpy(data_to_encrypt + crypto_secretbox_ZEROBYTES, data, data_len); + memcpy(data_to_encrypt + crypto_secretbox_ZEROBYTES, ZSTR_VAL(data), + ZSTR_LEN(data)); assert(sizeof(zend_long) <= crypto_secretbox_NONCEBYTES); @@ -149,4 +156,4 @@ zend_string *encrypt_zval(char *data, unsigned long long data_len) { zend_string *z = php_base64_encode(encrypted_data, emsg_and_nonce_len); return z; -} \ No newline at end of file +} diff --git a/src/sp_crypt.h b/src/sp_crypt.h index 3ede104..22a571e 100644 --- a/src/sp_crypt.h +++ b/src/sp_crypt.h @@ -12,6 +12,6 @@ void generate_key(unsigned char *key); int decrypt_zval(zval *pDest, bool simulation, zend_hash_key *hask_key); -zend_string *encrypt_zval(char *data, unsigned long long data_len); +zend_string *encrypt_zval(zend_string *); -#endif /*__SP_CRYPT */ \ No newline at end of file +#endif /*__SP_CRYPT */ diff --git a/src/sp_disabled_functions.c b/src/sp_disabled_functions.c index 341c0a4..14783f6 100644 --- a/src/sp_disabled_functions.c +++ b/src/sp_disabled_functions.c @@ -65,7 +65,7 @@ static bool is_functions_list_matching(zend_execute_data* execute_data, static bool is_local_var_matching( zend_execute_data* execute_data, const sp_disabled_function* const config_node) { - zval* var_value; + zval* var_value = {0}; var_value = sp_get_var_value(execute_data, config_node->var, false); if (var_value) { @@ -76,14 +76,13 @@ static bool is_local_var_matching( return true; } } else if (sp_match_array_value(var_value, config_node->value, - config_node->value_r)) { + config_node->r_value)) { return true; } } else { - char* var_value_str = sp_convert_to_string(var_value); + const zend_string* var_value_str = sp_zval_to_zend_string(var_value); bool match = sp_match_value(var_value_str, config_node->value, - config_node->value_r); - efree(var_value_str); + config_node->r_value); if (true == match) { return true; @@ -93,28 +92,12 @@ static bool is_local_var_matching( return false; } -static inline const sp_list_node* get_config_node(const char* builtin_name) { - if (EXPECTED(!builtin_name)) { - return SNUFFLEUPAGUS_G(config) - .config_disabled_functions->disabled_functions; - } else if (!strcmp(builtin_name, "eval")) { - return SNUFFLEUPAGUS_G(config).config_disabled_constructs->construct_eval; - } else if (!strcmp(builtin_name, "include") || - !strcmp(builtin_name, "include_once") || - !strcmp(builtin_name, "require") || - !strcmp(builtin_name, "require_once")) { - return SNUFFLEUPAGUS_G(config) - .config_disabled_constructs->construct_include; - } - ZEND_ASSUME(0); -} - static bool is_param_matching(zend_execute_data* execute_data, sp_disabled_function const* const config_node, - const char* builtin_name, - const char* builtin_param, const char** arg_name, + const zend_string* builtin_param, + const char** arg_name, const char* builtin_param_name, - const char** arg_value_str) { + const zend_string** arg_value_str) { int nb_param = ZEND_CALL_NUM_ARGS(execute_data); int i = 0; zval* arg_value; @@ -136,14 +119,14 @@ static bool is_param_matching(zend_execute_data* execute_data, } } - if (builtin_name) { + if (builtin_param) { /* We're matching on a language construct (here named "builtin"), * and they can only take a single argument, but PHP considers them * differently than functions arguments. */ *arg_name = builtin_param_name; *arg_value_str = builtin_param; return sp_match_value(builtin_param, config_node->value, - config_node->value_r); + config_node->r_value); } else if (config_node->r_param || config_node->pos != -1) { // We're matching on a function (and not a language construct) for (; i < nb_param; i++) { @@ -165,20 +148,20 @@ static bool is_param_matching(zend_execute_data* execute_data, return true; } } else if (Z_TYPE_P(arg_value) == IS_ARRAY) { - *arg_value_str = sp_convert_to_string(arg_value); + *arg_value_str = sp_zval_to_zend_string(arg_value); if (config_node->key || config_node->r_key) { if (sp_match_array_key(arg_value, config_node->key, config_node->r_key)) { return true; } } else if (sp_match_array_value(arg_value, config_node->value, - config_node->value_r)) { + config_node->r_value)) { return true; } } else { - *arg_value_str = sp_convert_to_string(arg_value); + *arg_value_str = sp_zval_to_zend_string(arg_value); if (sp_match_value(*arg_value_str, config_node->value, - config_node->value_r)) { + config_node->r_value)) { return true; } } @@ -189,7 +172,7 @@ static bool is_param_matching(zend_execute_data* execute_data, arg_value = sp_get_var_value(execute_data, config_node->param, true); if (arg_value) { - *arg_value_str = sp_convert_to_string(arg_value); + *arg_value_str = sp_zval_to_zend_string(arg_value); if (config_node->param_type) { // Are we matching on the `type`? if (config_node->param_type == Z_TYPE_P(arg_value)) { return true; @@ -201,11 +184,11 @@ static bool is_param_matching(zend_execute_data* execute_data, return true; } } else if (sp_match_array_value(arg_value, config_node->value, - config_node->value_r)) { + config_node->r_value)) { return true; } } else if (sp_match_value(*arg_value_str, config_node->value, - config_node->value_r)) { + config_node->r_value)) { return true; } } @@ -216,7 +199,7 @@ static bool is_param_matching(zend_execute_data* execute_data, static zend_execute_data* is_file_matching( zend_execute_data* const execute_data, sp_disabled_function const* const config_node, - char const* const current_filename) { + zend_string const* const current_filename) { #define ITERATE(ex) \ ex = ex->prev_execute_data; \ while (ex && (!ex->func || !ZEND_USER_CODE(ex->func->type))) \ @@ -225,22 +208,21 @@ static zend_execute_data* is_file_matching( zend_execute_data* ex = execute_data; if (config_node->filename) { - if (0 == strcmp(current_filename, config_node->filename)) { + if (zend_string_equals_literal(current_filename, config_node->filename)) { return ex; } ITERATE(ex); - if (0 == - strcmp(ZSTR_VAL(ex->func->op_array.filename), config_node->filename)) { + if (zend_string_equals_literal(ex->func->op_array.filename, + config_node->filename)) { return ex; } } else if (config_node->r_filename) { - if (true == - sp_is_regexp_matching(config_node->r_filename, current_filename)) { + if (sp_is_regexp_matching_zend(config_node->r_filename, current_filename)) { return ex; } ITERATE(ex); - if (true == sp_is_regexp_matching(config_node->r_filename, - ZSTR_VAL(ex->func->op_array.filename))) { + if (sp_is_regexp_matching_zend(config_node->r_filename, + ex->func->op_array.filename)) { return ex; } } @@ -251,10 +233,10 @@ static zend_execute_data* is_file_matching( static bool check_is_builtin_name( sp_disabled_function const* const config_node) { if (config_node->function) { - return (!strcmp(config_node->function, "include") || - !strcmp(config_node->function, "include_once") || - !strcmp(config_node->function, "require") || - !strcmp(config_node->function, "require_once")); + return (zend_string_equals_literal(config_node->function, "include") || + zend_string_equals_literal(config_node->function, "include_once") || + zend_string_equals_literal(config_node->function, "require") || + zend_string_equals_literal(config_node->function, "require_once")); } if (config_node->r_function) { return (sp_is_regexp_matching(config_node->r_function, "include") || @@ -265,43 +247,67 @@ static bool check_is_builtin_name( return false; } -bool should_disable(zend_execute_data* execute_data, const char* builtin_name, - const char* builtin_param, const char* builtin_param_name) { - char current_file_hash[SHA256_SIZE * 2 + 1] = {0}; - const sp_list_node* config = get_config_node(builtin_name); - char* complete_path_function = NULL; - const char* current_filename = NULL; - unsigned int line = 0; - char* filename = NULL; +bool should_disable_ht(zend_execute_data* execute_data, + const char* builtin_name, + const zend_string* builtin_param, + const char* builtin_param_name, + const sp_list_node* config, const HashTable* ht) { + char* complete_function_path = NULL; + const sp_list_node* ht_entry = NULL; + bool ret = false; + zend_string* current_filename; if (!execute_data) { return false; } - if (!config || !config->data) { - return false; + if (builtin_name) { + complete_function_path = estrdup(builtin_name); + } else { + complete_function_path = get_complete_function_path(execute_data); + if (!complete_function_path) { + return false; + } } - if (UNEXPECTED(builtin_name && !strcmp(builtin_name, "eval"))) { + if (UNEXPECTED(builtin_param && !strcmp(complete_function_path, "eval"))) { current_filename = get_eval_filename(zend_get_executed_filename()); } else { - current_filename = zend_get_executed_filename(); + const char* tmp = zend_get_executed_filename(); + current_filename = zend_string_init(tmp, strlen(tmp), 0); } - complete_path_function = get_complete_function_path(execute_data); - if (!complete_path_function) { - if (builtin_name) { - complete_path_function = estrdup(builtin_name); - } else { - return false; - } + ht_entry = zend_hash_str_find_ptr(ht, complete_function_path, + strlen(complete_function_path)); + + if (ht_entry && + should_disable(execute_data, complete_function_path, builtin_param, + builtin_param_name, ht_entry, current_filename)) { + ret = true; + } else if (config && config->data) { + ret = should_disable(execute_data, complete_function_path, builtin_param, + builtin_param_name, config, current_filename); } + efree(complete_function_path); + efree(current_filename); + return ret; +} + +bool should_disable(zend_execute_data* execute_data, + const char* complete_function_path, + const zend_string* builtin_param, + const char* builtin_param_name, const sp_list_node* config, + const zend_string* current_filename) { + char current_file_hash[SHA256_SIZE * 2 + 1] = {0}; + unsigned int line = 0; + char* filename = NULL; + while (config) { sp_disabled_function const* const config_node = (sp_disabled_function*)(config->data); const char* arg_name = NULL; - const char* arg_value_str = NULL; + const zend_string* arg_value_str = NULL; /* The order matters, since when we have `config_node->functions_list`, we also do have `config_node->function` */ @@ -311,12 +317,13 @@ bool should_disable(zend_execute_data* execute_data, const char* builtin_name, goto next; } } else if (config_node->function) { - if (0 != strcmp(config_node->function, complete_path_function)) { + if (0 != + strcmp(ZSTR_VAL(config_node->function), complete_function_path)) { goto next; } } else if (config_node->r_function) { if (false == sp_is_regexp_matching(config_node->r_function, - complete_path_function)) { + complete_function_path)) { goto next; } } @@ -350,9 +357,10 @@ bool should_disable(zend_execute_data* execute_data, const char* builtin_name, if (config_node->hash) { if ('\0' == current_file_hash[0]) { - compute_hash(current_filename, current_file_hash); + compute_hash(ZSTR_VAL(current_filename), current_file_hash); } - if (0 != strncmp(current_file_hash, config_node->hash, SHA256_SIZE)) { + if (0 != strncmp(current_file_hash, ZSTR_VAL(config_node->hash), + SHA256_SIZE)) { goto next; } } @@ -360,25 +368,25 @@ bool should_disable(zend_execute_data* execute_data, const char* builtin_name, /* Check if we filter on parameter value*/ if (config_node->param || config_node->r_param || (config_node->pos != -1)) { - if (!builtin_name && execute_data->func->op_array.arg_info->is_variadic) { + if (!builtin_param && + execute_data->func->op_array.arg_info->is_variadic) { sp_log_err( "disable_function", "Snuffleupagus doesn't support variadic functions yet, sorry. " "Check https://github.com/nbs-system/snuffleupagus/issues/164 for " "details."); - } else if (false == is_param_matching(execute_data, config_node, - builtin_name, builtin_param, - &arg_name, builtin_param_name, - &arg_value_str)) { + } else if (false == is_param_matching( + execute_data, config_node, builtin_param, + &arg_name, builtin_param_name, &arg_value_str)) { goto next; } } - if (config_node->value_r || config_node->value) { + if (config_node->r_value || config_node->value) { if (check_is_builtin_name(config_node)) { - if (false == is_param_matching(execute_data, config_node, builtin_name, - builtin_param, &arg_name, - builtin_param_name, &arg_value_str)) { + if (false == is_param_matching(execute_data, config_node, builtin_param, + &arg_name, builtin_param_name, + &arg_value_str)) { goto next; } } @@ -390,63 +398,76 @@ bool should_disable(zend_execute_data* execute_data, const char* builtin_name, } if (config_node->functions_list) { - sp_log_disable(config_node->function, arg_name, arg_value_str, + sp_log_disable(ZSTR_VAL(config_node->function), arg_name, arg_value_str, config_node, line, filename); } else { - sp_log_disable(complete_path_function, arg_name, arg_value_str, + sp_log_disable(complete_function_path, arg_name, arg_value_str, config_node, line, filename); } if (true == config_node->simulation) { goto next; } else { // We've got a match, the function won't be executed - efree(complete_path_function); return true; } next: config = config->next; } allow: - efree(complete_path_function); return false; } -bool should_drop_on_ret(zval* return_value, - const zend_execute_data* const execute_data) { - const sp_list_node* config = - SNUFFLEUPAGUS_G(config).config_disabled_functions_ret->disabled_functions; - char* complete_path_function = get_complete_function_path(execute_data); - const char* current_filename = zend_get_executed_filename(TSRMLS_C); - char current_file_hash[SHA256_SIZE * 2 + 1] = {0}; - bool match_type = false, match_value = false; +bool should_drop_on_ret_ht(zval* return_value, + const zend_execute_data* const execute_data, + const sp_list_node* config, const HashTable* ht) { + char* complete_function_path = get_complete_function_path(execute_data); + const sp_list_node* ht_entry = NULL; + bool ret = false; - if (!complete_path_function) { - return false; + if (!complete_function_path) { + return ret; } - if (!config || !config->data) { - return false; + ht_entry = zend_hash_str_find_ptr(ht, complete_function_path, + strlen(complete_function_path)); + + if (ht_entry && + should_drop_on_ret(return_value, ht_entry, complete_function_path)) { + ret = true; + } else if (config && config->data) { + ret = should_drop_on_ret(return_value, config, complete_function_path); } + efree(complete_function_path); + return ret; +} + +bool should_drop_on_ret(zval* return_value, const sp_list_node* config, + const char* complete_function_path) { + const char* current_filename = zend_get_executed_filename(TSRMLS_C); + char current_file_hash[SHA256_SIZE * 2 + 1] = {0}; + bool match_type = false, match_value = false; + while (config) { - char* ret_value_str = NULL; + const zend_string* ret_value_str = NULL; sp_disabled_function const* const config_node = (sp_disabled_function*)(config->data); assert(config_node->function || config_node->r_function); if (config_node->function) { - if (0 != strcmp(config_node->function, complete_path_function)) { + if (0 != + strcmp(ZSTR_VAL(config_node->function), complete_function_path)) { goto next; } } else if (config_node->r_function) { if (false == sp_is_regexp_matching(config_node->r_function, - complete_path_function)) { + complete_function_path)) { goto next; } } if (config_node->filename) { /* Check the current file name. */ - if (0 != strcmp(current_filename, config_node->filename)) { + if (0 != strcmp(current_filename, ZSTR_VAL(config_node->filename))) { goto next; } } else if (config_node->r_filename) { @@ -460,12 +481,13 @@ bool should_drop_on_ret(zval* return_value, if ('\0' == current_file_hash[0]) { compute_hash(current_filename, current_file_hash); } - if (0 != strncmp(current_file_hash, config_node->hash, SHA256_SIZE)) { + if (0 != strncmp(current_file_hash, ZSTR_VAL(config_node->hash), + SHA256_SIZE)) { goto next; } } - ret_value_str = sp_convert_to_string(return_value); + ret_value_str = sp_zval_to_zend_string(return_value); match_type = (config_node->ret_type) && (config_node->ret_type == Z_TYPE_P(return_value)); @@ -475,22 +497,16 @@ bool should_drop_on_ret(zval* return_value, if (true == match_type || true == 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); + sp_log_disable_ret(complete_function_path, 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); return false; } @@ -498,7 +514,11 @@ ZEND_FUNCTION(check_disabled_function) { void (*orig_handler)(INTERNAL_FUNCTION_PARAMETERS); const char* current_function_name = get_active_function_name(TSRMLS_C); - if (true == should_disable(execute_data, NULL, NULL, NULL)) { + if (true == should_disable_ht( + execute_data, NULL, NULL, NULL, + SNUFFLEUPAGUS_G(config) + .config_disabled_functions_reg->disabled_functions, + SNUFFLEUPAGUS_G(config).config_disabled_functions_hooked)) { sp_terminate(); } @@ -506,23 +526,29 @@ ZEND_FUNCTION(check_disabled_function) { SNUFFLEUPAGUS_G(disabled_functions_hook), current_function_name, strlen(current_function_name)); orig_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU); - if (true == should_drop_on_ret(return_value, execute_data)) { + if (true == + should_drop_on_ret_ht( + return_value, execute_data, + SNUFFLEUPAGUS_G(config) + .config_disabled_functions_reg_ret->disabled_functions, + SNUFFLEUPAGUS_G(config).config_disabled_functions_ret_hooked)) { sp_terminate(); } } -static int hook_functions(const sp_list_node* config) { +static int hook_functions_regexp(const sp_list_node* config) { while (config && config->data) { - const char* function_name = ((sp_disabled_function*)config->data)->function; + const zend_string* function_name = + ((sp_disabled_function*)config->data)->function; const sp_pcre* function_name_regexp = ((sp_disabled_function*)config->data)->r_function; assert(function_name || function_name_regexp); - if (NULL != function_name) { // hook function by name - HOOK_FUNCTION(function_name, disabled_functions_hook, + if (function_name) { + HOOK_FUNCTION(ZSTR_VAL(function_name), disabled_functions_hook, PHP_FN(check_disabled_function)); - } else if (NULL != function_name_regexp) { // hook function by regexp + } else { HOOK_FUNCTION_BY_REGEXP(function_name_regexp, disabled_functions_hook, PHP_FN(check_disabled_function)); } @@ -532,16 +558,36 @@ static int hook_functions(const sp_list_node* config) { return SUCCESS; } +static int hook_functions(HashTable* to_hook_ht, HashTable* hooked_ht) { + zend_string* key; + zval* value; + + ZEND_HASH_FOREACH_STR_KEY_VAL(to_hook_ht, key, value) { + if (!HOOK_FUNCTION(ZSTR_VAL(key), disabled_functions_hook, + PHP_FN(check_disabled_function)) || + check_is_builtin_name(((sp_list_node*)Z_PTR_P(value))->data)) { + zend_symtable_add_new(hooked_ht, key, value); + zend_hash_del(to_hook_ht, key); + } + } + ZEND_HASH_FOREACH_END(); + return SUCCESS; +} + ZEND_FUNCTION(eval_blacklist_callback) { void (*orig_handler)(INTERNAL_FUNCTION_PARAMETERS); const char* current_function_name = get_active_function_name(TSRMLS_C); + zend_string* tmp = + zend_string_init(current_function_name, strlen(current_function_name), 0); - if (true == check_is_in_eval_whitelist(current_function_name)) { + if (true == check_is_in_eval_whitelist(tmp)) { + zend_string_release(tmp); goto whitelisted; } + zend_string_release(tmp); if (SNUFFLEUPAGUS_G(in_eval) > 0) { - char* filename = get_eval_filename(zend_get_executed_filename()); + zend_string* filename = get_eval_filename(zend_get_executed_filename()); const int line_number = zend_get_executed_lineno(TSRMLS_C); if (SNUFFLEUPAGUS_G(config).config_eval->dump) { sp_log_request( @@ -549,14 +595,14 @@ ZEND_FUNCTION(eval_blacklist_callback) { SNUFFLEUPAGUS_G(config).config_eval->textual_representation, SP_TOKEN_EVAL_BLACKLIST); } - if (1 == SNUFFLEUPAGUS_G(config).config_eval->simulation) { + if (SNUFFLEUPAGUS_G(config).config_eval->simulation) { sp_log_msg("eval", SP_LOG_SIMULATION, "A call to %s was tried in eval, in %s:%d, logging it.", - current_function_name, filename, line_number); + current_function_name, ZSTR_VAL(filename), line_number); } else { sp_log_msg("eval", SP_LOG_DROP, "A call to %s was tried in eval, in %s:%d, dropping it.", - current_function_name, filename, line_number); + current_function_name, ZSTR_VAL(filename), line_number); sp_terminate(); } efree(filename); @@ -574,21 +620,31 @@ int hook_disabled_functions(void) { int ret = SUCCESS; + ret |= + hook_functions(SNUFFLEUPAGUS_G(config).config_disabled_functions, + SNUFFLEUPAGUS_G(config).config_disabled_functions_hooked); + ret |= hook_functions( - SNUFFLEUPAGUS_G(config).config_disabled_functions->disabled_functions); - ret |= hook_functions(SNUFFLEUPAGUS_G(config) - .config_disabled_functions_ret->disabled_functions); + SNUFFLEUPAGUS_G(config).config_disabled_functions_ret, + SNUFFLEUPAGUS_G(config).config_disabled_functions_ret_hooked); + + ret |= hook_functions_regexp( + SNUFFLEUPAGUS_G(config) + .config_disabled_functions_reg->disabled_functions); + + ret |= hook_functions_regexp( + SNUFFLEUPAGUS_G(config) + .config_disabled_functions_reg_ret->disabled_functions); if (NULL != SNUFFLEUPAGUS_G(config).config_eval->blacklist) { sp_list_node* it = SNUFFLEUPAGUS_G(config).config_eval->blacklist; while (it) { - hook_function((char*)it->data, + hook_function(ZSTR_VAL((zend_string*)it->data), SNUFFLEUPAGUS_G(sp_eval_blacklist_functions_hook), PHP_FN(eval_blacklist_callback)); it = it->next; } } - return ret; } diff --git a/src/sp_disabled_functions.h b/src/sp_disabled_functions.h index f80c9c2..4e795a1 100644 --- a/src/sp_disabled_functions.h +++ b/src/sp_disabled_functions.h @@ -2,8 +2,11 @@ #define __SP_DISABLE_FUNCTIONS_H int hook_disabled_functions(); -bool should_disable(zend_execute_data *, const char *, const char *, - const char *); -bool should_drop_on_ret(zval *, const zend_execute_data *const); +bool should_disable(zend_execute_data *, const char *, const zend_string *, + const char *, const sp_list_node *, const zend_string *); +bool should_disable_ht(zend_execute_data *, const char *, const zend_string *, + const char *, const sp_list_node *, const HashTable *); +bool should_drop_on_ret_ht(zval *, const zend_execute_data *const, const sp_list_node* config, const HashTable *); +bool should_drop_on_ret(zval *, const sp_list_node* config, const char *); #endif /* __SP_DISABLE_FUNCTIONS_H */ diff --git a/src/sp_execute.c b/src/sp_execute.c index fb956ca..6e38c75 100644 --- a/src/sp_execute.c +++ b/src/sp_execute.c @@ -36,16 +36,21 @@ ZEND_COLD static inline void terminate_if_writable(const char *filename) { } } -inline static void is_builtin_matching(const char *restrict const filename, - const char *restrict const function_name, - const char *restrict const param_name, - const sp_list_node *config) { +inline static void is_builtin_matching( + const zend_string *restrict const param_value, + const char *restrict const function_name, + const char *restrict const param_name, const sp_list_node *config, + const HashTable *ht) { if (!config || !config->data) { return; } - if (true == should_disable(EG(current_execute_data), function_name, filename, - param_name)) { + if (true == + should_disable_ht(EG(current_execute_data), function_name, param_value, + param_name, + SNUFFLEUPAGUS_G(config) + .config_disabled_functions_reg->disabled_functions, + ht)) { sp_terminate(); } } @@ -70,7 +75,7 @@ is_in_eval_and_whitelisted(const zend_execute_data *execute_data) { return; } - char const *const current_function = ZSTR_VAL(EX(func)->common.function_name); + zend_string const *const current_function = EX(func)->common.function_name; if (EXPECTED(NULL != current_function)) { if (UNEXPECTED(false == check_is_in_eval_whitelist(current_function))) { @@ -84,13 +89,13 @@ is_in_eval_and_whitelisted(const zend_execute_data *execute_data) { sp_log_msg( "Eval_whitelist", SP_LOG_SIMULATION, "The function '%s' isn't in the eval whitelist, logging its call.", - current_function); + ZSTR_VAL(current_function)); return; } else { sp_log_msg( "Eval_whitelist", SP_LOG_DROP, "The function '%s' isn't in the eval whitelist, dropping its call.", - current_function); + ZSTR_VAL(current_function)); sp_terminate(); } } @@ -100,15 +105,15 @@ is_in_eval_and_whitelisted(const zend_execute_data *execute_data) { /* This function gets the filename in which `eval()` is called from, * since it looks like "foo.php(1) : eval()'d code", so we're starting * from the end of the string until the second closing parenthesis. */ -char *get_eval_filename(const char *const filename) { - size_t i = strlen(filename); +zend_string *get_eval_filename(const char *const filename) { int count = 0; - char *clean_filename = estrdup(filename); + zend_string *clean_filename = zend_string_init(filename, strlen(filename), 0); - while (i--) { - if (clean_filename[i] == '(') { + for (int i = ZSTR_LEN(clean_filename); i >= 0; i--) { + if (ZSTR_VAL(clean_filename)[i] == '(') { if (count == 1) { - clean_filename[i] = '\0'; + ZSTR_VAL(clean_filename)[i] = '\0'; + clean_filename = zend_string_truncate(clean_filename, i, 0); break; } count++; @@ -125,11 +130,12 @@ static void sp_execute_ex(zend_execute_data *execute_data) { } if (UNEXPECTED(EX(func)->op_array.type == ZEND_EVAL_CODE)) { - const sp_list_node *config = - SNUFFLEUPAGUS_G(config).config_disabled_constructs->construct_eval; - char *filename = get_eval_filename((char *)zend_get_executed_filename()); - is_builtin_matching(filename, "eval", NULL, config); - efree(filename); + const sp_list_node *config = zend_hash_str_find_ptr( + SNUFFLEUPAGUS_G(config).config_disabled_functions, "eval", 4); + zend_string *filename = get_eval_filename(zend_get_executed_filename()); + is_builtin_matching(filename, "eval", NULL, config, + SNUFFLEUPAGUS_G(config).config_disabled_functions); + zend_string_release(filename); SNUFFLEUPAGUS_G(in_eval)++; orig_execute_ex(execute_data); @@ -137,34 +143,54 @@ static void sp_execute_ex(zend_execute_data *execute_data) { return; } - if (!execute_data->prev_execute_data || - !execute_data->prev_execute_data->func || - !ZEND_USER_CODE(execute_data->prev_execute_data->func->type) || - !execute_data->prev_execute_data->opline) { - if (UNEXPECTED(true == should_disable(execute_data, NULL, NULL, NULL))) { - sp_terminate(); - } - } else if ((execute_data->prev_execute_data->opline->opcode == - ZEND_DO_FCALL || - execute_data->prev_execute_data->opline->opcode == - ZEND_DO_UCALL || - execute_data->prev_execute_data->opline->opcode == - ZEND_DO_FCALL_BY_NAME)) { - if (UNEXPECTED(true == should_disable(execute_data, NULL, NULL, NULL))) { - sp_terminate(); - } - } - if (NULL != EX(func)->op_array.filename) { if (true == SNUFFLEUPAGUS_G(config).config_readonly_exec->enable) { terminate_if_writable(ZSTR_VAL(EX(func)->op_array.filename)); } } - orig_execute_ex(execute_data); + if (SNUFFLEUPAGUS_G(config).hook_execute) { + if (!execute_data->prev_execute_data || + !execute_data->prev_execute_data->func || + !ZEND_USER_CODE(execute_data->prev_execute_data->func->type) || + !execute_data->prev_execute_data->opline) { + if (UNEXPECTED(true == + should_disable_ht( + execute_data, NULL, NULL, NULL, + SNUFFLEUPAGUS_G(config) + .config_disabled_functions_reg->disabled_functions, + SNUFFLEUPAGUS_G(config).config_disabled_functions))) { + sp_terminate(); + } + } else if ((execute_data->prev_execute_data->opline->opcode == + ZEND_DO_FCALL || + execute_data->prev_execute_data->opline->opcode == + ZEND_DO_UCALL || + execute_data->prev_execute_data->opline->opcode == + ZEND_DO_FCALL_BY_NAME)) { + if (UNEXPECTED(true == + should_disable_ht( + execute_data, NULL, NULL, NULL, + SNUFFLEUPAGUS_G(config) + .config_disabled_functions_reg->disabled_functions, + SNUFFLEUPAGUS_G(config).config_disabled_functions))) { + sp_terminate(); + } + } + + orig_execute_ex(execute_data); - if (UNEXPECTED(true == should_drop_on_ret(EX(return_value), execute_data))) { - sp_terminate(); + if (UNEXPECTED( + true == + should_drop_on_ret_ht( + EX(return_value), execute_data, + SNUFFLEUPAGUS_G(config) + .config_disabled_functions_reg_ret->disabled_functions, + SNUFFLEUPAGUS_G(config).config_disabled_functions_ret))) { + sp_terminate(); + } + } else { + orig_execute_ex(execute_data); } } @@ -186,31 +212,49 @@ static int sp_stream_open(const char *filename, zend_file_handle *handle) { goto end; } + zend_string *zend_filename = zend_string_init(filename, strlen(filename), 0); switch (data->opline->opcode) { case ZEND_INCLUDE_OR_EVAL: if (true == SNUFFLEUPAGUS_G(config).config_readonly_exec->enable) { terminate_if_writable(filename); } - const sp_list_node *config = - SNUFFLEUPAGUS_G(config).config_disabled_constructs->construct_include; switch (data->opline->extended_value) { case ZEND_INCLUDE: - is_builtin_matching(filename, "include", "inclusion path", config); + is_builtin_matching( + zend_filename, "include", "inclusion path", + zend_hash_str_find_ptr( + SNUFFLEUPAGUS_G(config).config_disabled_functions_hooked, + "include", 7), + SNUFFLEUPAGUS_G(config).config_disabled_functions_hooked); break; case ZEND_REQUIRE: - is_builtin_matching(filename, "require", "inclusion path", config); + is_builtin_matching( + zend_filename, "require", "inclusion path", + zend_hash_str_find_ptr( + SNUFFLEUPAGUS_G(config).config_disabled_functions_hooked, + "require", 7), + SNUFFLEUPAGUS_G(config).config_disabled_functions_hooked); break; case ZEND_REQUIRE_ONCE: - is_builtin_matching(filename, "require_once", "inclusion path", - config); + is_builtin_matching( + zend_filename, "require_once", "inclusion path", + zend_hash_str_find_ptr( + SNUFFLEUPAGUS_G(config).config_disabled_functions_hooked, + "require_once", 12), + SNUFFLEUPAGUS_G(config).config_disabled_functions_hooked); break; case ZEND_INCLUDE_ONCE: - is_builtin_matching(filename, "include_once", "inclusion path", - config); + is_builtin_matching( + zend_filename, "include_once", "inclusion path", + zend_hash_str_find_ptr( + SNUFFLEUPAGUS_G(config).config_disabled_functions_hooked, + "include_once", 12), + SNUFFLEUPAGUS_G(config).config_disabled_functions_hooked); break; EMPTY_SWITCH_DEFAULT_CASE(); } } + efree(zend_filename); end: return orig_zend_stream_open(filename, handle); diff --git a/src/sp_execute.h b/src/sp_execute.h index fcd0e11..d9eeee8 100644 --- a/src/sp_execute.h +++ b/src/sp_execute.h @@ -2,6 +2,6 @@ #define SP_EXECUTE_H int hook_execute(void); -char *get_eval_filename(const char * const filename); +zend_string *get_eval_filename(const char * const filename); #endif /* SP_EXECUTE_H */ diff --git a/src/sp_pcre_compat.h b/src/sp_pcre_compat.h index a9eb253..6658316 100644 --- a/src/sp_pcre_compat.h +++ b/src/sp_pcre_compat.h @@ -27,6 +27,8 @@ #endif sp_pcre* sp_pcre_compile(const char* str); +#define sp_is_regexp_matching_zend(regexp, zstr) \ + sp_is_regexp_matching_len(regexp, ZSTR_VAL(zstr), ZSTR_LEN(zstr)) #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); diff --git a/src/sp_session.c b/src/sp_session.c index ce852ad..ea65f57 100644 --- a/src/sp_session.c +++ b/src/sp_session.c @@ -51,7 +51,7 @@ static int sp_hook_s_read(PS_READ_ARGS) { static int sp_hook_s_write(PS_WRITE_ARGS) { if (ZSTR_LEN(val) > 0 && SNUFFLEUPAGUS_G(config).config_session->encrypt) { - zend_string *new_val = encrypt_zval(ZSTR_VAL(val), ZSTR_LEN(val)); + zend_string *new_val = encrypt_zval(val); return old_s_write(mod_data, key, new_val, maxlifetime); } return old_s_write(mod_data, key, val, maxlifetime); @@ -150,4 +150,4 @@ void hook_session() { s_module = NULL; sp_hook_session_module(); -} \ No newline at end of file +} diff --git a/src/sp_sloppy.c b/src/sp_sloppy.c index 05d2505..09e79d8 100644 --- a/src/sp_sloppy.c +++ b/src/sp_sloppy.c @@ -1,7 +1,9 @@ #include "sp_sloppy.h" -ZEND_API zend_op_array* (*zend_compile_file_default)(zend_file_handle* file_handle, int type) = NULL; -ZEND_API zend_op_array* (*zend_compile_string_default)(zval* source_string, char* filename) = NULL; +ZEND_API zend_op_array* (*zend_compile_file_default)( + zend_file_handle* file_handle, int type) = NULL; +ZEND_API zend_op_array* (*zend_compile_string_default)(zval* source_string, + char* filename) = NULL; static void modify_opcode(zend_op_array* opline) { if (NULL != opline) { @@ -24,7 +26,8 @@ ZEND_API zend_op_array* sp_compile_string(zval* source_string, char* filename) { return opline; } -ZEND_API zend_op_array* sp_compile_file(zend_file_handle* file_handle, int type) { +ZEND_API zend_op_array* sp_compile_file(zend_file_handle* file_handle, + int type) { zend_op_array* opline = zend_compile_file_default(file_handle, type); modify_opcode(opline); return opline; diff --git a/src/sp_unserialize.c b/src/sp_unserialize.c index 60ef7be..db99389 100644 --- a/src/sp_unserialize.c +++ b/src/sp_unserialize.c @@ -18,8 +18,9 @@ PHP_FUNCTION(sp_serialize) { ZVAL_STRING(&func_name, "hash_hmac"); ZVAL_STRING(¶ms[0], "sha256"); params[1] = *return_value; - ZVAL_STRING(¶ms[2], - SNUFFLEUPAGUS_G(config).config_snuffleupagus->encryption_key); + ZVAL_STRING( + ¶ms[2], + ZSTR_VAL(SNUFFLEUPAGUS_G(config).config_snuffleupagus->encryption_key)); call_user_function(CG(function_table), NULL, &func_name, &hmac, 3, params); size_t len = Z_STRLEN_P(return_value) + Z_STRLEN(hmac); @@ -66,8 +67,9 @@ PHP_FUNCTION(sp_unserialize) { zval params[3]; ZVAL_STRING(¶ms[0], "sha256"); ZVAL_STRING(¶ms[1], serialized_str); - ZVAL_STRING(¶ms[2], - SNUFFLEUPAGUS_G(config).config_snuffleupagus->encryption_key); + ZVAL_STRING( + ¶ms[2], + ZSTR_VAL(SNUFFLEUPAGUS_G(config).config_snuffleupagus->encryption_key)); call_user_function(CG(function_table), NULL, &func_name, &expected_hmac, 3, params); @@ -113,4 +115,4 @@ int hook_serialize(void) { PHP_FN(sp_unserialize)); return SUCCESS; -} \ No newline at end of file +} diff --git a/src/sp_upload_validation.c b/src/sp_upload_validation.c index 73244ae..794c61f 100644 --- a/src/sp_upload_validation.c +++ b/src/sp_upload_validation.c @@ -41,12 +41,14 @@ int sp_rfc1867_callback(unsigned int event, void *event_data, void **extra) { char *cmd[3] = {0}; char *env[5] = {0}; - sp_log_debug("Filename: %s\nTmpname: %s\nSize: %d\nError: %d\nScript: %s", - filename, tmp_name, filesize, - Z_LVAL_P(zend_hash_str_find(Z_ARRVAL_P(file), "error", 5)), - SNUFFLEUPAGUS_G(config).config_upload_validation->script); + sp_log_debug( + "Filename: %s\nTmpname: %s\nSize: %d\nError: %d\nScript: %s", + filename, tmp_name, filesize, + Z_LVAL_P(zend_hash_str_find(Z_ARRVAL_P(file), "error", 5)), + ZSTR_VAL(SNUFFLEUPAGUS_G(config).config_upload_validation->script)); - cmd[0] = SNUFFLEUPAGUS_G(config).config_upload_validation->script; + cmd[0] = + ZSTR_VAL(SNUFFLEUPAGUS_G(config).config_upload_validation->script); cmd[1] = tmp_name; cmd[2] = NULL; @@ -58,11 +60,15 @@ int sp_rfc1867_callback(unsigned int event, void *event_data, void **extra) { env[4] = NULL; if ((pid = fork()) == 0) { - if (execve(SNUFFLEUPAGUS_G(config).config_upload_validation->script, - cmd, env) == -1) { - sp_log_err("upload_validation", "Could not call '%s' : %s", - SNUFFLEUPAGUS_G(config).config_upload_validation->script, - strerror(errno)); + if (execve( + ZSTR_VAL( + SNUFFLEUPAGUS_G(config).config_upload_validation->script), + cmd, env) == -1) { + sp_log_err( + "upload_validation", "Could not call '%s' : %s", + ZSTR_VAL( + SNUFFLEUPAGUS_G(config).config_upload_validation->script), + strerror(errno)); EFREE_3(env); exit(1); } diff --git a/src/sp_utils.c b/src/sp_utils.c index 8b7ce49..a94ab2a 100644 --- a/src/sp_utils.c +++ b/src/sp_utils.c @@ -19,6 +19,14 @@ static inline void _sp_log_err(const char* fmt, ...) { php_log_err(msg); } +static bool sp_zend_string_equals(const zend_string* s1, + const zend_string* s2) { + // We can't use `zend_string_equals` here because it doesn't work on + // `const` zend_string. + return ZSTR_LEN(s1) == ZSTR_LEN(s2) && + !memcmp(ZSTR_VAL(s1), ZSTR_VAL(s2), ZSTR_LEN(s1)); +} + void sp_log_msg(char const* feature, char const* level, const char* fmt, ...) { char* msg; va_list args; @@ -56,14 +64,15 @@ int compute_hash(const char* const filename, char* file_hash) { return SUCCESS; } -static int construct_filename(char* filename, const char* folder, - const char* textual) { +static int construct_filename(char* filename, const zend_string* folder, + const zend_string* textual) { PHP_SHA256_CTX context; unsigned char digest[SHA256_SIZE] = {0}; char strhash[65] = {0}; - if (-1 == mkdir(folder, 0700) && errno != EEXIST) { - sp_log_err("request_logging", "Unable to create the folder '%s'.", folder); + if (-1 == mkdir(ZSTR_VAL(folder), 0700) && errno != EEXIST) { + sp_log_err("request_logging", "Unable to create the folder '%s'.", + ZSTR_VAL(folder)); return -1; } @@ -71,15 +80,17 @@ static int construct_filename(char* filename, const char* folder, * as filename, in order to only have one dump per rule, to migitate * DoS attacks. */ PHP_SHA256Init(&context); - PHP_SHA256Update(&context, (const unsigned char*)textual, strlen(textual)); + PHP_SHA256Update(&context, (const unsigned char*)ZSTR_VAL(textual), + ZSTR_LEN(textual)); PHP_SHA256Final(digest, &context); make_digest_ex(strhash, digest, SHA256_SIZE); - snprintf(filename, PATH_MAX - 1, "%s/sp_dump.%s", folder, strhash); + snprintf(filename, PATH_MAX - 1, "%s/sp_dump.%s", ZSTR_VAL(folder), strhash); return 0; } -int sp_log_request(const char* folder, const char* text_repr, char* from) { +int sp_log_request(const zend_string* folder, const zend_string* text_repr, + char* from) { FILE* file; const char* current_filename = zend_get_executed_filename(TSRMLS_C); const int current_line = zend_get_executed_lineno(TSRMLS_C); @@ -100,7 +111,7 @@ int sp_log_request(const char* folder, const char* text_repr, char* from) { return -1; } - fprintf(file, "RULE: sp%s%s\n", from, text_repr); + fprintf(file, "RULE: sp%s%s\n", from, ZSTR_VAL(text_repr)); fprintf(file, "FILE: %s:%d\n", current_filename, current_line); for (size_t i = 0; i < (sizeof(zones) / sizeof(zones[0])) - 1; i++) { @@ -130,57 +141,65 @@ int sp_log_request(const char* folder, const char* text_repr, char* from) { return 0; } -static char* zv_str_to_char(zval* zv) { - zval copy; +static char* zend_string_to_char(const zend_string* zs) { + // Remove \0 from the middle of a string + char* copy = emalloc(ZSTR_LEN(zs) + 1); - ZVAL_ZVAL(©, zv, 1, 0); - for (size_t i = 0; i < Z_STRLEN(copy); i++) { - if (Z_STRVAL(copy)[i] == '\0') { - Z_STRVAL(copy)[i] = '0'; + copy[ZSTR_LEN(zs)] = 0; + for (size_t i = 0; i < ZSTR_LEN(zs); i++) { + if (ZSTR_VAL(zs)[i] == '\0') { + copy[i] = '0'; + } else { + copy[i] = ZSTR_VAL(zs)[i]; } } - return estrdup(Z_STRVAL(copy)); + return copy; } -char* sp_convert_to_string(zval* zv) { +const zend_string* sp_zval_to_zend_string(zval* zv) { switch (Z_TYPE_P(zv)) { - case IS_FALSE: - return estrdup("FALSE"); - case IS_TRUE: - return estrdup("TRUE"); - case IS_NULL: - return estrdup("NULL"); case IS_LONG: { char* msg; spprintf(&msg, 0, ZEND_LONG_FMT, Z_LVAL_P(zv)); - return msg; + zend_string* zs = zend_string_init(msg, strlen(msg), 0); + efree(msg); + return zs; } case IS_DOUBLE: { char* msg; spprintf(&msg, 0, "%f", Z_DVAL_P(zv)); - return msg; + zend_string* zs = zend_string_init(msg, strlen(msg), 0); + efree(msg); + return zs; } case IS_STRING: { - return zv_str_to_char(zv); + return Z_STR_P(zv); } + case IS_FALSE: + return zend_string_init("FALSE", 5, 0); + case IS_TRUE: + return zend_string_init("TRUE", 4, 0); + case IS_NULL: + return zend_string_init("NULL", 4, 0); case IS_OBJECT: - return estrdup("OBJECT"); + return zend_string_init("OBJECT", 6, 0); case IS_ARRAY: - return estrdup("ARRAY"); + return zend_string_init("ARRAY", 5, 0); case IS_RESOURCE: - return estrdup("RESOURCE"); + return zend_string_init("RESOURCE", 8, 0); } - return estrdup(""); + return zend_string_init("", 0, 0); } -bool sp_match_value(const char* value, const char* to_match, +bool sp_match_value(const zend_string* value, const zend_string* to_match, const sp_pcre* rx) { if (to_match) { - if (0 == strcmp(to_match, value)) { - return true; - } + return (sp_zend_string_equals(to_match, value)); } else if (rx) { - return sp_is_regexp_matching(rx, value); + char* tmp = zend_string_to_char(value); + bool ret = sp_is_regexp_matching(rx, tmp); + efree(tmp); + return ret; } else { return true; } @@ -188,35 +207,41 @@ bool sp_match_value(const char* value, const char* to_match, } void sp_log_disable(const char* restrict path, const char* restrict arg_name, - const char* restrict arg_value, + const zend_string* restrict arg_value, const sp_disabled_function* config_node, unsigned int line, const char* restrict filename) { - const char* dump = config_node->dump; - const char* alias = config_node->alias; + const zend_string* dump = config_node->dump; + const zend_string* alias = config_node->alias; const int sim = config_node->simulation; filename = filename ? filename : zend_get_executed_filename(TSRMLS_C); line = line ? line : zend_get_executed_lineno(TSRMLS_C); if (arg_name) { + char* char_repr = NULL; + if (arg_value) { + char_repr = zend_string_to_char(arg_value); + } if (alias) { sp_log_msg( "disabled_function", sim ? SP_LOG_SIMULATION : SP_LOG_DROP, "Aborted execution on call of the function '%s' in %s:%d, " "because its argument '%s' content (%s) matched the rule '%s'.", - path, filename, line, arg_name, arg_value ? arg_value : "?", alias); + path, filename, line, arg_name, char_repr ? char_repr : "?", + ZSTR_VAL(alias)); } else { sp_log_msg("disabled_function", sim ? SP_LOG_SIMULATION : SP_LOG_DROP, "Aborted execution on call of the function '%s' in %s:%d, " "because its argument '%s' content (%s) matched a rule.", - path, filename, line, arg_name, arg_value ? arg_value : "?"); + path, filename, line, arg_name, char_repr ? char_repr : "?"); } + efree(char_repr); } else { if (alias) { sp_log_msg("disabled_function", sim ? SP_LOG_SIMULATION : SP_LOG_DROP, "Aborted execution on call of the function '%s' in %s:%d, " "because of the the rule '%s'.", - path, filename, line, alias); + path, filename, line, ZSTR_VAL(alias)); } else { sp_log_msg("disabled_function", sim ? SP_LOG_SIMULATION : SP_LOG_DROP, "Aborted execution on call of the function '%s' in %s:%d.", @@ -230,45 +255,53 @@ void sp_log_disable(const char* restrict path, const char* restrict arg_name, } void sp_log_disable_ret(const char* restrict path, - const char* restrict ret_value, + const zend_string* restrict ret_value, const sp_disabled_function* config_node) { - const char* dump = config_node->dump; - const char* alias = config_node->alias; + const zend_string* dump = config_node->dump; + const zend_string* alias = config_node->alias; const int sim = config_node->simulation; + char* char_repr = NULL; + + if (ret_value) { + char_repr = zend_string_to_char(ret_value); + } if (alias) { sp_log_msg( "disabled_function", sim ? SP_LOG_SIMULATION : SP_LOG_DROP, "Aborted execution on return of the function '%s' in %s:%d, " "because the function returned '%s', which matched the rule '%s'.", path, zend_get_executed_filename(TSRMLS_C), - zend_get_executed_lineno(TSRMLS_C), ret_value ? ret_value : "?", alias); + zend_get_executed_lineno(TSRMLS_C), char_repr ? char_repr : "?", + ZSTR_VAL(alias)); } else { sp_log_msg("disabled_function", sim ? SP_LOG_SIMULATION : SP_LOG_DROP, "Aborted execution on return of the function '%s' in %s:%d, " "because the function returned '%s', which matched a rule.", path, zend_get_executed_filename(TSRMLS_C), - zend_get_executed_lineno(TSRMLS_C), ret_value ? ret_value : "?"); + zend_get_executed_lineno(TSRMLS_C), char_repr ? char_repr : "?"); } + efree(char_repr); if (dump) { sp_log_request(dump, config_node->textual_representation, SP_TOKEN_DISABLE_FUNC); } } -bool sp_match_array_key(const zval* zv, const char* to_match, +bool sp_match_array_key(const zval* zv, const zend_string* to_match, const sp_pcre* rx) { zend_string* key; zend_ulong idx; ZEND_HASH_FOREACH_KEY(Z_ARRVAL_P(zv), idx, key) { if (key) { - if (sp_match_value(ZSTR_VAL(key), to_match, rx)) { + if (sp_match_value(key, to_match, rx)) { return true; } } else { char* idx_str = NULL; spprintf(&idx_str, 0, "%lu", idx); - if (sp_match_value(idx_str, to_match, rx)) { + zend_string* tmp = zend_string_init(idx_str, strlen(idx_str), 0); + if (sp_match_value(tmp, to_match, rx)) { efree(idx_str); return true; } @@ -279,18 +312,16 @@ bool sp_match_array_key(const zval* zv, const char* to_match, return false; } -bool sp_match_array_value(const zval* arr, const char* to_match, +bool sp_match_array_value(const zval* arr, const zend_string* to_match, const sp_pcre* rx) { zval* value; ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(arr), value) { if (Z_TYPE_P(value) != IS_ARRAY) { - char* value_str = sp_convert_to_string(value); + const zend_string* value_str = sp_zval_to_zend_string(value); if (sp_match_value(value_str, to_match, rx)) { - efree(value_str); return true; } else { - efree(value_str); } } else if (sp_match_array_value(value, to_match, rx)) { return true; @@ -303,6 +334,7 @@ bool sp_match_array_value(const zval* arr, const char* to_match, int hook_function(const char* original_name, HashTable* hook_table, void (*new_function)(INTERNAL_FUNCTION_PARAMETERS)) { zend_internal_function* func; + bool ret = FAILURE; /* The `mb` module likes to hook functions, like strlen->mb_strlen, * so we have to hook both of them. */ @@ -317,6 +349,7 @@ int hook_function(const char* original_name, HashTable* hook_table, return FAILURE; } func->handler = new_function; + ret = SUCCESS; } else { return SUCCESS; } @@ -326,7 +359,7 @@ int hook_function(const char* original_name, HashTable* hook_table, CG(compiler_options) |= ZEND_COMPILE_NO_BUILTIN_STRLEN; if (zend_hash_str_find(CG(function_table), VAR_AND_LEN(original_name + 3))) { - hook_function(original_name + 3, hook_table, new_function); + return hook_function(original_name + 3, hook_table, new_function); } } else { // TODO this can be moved somewhere else to gain some marginal perfs CG(compiler_options) |= ZEND_COMPILE_NO_BUILTIN_STRLEN; @@ -334,11 +367,11 @@ int hook_function(const char* original_name, HashTable* hook_table, 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); + return hook_function(mb_name, hook_table, new_function); } } - return SUCCESS; + return ret; } int hook_regexp(const sp_pcre* regexp, HashTable* hook_table, @@ -356,7 +389,7 @@ int hook_regexp(const sp_pcre* regexp, HashTable* hook_table, return SUCCESS; } -bool check_is_in_eval_whitelist(const char* const function_name) { +bool check_is_in_eval_whitelist(const zend_string* const function_name) { const sp_list_node* it = SNUFFLEUPAGUS_G(config).config_eval->whitelist; if (!it) { @@ -366,7 +399,7 @@ bool check_is_in_eval_whitelist(const char* const function_name) { /* yes, we could use a HashTable instead, but since the list is pretty * small, it doesn't maka a difference in practise. */ while (it && it->data) { - if (0 == strcmp(function_name, (char*)(it->data))) { + if (sp_zend_string_equals(function_name, (const zend_string*)(it->data))) { /* We've got a match, the function is whiteslited. */ return true; } diff --git a/src/sp_utils.h b/src/sp_utils.h index 61a23f9..a21a4b0 100644 --- a/src/sp_utils.h +++ b/src/sp_utils.h @@ -44,20 +44,20 @@ 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 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 *); +const zend_string* sp_zval_to_zend_string(zval *); +bool sp_match_value(const zend_string *, const zend_string *, const sp_pcre *); +bool sp_match_array_key(const zval *, const zend_string *, const sp_pcre *); +bool sp_match_array_value(const zval *, const zend_string *, const sp_pcre *); void sp_log_disable(const char *restrict, const char *restrict, - const char *restrict, const sp_disabled_function *, + const zend_string *restrict, const sp_disabled_function *, unsigned int, const char*restrict); -void sp_log_disable_ret(const char *restrict, const char *restrict, +void sp_log_disable_ret(const char *restrict, const zend_string *restrict, const sp_disabled_function *); int hook_function(const char *, HashTable *, void (*)(INTERNAL_FUNCTION_PARAMETERS)); int hook_regexp(const sp_pcre *, HashTable *, void (*)(INTERNAL_FUNCTION_PARAMETERS)); -bool check_is_in_eval_whitelist(const char * const function_name); -int sp_log_request(const char* folder, const char* text_repr, char* from); +bool check_is_in_eval_whitelist(const zend_string * const function_name); +int sp_log_request(const zend_string* folder, const zend_string* text_repr, char* from); #endif /* SP_UTILS_H */ diff --git a/src/sp_var_value.c b/src/sp_var_value.c index 7ed8dfa..e91c3d8 100644 --- a/src/sp_var_value.c +++ b/src/sp_var_value.c @@ -107,10 +107,9 @@ static zval *get_array_value(zend_execute_data *ed, zval *zvalue, } if (Z_TYPE_P(zvalue) == IS_ARRAY) { - char *idx = sp_convert_to_string(idx_value); - zval *ret = get_entry_hashtable(Z_ARRVAL_P(zvalue), idx, strlen(idx)); - efree(idx); - return ret; + const zend_string *idx = sp_zval_to_zend_string(idx_value); + return get_entry_hashtable(Z_ARRVAL_P(zvalue), ZSTR_VAL(idx), + ZSTR_LEN(idx)); } return NULL; -- cgit v1.3