1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
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();
}
|