diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/snuffleupagus.c | 3 | ||||
| -rw-r--r-- | src/sp_config.h | 2 | ||||
| -rw-r--r-- | src/sp_config_keywords.c | 1 | ||||
| -rw-r--r-- | src/sp_wrapper.c | 135 | ||||
| -rw-r--r-- | src/tests/stream_wrapper/config/config_stream_wrapper_php.ini | 2 | ||||
| -rw-r--r-- | src/tests/stream_wrapper/stream_wrapper_php.phpt | 76 |
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 | ||
| 71 | typedef struct { | 71 | typedef 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 | |||
| 3 | static bool wrapper_is_whitelisted(const zend_string *const zs) { | 5 | static 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 | ||
| 21 | static 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 | |||
| 27 | static 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 | */ | ||
| 43 | static 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 | */ | ||
| 110 | static 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 | }; | ||
| 123 | static const php_stream_wrapper sp_php_stream_php_wrapper = { | ||
| 124 | &sp_php_stdio_wops, | ||
| 125 | NULL, | ||
| 126 | 0, /* is_url */ | ||
| 127 | }; | ||
| 128 | |||
| 129 | static 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 | |||
| 19 | void sp_disable_wrapper() { | 146 | void 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, ¶ms, ¶m_count); | 177 | zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "S*", &protocol_name, ¶ms, ¶m_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 @@ | |||
| 1 | sp.wrappers_whitelist.list("php"); | ||
| 2 | sp.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-- | ||
| 2 | Stream wrapper (php) | ||
| 3 | --SKIPIF-- | ||
| 4 | <?php | ||
| 5 | if (!extension_loaded("snuffleupagus")) print "skip snuffleupagus extension missing"; | ||
| 6 | ?> | ||
| 7 | --INI-- | ||
| 8 | sp.configuration_file={PWD}/config/config_stream_wrapper_php.ini | ||
| 9 | --FILE-- | ||
| 10 | <?php | ||
| 11 | echo file_get_contents('php://input'); | ||
| 12 | file_put_contents('php://output', "Hello from stdout\n"); | ||
| 13 | file_put_contents('php://stderr', "Hello from stderr #1\n"); | ||
| 14 | file_put_contents('php://memory', "Bye from memory\n"); | ||
| 15 | echo file_get_contents('php://memory'); | ||
| 16 | file_put_contents('php://temp', "Bye from temp\n"); | ||
| 17 | echo file_get_contents('php://temp'); | ||
| 18 | |||
| 19 | file_put_contents('php://stderr', "Hello from stderr #2\n"); | ||
| 20 | |||
| 21 | file_put_contents('php://filter/write=string.toupper/resource=output.tmp', "Hello from stdout filtered\n"); | ||
| 22 | echo file_get_contents('php://filter/read=string.toupper/resource=output.tmp'); | ||
| 23 | |||
| 24 | $foo = stream_wrapper_unregister("php"); | ||
| 25 | fwrite(STDERR, $foo); | ||
| 26 | file_put_contents('php://stderr', "Hello from stderr #3\n"); | ||
| 27 | |||
| 28 | stream_wrapper_restore("php"); | ||
| 29 | file_put_contents('php://stderr', "Hello from stderr #4\n"); | ||
| 30 | file_put_contents('php://memory', "Bye from memory\n"); | ||
| 31 | ?> | ||
| 32 | --EXPECTF-- | ||
| 33 | Warning: [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 | |||
| 35 | Warning: file_get_contents(php://input): %s to open stream: operation failed in %a/stream_wrapper_php.php on line 2 | ||
| 36 | |||
| 37 | Warning: [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 | |||
| 39 | Warning: file_put_contents(php://output): %s to open stream: operation failed in %a/stream_wrapper_php.php on line 3 | ||
| 40 | Hello from stderr #1 | ||
| 41 | |||
| 42 | Warning: [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 | |||
| 44 | Warning: file_put_contents(php://memory): %s to open stream: operation failed in %a/stream_wrapper_php.php on line 5 | ||
| 45 | |||
| 46 | Warning: [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 | |||
| 48 | Warning: file_get_contents(php://memory): %s to open stream: operation failed in %a/stream_wrapper_php.php on line 6 | ||
| 49 | |||
| 50 | Warning: [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 | |||
| 52 | Warning: file_put_contents(php://temp): %s to open stream: operation failed in %a/stream_wrapper_php.php on line 7 | ||
| 53 | |||
| 54 | Warning: [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 | |||
| 56 | Warning: file_get_contents(php://temp): %s to open stream: operation failed in %a/stream_wrapper_php.php on line 8 | ||
| 57 | Hello from stderr #2 | ||
| 58 | |||
| 59 | Warning: [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 | |||
| 61 | Warning: 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 | |||
| 63 | Warning: [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 | |||
| 65 | Warning: 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 | ||
| 66 | 1 | ||
| 67 | Warning: 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 | |||
| 69 | Warning: file_put_contents(): file:// wrapper is disabled in the server configuration in %a/stream_wrapper_php.php on line 17 | ||
| 70 | |||
| 71 | Warning: file_put_contents(php://stderr): %s to open stream: no suitable wrapper could be found in %a/stream_wrapper_php.php on line 17 | ||
| 72 | Hello from stderr #4 | ||
| 73 | |||
| 74 | Warning: [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 | |||
| 76 | Warning: file_put_contents(php://memory): %s to open stream: operation failed in %a/stream_wrapper_php.php on line 21 | ||
