summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--config/ini_protection.php8.rules20
-rw-r--r--src/sp_config.h8
-rw-r--r--src/sp_config_keywords.c21
-rw-r--r--src/sp_ini.c52
4 files changed, 76 insertions, 25 deletions
diff --git a/config/ini_protection.php8.rules b/config/ini_protection.php8.rules
index 081048f..b4ddb30 100644
--- a/config/ini_protection.php8.rules
+++ b/config/ini_protection.php8.rules
@@ -1,6 +1,20 @@
1## INI protection - prevent unwanted runtime ini changes made by ini_set() or other functions or by .htaccess 1## INI protection - prevent unwanted runtime ini changes made by ini_set() or other functions or by .htaccess
2sp.ini_protection.enable(); 2sp.ini_protection.enable();
3 3
4## simulation mode: only log violations
5#sp.ini_protection.simulation();
6
7## drop policy: drop request on rule violation
8#sp.ini_protection.policy_drop();
9
10## do not log violations.
11## this setting has no effect in simulation or drop mode
12#sp.ini_protection.policy_silent_fail();
13
14## do not log read-only violations
15## this setting has no effect in simulation or drop mode
16sp.ini_protection.policy_silent_ro();
17
4## access policy can be one of 18## access policy can be one of
5## .policy_readonly(): All entries are read-only by default. 19## .policy_readonly(): All entries are read-only by default.
6## Individual entries can be set read-write using .readwrite() or .rw() 20## Individual entries can be set read-write using .readwrite() or .rw()
@@ -10,13 +24,17 @@ sp.ini_protection.enable();
10 24
11## sp.ini entries can have the following attributes 25## sp.ini entries can have the following attributes
12## .key("..."): mandatory ini name. 26## .key("..."): mandatory ini name.
13## .set("..."): set the value. This overrides php.ini. 27## .set("..."): set the initial value. This overrides php.ini.
28## checks are not performed for this initial value.
14## .min("...") / .max("..."): value must be an integer between .min and .max. 29## .min("...") / .max("..."): value must be an integer between .min and .max.
15## shorthand notation (e.g. 1k = 1024) is allowed 30## shorthand notation (e.g. 1k = 1024) is allowed
16## .regexp("..."): value must match the regular expression 31## .regexp("..."): value must match the regular expression
32## .allow_null(): allow setting a NULL-value
17## .msg("..."): message is shown in logs on rule violation instead of default message 33## .msg("..."): message is shown in logs on rule violation instead of default message
18## .readonly() / .ro() / .readwrite() / .rw(): set entry to read-only or read-write respectively 34## .readonly() / .ro() / .readwrite() / .rw(): set entry to read-only or read-write respectively
19## If no access keyword is provided, the entry inherits the default policy set by sp.ini_protection.policy_*-rules. 35## If no access keyword is provided, the entry inherits the default policy set by sp.ini_protection.policy_*-rules.
36## .drop(): drop request on rule violation for this entry
37## .simulation(): only log rule violation for this entry
20 38
21## FOR PRODUCTION SYSTEMS: disable error messages and version numbers 39## FOR PRODUCTION SYSTEMS: disable error messages and version numbers
22sp.ini.key("display_errors").set("0").ro(); 40sp.ini.key("display_errors").set("0").ro();
diff --git a/src/sp_config.h b/src/sp_config.h
index bd2530a..0ba2e7f 100644
--- a/src/sp_config.h
+++ b/src/sp_config.h
@@ -170,17 +170,21 @@ typedef struct {
170 zend_string *min; 170 zend_string *min;
171 zend_string *max; 171 zend_string *max;
172 sp_pcre *regexp; 172 sp_pcre *regexp;
173 bool simulation;
174 zend_string *msg; 173 zend_string *msg;
175 zend_string *set; 174 zend_string *set;
175 bool allow_null;
176 bool simulation;
177 bool drop;
176 PHP_INI_MH((*orig_onmodify)); 178 PHP_INI_MH((*orig_onmodify));
177} sp_ini_entry; 179} sp_ini_entry;
178 180
179typedef struct { 181typedef struct {
180 bool enable; 182 bool enable;
181 bool simulation; 183 bool simulation;
182 // sp_ini_permission access_policy;
183 bool policy_readonly; 184 bool policy_readonly;
185 bool policy_silent_ro;
186 bool policy_silent_fail;
187 bool policy_drop;
184 HashTable *entries; // ht of sp_ini_entry 188 HashTable *entries; // ht of sp_ini_entry
185} sp_config_ini; 189} sp_config_ini;
186 190
diff --git a/src/sp_config_keywords.c b/src/sp_config_keywords.c
index e6eb05e..c547f10 100644
--- a/src/sp_config_keywords.c
+++ b/src/sp_config_keywords.c
@@ -566,14 +566,19 @@ int parse_upload_validation(char *line) {
566int parse_ini_protection(char *line) { 566int parse_ini_protection(char *line) {
567 bool disable = false, enable = false; 567 bool disable = false, enable = false;
568 bool rw = false, ro = false; // rw is ignored, but declaring .policy_rw is valid for readability 568 bool rw = false, ro = false; // rw is ignored, but declaring .policy_rw is valid for readability
569 sp_config_ini *cfg = SNUFFLEUPAGUS_G(config).config_ini;
569 sp_config_functions sp_config_ini_protection[] = { 570 sp_config_functions sp_config_ini_protection[] = {
570 {parse_empty, SP_TOKEN_ENABLE, &(enable)}, 571 {parse_empty, SP_TOKEN_ENABLE, &(enable)},
571 {parse_empty, SP_TOKEN_DISABLE, &(disable)}, 572 {parse_empty, SP_TOKEN_DISABLE, &(disable)},
572 {parse_empty, SP_TOKEN_SIMULATION, &(SNUFFLEUPAGUS_G(config).config_ini->simulation)}, 573 {parse_empty, SP_TOKEN_SIMULATION, &cfg->simulation},
573 {parse_empty, ".policy_readonly(", &ro}, 574 {parse_empty, ".policy_readonly(", &ro},
574 {parse_empty, ".policy_ro(", &ro}, 575 {parse_empty, ".policy_ro(", &ro},
575 {parse_empty, ".policy_readwrite(", &rw}, 576 {parse_empty, ".policy_readwrite(", &rw},
576 {parse_empty, ".policy_rw(", &rw}, 577 {parse_empty, ".policy_rw(", &rw},
578 {parse_empty, ".policy_silent_ro(", &cfg->policy_silent_ro},
579 {parse_empty, ".policy_silent_fail(", &cfg->policy_silent_fail},
580 {parse_empty, ".policy_no_log(", &cfg->policy_silent_fail},
581 {parse_empty, ".policy_drop(", &cfg->policy_drop},
577 {0, 0, 0}}; 582 {0, 0, 0}};
578 583
579 int ret = parse_keywords(sp_config_ini_protection, line); 584 int ret = parse_keywords(sp_config_ini_protection, line);
@@ -585,15 +590,19 @@ int parse_ini_protection(char *line) {
585 return -1; 590 return -1;
586 } 591 }
587 if (enable || disable) { 592 if (enable || disable) {
588 SNUFFLEUPAGUS_G(config).config_ini->enable = (enable || !disable); 593 cfg->enable = (enable || !disable);
589 } 594 }
590 595
591 if (ro && rw) { 596 if (ro && rw) {
592 sp_log_err("config", "rule cannot be both read-write and read-only on line %zu", sp_line_no); 597 sp_log_err("config", "rule cannot be both read-write and read-only on line %zu", sp_line_no);
593 return -1; 598 return -1;
594 } 599 }
595 SNUFFLEUPAGUS_G(config).config_ini->policy_readonly = ro; 600 cfg->policy_readonly = ro;
596 601
602 if (cfg->policy_silent_fail && cfg->policy_drop) {
603 sp_log_err("config", "policy cannot be drop and silent at the same time on line %zu", sp_line_no);
604 return -1;
605 }
597 return ret; 606 return ret;
598} 607}
599 608
@@ -611,8 +620,10 @@ int parse_ini_entry(char *line) {
611 {parse_regexp, ".regexp(", &entry->regexp}, 620 {parse_regexp, ".regexp(", &entry->regexp},
612 {parse_empty, ".readonly(", &ro}, 621 {parse_empty, ".readonly(", &ro},
613 {parse_empty, ".ro(", &ro}, 622 {parse_empty, ".ro(", &ro},
614 {parse_empty, ".readwrite()", &rw}, 623 {parse_empty, ".readwrite(", &rw},
615 {parse_empty, ".rw()", &rw}, 624 {parse_empty, ".rw(", &rw},
625 {parse_empty, ".drop(", &entry->drop},
626 {parse_empty, ".allow_null(", &entry->allow_null},
616 {0, 0, 0}}; 627 {0, 0, 0}};
617 628
618 int ret = parse_keywords(sp_config_ini_protection, line); 629 int ret = parse_keywords(sp_config_ini_protection, line);
diff --git a/src/sp_ini.c b/src/sp_ini.c
index 05d7d99..5777ca3 100644
--- a/src/sp_ini.c
+++ b/src/sp_ini.c
@@ -3,6 +3,15 @@
3#define SP_INI_HAS_CHECKS_COND(entry) (entry->min || entry->max || entry->regexp) 3#define SP_INI_HAS_CHECKS_COND(entry) (entry->min || entry->max || entry->regexp)
4#define SP_INI_ACCESS_READONLY_COND(entry, cfg) (entry->access == SP_READONLY || (!entry->access && cfg->policy_readonly)) 4#define SP_INI_ACCESS_READONLY_COND(entry, cfg) (entry->access == SP_READONLY || (!entry->access && cfg->policy_readonly))
5 5
6#define sp_log_auto2(feature, is_simulation, drop, ...) \
7 sp_log_msgf(feature, ((is_simulation || !drop) ? SP_LOG_WARN : SP_LOG_ERROR), \
8 (is_simulation ? SP_TYPE_SIMULATION : (drop ? SP_TYPE_DROP : SP_TYPE_LOG)), \
9 __VA_ARGS__)
10#define sp_log_ini_check_violation(...) if (simulation || cfg->policy_drop || (entry && entry->drop) || !cfg->policy_silent_fail) { \
11 sp_log_auto2("ini_protection", simulation, (cfg->policy_drop || (entry && entry->drop)), __VA_ARGS__); \
12 }
13
14
6static bool /* success */ sp_ini_check(zend_string *varname, zend_string *new_value, sp_ini_entry **sp_entry_p) { 15static bool /* success */ sp_ini_check(zend_string *varname, zend_string *new_value, sp_ini_entry **sp_entry_p) {
7 if (!varname || ZSTR_LEN(varname) == 0) { 16 if (!varname || ZSTR_LEN(varname) == 0) {
8 return false; 17 return false;
@@ -17,40 +26,49 @@ static bool /* success */ sp_ini_check(zend_string *varname, zend_string *new_va
17 26
18 if (!entry) { 27 if (!entry) {
19 if (cfg->policy_readonly) { 28 if (cfg->policy_readonly) {
20 sp_log_auto("ini_protection", simulation, "INI setting is read-only"); 29 if (!cfg->policy_silent_ro) {
21 if (simulation) { return true; } 30 sp_log_ini_check_violation("INI setting is read-only");
22 return false; 31 }
32 return simulation;
23 } 33 }
24 return true; 34 return true;
25 } 35 }
26 36
37 // we have an entry.
38
27 if (SP_INI_ACCESS_READONLY_COND(entry, cfg)) { 39 if (SP_INI_ACCESS_READONLY_COND(entry, cfg)) {
28 sp_log_auto("ini_protection", simulation, "%s", (entry->msg ? ZSTR_VAL(entry->msg) : "INI setting is read-only")); 40 if (!cfg->policy_silent_ro) {
29 if (simulation) { return true; } 41 sp_log_ini_check_violation("%s", (entry->msg ? ZSTR_VAL(entry->msg) : "INI setting is read-only"));
30 return false; 42 }
43 return simulation;
31 } 44 }
32 45
33 if (!new_value && SP_INI_HAS_CHECKS_COND(entry)) { 46 if (!new_value || ZSTR_LEN(new_value) == 0) {
34 sp_log_auto("ini_protection", simulation, "new INI value must not be NULL"); 47 if (entry->allow_null) {
35 if (simulation) { return true; } 48 return true; // allow NULL value and skip other tests
36 return false; 49 }
50 if (SP_INI_HAS_CHECKS_COND(entry)) {
51 sp_log_ini_check_violation("new INI value must not be NULL or empty");
52 return simulation;
53 }
54 return true; // no new_value, but no checks to perform
37 } 55 }
38 56
57 // we have a new_value.
58
39 if (entry->min || entry->max) { 59 if (entry->min || entry->max) {
40 zend_long lvalue = zend_atol(ZSTR_VAL(new_value), ZSTR_LEN(new_value)); 60 zend_long lvalue = zend_atol(ZSTR_VAL(new_value), ZSTR_LEN(new_value));
41 if ((entry->min && zend_atol(ZSTR_VAL(entry->min), ZSTR_LEN(entry->min)) > lvalue) || 61 if ((entry->min && zend_atol(ZSTR_VAL(entry->min), ZSTR_LEN(entry->min)) > lvalue) ||
42 (entry->max && zend_atol(ZSTR_VAL(entry->max), ZSTR_LEN(entry->max)) < lvalue)) { 62 (entry->max && zend_atol(ZSTR_VAL(entry->max), ZSTR_LEN(entry->max)) < lvalue)) {
43 sp_log_auto("ini_protection", simulation, "%s", (entry->msg ? ZSTR_VAL(entry->msg) : "INI value out of range")); 63 sp_log_ini_check_violation("%s", (entry->msg ? ZSTR_VAL(entry->msg) : "INI value out of range"));
44 if (simulation) { return true; } 64 return simulation;
45 return false;
46 } 65 }
47 } 66 }
48 67
49 if (entry->regexp) { 68 if (entry->regexp) {
50 if (!sp_is_regexp_matching_len(entry->regexp, ZSTR_VAL(new_value), ZSTR_LEN(new_value))) { 69 if (!sp_is_regexp_matching_len(entry->regexp, ZSTR_VAL(new_value), ZSTR_LEN(new_value))) {
51 sp_log_auto("ini_protection", simulation, "%s", (entry->msg ? ZSTR_VAL(entry->msg) : "INI value does not match regex")); 70 sp_log_ini_check_violation("%s", (entry->msg ? ZSTR_VAL(entry->msg) : "INI value does not match regex"));
52 if (simulation) { return true; } 71 return simulation;
53 return false;
54 } 72 }
55 } 73 }
56 74
@@ -83,7 +101,7 @@ void sp_hook_ini() {
83 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)); 101 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));
84 continue; 102 continue;
85 } 103 }
86 if (SP_INI_ACCESS_READONLY_COND(sp_entry, cfg)) { 104 if (SP_INI_ACCESS_READONLY_COND(sp_entry, cfg) && (cfg->policy_silent_ro || cfg->policy_silent_fail) && !sp_entry->drop && !(sp_entry->simulation || cfg->simulation)) {
87 ini_entry->modifiable = ini_entry->orig_modifiable = 0; 105 ini_entry->modifiable = ini_entry->orig_modifiable = 0;
88 } 106 }
89 PHP_INI_MH((*orig_onmodify)) = ini_entry->on_modify; 107 PHP_INI_MH((*orig_onmodify)) = ini_entry->on_modify;