summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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