]> granicus.if.org Git - php/commitdiff
MFB: fix #40931 (open_basedir bypass via symlink and move_uploaded_file())
authorAntony Dovgal <tony2001@php.net>
Tue, 10 Apr 2007 22:31:27 +0000 (22:31 +0000)
committerAntony Dovgal <tony2001@php.net>
Tue, 10 Apr 2007 22:31:27 +0000 (22:31 +0000)
main/fopen_wrappers.c

index 93020f78d386693cf8ef5203a21981abe7d0077e..843246e7eb53f46ba47e54b9830adc950edb62e4 100644 (file)
@@ -147,8 +147,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)) {
@@ -156,8 +160,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) {
@@ -167,7 +227,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;