diff --git a/NEWS b/NEWS index 08b326be82b7b..176a1b13ae4d1 100644 --- a/NEWS +++ b/NEWS @@ -57,6 +57,8 @@ PHP NEWS - Fixed bug #40998 (long session array keys are truncated). (Tony) - Implement feature request #40947, allow a single filter as argument for filter_var_array (Pierre) +- Fixed bug #40931 (open_basedir bypass via symlink and move_uploaded_file()). + (Tony) - Fixed bug #40921 (php_default_post_reader crashes when post_max_size is exceeded). (trickie at gmail dot com, Ilia) - Fixed bug #40915 (addcslashes unexpected behavior with binary input). (Tony) diff --git a/main/fopen_wrappers.c b/main/fopen_wrappers.c index ebcdb711fa78e..0b476cf5e47f5 100644 --- a/main/fopen_wrappers.c +++ b/main/fopen_wrappers.c @@ -90,8 +90,12 @@ PHPAPI int php_check_specific_open_basedir(const char *basedir, const char *path char resolved_name[MAXPATHLEN]; char resolved_basedir[MAXPATHLEN]; char local_open_basedir[MAXPATHLEN]; + char path_tmp[MAXPATHLEN]; + char *path_file; int resolved_basedir_len; int resolved_name_len; + int path_len; + int nesting_level = 0; /* Special case basedir==".": Use script-directory */ if (strcmp(basedir, ".") || !VCWD_GETCWD(local_open_basedir, MAXPATHLEN)) { @@ -99,8 +103,64 @@ PHPAPI int php_check_specific_open_basedir(const char *basedir, const char *path strlcpy(local_open_basedir, basedir, sizeof(local_open_basedir)); } - /* Resolve the real path into resolved_name */ - if ((expand_filepath(path, resolved_name TSRMLS_CC) != NULL) && (expand_filepath(local_open_basedir, resolved_basedir TSRMLS_CC) != NULL)) { + path_len = strlen(path); + if (path_len > (MAXPATHLEN - 1)) { + /* empty and too long paths are invalid */ + return -1; + } + + /* normalize and expand path */ + if (expand_filepath(path, resolved_name TSRMLS_CC) == NULL) { + return -1; + } + + path_len = strlen(resolved_name); + memcpy(path_tmp, resolved_name, path_len + 1); /* safe */ + + while (VCWD_REALPATH(path_tmp, resolved_name) == NULL) { +#ifdef HAVE_SYMLINK + if (nesting_level == 0) { + int ret; + char buf[MAXPATHLEN]; + + ret = readlink(path_tmp, buf, MAXPATHLEN - 1); + if (ret < 0) { + /* not a broken symlink, move along.. */ + } else { + /* put the real path into the path buffer */ + memcpy(path_tmp, buf, ret); + path_tmp[ret] = '\0'; + } + } +#endif + +#if defined(PHP_WIN32) || defined(NETWARE) + path_file = strrchr(path_tmp, DEFAULT_SLASH); + if (!path_file) { + path_file = strrchr(path_tmp, '/'); + } +#else + path_file = strrchr(path_tmp, DEFAULT_SLASH); +#endif + if (!path_file) { + /* none of the path components exist. definitely not in open_basedir.. */ + return -1; + } else { + path_len = path_file - path_tmp + 1; +#if defined(PHP_WIN32) || defined(NETWARE) + if (path_len > 1 && path_tmp[path_len - 2] == ':') { + /* this is c:\, */ + path_tmp[path_len] = '\0'; + } +#else + path_tmp[path_len - 1] = '\0'; +#endif + } + nesting_level++; + } + + /* Resolve open_basedir to resolved_basedir */ + if (expand_filepath(local_open_basedir, resolved_basedir TSRMLS_CC) != NULL) { /* Handler for basedirs that end with a / */ resolved_basedir_len = strlen(resolved_basedir); if (basedir[strlen(basedir) - 1] == PHP_DIR_SEPARATOR) { @@ -110,7 +170,7 @@ PHPAPI int php_check_specific_open_basedir(const char *basedir, const char *path } } - if (path[strlen(path)-1] == PHP_DIR_SEPARATOR) { + if (path_tmp[path_len - 1] == PHP_DIR_SEPARATOR) { resolved_name_len = strlen(resolved_name); if (resolved_name[resolved_name_len - 1] != PHP_DIR_SEPARATOR) { resolved_name[resolved_name_len] = PHP_DIR_SEPARATOR;