summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChristian Göttsche2024-05-27 21:33:00 +0200
committerjvoisin2024-06-09 17:16:16 +0200
commitc7ce5c3528e8da8762e6e7067001549e109397ba (patch)
tree4c9606730af25a8f893193b7cc5cb718a20c3f35 /src
parent849252c6a48b428dde3ad8930b40a2bdf9874cb7 (diff)
Add option to specify the allowed "php" wrapper types
In addition of the current possibility to filter wrappers by their protocol name, also add the option to filter the "php" wrapper by the requested kind. Especially the 'filter' backend can be disabled that way.
Diffstat (limited to '')
-rw-r--r--src/snuffleupagus.c3
-rw-r--r--src/sp_config.h2
-rw-r--r--src/sp_config_keywords.c1
-rw-r--r--src/sp_wrapper.c135
-rw-r--r--src/tests/stream_wrapper/config/config_stream_wrapper_php.ini2
-rw-r--r--src/tests/stream_wrapper/stream_wrapper_php.phpt76
6 files changed, 219 insertions, 0 deletions
diff --git a/src/snuffleupagus.c b/src/snuffleupagus.c
index e549692..8c09a37 100644
--- a/src/snuffleupagus.c
+++ b/src/snuffleupagus.c
@@ -113,6 +113,7 @@ static PHP_GINIT_FUNCTION(snuffleupagus) {
113 SP_INIT_NULL(config_eval.blacklist); 113 SP_INIT_NULL(config_eval.blacklist);
114 SP_INIT_NULL(config_eval.whitelist); 114 SP_INIT_NULL(config_eval.whitelist);
115 SP_INIT_NULL(config_wrapper.whitelist); 115 SP_INIT_NULL(config_wrapper.whitelist);
116 SP_INIT_NULL(config_wrapper.php_stream_allowlist);
116#undef SP_INIT_NULL 117#undef SP_INIT_NULL
117} 118}
118 119
@@ -175,6 +176,7 @@ static PHP_GSHUTDOWN_FUNCTION(snuffleupagus) {
175 FREE_LST(config_eval.blacklist); 176 FREE_LST(config_eval.blacklist);
176 FREE_LST(config_eval.whitelist); 177 FREE_LST(config_eval.whitelist);
177 FREE_LST(config_wrapper.whitelist); 178 FREE_LST(config_wrapper.whitelist);
179 FREE_LST(config_wrapper.php_stream_allowlist);
178#undef FREE_LST 180#undef FREE_LST
179 181
180 182
@@ -388,6 +390,7 @@ static void dump_config(void) {
388 add_assoc_bool(&arr, SP_TOKEN_SLOPPY_COMPARISON "." SP_TOKEN_ENABLE, SPCFG(sloppy).enable); 390 add_assoc_bool(&arr, SP_TOKEN_SLOPPY_COMPARISON "." SP_TOKEN_ENABLE, SPCFG(sloppy).enable);
389 391
390 ADD_ASSOC_SPLIST(&arr, SP_TOKEN_ALLOW_WRAPPERS "." SP_TOKEN_LIST, SPCFG(wrapper).whitelist); 392 ADD_ASSOC_SPLIST(&arr, SP_TOKEN_ALLOW_WRAPPERS "." SP_TOKEN_LIST, SPCFG(wrapper).whitelist);
393 ADD_ASSOC_SPLIST(&arr, SP_TOKEN_ALLOW_WRAPPERS "." SP_TOKEN_ALLOW_PHP_STREAMS, SPCFG(wrapper).php_stream_allowlist);
391 394
392#undef ADD_ASSOC_SPLIST 395#undef ADD_ASSOC_SPLIST
393 396
diff --git a/src/sp_config.h b/src/sp_config.h
index f245943..af227ba 100644
--- a/src/sp_config.h
+++ b/src/sp_config.h
@@ -70,6 +70,7 @@ typedef struct {
70 70
71typedef struct { 71typedef struct {
72 sp_list_node *whitelist; 72 sp_list_node *whitelist;
73 sp_list_node *php_stream_allowlist;
73 bool enabled; 74 bool enabled;
74 size_t num_wrapper; // Used to verify if wrappers were added. 75 size_t num_wrapper; // Used to verify if wrappers were added.
75} sp_config_wrapper; 76} sp_config_wrapper;
@@ -214,6 +215,7 @@ typedef struct {
214#define SP_TOKEN_EVAL_WHITELIST "eval_whitelist" 215#define SP_TOKEN_EVAL_WHITELIST "eval_whitelist"
215#define SP_TOKEN_SLOPPY_COMPARISON "sloppy_comparison" 216#define SP_TOKEN_SLOPPY_COMPARISON "sloppy_comparison"
216#define SP_TOKEN_ALLOW_WRAPPERS "wrappers_whitelist" 217#define SP_TOKEN_ALLOW_WRAPPERS "wrappers_whitelist"
218#define SP_TOKEN_ALLOW_PHP_STREAMS "php_list"
217#define SP_TOKEN_INI_PROTECTION "ini_protection" 219#define SP_TOKEN_INI_PROTECTION "ini_protection"
218#define SP_TOKEN_INI "ini" 220#define SP_TOKEN_INI "ini"
219 221
diff --git a/src/sp_config_keywords.c b/src/sp_config_keywords.c
index bf54428..0150a1e 100644
--- a/src/sp_config_keywords.c
+++ b/src/sp_config_keywords.c
@@ -190,6 +190,7 @@ SP_PARSE_FN(parse_wrapper_whitelist) {
190 190
191 sp_config_keyword config_keywords[] = { 191 sp_config_keyword config_keywords[] = {
192 {parse_list, SP_TOKEN_LIST, &cfg->whitelist}, 192 {parse_list, SP_TOKEN_LIST, &cfg->whitelist},
193 {parse_list, SP_TOKEN_ALLOW_PHP_STREAMS, &cfg->php_stream_allowlist},
193 {0, 0, 0}}; 194 {0, 0, 0}};
194 195
195 SP_PROCESS_CONFIG_KEYWORDS_ERR(); 196 SP_PROCESS_CONFIG_KEYWORDS_ERR();
diff --git a/src/sp_wrapper.c b/src/sp_wrapper.c
index 9eb5cbc..54a3a7a 100644
--- a/src/sp_wrapper.c
+++ b/src/sp_wrapper.c
@@ -1,5 +1,7 @@
1#include "php_snuffleupagus.h" 1#include "php_snuffleupagus.h"
2 2
3#define LOG_FEATURE "wrappers_whitelist"
4
3static bool wrapper_is_whitelisted(const zend_string *const zs) { 5static bool wrapper_is_whitelisted(const zend_string *const zs) {
4 const sp_list_node *list = SPCFG(wrapper).whitelist; 6 const sp_list_node *list = SPCFG(wrapper).whitelist;
5 7
@@ -16,6 +18,131 @@ static bool wrapper_is_whitelisted(const zend_string *const zs) {
16 return false; 18 return false;
17} 19}
18 20
21static bool sp_php_stream_is_filtered(void) {
22 const sp_list_node *list = SPCFG(wrapper).php_stream_allowlist;
23
24 return list != NULL;
25}
26
27static bool sp_php_stream_is_whitelisted(const char *const kind) {
28 const sp_list_node *list = SPCFG(wrapper).php_stream_allowlist;
29
30 while (list) {
31 if (!strcasecmp(kind, ZSTR_VAL((const zend_string *)list->data))) {
32 return true;
33 }
34 list = list->next;
35 }
36 return false;
37}
38
39/*
40 * Adopted from
41 * https://github.com/php/php-src/blob/8896bd3200892000d8aaa01595d6c64b926a26f7/ext/standard/php_fopen_wrapper.c#L176
42 */
43static php_stream * sp_php_stream_url_wrap_php(php_stream_wrapper *wrapper,
44 const char *path, const char *mode,
45 int options, zend_string **opened_path,
46 php_stream_context *context STREAMS_DC) {
47 if (!strncasecmp(path, "php://", 6)) {
48 path += 6;
49 }
50
51 if (!strncasecmp(path, "temp", 4)) {
52 if (!sp_php_stream_is_whitelisted("temp")) {
53 sp_log_warn(LOG_FEATURE, "Call to not allowed php stream type \"temp\" dropped");
54 return NULL;
55 }
56 } else if (!strcasecmp(path, "memory")) {
57 if (!sp_php_stream_is_whitelisted("memory")) {
58 sp_log_warn(LOG_FEATURE, "Call to not allowed php stream type \"memory\" dropped");
59 return NULL;
60 }
61 } else if (!strcasecmp(path, "output")) {
62 if (!sp_php_stream_is_whitelisted("output")) {
63 sp_log_warn(LOG_FEATURE, "Call to not allowed php stream type \"output\" dropped");
64 return NULL;
65 }
66 } else if (!strcasecmp(path, "input")) {
67 if (!sp_php_stream_is_whitelisted("input")) {
68 sp_log_warn(LOG_FEATURE, "Call to not allowed php stream type \"input\" dropped");
69 return NULL;
70 }
71 } else if (!strcasecmp(path, "stdin")) {
72 if (!sp_php_stream_is_whitelisted("stdin")) {
73 sp_log_warn(LOG_FEATURE, "Call to not allowed php stream type \"stdin\" dropped");
74 return NULL;
75 }
76 } else if (!strcasecmp(path, "stdout")) {
77 if (!sp_php_stream_is_whitelisted("stdout")) {
78 sp_log_warn(LOG_FEATURE, "Call to not allowed php stream type \"stdout\" dropped");
79 return NULL;
80 }
81 } else if (!strcasecmp(path, "stderr")) {
82 if (!sp_php_stream_is_whitelisted("stderr")) {
83 sp_log_warn(LOG_FEATURE, "Call to not allowed php stream type \"stderr\" dropped");
84 return NULL;
85 }
86 } else if (!strncasecmp(path, "fd/", 3)) {
87 if (!sp_php_stream_is_whitelisted("fd")) {
88 sp_log_warn(LOG_FEATURE, "Call to not allowed php stream type \"fd\" dropped");
89 return NULL;
90 }
91 } else if (!strncasecmp(path, "filter/", 7)) {
92 if (!sp_php_stream_is_whitelisted("filter")) {
93 sp_log_warn(LOG_FEATURE, "Call to not allowed php stream type \"filter\" dropped");
94 return NULL;
95 }
96 } else {
97 sp_log_warn(LOG_FEATURE, "Call to unknown php stream type dropped");
98 return NULL;
99 }
100
101 extern PHPAPI const php_stream_wrapper php_stream_php_wrapper;
102
103 return php_stream_php_wrapper.wops->stream_opener(wrapper, path, mode, options, opened_path, context STREAMS_DC);
104}
105
106/*
107 * Adopted from
108 * https://github.com/php/php-src/blob/8896bd3200892000d8aaa01595d6c64b926a26f7/ext/standard/php_fopen_wrapper.c#L428-L446
109 */
110static const php_stream_wrapper_ops sp_php_stdio_wops = {
111 sp_php_stream_url_wrap_php,
112 NULL, /* close */
113 NULL, /* fstat */
114 NULL, /* stat */
115 NULL, /* opendir */
116 "PHP",
117 NULL, /* unlink */
118 NULL, /* rename */
119 NULL, /* mkdir */
120 NULL, /* rmdir */
121 NULL
122};
123static const php_stream_wrapper sp_php_stream_php_wrapper = {
124 &sp_php_stdio_wops,
125 NULL,
126 0, /* is_url */
127};
128
129static void sp_reregister_php_wrapper(void) {
130 if (!sp_php_stream_is_filtered()) {
131 return;
132 }
133
134 if (php_unregister_url_stream_wrapper("php") != SUCCESS) {
135 sp_log_warn(LOG_FEATURE, "Failed to unregister stream wrapper \"php\"");
136 return;
137 }
138
139 if (php_register_url_stream_wrapper("php", &sp_php_stream_php_wrapper) != SUCCESS) {
140 sp_log_warn(LOG_FEATURE, "Failed to register custom stream wrapper \"php\"");
141 }
142
143 sp_log_debug(LOG_FEATURE, "Stream \"php\" successfully re-registered");
144}
145
19void sp_disable_wrapper() { 146void sp_disable_wrapper() {
20 HashTable *orig = php_stream_get_url_stream_wrappers_hash(); 147 HashTable *orig = php_stream_get_url_stream_wrappers_hash();
21 HashTable *orig_complete = pemalloc(sizeof(HashTable), 1); 148 HashTable *orig_complete = pemalloc(sizeof(HashTable), 1);
@@ -50,6 +177,12 @@ PHP_FUNCTION(sp_stream_wrapper_register) {
50 zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "S*", &protocol_name, &params, &param_count); 177 zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "S*", &protocol_name, &params, &param_count);
51 // ignore proper arguments here and just let the original handler deal with it 178 // ignore proper arguments here and just let the original handler deal with it
52 if (!protocol_name || wrapper_is_whitelisted(protocol_name)) { 179 if (!protocol_name || wrapper_is_whitelisted(protocol_name)) {
180
181 // reject manual loading of "php" wrapper
182 if (!strcasecmp(ZSTR_VAL(protocol_name), "php") && sp_php_stream_is_filtered()) {
183 return;
184 }
185
53 orig_handler = zend_hash_str_find_ptr(SPG(sp_internal_functions_hook), ZEND_STRL("stream_wrapper_register")); 186 orig_handler = zend_hash_str_find_ptr(SPG(sp_internal_functions_hook), ZEND_STRL("stream_wrapper_register"));
54 orig_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU); 187 orig_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU);
55 } 188 }
@@ -61,5 +194,7 @@ int hook_stream_wrappers() {
61 HOOK_FUNCTION("stream_wrapper_register", sp_internal_functions_hook, 194 HOOK_FUNCTION("stream_wrapper_register", sp_internal_functions_hook,
62 PHP_FN(sp_stream_wrapper_register)); 195 PHP_FN(sp_stream_wrapper_register));
63 196
197 sp_reregister_php_wrapper();
198
64 return SUCCESS; 199 return SUCCESS;
65} 200}
diff --git a/src/tests/stream_wrapper/config/config_stream_wrapper_php.ini b/src/tests/stream_wrapper/config/config_stream_wrapper_php.ini
new file mode 100644
index 0000000..bec516c
--- /dev/null
+++ b/src/tests/stream_wrapper/config/config_stream_wrapper_php.ini
@@ -0,0 +1,2 @@
1sp.wrappers_whitelist.list("php");
2sp.wrappers_whitelist.php_list("stdin,stderr,stdout");
diff --git a/src/tests/stream_wrapper/stream_wrapper_php.phpt b/src/tests/stream_wrapper/stream_wrapper_php.phpt
new file mode 100644
index 0000000..c82d2f6
--- /dev/null
+++ b/src/tests/stream_wrapper/stream_wrapper_php.phpt
@@ -0,0 +1,76 @@
1--TEST--
2Stream wrapper (php)
3--SKIPIF--
4<?php
5if (!extension_loaded("snuffleupagus")) print "skip snuffleupagus extension missing";
6?>
7--INI--
8sp.configuration_file={PWD}/config/config_stream_wrapper_php.ini
9--FILE--
10<?php
11echo file_get_contents('php://input');
12file_put_contents('php://output', "Hello from stdout\n");
13file_put_contents('php://stderr', "Hello from stderr #1\n");
14file_put_contents('php://memory', "Bye from memory\n");
15echo file_get_contents('php://memory');
16file_put_contents('php://temp', "Bye from temp\n");
17echo file_get_contents('php://temp');
18
19file_put_contents('php://stderr', "Hello from stderr #2\n");
20
21file_put_contents('php://filter/write=string.toupper/resource=output.tmp', "Hello from stdout filtered\n");
22echo file_get_contents('php://filter/read=string.toupper/resource=output.tmp');
23
24$foo = stream_wrapper_unregister("php");
25fwrite(STDERR, $foo);
26file_put_contents('php://stderr', "Hello from stderr #3\n");
27
28stream_wrapper_restore("php");
29file_put_contents('php://stderr', "Hello from stderr #4\n");
30file_put_contents('php://memory', "Bye from memory\n");
31?>
32--EXPECTF--
33Warning: [snuffleupagus][0.0.0.0][wrappers_whitelist][log] Call to not allowed php stream type "input" dropped in %a/stream_wrapper_php.php on line 2
34
35Warning: file_get_contents(php://input): %s to open stream: operation failed in %a/stream_wrapper_php.php on line 2
36
37Warning: [snuffleupagus][0.0.0.0][wrappers_whitelist][log] Call to not allowed php stream type "output" dropped in %a/stream_wrapper_php.php on line 3
38
39Warning: file_put_contents(php://output): %s to open stream: operation failed in %a/stream_wrapper_php.php on line 3
40Hello from stderr #1
41
42Warning: [snuffleupagus][0.0.0.0][wrappers_whitelist][log] Call to not allowed php stream type "memory" dropped in %a/stream_wrapper_php.php on line 5
43
44Warning: file_put_contents(php://memory): %s to open stream: operation failed in %a/stream_wrapper_php.php on line 5
45
46Warning: [snuffleupagus][0.0.0.0][wrappers_whitelist][log] Call to not allowed php stream type "memory" dropped in %a/stream_wrapper_php.php on line 6
47
48Warning: file_get_contents(php://memory): %s to open stream: operation failed in %a/stream_wrapper_php.php on line 6
49
50Warning: [snuffleupagus][0.0.0.0][wrappers_whitelist][log] Call to not allowed php stream type "temp" dropped in %a/stream_wrapper_php.php on line 7
51
52Warning: file_put_contents(php://temp): %s to open stream: operation failed in %a/stream_wrapper_php.php on line 7
53
54Warning: [snuffleupagus][0.0.0.0][wrappers_whitelist][log] Call to not allowed php stream type "temp" dropped in %a/stream_wrapper_php.php on line 8
55
56Warning: file_get_contents(php://temp): %s to open stream: operation failed in %a/stream_wrapper_php.php on line 8
57Hello from stderr #2
58
59Warning: [snuffleupagus][0.0.0.0][wrappers_whitelist][log] Call to not allowed php stream type "filter" dropped in %a/stream_wrapper_php.php on line 12
60
61Warning: file_put_contents(php://filter/write=string.toupper/resource=output.tmp): %s to open stream: operation failed in %a/stream_wrapper_php.php on line 12
62
63Warning: [snuffleupagus][0.0.0.0][wrappers_whitelist][log] Call to not allowed php stream type "filter" dropped in %a/stream_wrapper_php.php on line 13
64
65Warning: file_get_contents(php://filter/read=string.toupper/resource=output.tmp): %s to open stream: operation failed in %a/stream_wrapper_php.php on line 13
661
67Warning: file_put_contents(): Unable to find the wrapper "php" - did you forget to enable it when you configured PHP? in %a/stream_wrapper_php.php on line 17
68
69Warning: file_put_contents(): file:// wrapper is disabled in the server configuration in %a/stream_wrapper_php.php on line 17
70
71Warning: file_put_contents(php://stderr): %s to open stream: no suitable wrapper could be found in %a/stream_wrapper_php.php on line 17
72Hello from stderr #4
73
74Warning: [snuffleupagus][0.0.0.0][wrappers_whitelist][log] Call to not allowed php stream type "memory" dropped in %a/stream_wrapper_php.php on line 21
75
76Warning: file_put_contents(php://memory): %s to open stream: operation failed in %a/stream_wrapper_php.php on line 21