diff options
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 | } |
