From 868f96c759b6650d88ff9f4fbc5c048302134248 Mon Sep 17 00:00:00 2001 From: Sebastien Blot Date: Wed, 20 Sep 2017 10:11:01 +0200 Subject: Initial import --- src/sp_disabled_functions.c | 356 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 356 insertions(+) create mode 100644 src/sp_disabled_functions.c (limited to 'src/sp_disabled_functions.c') diff --git a/src/sp_disabled_functions.c b/src/sp_disabled_functions.c new file mode 100644 index 0000000..55d782b --- /dev/null +++ b/src/sp_disabled_functions.c @@ -0,0 +1,356 @@ +#include "php_snuffleupagus.h" + +#include "zend_execute.h" +#include "zend_hash.h" + +ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus); + +ZEND_COLD static zend_always_inline bool is_hash_matching( + const char* current_filename, + sp_disabled_function const* const config_node) { + char current_file_hash[SHA256_SIZE * 2]; + compute_hash(current_filename, current_file_hash); + return (0 == strncmp(current_file_hash, config_node->hash, SHA256_SIZE)); +} + +static zend_always_inline char* get_complete_function_path( + zend_execute_data const* const execute_data) { + char const* class_name; + char const* const function_name = + ZSTR_VAL(execute_data->func->common.function_name); + char* complete_path_function = NULL; + + class_name = get_active_class_name(NULL); + if (*class_name) { + const size_t len = strlen(class_name) + 2 + strlen(function_name) + 1; + complete_path_function = emalloc(len); + snprintf(complete_path_function, len, "%s::%s", class_name, function_name); + } else { + complete_path_function = estrdup(function_name); + } + return complete_path_function; +} + +static bool is_local_var_matching(zend_execute_data *execute_data, const sp_disabled_function *const config_node) { + zend_execute_data *orig_execute_data = execute_data; + + /*because execute_data points to hooked function data, + which we dont care about */ + zend_execute_data *current = execute_data->prev_execute_data; + zval *value = NULL; + + while (current) { + zend_string *key = NULL; + EG(current_execute_data) = current; + zend_array *symtable = zend_rebuild_symbol_table(); + ZEND_HASH_FOREACH_STR_KEY_VAL(symtable, key, value) { + if (0 == strcmp(config_node->var, key->val)) { // is the var name right? + if (Z_TYPE_P(value) == IS_INDIRECT) { + value = Z_INDIRECT_P(value); + } + if (Z_TYPE_P(value) != IS_ARRAY) { + char *var_value_str = sp_convert_to_string(value); + if (true == sp_match_value(var_value_str, config_node->value, config_node->regexp)) { + efree(var_value_str); + EG(current_execute_data) = orig_execute_data; + return true; + } + efree(var_value_str); + } + else { + EG(current_execute_data) = orig_execute_data; + return sp_match_array_key_recurse(value, config_node->var_array_keys, config_node->value, NULL); + } + } + } + ZEND_HASH_FOREACH_END(); + current = current->prev_execute_data; + } + + EG(current_execute_data) = orig_execute_data; + return false; +} + +bool should_disable(zend_execute_data* execute_data) { + const char* current_filename = zend_get_executed_filename(TSRMLS_C); + const sp_node_t* config = + SNUFFLEUPAGUS_G(config).config_disabled_functions->disabled_functions; + const char* function_name = + ZSTR_VAL(execute_data->func->common.function_name); + char* complete_path_function; + char const* client_ip = sp_getenv("REMOTE_ADDR"); + + if (!function_name) { + return false; + } + + if (!config || !config->data) { + return false; + } + + complete_path_function = get_complete_function_path(execute_data); + + 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; + + if (false == config_node->enable) { + goto next; + } + + if (config_node->function) { /* Litteral match against the function name. */ + if (0 != strcmp(config_node->function, complete_path_function)) { + goto next; + } + } else if (config_node->r_function) { + if (false == + is_regexp_matching(config_node->r_function, complete_path_function)) { + goto next; + } + } + if (config_node->var) { + if (false == is_local_var_matching(execute_data, config_node)) { + goto next; + } + } + + if (config_node->filename) { /* Check the current file name. */ + if (0 != strcmp(current_filename, config_node->filename)) { + goto next; + } + } else if (config_node->r_filename) { + if (false == + is_regexp_matching(config_node->r_filename, current_filename)) { + goto next; + } + } + + if (config_node->hash) { + if (false == is_hash_matching(current_filename, config_node)) { + goto next; + } + } + + if (client_ip && config_node->cidr && + (false == cidr_match(client_ip, config_node->cidr))) { + goto next; + } + + /* Check if we filter on parameter value*/ + if (config_node->param || config_node->r_param) { + const unsigned int nb_param = execute_data->func->common.num_args; + bool arg_matched = false; + + for (unsigned int i = 0; i < nb_param; i++) { + arg_matched = false; + if (ZEND_USER_CODE(execute_data->func->type)) { // yay consistency + arg_name = ZSTR_VAL(execute_data->func->common.arg_info[i].name); + } else { + arg_name = execute_data->func->internal_function.arg_info[i].name; + } + + const bool arg_matching = + config_node->param && (0 == strcmp(arg_name, config_node->param)); + const bool pcre_matching = + config_node->r_param && + (true == is_regexp_matching(config_node->r_param, arg_name)); + + /* This is the parameter name we're looking for. */ + if (true == arg_matching || true == pcre_matching) { + zval* arg_value = ZEND_CALL_VAR_NUM(execute_data, i); + + if (config_node->param_type) { // Are we matching on the `type`? + if (config_node->param_type == Z_TYPE_P(arg_value)) { + arg_matched = true; + break; + } + } else if (Z_TYPE_P(arg_value) == IS_ARRAY) { + arg_value_str = estrdup("Array"); + // match on arr -> match on all key content, if a key is an array, + // ignore it + // match on arr[foo] -> match only on key foo, if the key is an + // array, match on all keys content + if (config_node->param_is_array == true) { + if (true == sp_match_array_key_recurse( + arg_value, config_node->param_array_keys, + config_node->value, config_node->regexp)) { + arg_matched = true; + break; + } + } else { // match on all keys, but don't go into subarray + if (true == sp_match_array_key(arg_value, config_node->value, + config_node->regexp)) { + arg_matched = true; + break; + } + } + } else { + arg_value_str = sp_convert_to_string(arg_value); + if (true == sp_match_value(arg_value_str, config_node->value, + config_node->regexp)) { + arg_matched = true; + break; + } + } + } + } + if (false == arg_matched) { + goto next; + } + } + + /* Everything matched.*/ + + if (true == config_node->allow) { + goto allow; + } + + sp_log_disable(complete_path_function, arg_name, arg_value_str, + config_node); + 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; +} + +static bool should_drop_on_ret(zval* return_value, + const zend_execute_data* const execute_data) { + const sp_node_t* 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); + + if (!config || !config->data) { + return false; + } + + while (config) { + char* ret_value_str = NULL; + sp_disabled_function const* const config_node = + (sp_disabled_function*)(config->data); + + if (false == config_node->enable) { + goto next; + } + + if (config_node->function) { + if (0 != strcmp(config_node->function, complete_path_function)) { + goto next; + } + } else if (config_node->r_function) { + if (false == + is_regexp_matching(config_node->r_function, complete_path_function)) { + goto next; + } + } + + if (config_node->filename) { /* Check the current file name. */ + if (0 != strcmp(current_filename, config_node->filename)) { + goto next; + } + } else if (config_node->r_filename) { + if (false == + is_regexp_matching(config_node->r_filename, current_filename)) { + goto next; + } + } + + if (config_node->hash) { + if (false == is_hash_matching(current_filename, config_node)) { + goto next; + } + } + + ret_value_str = sp_convert_to_string(return_value); // FIXME memleak + + bool match_type = (config_node->ret_type) && + (config_node->ret_type == Z_TYPE_P(return_value)); + bool match_value = (config_node->ret || config_node->r_ret) && + (true == sp_match_value(ret_value_str, config_node->ret, + config_node->r_ret)); + + if (true == match_type || match_value) { + if (true == config_node->allow) { + efree(complete_path_function); + return false; + } + sp_log_disable_ret(complete_path_function, ret_value_str, config_node); + if (false == config_node->simulation) { + efree(complete_path_function); + return true; + } + } + next: + config = config->next; + } + efree(complete_path_function); + return false; +} + +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)) { + return; + } + + if ((orig_handler = zend_hash_str_find_ptr( + 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)) { + zend_bailout(); + } + } else { + sp_log_err( + "disabled_functions", + "Unable to find the pointer to the original function '%s' in the " + "hashtable.\n", + current_function_name); + } +} + +static int hook_functions(const sp_node_t* config) { + while (config && config->data) { + const char* function_name = ((sp_disabled_function*)config->data)->function; + const pcre* function_name_regexp = + ((sp_disabled_function*)config->data)->r_function; + + if (NULL != function_name) { // hook function by name + HOOK_FUNCTION(function_name, disabled_functions_hook, + PHP_FN(check_disabled_function), false); + } else if (NULL != function_name_regexp) { // hook function by regexp + HOOK_FUNCTION_BY_REGEXP(function_name_regexp, disabled_functions_hook, + PHP_FN(check_disabled_function), false); + } else { + return FAILURE; + } + + config = config->next; + } + return SUCCESS; +} + +int hook_disabled_functions(void) { + TSRMLS_FETCH(); + + int ret = SUCCESS; + + ret |= hook_functions( + SNUFFLEUPAGUS_G(config).config_disabled_functions->disabled_functions); + ret |= hook_functions(SNUFFLEUPAGUS_G(config) + .config_disabled_functions_ret->disabled_functions); + + return ret; +} -- cgit v1.3