summaryrefslogtreecommitdiff
path: root/src/sp_ini.c
blob: 05d7d997c48451a79d6bf48f627e6f7927346b0f (plain)
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();
}