Marcus Boerger wrote:
> Hello Gregory,
> 
> Tuesday, March 25, 2008, 8:01:56 PM, you wrote:
> 
>> Stanislav Malyshev wrote:
>>>> stream wrapper.  Here is an example:
>>>>
>>>> oops.broken://UNC/path
>>> I wonder if .://UNC/path is treated as "."+//UNC/path (and the same
>>> for ..). It should anyway :) However I'm not too worried without
>>> pathes like foo.bar - not likely to have path without any slashes
>>> unless it's . or .., and if you do, you always can say ./foo.bar
>>>
>> That's a great question.  In attempting to answer, I think I may have
>> unfortunately found a severe flaw in the patch, allowing reading past
>> the end of the filename and the include_path.
> 
>> If we pass a file named "hello:" to php_resolve_path, this code:
> 
>> if ((*p == ':') && (p - filename > 1) && (p[1] == '/') && (p[2] == '/')) {
> 
>> would look past the end of the filename by 1 (p[1] = '\0', but p[2] is
>> an off by one read).  Instead, we should be checking to see if p -
>> filename < filename_length - 2 so we have enough room for the stream
>> wrapper and 1 character (i.e. blah://a, not blah://).
> 
> How so?
> 
> Just reorder and put (p - filename > 1 first)
<snip>

Hi Marcus,

It looks like I wasn't clear - the problem is not checking characters
before the :, but after.  With the existing code, if the string ended
with ":" the code would attempt the check ":" +1 and ":" + 2, which
could result in out-of-bounds read (read after the terminating '\0'.
However, in answer to your other question, we do need stream wrappers
longer than 1 character, as we would otherwise could be unnecessarily
checking drive letters on windows thinking they're stream wrappers.


However, I took your excellent suggestions into account in the attached
patch, which fully works and prevents use of either . or .. as stream
wrapper names.
Otherwise, its identical to wrapper6.patch.txt

One last check by Dmitry before commit and we should be good to go.
Thanks for the catch on the logical problem Stas and the coding
suggestions Marcus - this is the kind of teamwork that I dream of.  The
open source gods must be looking down on us kindly.

Greg
Index: main/fopen_wrappers.c
===================================================================
RCS file: /repository/php-src/main/fopen_wrappers.c,v
retrieving revision 1.175.2.3.2.13.2.9
diff -u -r1.175.2.3.2.13.2.9 fopen_wrappers.c
--- main/fopen_wrappers.c       24 Mar 2008 09:30:41 -0000      
1.175.2.3.2.13.2.9
+++ main/fopen_wrappers.c       26 Mar 2008 03:43:28 -0000
@@ -447,14 +447,24 @@
        char resolved_path[MAXPATHLEN];
        char trypath[MAXPATHLEN];
        const char *ptr, *end, *p;
+       char *actual_path;
+       php_stream_wrapper *wrapper;
+       int path_len = strlen(path);
 
        if (!filename) {
                return NULL;
        }
 
-       /* Don't resolve paths which contain protocol */
+       /* Don't resolve paths which contain protocol (except of file://) */
        for (p = filename; isalnum((int)*p) || *p == '+' || *p == '-' || *p == 
'.'; p++);
-       if ((*p == ':') && (p - filename > 1) && (p[1] == '/') && (p[2] == 
'/')) {
+       /* checking for enough length after p to ensure we don't read past the 
end of filename */
+       if ((*p == ':') && (filename_length - (p - filename) > 2) && (p[1] == 
'/') && (p[2] == '/')) {
+               wrapper = php_stream_locate_url_wrapper(filename, &actual_path, 
STREAM_OPEN_FOR_INCLUDE TSRMLS_CC);                     
+               if (wrapper == &php_plain_files_wrapper) {
+                       if (tsrm_realpath(actual_path, resolved_path 
TSRMLS_CC)) {
+                               return estrdup(resolved_path);
+                       }
+               }
                return NULL;
        }
 
@@ -473,7 +483,19 @@
 
        ptr = path;
        while (ptr && *ptr) {
-               end = strchr(ptr, DEFAULT_DIR_SEPARATOR);
+               /* Check for stream wrapper */
+               int is_stream_wrapper = 0;
+
+               for (p = ptr; isalnum((int)*p) || *p == '+' || *p == '-' || *p 
== '.'; p++);
+               /* p - ptr > 1 allows us to skip things like "C:\whatever" */
+               if ((*p == ':') && (p - ptr > 1) && (path_len - (p - path) > 2) 
&& (p[1] == '/') && (p[2] == '/')) {
+                       /* .:// or ..:// is not a stream wrapper */
+                       if (p[-1] != '.' || p[-2] != '.' || p - 2 != ptr) {
+                               p += 3;
+                               is_stream_wrapper = 1;
+                       }
+               }
+               end = strchr(p, DEFAULT_DIR_SEPARATOR);
                if (end) {
                        if ((end-ptr) + 1 + filename_length + 1 >= MAXPATHLEN) {
                                ptr = end + 1;
@@ -494,7 +516,23 @@
                        memcpy(trypath+len+1, filename, filename_length+1);
                        ptr = NULL;
                }
-               if (tsrm_realpath(trypath, resolved_path TSRMLS_CC)) {
+               actual_path = trypath;
+               if (is_stream_wrapper) {
+                       wrapper = php_stream_locate_url_wrapper(trypath, 
&actual_path, STREAM_OPEN_FOR_INCLUDE TSRMLS_CC);                      
+                       if (!wrapper) {
+                               continue;
+                       } else if (wrapper != &php_plain_files_wrapper) {
+                               if (wrapper->wops->url_stat) {
+                                       php_stream_statbuf ssb;
+
+                                       if (SUCCESS == 
wrapper->wops->url_stat(wrapper, trypath, 0, &ssb, NULL TSRMLS_CC)) {
+                                               return estrdup(trypath);
+                                       }
+                               }
+                               continue;
+                       }
+               }
+               if (tsrm_realpath(actual_path, resolved_path TSRMLS_CC)) {
                        return estrdup(resolved_path);
                }
        } /* end provided path */
@@ -511,7 +549,27 @@
                    exec_fname_length + 1 + filename_length + 1 < MAXPATHLEN) {
                        memcpy(trypath, exec_fname, exec_fname_length + 1);
                        memcpy(trypath+exec_fname_length + 1, filename, 
filename_length+1);
-                       if (tsrm_realpath(trypath, resolved_path TSRMLS_CC)) {
+                       actual_path = trypath;
+
+                       /* Check for stream wrapper */
+                       for (p = trypath; isalnum((int)*p) || *p == '+' || *p 
== '-' || *p == '.'; p++);
+                       if ((*p == ':') && (p - trypath > 1) && (p[1] == '/') 
&& (p[2] == '/')) {
+                               wrapper = 
php_stream_locate_url_wrapper(trypath, &actual_path, STREAM_OPEN_FOR_INCLUDE 
TSRMLS_CC);                      
+                               if (!wrapper) {
+                                       return NULL;
+                               } else if (wrapper != &php_plain_files_wrapper) 
{
+                                       if (wrapper->wops->url_stat) {
+                                               php_stream_statbuf ssb;
+
+                                               if (SUCCESS == 
wrapper->wops->url_stat(wrapper, trypath, 0, &ssb, NULL TSRMLS_CC)) {
+                                                       return estrdup(trypath);
+                                               }
+                                       }
+                                       return NULL;
+                               }
+                       }
+
+                       if (tsrm_realpath(actual_path, resolved_path 
TSRMLS_CC)) {
                                return estrdup(resolved_path);
                        }
                }
Index: main/php_streams.h
===================================================================
RCS file: /repository/php-src/main/php_streams.h,v
retrieving revision 1.103.2.1.2.4.2.2
diff -u -r1.103.2.1.2.4.2.2 php_streams.h
--- main/php_streams.h  31 Dec 2007 07:17:17 -0000      1.103.2.1.2.4.2.2
+++ main/php_streams.h  26 Mar 2008 03:43:28 -0000
@@ -511,6 +511,9 @@
 /* don't check allow_url_fopen and allow_url_include */
 #define STREAM_DISABLE_URL_PROTECTION   0x00002000
 
+/* assume the path passed in exists and is fully expanded, avoiding syscalls */
+#define STREAM_ASSUME_REALPATH          0x00004000
+
 /* Antique - no longer has meaning */
 #define IGNORE_URL_WIN 0
 
Index: main/streams/plain_wrapper.c
===================================================================
RCS file: /repository/php-src/main/streams/plain_wrapper.c,v
retrieving revision 1.52.2.6.2.23.2.5
diff -u -r1.52.2.6.2.23.2.5 plain_wrapper.c
--- main/streams/plain_wrapper.c        31 Dec 2007 07:17:17 -0000      
1.52.2.6.2.23.2.5
+++ main/streams/plain_wrapper.c        26 Mar 2008 03:43:29 -0000
@@ -892,9 +892,13 @@
                }
                return NULL;
        }
-       
-       if ((realpath = expand_filepath(filename, NULL TSRMLS_CC)) == NULL) {
-               return NULL;
+
+       if (options & STREAM_ASSUME_REALPATH) {
+               realpath = estrdup(filename);
+       } else {
+               if ((realpath = expand_filepath(filename, NULL TSRMLS_CC)) == 
NULL) {
+                       return NULL;
+               }
        }
 
        if (persistent) {
Index: main/streams/streams.c
===================================================================
RCS file: /repository/php-src/main/streams/streams.c,v
retrieving revision 1.82.2.6.2.18.2.6
diff -u -r1.82.2.6.2.18.2.6 streams.c
--- main/streams/streams.c      24 Mar 2008 16:28:35 -0000      
1.82.2.6.2.18.2.6
+++ main/streams/streams.c      26 Mar 2008 03:43:30 -0000
@@ -1494,7 +1494,7 @@
        HashTable *wrapper_hash = (FG(stream_wrappers) ? FG(stream_wrappers) : 
&url_stream_wrappers_hash);
        php_stream_wrapper **wrapperpp = NULL;
        const char *p, *protocol = NULL;
-       int n = 0;
+       int n = 0, path_len = strlen(path);
 
        if (path_for_open) {
                *path_for_open = (char*)path;
@@ -1508,7 +1508,16 @@
                n++;
        }
 
-       if ((*p == ':') && (n > 1) && (!strncmp("//", p+1, 2) || 
!memcmp("data", path, 4))) {
+       if ((*p == ':') && (n > 1) && ((path_len - n > 2 && !strncmp("//", p+1, 
2)) || (n == 4 && !memcmp("data", path, 4)))) {
+               /* . and .. are invalid stream wrapper names */
+               if (*path == '.') {
+                       if (n == 1) {
+                               return NULL;
+                       }
+                       if (path[1] == '.' && n == 2) {
+                               return NULL;
+                       }
+               }
                protocol = path;
        } else if (n == 5 && strncasecmp(path, "zlib:", 5) == 0) {
                /* BC with older php scripts and zlib wrapper */
@@ -1754,6 +1763,7 @@
        php_stream_wrapper *wrapper = NULL;
        char *path_to_open;
        int persistent = options & STREAM_OPEN_PERSISTENT;
+       char *resolved_path = NULL;
        char *copy_of_path = NULL;
 
        
@@ -1765,11 +1775,23 @@
                return NULL;
        }
 
-       path_to_open = path;
+       if (options & USE_PATH) {
+               resolved_path = php_resolve_path(path, strlen(path), 
PG(include_path) TSRMLS_CC);
+               if (resolved_path) {
+                       path = resolved_path;
+                       /* we've found this file, don't re-check include_path 
or run realpath */
+                       options |= STREAM_ASSUME_REALPATH;
+                       options &= ~USE_PATH;
+               }
+       }
 
+       path_to_open = path;
        wrapper = php_stream_locate_url_wrapper(path, &path_to_open, options 
TSRMLS_CC);
        if (options & STREAM_USE_URL && (!wrapper || !wrapper->is_url)) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "This function may 
only be used against URLs");
+               if (resolved_path) {
+                       efree(resolved_path);
+               }
                return NULL;
        }
 
@@ -1816,12 +1838,18 @@
                                        (options & STREAM_WILL_CAST)
                                                ? PHP_STREAM_PREFER_STDIO : 
PHP_STREAM_NO_PREFERENCE)) {
                        case PHP_STREAM_UNCHANGED:
+                               if (resolved_path) {
+                                       efree(resolved_path);
+                               }
                                return stream;
                        case PHP_STREAM_RELEASED:
                                if (newstream->orig_path) {
                                        pefree(newstream->orig_path, 
persistent);
                                }
                                newstream->orig_path = pestrdup(path, 
persistent);
+                               if (resolved_path) {
+                                       efree(resolved_path);
+                               }
                                return newstream;
                        default:
                                php_stream_close(stream);
@@ -1860,6 +1888,9 @@
                pefree(copy_of_path, persistent);
        }
 #endif
+       if (resolved_path) {
+               efree(resolved_path);
+       }
        return stream;
 }
 /* }}} */

-- 
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php

Reply via email to