summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/snuffleupagus.c52
-rw-r--r--src/sp_config_utils.c5
-rw-r--r--src/sp_cookie_encryption.c5
-rw-r--r--src/sp_crypt.c9
-rw-r--r--src/sp_ifilter.c10
-rw-r--r--src/sp_upload_validation.c8
-rw-r--r--src/sp_utils.c23
-rw-r--r--src/sp_wrapper.c2
-rw-r--r--src/tests/disable_function/disabled_function_echo_2.phpt6
-rw-r--r--src/tests/unserialize_php8/equivalent.phpt23
10 files changed, 115 insertions, 28 deletions
diff --git a/src/snuffleupagus.c b/src/snuffleupagus.c
index 4d5fa09..6b0a327 100644
--- a/src/snuffleupagus.c
+++ b/src/snuffleupagus.c
@@ -25,6 +25,39 @@ static inline void sp_op_array_handler(zend_op_array *const op) {
25 op->fn_flags |= ZEND_ACC_STRICT_TYPES; 25 op->fn_flags |= ZEND_ACC_STRICT_TYPES;
26 } 26 }
27 } 27 }
28#if PHP_VERSION_ID >= 80500
29 /* Prevent opcache from inlining user functions that have return-value
30 * monitoring rules, otherwise zend_execute_ex is never called and the
31 * hook never fires. ZEND_ACC_HAS_TYPE_HINTS is checked by
32 * zend_try_inline_call() and blocks inlining. For functions without
33 * actual type hints the only runtime effect is that ZEND_RECV opcodes
34 * are executed instead of skipped; for 0-arg functions (the common
35 * inlineable case) there are no RECV opcodes so the impact is zero. */
36 if (op->function_name && ZEND_USER_CODE(op->type) &&
37 ((SPCFG(disabled_functions_ret) && zend_hash_num_elements(SPCFG(disabled_functions_ret))) ||
38 SPCFG(disabled_functions_reg_ret).disabled_functions)) {
39 char *fname = NULL;
40 if (op->scope) {
41 const size_t len = ZSTR_LEN(op->scope->name) + 2 + ZSTR_LEN(op->function_name) + 1;
42 fname = emalloc(len);
43 snprintf(fname, len, "%s::%s", ZSTR_VAL(op->scope->name), ZSTR_VAL(op->function_name));
44 } else {
45 fname = estrdup(ZSTR_VAL(op->function_name));
46 }
47 bool has_ret_rule = false;
48 if (SPCFG(disabled_functions_ret) &&
49 zend_hash_str_find_ptr(SPCFG(disabled_functions_ret), fname, strlen(fname))) {
50 has_ret_rule = true;
51 }
52 if (!has_ret_rule && SPCFG(disabled_functions_reg_ret).disabled_functions) {
53 has_ret_rule = true; /* regex rules require runtime matching */
54 }
55 if (has_ret_rule) {
56 op->fn_flags |= ZEND_ACC_HAS_TYPE_HINTS;
57 }
58 efree(fname);
59 }
60#endif
28} 61}
29 62
30ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus) 63ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus)
@@ -246,7 +279,7 @@ PHP_MINFO_FUNCTION(snuffleupagus) {
246 php_info_print_table_start(); 279 php_info_print_table_start();
247 php_info_print_table_row( 280 php_info_print_table_row(
248 2, "snuffleupagus support", 281 2, "snuffleupagus support",
249 SPG(is_config_valid) ? "enabled" : "disabled"); 282 SPG(is_config_valid) == SP_CONFIG_VALID ? "enabled" : "disabled");
250 php_info_print_table_row(2, "Version", PHP_SNUFFLEUPAGUS_VERSION); 283 php_info_print_table_row(2, "Version", PHP_SNUFFLEUPAGUS_VERSION);
251 php_info_print_table_row(2, "Valid config", valid_config); 284 php_info_print_table_row(2, "Valid config", valid_config);
252 php_info_print_table_end(); 285 php_info_print_table_end();
@@ -582,12 +615,25 @@ static PHP_INI_MH(OnUpdateConfiguration) {
582 615
583 sp_hook_register_server_variables(); 616 sp_hook_register_server_variables();
584 617
585 if (SPCFG(global_strict).enable) { 618 bool need_op_array_handler = SPCFG(global_strict).enable;
619
620#if PHP_VERSION_ID >= 80500
621 /* Register as zend extension to get op_array_handler callbacks, which we
622 * use to prevent opcache from inlining monitored functions. */
623 if (SPCFG(disabled_functions_ret) && zend_hash_num_elements(SPCFG(disabled_functions_ret))) {
624 need_op_array_handler = true;
625 }
626 if (SPCFG(disabled_functions_reg_ret).disabled_functions) {
627 need_op_array_handler = true;
628 }
629#endif
630
631 if (need_op_array_handler) {
586 if (!zend_get_extension(PHP_SNUFFLEUPAGUS_EXTNAME)) { 632 if (!zend_get_extension(PHP_SNUFFLEUPAGUS_EXTNAME)) {
587 zend_extension_entry.startup = NULL; 633 zend_extension_entry.startup = NULL;
588 zend_register_extension(&zend_extension_entry, NULL); 634 zend_register_extension(&zend_extension_entry, NULL);
589 } 635 }
590 // This is needed to implement the global strict mode 636 // This is needed to enable the op_array_handler callback
591 CG(compiler_options) |= ZEND_COMPILE_HANDLE_OP_ARRAY; 637 CG(compiler_options) |= ZEND_COMPILE_HANDLE_OP_ARRAY;
592 } 638 }
593 639
diff --git a/src/sp_config_utils.c b/src/sp_config_utils.c
index 84b1f30..415e434 100644
--- a/src/sp_config_utils.c
+++ b/src/sp_config_utils.c
@@ -11,8 +11,9 @@ sp_list_node *parse_functions_list(const char *const value) {
11 sp_list_node *list = NULL; 11 sp_list_node *list = NULL;
12 char *tmp = strdup(value); 12 char *tmp = strdup(value);
13 const char *function_name; 13 const char *function_name;
14 char *next_token = tmp; 14 char *next_token = NULL;
15 while ((function_name = strtok_r(NULL, sep, &next_token))) { 15 for (function_name = strtok_r(tmp, sep, &next_token); function_name;
16 function_name = strtok_r(NULL, sep, &next_token)) {
16 list = sp_list_prepend(list, strdup(function_name)); 17 list = sp_list_prepend(list, strdup(function_name));
17 } 18 }
18 free(tmp); 19 free(tmp);
diff --git a/src/sp_cookie_encryption.c b/src/sp_cookie_encryption.c
index 0c14c70..7b3e088 100644
--- a/src/sp_cookie_encryption.c
+++ b/src/sp_cookie_encryption.c
@@ -31,6 +31,11 @@ int decrypt_cookie(zval *pDest, int num_args, va_list args,
31 return ZEND_HASH_APPLY_KEEP; 31 return ZEND_HASH_APPLY_KEEP;
32 } 32 }
33 33
34 /* Cookies can be arrays, like session_id[]=x */
35 if (Z_TYPE_P(pDest) != IS_STRING) {
36 return ZEND_HASH_APPLY_KEEP;
37 }
38
34 /* If the cookie has no value, it shouldn't be encrypted. */ 39 /* If the cookie has no value, it shouldn't be encrypted. */
35 if (0 == Z_STRLEN_P(pDest)) { 40 if (0 == Z_STRLEN_P(pDest)) {
36 return ZEND_HASH_APPLY_KEEP; 41 return ZEND_HASH_APPLY_KEEP;
diff --git a/src/sp_crypt.c b/src/sp_crypt.c
index 6d48554..3b65616 100644
--- a/src/sp_crypt.c
+++ b/src/sp_crypt.c
@@ -32,6 +32,7 @@ void generate_key(unsigned char *key) {
32 } 32 }
33 33
34 PHP_SHA256Final((unsigned char *)key, &ctx); 34 PHP_SHA256Final((unsigned char *)key, &ctx);
35 ZEND_SECURE_ZERO(&ctx, sizeof(ctx));
35} 36}
36 37
37// This function return 0 upon success , non-zero otherwise 38// This function return 0 upon success , non-zero otherwise
@@ -42,6 +43,11 @@ int decrypt_zval(zval *pDest, bool simulation, zend_hash_key *hash_key) {
42 43
43 zend_string *debase64 = php_base64_decode((unsigned char *)(Z_STRVAL_P(pDest)), Z_STRLEN_P(pDest)); 44 zend_string *debase64 = php_base64_decode((unsigned char *)(Z_STRVAL_P(pDest)), Z_STRLEN_P(pDest));
44 45
46 if (!debase64) {
47 sp_log_drop( "cookie_encryption", "Unable to base64-decode the cookie");
48 return ZEND_HASH_APPLY_REMOVE;
49 }
50
45 if (ZSTR_LEN(debase64) < crypto_secretbox_NONCEBYTES) { 51 if (ZSTR_LEN(debase64) < crypto_secretbox_NONCEBYTES) {
46 if (true == simulation) { 52 if (true == simulation) {
47 sp_log_simulation( 53 sp_log_simulation(
@@ -115,6 +121,7 @@ int decrypt_zval(zval *pDest, bool simulation, zend_hash_key *hash_key) {
115 ret = ZEND_HASH_APPLY_KEEP; 121 ret = ZEND_HASH_APPLY_KEEP;
116 122
117out: 123out:
124 ZEND_SECURE_ZERO(key, sizeof(key));
118 zend_string_efree(debase64); 125 zend_string_efree(debase64);
119 efree(decrypted); 126 efree(decrypted);
120 efree(backup); 127 efree(backup);
@@ -164,6 +171,8 @@ zend_string *encrypt_zval(zend_string *data) {
164 z = php_base64_encode(encrypted_data, emsg_and_nonce_len); 171 z = php_base64_encode(encrypted_data, emsg_and_nonce_len);
165 } 172 }
166 173
174 ZEND_SECURE_ZERO(key, sizeof(key));
175 ZEND_SECURE_ZERO(nonce, sizeof(nonce));
167 efree(data_to_encrypt); 176 efree(data_to_encrypt);
168 efree(encrypted_data); 177 efree(encrypted_data);
169 178
diff --git a/src/sp_ifilter.c b/src/sp_ifilter.c
index 67eb5f3..ffdeec1 100644
--- a/src/sp_ifilter.c
+++ b/src/sp_ifilter.c
@@ -33,7 +33,7 @@ static void sp_server_strip(HashTable *svars, const char *key, size_t keylen) {
33 char *tmpend = tmp + ZSTR_LEN(tmp_zstr); 33 char *tmpend = tmp + ZSTR_LEN(tmp_zstr);
34 34
35 for (char *p = tmp; p < tmpend; p++) { 35 for (char *p = tmp; p < tmpend; p++) {
36 if (sp_is_dangerous_char[(int)*p]) { 36 if (sp_is_dangerous_char[(unsigned char)*p]) {
37 *p = '_'; 37 *p = '_';
38 } 38 }
39 } 39 }
@@ -49,17 +49,17 @@ static void sp_server_encode(HashTable *svars, const char *key, size_t keylen) {
49 int extra = 0; 49 int extra = 0;
50 50
51 for (char *p = tmp; p < tmpend; p++) { 51 for (char *p = tmp; p < tmpend; p++) {
52 extra += sp_is_dangerous_char[(int)*p] * 2; 52 extra += sp_is_dangerous_char[(unsigned char)*p] * 2;
53 } 53 }
54 if (!extra) { return; } 54 if (!extra) { return; }
55 55
56 zend_string *new_zstr = zend_string_alloc(ZSTR_LEN(tmp_zstr) + extra, 0); 56 zend_string *new_zstr = zend_string_alloc(ZSTR_LEN(tmp_zstr) + extra, 0);
57 char *n = ZSTR_VAL(new_zstr); 57 char *n = ZSTR_VAL(new_zstr);
58 for (char *p = tmp; p < tmpend; p++, n++) { 58 for (char *p = tmp; p < tmpend; p++, n++) {
59 if (sp_is_dangerous_char[(int)*p]) { 59 if (sp_is_dangerous_char[(unsigned char)*p]) {
60 *n++ = '%'; 60 *n++ = '%';
61 *n++ = sp_hexchars[*p >> 4]; 61 *n++ = sp_hexchars[(unsigned char)*p >> 4];
62 *n = sp_hexchars[*p & 15]; 62 *n = sp_hexchars[(unsigned char)*p & 15];
63 } else { 63 } else {
64 *n = *p; 64 *n = *p;
65 } 65 }
diff --git a/src/sp_upload_validation.c b/src/sp_upload_validation.c
index 4ac4992..b5babed 100644
--- a/src/sp_upload_validation.c
+++ b/src/sp_upload_validation.c
@@ -54,8 +54,9 @@ static int sp_rfc1867_callback(unsigned int event, void *event_data, void **extr
54 cmd[1] = tmp_name; 54 cmd[1] = tmp_name;
55 cmd[2] = NULL; 55 cmd[2] = NULL;
56 56
57 const char *remote_addr = getenv("REMOTE_ADDR");
57 spprintf(&env[0], 0, "SP_FILENAME=%s", filename); 58 spprintf(&env[0], 0, "SP_FILENAME=%s", filename);
58 spprintf(&env[1], 0, "SP_REMOTE_ADDR=%s", getenv("REMOTE_ADDR")); 59 spprintf(&env[1], 0, "SP_REMOTE_ADDR=%s", remote_addr ? remote_addr : "");
59 spprintf(&env[2], 0, "SP_CURRENT_FILE=%s", zend_get_executed_filename(TSRMLS_C)); 60 spprintf(&env[2], 0, "SP_CURRENT_FILE=%s", zend_get_executed_filename(TSRMLS_C));
60 spprintf(&env[3], 0, "SP_FILESIZE=%zu", filesize); 61 spprintf(&env[3], 0, "SP_FILESIZE=%zu", filesize);
61 env[4] = NULL; 62 env[4] = NULL;
@@ -63,8 +64,7 @@ static int sp_rfc1867_callback(unsigned int event, void *event_data, void **extr
63 if ((pid = fork()) == 0) { 64 if ((pid = fork()) == 0) {
64 if (execve(ZSTR_VAL(config_upload->script), cmd, env) == -1) { 65 if (execve(ZSTR_VAL(config_upload->script), cmd, env) == -1) {
65 sp_log_warn("upload_validation", "Could not call '%s' : %s", ZSTR_VAL(config_upload->script), strerror(errno)); 66 sp_log_warn("upload_validation", "Could not call '%s' : %s", ZSTR_VAL(config_upload->script), strerror(errno));
66 EFREE_3(env); 67 _exit(1);
67 exit(1);
68 } 68 }
69 } else if (pid == -1) { 69 } else if (pid == -1) {
70 // LCOV_EXCL_START 70 // LCOV_EXCL_START
@@ -76,7 +76,7 @@ static int sp_rfc1867_callback(unsigned int event, void *event_data, void **extr
76 76
77 EFREE_3(env); 77 EFREE_3(env);
78 int waitstatus; 78 int waitstatus;
79 wait(&waitstatus); 79 waitpid(pid, &waitstatus, 0);
80 if (WEXITSTATUS(waitstatus) != 0) { // Nope 80 if (WEXITSTATUS(waitstatus) != 0) { // Nope
81 char *uri = getenv("REQUEST_URI"); 81 char *uri = getenv("REQUEST_URI");
82 int sim = config_upload->simulation; 82 int sim = config_upload->simulation;
diff --git a/src/sp_utils.c b/src/sp_utils.c
index d49d459..10beb8b 100644
--- a/src/sp_utils.c
+++ b/src/sp_utils.c
@@ -315,11 +315,11 @@ void sp_log_disable(const char* restrict path, const char* restrict arg_name,
315 if (arg_name) { 315 if (arg_name) {
316 char* char_repr = NULL; 316 char* char_repr = NULL;
317 if (arg_value) { 317 if (arg_value) {
318 zend_string *arg_value_dup = zend_string_init(ZSTR_VAL(arg_value), ZSTR_LEN(arg_value), 0); 318 zend_string *arg_value_enc = php_raw_url_encode(ZSTR_VAL(arg_value), ZSTR_LEN(arg_value));
319 arg_value_dup = php_raw_url_encode(ZSTR_VAL(arg_value_dup), ZSTR_LEN(arg_value_dup)); 319 char_repr = zend_string_to_char(arg_value_enc);
320 char_repr = zend_string_to_char(arg_value_dup); 320 size_t max_len = MIN(ZSTR_LEN(arg_value_enc), (size_t)SPCFG(log_max_len));
321 size_t max_len = MIN(ZSTR_LEN(arg_value_dup), (size_t)SPCFG(log_max_len));
322 char_repr[max_len] = '\0'; 321 char_repr[max_len] = '\0';
322 zend_string_release(arg_value_enc);
323 } 323 }
324 if (alias) { 324 if (alias) {
325 sp_log_auto( 325 sp_log_auto(
@@ -359,11 +359,11 @@ void sp_log_disable_ret(const char* restrict path,
359 sp_log_request(dump, config_node->textual_representation); 359 sp_log_request(dump, config_node->textual_representation);
360 } 360 }
361 if (ret_value) { 361 if (ret_value) {
362 zend_string *ret_value_dup = zend_string_init(ZSTR_VAL(ret_value), ZSTR_LEN(ret_value), 0); 362 zend_string *ret_value_enc = php_raw_url_encode(ZSTR_VAL(ret_value), ZSTR_LEN(ret_value));
363 ret_value_dup = php_raw_url_encode(ZSTR_VAL(ret_value_dup), ZSTR_LEN(ret_value_dup)); 363 char_repr = zend_string_to_char(ret_value_enc);
364 char_repr = zend_string_to_char(ret_value_dup); 364 size_t max_len = MIN(ZSTR_LEN(ret_value_enc), (size_t)SPCFG(log_max_len));
365 size_t max_len = MIN(ZSTR_LEN(ret_value_dup), (size_t)SPCFG(log_max_len));
366 char_repr[max_len] = '\0'; 365 char_repr[max_len] = '\0';
366 zend_string_release(ret_value_enc);
367 } 367 }
368 if (alias) { 368 if (alias) {
369 sp_log_auto( 369 sp_log_auto(
@@ -393,11 +393,12 @@ bool sp_match_array_key(const zval* zv, const zend_string* to_match, const sp_re
393 char* idx_str = NULL; 393 char* idx_str = NULL;
394 spprintf(&idx_str, 0, ZEND_ULONG_FMT, idx); 394 spprintf(&idx_str, 0, ZEND_ULONG_FMT, idx);
395 zend_string* tmp = zend_string_init(idx_str, strlen(idx_str), 0); 395 zend_string* tmp = zend_string_init(idx_str, strlen(idx_str), 0);
396 if (sp_match_value(tmp, to_match, rx)) { 396 bool match = sp_match_value(tmp, to_match, rx);
397 efree(idx_str); 397 zend_string_release(tmp);
398 efree(idx_str);
399 if (match) {
398 return true; 400 return true;
399 } 401 }
400 efree(idx_str);
401 } 402 }
402 } 403 }
403 ZEND_HASH_FOREACH_END(); 404 ZEND_HASH_FOREACH_END();
diff --git a/src/sp_wrapper.c b/src/sp_wrapper.c
index 6b6c5cd..22b1d88 100644
--- a/src/sp_wrapper.c
+++ b/src/sp_wrapper.c
@@ -180,7 +180,7 @@ PHP_FUNCTION(sp_stream_wrapper_register) {
180 if (!protocol_name || wrapper_is_whitelisted(protocol_name)) { 180 if (!protocol_name || wrapper_is_whitelisted(protocol_name)) {
181 181
182 // reject manual loading of "php" wrapper 182 // reject manual loading of "php" wrapper
183 if (!strcasecmp(ZSTR_VAL(protocol_name), "php") && sp_php_stream_is_filtered()) { 183 if (protocol_name && !strcasecmp(ZSTR_VAL(protocol_name), "php") && sp_php_stream_is_filtered()) {
184 return; 184 return;
185 } 185 }
186 186
diff --git a/src/tests/disable_function/disabled_function_echo_2.phpt b/src/tests/disable_function/disabled_function_echo_2.phpt
index c1d9817..ce3488e 100644
--- a/src/tests/disable_function/disabled_function_echo_2.phpt
+++ b/src/tests/disable_function/disabled_function_echo_2.phpt
@@ -4,11 +4,13 @@ Echo hooking
4<?php if (!extension_loaded("snuffleupagus")) print "skip"; ?> 4<?php if (!extension_loaded("snuffleupagus")) print "skip"; ?>
5--INI-- 5--INI--
6sp.configuration_file={PWD}/config/disabled_function_echo.ini 6sp.configuration_file={PWD}/config/disabled_function_echo.ini
7opcache.optimization_level=0
7--FILE-- 8--FILE--
8<?php 9<?php
9echo "qwe"; 10echo "qwe";
10echo "1", "oops"; 11echo "1";
12echo "oops";
11?> 13?>
12--EXPECTF-- 14--EXPECTF--
13qwe1 15qwe1
14Fatal error: [snuffleupagus][0.0.0.0][disabled_function][drop] Aborted execution on call of the function 'echo' in %a/disabled_function_echo_2.php on line 3 16Fatal error: [snuffleupagus][0.0.0.0][disabled_function][drop] Aborted execution on call of the function 'echo' in %a/disabled_function_echo_2.php on line 4
diff --git a/src/tests/unserialize_php8/equivalent.phpt b/src/tests/unserialize_php8/equivalent.phpt
new file mode 100644
index 0000000..57dcf72
--- /dev/null
+++ b/src/tests/unserialize_php8/equivalent.phpt
@@ -0,0 +1,23 @@
1--TEST--
2Test idempotence of serialize+unserialize
3--SKIPIF--
4<?php if (!extension_loaded("snuffleupagus")) print "skip"; ?>
5--INI--
6sp.configuration_file={PWD}/config/config_serialize.ini
7--FILE--
8<?php
9class A {
10public $pub = "public data";
11protected $prot = "protected data";
12private $priv = "private data";
13}
14$a = new A;
15if (unserialize(serialize($a)) == $a) {
16 echo "OK";
17} else {
18 echo "FAIL";
19}
20?>
21--EXPECT--
22OK
23