diff options
Diffstat (limited to 'src/sp_disabled_functions.c')
| -rw-r--r-- | src/sp_disabled_functions.c | 356 |
1 files changed, 356 insertions, 0 deletions
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 @@ | |||
| 1 | #include "php_snuffleupagus.h" | ||
| 2 | |||
| 3 | #include "zend_execute.h" | ||
| 4 | #include "zend_hash.h" | ||
| 5 | |||
| 6 | ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus); | ||
| 7 | |||
| 8 | ZEND_COLD static zend_always_inline bool is_hash_matching( | ||
| 9 | const char* current_filename, | ||
| 10 | sp_disabled_function const* const config_node) { | ||
| 11 | char current_file_hash[SHA256_SIZE * 2]; | ||
| 12 | compute_hash(current_filename, current_file_hash); | ||
| 13 | return (0 == strncmp(current_file_hash, config_node->hash, SHA256_SIZE)); | ||
| 14 | } | ||
| 15 | |||
| 16 | static zend_always_inline char* get_complete_function_path( | ||
| 17 | zend_execute_data const* const execute_data) { | ||
| 18 | char const* class_name; | ||
| 19 | char const* const function_name = | ||
| 20 | ZSTR_VAL(execute_data->func->common.function_name); | ||
| 21 | char* complete_path_function = NULL; | ||
| 22 | |||
| 23 | class_name = get_active_class_name(NULL); | ||
| 24 | if (*class_name) { | ||
| 25 | const size_t len = strlen(class_name) + 2 + strlen(function_name) + 1; | ||
| 26 | complete_path_function = emalloc(len); | ||
| 27 | snprintf(complete_path_function, len, "%s::%s", class_name, function_name); | ||
| 28 | } else { | ||
| 29 | complete_path_function = estrdup(function_name); | ||
| 30 | } | ||
| 31 | return complete_path_function; | ||
| 32 | } | ||
| 33 | |||
| 34 | static bool is_local_var_matching(zend_execute_data *execute_data, const sp_disabled_function *const config_node) { | ||
| 35 | zend_execute_data *orig_execute_data = execute_data; | ||
| 36 | |||
| 37 | /*because execute_data points to hooked function data, | ||
| 38 | which we dont care about */ | ||
| 39 | zend_execute_data *current = execute_data->prev_execute_data; | ||
| 40 | zval *value = NULL; | ||
| 41 | |||
| 42 | while (current) { | ||
| 43 | zend_string *key = NULL; | ||
| 44 | EG(current_execute_data) = current; | ||
| 45 | zend_array *symtable = zend_rebuild_symbol_table(); | ||
| 46 | ZEND_HASH_FOREACH_STR_KEY_VAL(symtable, key, value) { | ||
| 47 | if (0 == strcmp(config_node->var, key->val)) { // is the var name right? | ||
| 48 | if (Z_TYPE_P(value) == IS_INDIRECT) { | ||
| 49 | value = Z_INDIRECT_P(value); | ||
| 50 | } | ||
| 51 | if (Z_TYPE_P(value) != IS_ARRAY) { | ||
| 52 | char *var_value_str = sp_convert_to_string(value); | ||
| 53 | if (true == sp_match_value(var_value_str, config_node->value, config_node->regexp)) { | ||
| 54 | efree(var_value_str); | ||
| 55 | EG(current_execute_data) = orig_execute_data; | ||
| 56 | return true; | ||
| 57 | } | ||
| 58 | efree(var_value_str); | ||
| 59 | } | ||
| 60 | else { | ||
| 61 | EG(current_execute_data) = orig_execute_data; | ||
| 62 | return sp_match_array_key_recurse(value, config_node->var_array_keys, config_node->value, NULL); | ||
| 63 | } | ||
| 64 | } | ||
| 65 | } | ||
| 66 | ZEND_HASH_FOREACH_END(); | ||
| 67 | current = current->prev_execute_data; | ||
| 68 | } | ||
| 69 | |||
| 70 | EG(current_execute_data) = orig_execute_data; | ||
| 71 | return false; | ||
| 72 | } | ||
| 73 | |||
| 74 | bool should_disable(zend_execute_data* execute_data) { | ||
| 75 | const char* current_filename = zend_get_executed_filename(TSRMLS_C); | ||
| 76 | const sp_node_t* config = | ||
| 77 | SNUFFLEUPAGUS_G(config).config_disabled_functions->disabled_functions; | ||
| 78 | const char* function_name = | ||
| 79 | ZSTR_VAL(execute_data->func->common.function_name); | ||
| 80 | char* complete_path_function; | ||
| 81 | char const* client_ip = sp_getenv("REMOTE_ADDR"); | ||
| 82 | |||
| 83 | if (!function_name) { | ||
| 84 | return false; | ||
| 85 | } | ||
| 86 | |||
| 87 | if (!config || !config->data) { | ||
| 88 | return false; | ||
| 89 | } | ||
| 90 | |||
| 91 | complete_path_function = get_complete_function_path(execute_data); | ||
| 92 | |||
| 93 | while (config) { | ||
| 94 | sp_disabled_function const* const config_node = | ||
| 95 | (sp_disabled_function*)(config->data); | ||
| 96 | const char* arg_name = NULL; | ||
| 97 | const char* arg_value_str = NULL; | ||
| 98 | |||
| 99 | if (false == config_node->enable) { | ||
| 100 | goto next; | ||
| 101 | } | ||
| 102 | |||
| 103 | if (config_node->function) { /* Litteral match against the function name. */ | ||
| 104 | if (0 != strcmp(config_node->function, complete_path_function)) { | ||
| 105 | goto next; | ||
| 106 | } | ||
| 107 | } else if (config_node->r_function) { | ||
| 108 | if (false == | ||
| 109 | is_regexp_matching(config_node->r_function, complete_path_function)) { | ||
| 110 | goto next; | ||
| 111 | } | ||
| 112 | } | ||
| 113 | if (config_node->var) { | ||
| 114 | if (false == is_local_var_matching(execute_data, config_node)) { | ||
| 115 | goto next; | ||
| 116 | } | ||
| 117 | } | ||
| 118 | |||
| 119 | if (config_node->filename) { /* Check the current file name. */ | ||
| 120 | if (0 != strcmp(current_filename, config_node->filename)) { | ||
| 121 | goto next; | ||
| 122 | } | ||
| 123 | } else if (config_node->r_filename) { | ||
| 124 | if (false == | ||
| 125 | is_regexp_matching(config_node->r_filename, current_filename)) { | ||
| 126 | goto next; | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | if (config_node->hash) { | ||
| 131 | if (false == is_hash_matching(current_filename, config_node)) { | ||
| 132 | goto next; | ||
| 133 | } | ||
| 134 | } | ||
| 135 | |||
| 136 | if (client_ip && config_node->cidr && | ||
| 137 | (false == cidr_match(client_ip, config_node->cidr))) { | ||
| 138 | goto next; | ||
| 139 | } | ||
| 140 | |||
| 141 | /* Check if we filter on parameter value*/ | ||
| 142 | if (config_node->param || config_node->r_param) { | ||
| 143 | const unsigned int nb_param = execute_data->func->common.num_args; | ||
| 144 | bool arg_matched = false; | ||
| 145 | |||
| 146 | for (unsigned int i = 0; i < nb_param; i++) { | ||
| 147 | arg_matched = false; | ||
| 148 | if (ZEND_USER_CODE(execute_data->func->type)) { // yay consistency | ||
| 149 | arg_name = ZSTR_VAL(execute_data->func->common.arg_info[i].name); | ||
| 150 | } else { | ||
| 151 | arg_name = execute_data->func->internal_function.arg_info[i].name; | ||
| 152 | } | ||
| 153 | |||
| 154 | const bool arg_matching = | ||
| 155 | config_node->param && (0 == strcmp(arg_name, config_node->param)); | ||
| 156 | const bool pcre_matching = | ||
| 157 | config_node->r_param && | ||
| 158 | (true == is_regexp_matching(config_node->r_param, arg_name)); | ||
| 159 | |||
| 160 | /* This is the parameter name we're looking for. */ | ||
| 161 | if (true == arg_matching || true == pcre_matching) { | ||
| 162 | zval* arg_value = ZEND_CALL_VAR_NUM(execute_data, i); | ||
| 163 | |||
| 164 | if (config_node->param_type) { // Are we matching on the `type`? | ||
| 165 | if (config_node->param_type == Z_TYPE_P(arg_value)) { | ||
| 166 | arg_matched = true; | ||
| 167 | break; | ||
| 168 | } | ||
| 169 | } else if (Z_TYPE_P(arg_value) == IS_ARRAY) { | ||
| 170 | arg_value_str = estrdup("Array"); | ||
| 171 | // match on arr -> match on all key content, if a key is an array, | ||
| 172 | // ignore it | ||
| 173 | // match on arr[foo] -> match only on key foo, if the key is an | ||
| 174 | // array, match on all keys content | ||
| 175 | if (config_node->param_is_array == true) { | ||
| 176 | if (true == sp_match_array_key_recurse( | ||
| 177 | arg_value, config_node->param_array_keys, | ||
| 178 | config_node->value, config_node->regexp)) { | ||
| 179 | arg_matched = true; | ||
| 180 | break; | ||
| 181 | } | ||
| 182 | } else { // match on all keys, but don't go into subarray | ||
| 183 | if (true == sp_match_array_key(arg_value, config_node->value, | ||
| 184 | config_node->regexp)) { | ||
| 185 | arg_matched = true; | ||
| 186 | break; | ||
| 187 | } | ||
| 188 | } | ||
| 189 | } else { | ||
| 190 | arg_value_str = sp_convert_to_string(arg_value); | ||
| 191 | if (true == sp_match_value(arg_value_str, config_node->value, | ||
| 192 | config_node->regexp)) { | ||
| 193 | arg_matched = true; | ||
| 194 | break; | ||
| 195 | } | ||
| 196 | } | ||
| 197 | } | ||
| 198 | } | ||
| 199 | if (false == arg_matched) { | ||
| 200 | goto next; | ||
| 201 | } | ||
| 202 | } | ||
| 203 | |||
| 204 | /* Everything matched.*/ | ||
| 205 | |||
| 206 | if (true == config_node->allow) { | ||
| 207 | goto allow; | ||
| 208 | } | ||
| 209 | |||
| 210 | sp_log_disable(complete_path_function, arg_name, arg_value_str, | ||
| 211 | config_node); | ||
| 212 | if (true == config_node->simulation) { | ||
| 213 | goto next; | ||
| 214 | } else { // We've got a match, the function won't be executed | ||
| 215 | efree(complete_path_function); | ||
| 216 | return true; | ||
| 217 | } | ||
| 218 | next: | ||
| 219 | config = config->next; | ||
| 220 | } | ||
| 221 | allow: | ||
| 222 | efree(complete_path_function); | ||
| 223 | return false; | ||
| 224 | } | ||
| 225 | |||
| 226 | static bool should_drop_on_ret(zval* return_value, | ||
| 227 | const zend_execute_data* const execute_data) { | ||
| 228 | const sp_node_t* config = | ||
| 229 | SNUFFLEUPAGUS_G(config).config_disabled_functions_ret->disabled_functions; | ||
| 230 | char* complete_path_function = get_complete_function_path(execute_data); | ||
| 231 | const char* current_filename = zend_get_executed_filename(TSRMLS_C); | ||
| 232 | |||
| 233 | if (!config || !config->data) { | ||
| 234 | return false; | ||
| 235 | } | ||
| 236 | |||
| 237 | while (config) { | ||
| 238 | char* ret_value_str = NULL; | ||
| 239 | sp_disabled_function const* const config_node = | ||
| 240 | (sp_disabled_function*)(config->data); | ||
| 241 | |||
| 242 | if (false == config_node->enable) { | ||
| 243 | goto next; | ||
| 244 | } | ||
| 245 | |||
| 246 | if (config_node->function) { | ||
| 247 | if (0 != strcmp(config_node->function, complete_path_function)) { | ||
| 248 | goto next; | ||
| 249 | } | ||
| 250 | } else if (config_node->r_function) { | ||
| 251 | if (false == | ||
| 252 | is_regexp_matching(config_node->r_function, complete_path_function)) { | ||
| 253 | goto next; | ||
| 254 | } | ||
| 255 | } | ||
| 256 | |||
| 257 | if (config_node->filename) { /* Check the current file name. */ | ||
| 258 | if (0 != strcmp(current_filename, config_node->filename)) { | ||
| 259 | goto next; | ||
| 260 | } | ||
| 261 | } else if (config_node->r_filename) { | ||
| 262 | if (false == | ||
| 263 | is_regexp_matching(config_node->r_filename, current_filename)) { | ||
| 264 | goto next; | ||
| 265 | } | ||
| 266 | } | ||
| 267 | |||
| 268 | if (config_node->hash) { | ||
| 269 | if (false == is_hash_matching(current_filename, config_node)) { | ||
| 270 | goto next; | ||
| 271 | } | ||
| 272 | } | ||
| 273 | |||
| 274 | ret_value_str = sp_convert_to_string(return_value); // FIXME memleak | ||
| 275 | |||
| 276 | bool match_type = (config_node->ret_type) && | ||
| 277 | (config_node->ret_type == Z_TYPE_P(return_value)); | ||
| 278 | bool match_value = (config_node->ret || config_node->r_ret) && | ||
| 279 | (true == sp_match_value(ret_value_str, config_node->ret, | ||
| 280 | config_node->r_ret)); | ||
| 281 | |||
| 282 | if (true == match_type || match_value) { | ||
| 283 | if (true == config_node->allow) { | ||
| 284 | efree(complete_path_function); | ||
| 285 | return false; | ||
| 286 | } | ||
| 287 | sp_log_disable_ret(complete_path_function, ret_value_str, config_node); | ||
| 288 | if (false == config_node->simulation) { | ||
| 289 | efree(complete_path_function); | ||
| 290 | return true; | ||
| 291 | } | ||
| 292 | } | ||
| 293 | next: | ||
| 294 | config = config->next; | ||
| 295 | } | ||
| 296 | efree(complete_path_function); | ||
| 297 | return false; | ||
| 298 | } | ||
| 299 | |||
| 300 | ZEND_FUNCTION(check_disabled_function) { | ||
| 301 | void (*orig_handler)(INTERNAL_FUNCTION_PARAMETERS); | ||
| 302 | const char* current_function_name = get_active_function_name(TSRMLS_C); | ||
| 303 | |||
| 304 | if (true == should_disable(execute_data)) { | ||
| 305 | return; | ||
| 306 | } | ||
| 307 | |||
| 308 | if ((orig_handler = zend_hash_str_find_ptr( | ||
| 309 | SNUFFLEUPAGUS_G(disabled_functions_hook), current_function_name, | ||
| 310 | strlen(current_function_name)))) { | ||
| 311 | orig_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU); | ||
| 312 | if (true == should_drop_on_ret(return_value, execute_data)) { | ||
| 313 | zend_bailout(); | ||
| 314 | } | ||
| 315 | } else { | ||
| 316 | sp_log_err( | ||
| 317 | "disabled_functions", | ||
| 318 | "Unable to find the pointer to the original function '%s' in the " | ||
| 319 | "hashtable.\n", | ||
| 320 | current_function_name); | ||
| 321 | } | ||
| 322 | } | ||
| 323 | |||
| 324 | static int hook_functions(const sp_node_t* config) { | ||
| 325 | while (config && config->data) { | ||
| 326 | const char* function_name = ((sp_disabled_function*)config->data)->function; | ||
| 327 | const pcre* function_name_regexp = | ||
| 328 | ((sp_disabled_function*)config->data)->r_function; | ||
| 329 | |||
| 330 | if (NULL != function_name) { // hook function by name | ||
| 331 | HOOK_FUNCTION(function_name, disabled_functions_hook, | ||
| 332 | PHP_FN(check_disabled_function), false); | ||
| 333 | } else if (NULL != function_name_regexp) { // hook function by regexp | ||
| 334 | HOOK_FUNCTION_BY_REGEXP(function_name_regexp, disabled_functions_hook, | ||
| 335 | PHP_FN(check_disabled_function), false); | ||
| 336 | } else { | ||
| 337 | return FAILURE; | ||
| 338 | } | ||
| 339 | |||
| 340 | config = config->next; | ||
| 341 | } | ||
| 342 | return SUCCESS; | ||
| 343 | } | ||
| 344 | |||
| 345 | int hook_disabled_functions(void) { | ||
| 346 | TSRMLS_FETCH(); | ||
| 347 | |||
| 348 | int ret = SUCCESS; | ||
| 349 | |||
| 350 | ret |= hook_functions( | ||
| 351 | SNUFFLEUPAGUS_G(config).config_disabled_functions->disabled_functions); | ||
| 352 | ret |= hook_functions(SNUFFLEUPAGUS_G(config) | ||
| 353 | .config_disabled_functions_ret->disabled_functions); | ||
| 354 | |||
| 355 | return ret; | ||
| 356 | } | ||
