diff options
| author | Christian Göttsche | 2024-05-27 21:33:00 +0200 |
|---|---|---|
| committer | jvoisin | 2024-06-09 17:16:16 +0200 |
| commit | c7ce5c3528e8da8762e6e7067001549e109397ba (patch) | |
| tree | 4c9606730af25a8f893193b7cc5cb718a20c3f35 /src/sp_wrapper.c | |
| parent | 849252c6a48b428dde3ad8930b40a2bdf9874cb7 (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 'src/sp_wrapper.c')
| -rw-r--r-- | src/sp_wrapper.c | 135 |
1 files changed, 135 insertions, 0 deletions
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 | } |
