From 2392c46836ceea520fa2a45369c8d638aadb943c Mon Sep 17 00:00:00 2001 From: Ben Fuhrmannek Date: Fri, 6 Aug 2021 20:23:52 +0200 Subject: implemented ini settings protection --- src/sp_ini.c | 126 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 src/sp_ini.c (limited to 'src/sp_ini.c') diff --git a/src/sp_ini.c b/src/sp_ini.c new file mode 100644 index 0000000..05d7d99 --- /dev/null +++ b/src/sp_ini.c @@ -0,0 +1,126 @@ +#include "php_snuffleupagus.h" + +#define SP_INI_HAS_CHECKS_COND(entry) (entry->min || entry->max || entry->regexp) +#define SP_INI_ACCESS_READONLY_COND(entry, cfg) (entry->access == SP_READONLY || (!entry->access && cfg->policy_readonly)) + +static bool /* success */ sp_ini_check(zend_string *varname, zend_string *new_value, sp_ini_entry **sp_entry_p) { + if (!varname || ZSTR_LEN(varname) == 0) { + return false; + } + + sp_config_ini *cfg = SNUFFLEUPAGUS_G(config).config_ini; + sp_ini_entry *entry = zend_hash_find_ptr(cfg->entries, varname); + if (sp_entry_p) { + *sp_entry_p = entry; + } + bool simulation = (cfg->simulation || (entry && entry->simulation)); + + if (!entry) { + if (cfg->policy_readonly) { + sp_log_auto("ini_protection", simulation, "INI setting is read-only"); + if (simulation) { return true; } + return false; + } + return true; + } + + if (SP_INI_ACCESS_READONLY_COND(entry, cfg)) { + sp_log_auto("ini_protection", simulation, "%s", (entry->msg ? ZSTR_VAL(entry->msg) : "INI setting is read-only")); + if (simulation) { return true; } + return false; + } + + if (!new_value && SP_INI_HAS_CHECKS_COND(entry)) { + sp_log_auto("ini_protection", simulation, "new INI value must not be NULL"); + if (simulation) { return true; } + return false; + } + + if (entry->min || entry->max) { + zend_long lvalue = zend_atol(ZSTR_VAL(new_value), ZSTR_LEN(new_value)); + if ((entry->min && zend_atol(ZSTR_VAL(entry->min), ZSTR_LEN(entry->min)) > lvalue) || + (entry->max && zend_atol(ZSTR_VAL(entry->max), ZSTR_LEN(entry->max)) < lvalue)) { + sp_log_auto("ini_protection", simulation, "%s", (entry->msg ? ZSTR_VAL(entry->msg) : "INI value out of range")); + if (simulation) { return true; } + return false; + } + } + + if (entry->regexp) { + if (!sp_is_regexp_matching_len(entry->regexp, ZSTR_VAL(new_value), ZSTR_LEN(new_value))) { + sp_log_auto("ini_protection", simulation, "%s", (entry->msg ? ZSTR_VAL(entry->msg) : "INI value does not match regex")); + if (simulation) { return true; } + return false; + } + } + + return true; +} + +static PHP_INI_MH(sp_ini_onmodify) { + zend_ini_entry *ini_entry = entry; + sp_ini_entry *sp_entry = NULL; + + sp_log_debug("%s =? %s", ZSTR_VAL(ini_entry->name), ZSTR_VAL(new_value)); + if (!sp_ini_check(ini_entry->name, new_value, &sp_entry)) { + return FAILURE; + } + + if (sp_entry && sp_entry->orig_onmodify) { + return sp_entry->orig_onmodify(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); + } + + return SUCCESS; +} + +void sp_hook_ini() { + sp_config_ini *cfg = SNUFFLEUPAGUS_G(config).config_ini; + sp_ini_entry *sp_entry; + zend_ini_entry *ini_entry; + ZEND_HASH_FOREACH_PTR(cfg->entries, sp_entry) + sp_log_debug("hook entry `%s`", ZSTR_VAL(sp_entry->key)); + if ((ini_entry = zend_hash_find_ptr(EG(ini_directives), sp_entry->key)) == NULL) { + sp_log_warn("ini_protection", "Cannot hook INI var `%s`. Maybe a typo or the PHP extension providing this var is not loaded yet.", ZSTR_VAL(sp_entry->key)); + continue; + } + if (SP_INI_ACCESS_READONLY_COND(sp_entry, cfg)) { + ini_entry->modifiable = ini_entry->orig_modifiable = 0; + } + PHP_INI_MH((*orig_onmodify)) = ini_entry->on_modify; + + if (SP_INI_HAS_CHECKS_COND(sp_entry) || SP_INI_ACCESS_READONLY_COND(sp_entry, cfg)) { + // only hook on_modify if there is any check to perform + sp_entry->orig_onmodify = ini_entry->on_modify; + ini_entry->on_modify = sp_ini_onmodify; + } + + if (sp_entry->set) { + zend_string *duplicate = zend_string_copy(sp_entry->set); + + if (!orig_onmodify || orig_onmodify(ini_entry, duplicate, ini_entry->mh_arg1, ini_entry->mh_arg2, ini_entry->mh_arg3, ZEND_INI_STAGE_STARTUP) == SUCCESS) { + ini_entry->value = duplicate; + } else { + zend_string_release(duplicate); + sp_log_warn("ini_protection", "Failed to set INI var `%s`.", ZSTR_VAL(sp_entry->key)); + continue; + } + } + ZEND_HASH_FOREACH_END(); +} + +void sp_unhook_ini() { + sp_ini_entry *sp_entry; + zend_ini_entry *ini_entry; + ZEND_HASH_FOREACH_PTR(SNUFFLEUPAGUS_G(config).config_ini->entries, sp_entry) + if (!sp_entry->orig_onmodify) { + // not hooked or no original onmodify + continue; + } + if ((ini_entry = zend_hash_find_ptr(EG(ini_directives), sp_entry->key)) == NULL) { + // unusual. ini entry is missing. + continue; + } + ini_entry->on_modify = sp_entry->orig_onmodify; + sp_entry->orig_onmodify = NULL; + ZEND_HASH_FOREACH_END(); +} -- cgit v1.3