From cc1b19cf0e57b8b288788c592214d7aa6cc2e495 Mon Sep 17 00:00:00 2001 From: Sara Golemon Date: Wed, 24 Jan 2007 21:43:47 +0000 Subject: [PATCH] Expand allow_url_fopen/allow_url_include functionality --- NEWS | 2 + ext/soap/php_http.c | 15 +- ext/soap/php_xml.c | 11 +- ext/standard/php_fopen_wrapper.c | 6 +- main/main.c | 10 +- main/php_globals.h | 4 +- main/php_streams.h | 10 +- main/streams/streams.c | 275 +++++++++++++++++++++++++++++-- main/streams/userspace.c | 2 +- 9 files changed, 301 insertions(+), 34 deletions(-) diff --git a/NEWS b/NEWS index ab89621038..7aef9bdf41 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,8 @@ PHP NEWS - Changed opendir/dir/scandir to use default context when no context argument is passed. (Sara) - Changed open_basedir to allow tightening in runtime contexts. (Sara) +- Changed allow_url_fopen/allow_url_include to allow + per-wrapper enable/disable and runtime tightening. (Sara) - Removed old legacy: . "register_globals" support. (Pierre) diff --git a/ext/soap/php_http.c b/ext/soap/php_http.c index 0f33c2c767..c505894ee8 100644 --- a/ext/soap/php_http.c +++ b/ext/soap/php_http.c @@ -232,7 +232,7 @@ int make_http_soap_request(zval *this_ptr, int content_type_xml = 0; char *content_encoding; char *http_msg = NULL; - zend_bool old_allow_url_fopen; + char *old_allow_url_fopen_list; soap_client_object *client; if (this_ptr == NULL || Z_TYPE_P(this_ptr) != IS_OBJECT) { @@ -317,13 +317,16 @@ try_again: return FALSE; } - old_allow_url_fopen = PG(allow_url_fopen); - zend_alter_ini_entry("allow_url_fopen", sizeof("allow_url_fopen"), "1", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_RUNTIME); + old_allow_url_fopen_list = PG(allow_url_fopen_list); + if (!old_allow_url_fopen_list) { + old_allow_url_fopen_list = ""; + } + zend_alter_ini_entry("allow_url_fopen", sizeof("allow_url_fopen"), "*", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_RUNTIME); if (use_ssl && php_stream_locate_url_wrapper("https://", NULL, STREAM_LOCATE_WRAPPERS_ONLY TSRMLS_CC) == NULL) { php_url_free(phpurl); if (request != buf) {efree(request);} add_soap_fault(this_ptr, "HTTP", "SSL support is not available in this build", NULL, NULL TSRMLS_CC); - zend_alter_ini_entry("allow_url_fopen", sizeof("allow_url_fopen"), old_allow_url_fopen ? "1" : "0", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_RUNTIME); + zend_alter_ini_entry("allow_url_fopen", sizeof("allow_url_fopen"), old_allow_url_fopen_list, strlen(old_allow_url_fopen_list), PHP_INI_SYSTEM, PHP_INI_STAGE_RUNTIME); return FALSE; } @@ -376,11 +379,11 @@ try_again: php_url_free(phpurl); if (request != buf) {efree(request);} add_soap_fault(this_ptr, "HTTP", "Could not connect to host", NULL, NULL TSRMLS_CC); - zend_alter_ini_entry("allow_url_fopen", sizeof("allow_url_fopen"), old_allow_url_fopen ? "1" : "0", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_RUNTIME); + zend_alter_ini_entry("allow_url_fopen", sizeof("allow_url_fopen"), old_allow_url_fopen_list, strlen(old_allow_url_fopen_list), PHP_INI_SYSTEM, PHP_INI_STAGE_RUNTIME); return FALSE; } } - zend_alter_ini_entry("allow_url_fopen", sizeof("allow_url_fopen"), old_allow_url_fopen ? "1" : "0", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_RUNTIME); + zend_alter_ini_entry("allow_url_fopen", sizeof("allow_url_fopen"), old_allow_url_fopen_list, strlen(old_allow_url_fopen_list), PHP_INI_SYSTEM, PHP_INI_STAGE_RUNTIME); if (stream) { if (client->url) { diff --git a/ext/soap/php_xml.c b/ext/soap/php_xml.c index 2e36751c26..c5dbd640de 100644 --- a/ext/soap/php_xml.c +++ b/ext/soap/php_xml.c @@ -80,16 +80,19 @@ xmlDocPtr soap_xmlParseFile(const char *filename TSRMLS_DC) { xmlParserCtxtPtr ctxt = NULL; xmlDocPtr ret; - zend_bool old_allow_url_fopen; + char *old_allow_url_fopen_list; /* xmlInitParser(); */ - old_allow_url_fopen = PG(allow_url_fopen); - zend_alter_ini_entry("allow_url_fopen", sizeof("allow_url_fopen"), "1", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_RUNTIME); + old_allow_url_fopen_list = PG(allow_url_fopen_list); + if (!old_allow_url_fopen_list) { + old_allow_url_fopen_list = ""; + } + zend_alter_ini_entry("allow_url_fopen", sizeof("allow_url_fopen"), "*", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_RUNTIME); ctxt = xmlCreateFileParserCtxt(filename); - zend_alter_ini_entry("allow_url_fopen", sizeof("allow_url_fopen"), old_allow_url_fopen ? "1" : "0", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_RUNTIME); + zend_alter_ini_entry("allow_url_fopen", sizeof("allow_url_fopen"), old_allow_url_fopen_list, strlen(old_allow_url_fopen_list), PHP_INI_SYSTEM, PHP_INI_STAGE_RUNTIME); if (ctxt) { ctxt->keepBlanks = 0; ctxt->sax->ignorableWhitespace = soap_ignorableWhitespace; diff --git a/ext/standard/php_fopen_wrapper.c b/ext/standard/php_fopen_wrapper.c index 1462f4a2e3..ea1861b28f 100644 --- a/ext/standard/php_fopen_wrapper.c +++ b/ext/standard/php_fopen_wrapper.c @@ -187,7 +187,8 @@ php_stream * php_stream_url_wrap_php(php_stream_wrapper *wrapper, char *path, ch } if (!strcasecmp(path, "input")) { - if ((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include) ) { + /* Override default behavior for php://input when used as an include and allow_url_include is being used in BC (off) mode */ + if ((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include_list) ) { if (options & REPORT_ERRORS) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "URL file-access is disabled in the server configuration"); } @@ -197,7 +198,8 @@ php_stream * php_stream_url_wrap_php(php_stream_wrapper *wrapper, char *path, ch } if (!strcasecmp(path, "stdin")) { - if ((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include) ) { + /* Override default behavior for php://stdin when used as an include and allow_url_include is being used in BC (off) mode */ + if ((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include_list) ) { if (options & REPORT_ERRORS) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "URL file-access is disabled in the server configuration"); } diff --git a/main/main.c b/main/main.c index 29b5c618d2..937b2772f9 100644 --- a/main/main.c +++ b/main/main.c @@ -419,8 +419,8 @@ PHP_INI_BEGIN() PHP_INI_ENTRY("disable_functions", "", PHP_INI_SYSTEM, NULL) PHP_INI_ENTRY("disable_classes", "", PHP_INI_SYSTEM, NULL) - STD_PHP_INI_BOOLEAN("allow_url_fopen", "1", PHP_INI_SYSTEM, OnUpdateBool, allow_url_fopen, php_core_globals, core_globals) - STD_PHP_INI_BOOLEAN("allow_url_include", "0", PHP_INI_SYSTEM, OnUpdateBool, allow_url_include, php_core_globals, core_globals) + STD_PHP_INI_BOOLEAN("allow_url_fopen", "1", PHP_INI_ALL, OnUpdateAllowUrl, allow_url_fopen_list, php_core_globals, core_globals) + STD_PHP_INI_BOOLEAN("allow_url_include", "0", PHP_INI_ALL, OnUpdateAllowUrl, allow_url_include_list, php_core_globals, core_globals) STD_PHP_INI_BOOLEAN("always_populate_raw_post_data", "0", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateBool, always_populate_raw_post_data, php_core_globals, core_globals) STD_PHP_INI_ENTRY("realpath_cache_size", "16K", PHP_INI_SYSTEM, OnUpdateLong, realpath_cache_size_limit, virtual_cwd_globals, cwd_globals) STD_PHP_INI_ENTRY("realpath_cache_ttl", "120", PHP_INI_SYSTEM, OnUpdateLong, realpath_cache_ttl, virtual_cwd_globals, cwd_globals) @@ -1509,6 +1509,12 @@ static void core_globals_dtor(php_core_globals *core_globals TSRMLS_DC) if (core_globals->disable_classes) { free(core_globals->disable_classes); } + if (core_globals->allow_url_fopen_list) { + free(core_globals->allow_url_fopen_list); + } + if (core_globals->allow_url_include_list) { + free(core_globals->allow_url_include_list); + } } /* }}} */ diff --git a/main/php_globals.h b/main/php_globals.h index ec341f50f7..65a093478e 100644 --- a/main/php_globals.h +++ b/main/php_globals.h @@ -124,7 +124,8 @@ struct _php_core_globals { zend_bool modules_activated; zend_bool file_uploads; zend_bool during_request_startup; - zend_bool allow_url_fopen; + char *allow_url_fopen_list; + char *allow_url_include_list; zend_bool always_populate_raw_post_data; zend_bool report_zend_debug; @@ -137,7 +138,6 @@ struct _php_core_globals { char *disable_functions; char *disable_classes; - zend_bool allow_url_include; #ifdef PHP_WIN32 zend_bool com_initialized; #endif diff --git a/main/php_streams.h b/main/php_streams.h index e377542314..fd0060dbac 100755 --- a/main/php_streams.h +++ b/main/php_streams.h @@ -21,6 +21,8 @@ #ifndef PHP_STREAMS_H #define PHP_STREAMS_H +#include "php_ini.h" + #ifdef HAVE_SYS_TIME_H #include #endif @@ -165,7 +167,7 @@ typedef struct _php_stream_wrapper_ops { struct _php_stream_wrapper { php_stream_wrapper_ops *wops; /* operations the wrapper can perform */ void *abstract; /* context for the wrapper */ - int is_url; /* so that PG(allow_url_fopen) can be respected */ + int is_url; /* so that PG(allow_url_fopen_list)/PG(allow_url_include_list) can be respected */ /* support for wrappers to return (multiple) error messages to the stream opener */ int err_count; @@ -658,6 +660,11 @@ PHPAPI void php_stream_wrapper_log_error(php_stream_wrapper *wrapper, int option PHPAPI int _php_stream_make_seekable(php_stream *origstream, php_stream **newstream, int flags STREAMS_DC TSRMLS_DC); #define php_stream_make_seekable(origstream, newstream, flags) _php_stream_make_seekable((origstream), (newstream), (flags) STREAMS_CC TSRMLS_CC) +PHP_INI_MH(OnUpdateAllowUrl); +PHPAPI int php_stream_wrapper_is_allowed(const char *wrapper, int wrapper_len, const char *setting TSRMLS_DC); +#define php_stream_allow_url_fopen(wrapper, wrapper_len) php_stream_wrapper_is_allowed((wrapper), (wrapper_len), PG(allow_url_fopen_list) TSRMLS_CC) +#define php_stream_allow_url_include(wrapper, wrapper_len) php_stream_wrapper_is_allowed((wrapper), (wrapper_len), PG(allow_url_include_list) TSRMLS_CC) + /* Give other modules access to the url_stream_wrappers_hash and stream_filters_hash */ PHPAPI HashTable *_php_stream_get_url_stream_wrappers_hash(TSRMLS_D); #define php_stream_get_url_stream_wrappers_hash() _php_stream_get_url_stream_wrappers_hash(TSRMLS_C) @@ -665,6 +672,7 @@ PHPAPI HashTable *php_stream_get_url_stream_wrappers_hash_global(void); PHPAPI HashTable *_php_get_stream_filters_hash(TSRMLS_D); #define php_get_stream_filters_hash() _php_get_stream_filters_hash(TSRMLS_C) PHPAPI HashTable *php_get_stream_filters_hash_global(); +extern php_stream_wrapper_ops *php_stream_user_wrapper_ops; END_EXTERN_C() #endif diff --git a/main/streams/streams.c b/main/streams/streams.c index b521dcae86..13d125f2e5 100755 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -2096,6 +2096,9 @@ PHPAPI php_stream_wrapper *php_stream_locate_url_wrapper(const char *path, char } /* TODO: curl based streams probably support file:// properly */ if (!protocol || !strncasecmp(protocol, "file", n)) { + /* fall back on regular file access */ + php_stream_wrapper *plain_files_wrapper = &php_plain_files_wrapper; + if (protocol) { int localhost = 0; @@ -2132,32 +2135,37 @@ PHPAPI php_stream_wrapper *php_stream_locate_url_wrapper(const char *path, char return NULL; } + /* The file:// wrapper may have been disabled/overridden */ if (FG(stream_wrappers)) { - /* The file:// wrapper may have been disabled/overridden */ - - if (wrapperpp) { - /* It was found so go ahead and provide it */ - return *wrapperpp; - } - - /* Check again, the original check might have not known the protocol name */ - if (zend_hash_find(wrapper_hash, "file", sizeof("file"), (void**)&wrapperpp) == SUCCESS) { - return *wrapperpp; + if (!wrapperpp || zend_hash_find(wrapper_hash, "file", sizeof("file"), (void**)&wrapperpp) == FAILURE) { + if (options & REPORT_ERRORS) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Plainfiles wrapper disabled"); + } + return NULL; } + /* Handles overridden plain files wrapper */ + plain_files_wrapper = *wrapperpp; + } + + if (!php_stream_allow_url_fopen("file", sizeof("file") - 1) || + ((options & STREAM_OPEN_FOR_INCLUDE) && !php_stream_allow_url_include("file", sizeof("file") - 1)) ) { if (options & REPORT_ERRORS) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Plainfiles wrapper disabled"); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "file:// wrapper is disabled in the server configuration"); } return NULL; } - - /* fall back on regular file access */ - return &php_plain_files_wrapper; + + return plain_files_wrapper; } - if ((wrapperpp && (*wrapperpp)->is_url) && (!PG(allow_url_fopen) || ((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include))) ) { + if (!php_stream_allow_url_fopen(protocol, n) || + ((options & STREAM_OPEN_FOR_INCLUDE) && !php_stream_allow_url_include(protocol, n)) ) { if (options & REPORT_ERRORS) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "URL file-access is disabled in the server configuration"); + /* protocol[n] probably isn't '\0' */ + char *protocol_dup = estrndup(protocol, n); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s:// wrapper is disabled in the server configuration", protocol_dup); + efree(protocol_dup); } return NULL; } @@ -2866,6 +2874,241 @@ PHPAPI int _php_stream_path_decode(php_stream_wrapper *wrapper, } /* }}} */ +/* {{{ allow_url_fopen / allow_url_include Handlers */ + +PHPAPI int php_stream_wrapper_is_allowed(const char *wrapper, int wrapper_len, const char *setting TSRMLS_DC) +{ + HashTable *wrapper_hash = (FG(stream_wrappers) ? FG(stream_wrappers) : &url_stream_wrappers_hash); + php_stream_wrapper **wrapperpp; + int setting_len = setting ? strlen(setting) : 0; + const char *s = setting, *e = s + setting_len; + char *wrapper_dup; + + /* BC: allow_url_* == on */ + if (setting_len == 1 && *setting == '*') { + /* "*" means everything is allowed */ + return 1; + } + + if (wrapper_len == (sizeof("zlib") - 1) && strncasecmp("zlib", wrapper, sizeof("zlib") - 1) == 0) { + wrapper = "compress.zlib"; + wrapper_len = sizeof("compress.zlib") - 1; + } + + wrapper_dup = estrndup(wrapper, wrapper_len); + php_strtolower(wrapper_dup, wrapper_len); + if (FAILURE == zend_hash_find(wrapper_hash, wrapper_dup, wrapper_len + 1, (void**)&wrapperpp)) { + /* Wrapper does not exist, assume disallow */ + efree(wrapper_dup); + return 0; + } + efree(wrapper_dup); + + /* BC: allow_url_* == off */ + if (!setting || !setting_len) { + /* NULL or empty indicates that only is_url == 0 wrappers are allowed */ + + if (wrapper_len == (sizeof("file") - 1) && strncasecmp("file", wrapper, sizeof("file") - 1) == 0) { + /* file:// is non-url */ + return 1; + } + + if ((*wrapperpp)->is_url) { + /* is_url types are disabled, but this is an is_url wrapper, disallow */ + return 0; + } + + /* Wrapper is not is_url, allow it */ + return 1; + } + + /* Otherwise, scan list */ + while (s < e) { + const char *p = php_memnstr((char*)s, ":", 1, (char*)e); + + if (!p) { + p = e; + } + + if (wrapper_len == (p - s) && + strncasecmp(s, wrapper, p - s) == 0) { + /* wrapper found in list */ + return 1; + } + + if ((*wrapperpp)->wops == php_stream_user_wrapper_ops && + (sizeof("user") - 1) == (p - s) && + strncasecmp(s, "user", sizeof("user") - 1) == 0) { + /* Wrapper is userspace wrapper and meta-wrapper "user" is enabled */ + return 1; + } + + s = p + 1; + } + + return 0; +} + +/* allow_url_*_list accepts: + * + * 1/on to enable all URL prefixes + * 0/off to disable all is_url=1 wrappers + * A colon delimited list of wrappers to allow (wildcards allowed) + * e.g. file:gzip:compress.*:php + */ +PHP_INI_MH(OnUpdateAllowUrl) +{ +#ifndef ZTS + char *base = (char *) mh_arg2; +#else + char *base = (char *) ts_resource(*((int *) mh_arg2)); +#endif + char **allow = (char **) (base+(size_t) mh_arg1); + + /* BC Enable */ + if ((new_value_length == 1 && *new_value == '1') || + (new_value_length == (sizeof("on") - 1) && strncasecmp(new_value, "on", sizeof("on") - 1) == 0) ) { + + if (*allow && strcmp(*allow, "*") == 0) { + /* Turning on, but that's no change from current, so leave it alone */ + return SUCCESS; + } + + if (stage != PHP_INI_STAGE_STARTUP) { + /* Not already on, and not in SYSTEM context, fail */ + return FAILURE; + } + + /* Otherwise, turn on setting */ + if (*allow) { + free(*allow); + } + + *allow = zend_strndup("*", 1); + + return SUCCESS; + } + + /* BC disable */ + if ((new_value_length == 1 && *new_value == '0') || + (new_value_length == (sizeof("off") - 1) && strncasecmp(new_value, "off", sizeof("off") - 1) == 0) ) { + + /* Always permit shutting off allowurl settings */ + if (*allow) { + free(*allow); + } + *allow = NULL; + + return SUCCESS; + } + + /* Specify as list */ + if (stage == PHP_INI_STAGE_STARTUP) { + /* Always allow new settings in startup stage */ + if (*allow) { + free(*allow); + } + *allow = zend_strndup(new_value, new_value_length); + + return SUCCESS; + } + + /* In PERDIR/RUNTIME context, do more work to ensure we're only tightening the restriction */ + + if (*allow && strcmp(*allow, "*") == 0) { + /* Currently allowing everying, so whatever we set it to will be more restrictive */ + free(*allow); + *allow = zend_strndup(new_value, new_value_length); + + return SUCCESS; + } + + if (!*allow) { + /* Currently allowing anything with is_url == 0 + * So long as this list doesn't contain any is_url == 1, allow it + */ + HashTable *wrapper_hash = (FG(stream_wrappers) ? FG(stream_wrappers) : &url_stream_wrappers_hash); + char *s = new_value, *e = new_value + new_value_length; + + while (s < e) { + php_stream_wrapper **wrapper; + char *p = php_memnstr(s, ":", 1, e); + char *scan; + int scan_len; + + if (!p) { + p = e; + } + + /* file:// is never a URL */ + if ( (p - s) == (sizeof("file") - 1) && strncasecmp(s, "file", sizeof("file") - 1) == 0 ) { + /* file is not a URL */ + s = p + 1; + continue; + } + + if ( (p - s) == (sizeof("zlib") - 1) && strncasecmp(s, "zlib", sizeof("zlib") - 1) == 0 ) { + /* Wastful since we know that compress.zlib is already lower cased, but forgivable */ + scan = estrndup("compress.zlib", sizeof("compress.zlib") - 1); + scan_len = sizeof("compress.zlib") - 1; + } else { + scan = estrndup(s, p - s);; + scan_len = p - s; + php_strtolower(scan, scan_len); + } + + if (FAILURE == zend_hash_find(wrapper_hash, scan, scan_len + 1, (void**) &wrapper)) { + /* Unknown wrapper, not allowed in this context */ + efree(scan); + return FAILURE; + } + efree(scan); + + if ((*wrapper)->is_url) { + /* Disallowed is_url wrapper specified when trying to escape is_url == 0 context */ + return FAILURE; + } + + /* Seems alright so far... */ + s = p+1; + } + + /* All tests passed, allow it */ + *allow = zend_strndup(new_value, new_value_length); + + return SUCCESS; + } + + /* The current allows are restricted to a specific list, + * Make certain that our new list is a subset of that list + */ + { + char *s = new_value, *e = new_value + new_value_length; + + while (s < e) { + char *p = php_memnstr(s, ":", 1, e); + + if (!p) { + p = e; + } + + if (!php_stream_wrapper_is_allowed(s, p - s, *allow TSRMLS_CC)) { + /* Current settings don't allow this wrapper, deny */ + return FAILURE; + } + + s = p + 1; + } + + free(*allow); + *allow = zend_strndup(new_value, new_value_length); + + return SUCCESS; + } +} + +/* }}} */ + /* * Local variables: * tab-width: 4 diff --git a/main/streams/userspace.c b/main/streams/userspace.c index 5aeeb2f47d..a5b1d79fd5 100644 --- a/main/streams/userspace.c +++ b/main/streams/userspace.c @@ -53,7 +53,7 @@ static php_stream_wrapper_ops user_stream_wops = { user_wrapper_mkdir, user_wrapper_rmdir }; - +php_stream_wrapper_ops *php_stream_user_wrapper_ops = &user_stream_wops; static void stream_wrapper_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC) { -- 2.50.1