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