1 #ifdef PHP_WIN32
2 #include "win32/glob.h"
3 #else
4 #include <glob.h>
5 #endif
6 
7 #include "php_snuffleupagus.h"
8 
9 #ifndef ZEND_EXT_API
10 #define ZEND_EXT_API ZEND_DLEXPORT
11 #endif
12 
13 static PHP_INI_MH(OnUpdateConfiguration);
14 static inline void sp_op_array_handler(zend_op_array *op);
15 
16 ZEND_EXTENSION();
17 
18 // LCOV_EXCL_START
sp_zend_startup(zend_extension * extension)19 ZEND_DLEXPORT int sp_zend_startup(zend_extension *extension) {
20   return zend_startup_module(&snuffleupagus_module_entry);
21 }
22 // LCOV_EXCL_END
23 
sp_op_array_handler(zend_op_array * op)24 static inline void sp_op_array_handler(zend_op_array *op) {
25   // We need a filename, and strict mode not already enabled on this op
26   if (NULL == op->filename || op->fn_flags & ZEND_ACC_STRICT_TYPES) {
27     return;
28   } else {
29     if (true == SNUFFLEUPAGUS_G(config).config_global_strict->enable) {
30       op->fn_flags |= ZEND_ACC_STRICT_TYPES;
31     }
32   }
33 }
34 
35 ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus)
36 
PHP_INI_MH(StrictMode)37 static PHP_INI_MH(StrictMode) {
38   TSRMLS_FETCH();
39 
40   SNUFFLEUPAGUS_G(allow_broken_configuration) = false;
41   if (new_value && zend_string_equals_literal(new_value, "1")) {
42     SNUFFLEUPAGUS_G(allow_broken_configuration) = true;
43   }
44   return SUCCESS;
45 }
46 
47 PHP_INI_BEGIN()
48 PHP_INI_ENTRY("sp.configuration_file", "", PHP_INI_SYSTEM,
49               OnUpdateConfiguration)
50 PHP_INI_ENTRY("sp.allow_broken_configuration", "0", PHP_INI_SYSTEM,
51               StrictMode)
52 PHP_INI_END()
53 
54 ZEND_DLEXPORT zend_extension zend_extension_entry = {
55     PHP_SNUFFLEUPAGUS_EXTNAME,
56     PHP_SNUFFLEUPAGUS_VERSION,
57     PHP_SNUFFLEUPAGUS_AUTHOR,
58     PHP_SNUFFLEUPAGUS_URL,
59     PHP_SNUFFLEUPAGUS_COPYRIGHT,
60     sp_zend_startup,
61     NULL,
62     NULL,                 /* activate_func_t */
63     NULL,                 /* deactivate_func_t */
64     NULL,                 /* message_handler_func_t */
65     sp_op_array_handler,  /* op_array_handler_func_t */
66     NULL,                 /* statement_handler_func_t */
67     NULL,                 /* fcall_begin_handler_func_t */
68     NULL,                 /* fcall_end_handler_func_t */
69     NULL,                 /* op_array_ctor_func_t */
70     NULL,                 /* op_array_dtor_func_t */
71     STANDARD_ZEND_EXTENSION_PROPERTIES};
72 
PHP_GINIT_FUNCTION(snuffleupagus)73 PHP_GINIT_FUNCTION(snuffleupagus) {
74   snuffleupagus_globals->is_config_valid = SP_CONFIG_NONE;
75   snuffleupagus_globals->in_eval = 0;
76 
77 #define SP_INIT_HT(F) snuffleupagus_globals->F = \
78     pemalloc(sizeof(*(snuffleupagus_globals->F)), 1); \
79     zend_hash_init(snuffleupagus_globals->F, 10, NULL, NULL, 1);
80   SP_INIT_HT(disabled_functions_hook);
81   SP_INIT_HT(sp_internal_functions_hook);
82   SP_INIT_HT(sp_eval_blacklist_functions_hook);
83   SP_INIT_HT(config.config_disabled_functions);
84   SP_INIT_HT(config.config_disabled_functions_hooked);
85   SP_INIT_HT(config.config_disabled_functions_ret);
86   SP_INIT_HT(config.config_disabled_functions_ret_hooked);
87 #undef SP_INIT_HT
88 
89 #define SP_INIT(F) snuffleupagus_globals->config.F = \
90     pecalloc(sizeof(*(snuffleupagus_globals->config.F)), 1, 1);
91   SP_INIT(config_unserialize);
92   SP_INIT(config_random);
93   SP_INIT(config_sloppy);
94   SP_INIT(config_readonly_exec);
95   SP_INIT(config_global_strict);
96   SP_INIT(config_auto_cookie_secure);
97   SP_INIT(config_snuffleupagus);
98   SP_INIT(config_disable_xxe);
99   SP_INIT(config_upload_validation);
100   SP_INIT(config_disabled_functions_reg);
101   SP_INIT(config_disabled_functions_reg_ret);
102   SP_INIT(config_cookie);
103   SP_INIT(config_session);
104   SP_INIT(config_eval);
105   SP_INIT(config_wrapper);
106 #undef SP_INIT
107 
108 #define SP_INIT_NULL(F) snuffleupagus_globals->config.F = NULL;
109   SP_INIT_NULL(config_disabled_functions_reg->disabled_functions);
110   SP_INIT_NULL(config_disabled_functions_reg_ret->disabled_functions);
111   SP_INIT_NULL(config_cookie->cookies);
112   SP_INIT_NULL(config_eval->blacklist);
113   SP_INIT_NULL(config_eval->whitelist);
114   SP_INIT_NULL(config_wrapper->whitelist);
115 #undef SP_INIT_NULL
116 }
117 
PHP_MINIT_FUNCTION(snuffleupagus)118 PHP_MINIT_FUNCTION(snuffleupagus) {
119   REGISTER_INI_ENTRIES();
120 
121   return SUCCESS;
122 }
123 
free_disabled_functions_hashtable(HashTable * ht)124 static void free_disabled_functions_hashtable(HashTable *ht) {
125   void *ptr = NULL;
126   ZEND_HASH_FOREACH_PTR(ht, ptr) { sp_list_free(ptr); }
127   ZEND_HASH_FOREACH_END();
128 }
129 
PHP_MSHUTDOWN_FUNCTION(snuffleupagus)130 PHP_MSHUTDOWN_FUNCTION(snuffleupagus) {
131 
132 #define FREE_HT(F)                       \
133       zend_hash_destroy(SNUFFLEUPAGUS_G(F)); \
134       pefree(SNUFFLEUPAGUS_G(F), 1);
135   FREE_HT(disabled_functions_hook);
136   FREE_HT(sp_eval_blacklist_functions_hook);
137 
138 #define FREE_HT_LIST(F) \
139       free_disabled_functions_hashtable(SNUFFLEUPAGUS_G(config).F); \
140       FREE_HT(config.F);
141   FREE_HT_LIST(config_disabled_functions);
142   FREE_HT_LIST(config_disabled_functions_hooked);
143   FREE_HT_LIST(config_disabled_functions_ret);
144   FREE_HT_LIST(config_disabled_functions_ret_hooked);
145 #undef FREE_HT_LIST
146 #undef FREE_HT
147 
148 #define FREE_LST_DISABLE(L)                \
149     do {                                     \
150       sp_list_node *_n = SNUFFLEUPAGUS_G(config).L; \
151       sp_disabled_function_list_free(_n);    \
152       sp_list_free(_n);                      \
153     } while (0)
154   FREE_LST_DISABLE(config_disabled_functions_reg->disabled_functions);
155   FREE_LST_DISABLE(config_disabled_functions_reg_ret->disabled_functions);
156 #undef FREE_LST_DISABLE
157 
158 #define FREE_LST(L) sp_list_free(SNUFFLEUPAGUS_G(config).L);
159   FREE_LST(config_cookie->cookies);
160   FREE_LST(config_eval->blacklist);
161   FREE_LST(config_eval->whitelist);
162   FREE_LST(config_wrapper->whitelist);
163 #undef FREE_LST
164 
165 #define FREE_CFG(C) pefree(SNUFFLEUPAGUS_G(config).C, 1);
166   FREE_CFG(config_unserialize);
167   FREE_CFG(config_random);
168   FREE_CFG(config_readonly_exec);
169   FREE_CFG(config_global_strict);
170   FREE_CFG(config_auto_cookie_secure);
171   FREE_CFG(config_snuffleupagus);
172   FREE_CFG(config_disable_xxe);
173   FREE_CFG(config_upload_validation);
174   FREE_CFG(config_session);
175   FREE_CFG(config_disabled_functions_reg);
176   FREE_CFG(config_disabled_functions_reg_ret);
177   FREE_CFG(config_cookie);
178   FREE_CFG(config_wrapper);
179 #undef FREE_CFG
180 
181   UNREGISTER_INI_ENTRIES();
182 
183   return SUCCESS;
184 }
185 
PHP_RINIT_FUNCTION(snuffleupagus)186 PHP_RINIT_FUNCTION(snuffleupagus) {
187   const sp_config_wrapper* config_wrapper =
188       SNUFFLEUPAGUS_G(config).config_wrapper;
189 #if defined(COMPILE_DL_SNUFFLEUPAGUS) && defined(ZTS)
190   ZEND_TSRMLS_CACHE_UPDATE();
191 #endif
192 
193   if (!SNUFFLEUPAGUS_G(allow_broken_configuration)) {
194     if (SNUFFLEUPAGUS_G(is_config_valid) == SP_CONFIG_INVALID ) {
195       sp_log_err("config", "Invalid configuration file");
196     } else if (SNUFFLEUPAGUS_G(is_config_valid) == SP_CONFIG_NONE) {
197       sp_log_warn("config", "No configuration specificed via sp.configuration_file");
198     }
199   }
200 
201   // We need to disable wrappers loaded by extensions loaded after SNUFFLEUPAGUS.
202   if (config_wrapper->enabled &&
203       zend_hash_num_elements(php_stream_get_url_stream_wrappers_hash()) !=
204       config_wrapper->num_wrapper) {
205     sp_disable_wrapper();
206   }
207 
208   if (NULL != SNUFFLEUPAGUS_G(config).config_snuffleupagus->encryption_key) {
209     if (NULL != SNUFFLEUPAGUS_G(config).config_cookie->cookies) {
210       zend_hash_apply_with_arguments(
211           Z_ARRVAL(PG(http_globals)[TRACK_VARS_COOKIE]), decrypt_cookie, 0);
212     }
213   }
214   return SUCCESS;
215 }
216 
PHP_RSHUTDOWN_FUNCTION(snuffleupagus)217 PHP_RSHUTDOWN_FUNCTION(snuffleupagus) { return SUCCESS; }
218 
PHP_MINFO_FUNCTION(snuffleupagus)219 PHP_MINFO_FUNCTION(snuffleupagus) {
220   const char *valid_config;
221   switch(SNUFFLEUPAGUS_G(is_config_valid)) {
222     case SP_CONFIG_VALID:
223       valid_config = "yes";
224       break;
225     case SP_CONFIG_INVALID:
226       valid_config = "invalid";
227       break;
228     case SP_CONFIG_NONE:
229     default:
230       valid_config = "no";
231   }
232   php_info_print_table_start();
233   php_info_print_table_row(2, "snuffleupagus support",
234       SNUFFLEUPAGUS_G(is_config_valid)?"enabled":"disabled");
235   php_info_print_table_row(2, "Version", PHP_SNUFFLEUPAGUS_VERSION);
236   php_info_print_table_row( 2, "Valid config", valid_config);
237   php_info_print_table_end();
238   DISPLAY_INI_ENTRIES();
239 }
240 
PHP_INI_MH(OnUpdateConfiguration)241 static PHP_INI_MH(OnUpdateConfiguration) {
242   TSRMLS_FETCH();
243 
244   if (!new_value || !new_value->len) {
245     return FAILURE;
246   }
247 
248   glob_t globbuf;
249   char *config_file;
250   char *rest = new_value->val;
251 
252   while ((config_file = strtok_r(rest, ",", &rest))) {
253     int ret = glob(config_file, GLOB_NOCHECK, NULL, &globbuf);
254 
255     if (ret != 0) {
256       SNUFFLEUPAGUS_G(is_config_valid) = SP_CONFIG_INVALID;
257       globfree(&globbuf);
258       return FAILURE;
259     }
260 
261     for (size_t i = 0; globbuf.gl_pathv[i]; i++) {
262       if (sp_parse_config(globbuf.gl_pathv[i]) != SUCCESS) {
263         SNUFFLEUPAGUS_G(is_config_valid) = SP_CONFIG_INVALID;
264         globfree(&globbuf);
265         return FAILURE;
266       }
267     }
268     globfree(&globbuf);
269   }
270 
271   SNUFFLEUPAGUS_G(is_config_valid) = SP_CONFIG_VALID;
272 
273   if ((SNUFFLEUPAGUS_G(config).config_sloppy->enable)) {
274     hook_sloppy();
275   }
276 
277   if (SNUFFLEUPAGUS_G(config).config_random->enable) {
278     hook_rand();
279   }
280 
281   if (SNUFFLEUPAGUS_G(config).config_upload_validation->enable) {
282     hook_upload();
283   }
284 
285   if (SNUFFLEUPAGUS_G(config).config_disable_xxe->enable == 0) {
286     hook_libxml_disable_entity_loader();
287   }
288 
289   if (SNUFFLEUPAGUS_G(config).config_wrapper->enabled) {
290     hook_stream_wrappers();
291   }
292 
293   if (SNUFFLEUPAGUS_G(config).config_session->encrypt) {
294     hook_session();
295   }
296 
297   if (NULL != SNUFFLEUPAGUS_G(config).config_snuffleupagus->encryption_key) {
298     if (SNUFFLEUPAGUS_G(config).config_unserialize->enable) {
299       hook_serialize();
300     }
301   }
302 
303   hook_disabled_functions();
304   hook_execute();
305   hook_cookies();
306 
307   if (true == SNUFFLEUPAGUS_G(config).config_global_strict->enable) {
308     if (!zend_get_extension(PHP_SNUFFLEUPAGUS_EXTNAME)) {
309       zend_extension_entry.startup = NULL;
310       zend_register_extension(&zend_extension_entry, NULL);
311     }
312     // This is needed to implement the global strict mode
313     CG(compiler_options) |= ZEND_COMPILE_HANDLE_OP_ARRAY;
314   }
315 
316   // If `zend_write_default` is not NULL it is already hooked.
317   if ((zend_hash_str_find(
318           SNUFFLEUPAGUS_G(config).config_disabled_functions_hooked, "echo",
319           sizeof("echo") - 1) ||
320       zend_hash_str_find(
321           SNUFFLEUPAGUS_G(config).config_disabled_functions_ret_hooked, "echo",
322           sizeof("echo") - 1)) && NULL == zend_write_default) {
323     zend_write_default = zend_write;
324     zend_write = hook_echo;
325   }
326 
327   SNUFFLEUPAGUS_G(config).hook_execute =
328       SNUFFLEUPAGUS_G(config)
329           .config_disabled_functions_reg->disabled_functions ||
330       SNUFFLEUPAGUS_G(config)
331           .config_disabled_functions_reg_ret->disabled_functions ||
332       zend_hash_num_elements(
333           SNUFFLEUPAGUS_G(config).config_disabled_functions) ||
334       zend_hash_num_elements(
335           SNUFFLEUPAGUS_G(config).config_disabled_functions_ret);
336 
337   return SUCCESS;
338 }
339 
340 const zend_function_entry snuffleupagus_functions[] = {PHP_FE_END};
341 
342 zend_module_entry snuffleupagus_module_entry = {
343     STANDARD_MODULE_HEADER,
344     PHP_SNUFFLEUPAGUS_EXTNAME,
345     snuffleupagus_functions,
346     PHP_MINIT(snuffleupagus),
347     PHP_MSHUTDOWN(snuffleupagus),
348     PHP_RINIT(snuffleupagus),
349     PHP_RSHUTDOWN(snuffleupagus),
350     PHP_MINFO(snuffleupagus),
351     PHP_SNUFFLEUPAGUS_VERSION,
352     PHP_MODULE_GLOBALS(snuffleupagus),
353     PHP_GINIT(snuffleupagus),
354     NULL,
355     NULL,
356     STANDARD_MODULE_PROPERTIES_EX};
357 
358 #ifdef COMPILE_DL_SNUFFLEUPAGUS
359 #ifdef ZTS
360 ZEND_TSRMLS_CACHE_DEFINE()
361 #endif
362 ZEND_GET_MODULE(snuffleupagus)
363 #endif
364