bin/52686: Problem with realpath(3) when traversing /

Uwe Doering gemini at geminix.org
Mon May 26 02:00:30 PDT 2003


>Number:         52686
>Category:       bin
>Synopsis:       Problem with realpath(3) when traversing /
>Confidential:   no
>Severity:       serious
>Priority:       high
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Mon May 26 02:00:28 PDT 2003
>Closed-Date:
>Last-Modified:
>Originator:     Uwe Doering
>Release:        FreeBSD 4.5-RELEASE i386
>Organization:
EscapeBox - Managed On-Demand UNIX Servers
		http://www.escapebox.net
>Environment:
System: FreeBSD geminix.private.geminix.org 4.5-RELEASE FreeBSD 4.5-RELEASE #2: Fri Mar 21 13:43:46 MET 2003 root at geminix.private.geminix.org:/usr/src/sys/compile/GEMINIX i386

libc/libc_r updated with 'src/lib/libc/stdlib/realpath.c' revision 1.9.2.1
(as MFC'ed to RELENG_4 a couple of days ago).

>Description:
The new impementation of 'realpath.c' (thread-safe) contains a bug that
causes it to return an empty string occasionally.  When truncating the
path string due to '..' components in the path given as argument it
removes not only the rightmost component but also the slash preceding
this component.

Normally, this slash will be re-appended with the next loop iteration,
but in case of only one component left the string length drops to zero,
which is clearly an illegal condition given the way the rest of the code
is written.  With the next loop iteration the code that is supposed to
re-append the trailing slash checks the path string buffer with an index
of -1.  Now, depending on whether it by chance finds a slash there it
won't append another slash and therefore leaves the string empty.

Here's the code fragment causing 'resolved_len' to become zero (line 113):

                else if (strcmp(next_token, "..") == 0) {
                        /*
                         * Strip the last path component except when we have
                         * single "/"
                         */
                        if (resolved_len > 1) {
                                resolved[resolved_len - 1] = '\0';
                                q = strrchr(resolved, '/');
                                *q = '\0';
                                resolved_len = q - resolved;
                        }
                        continue;
 
And this is where it can break with the next iteration step (line 101):

                if (resolved[resolved_len - 1] != '/') {
                        if (resolved_len + 1 >= PATH_MAX) {
                                errno = ENAMETOOLONG;
                                return (NULL);
                        }
                        resolved[resolved_len++] = '/';
                        resolved[resolved_len] = '\0';
                }

>How-To-Repeat:
With a version of libc/libc_r containing the new implementation of
realpath(3) and a version of realpath(1) built with these libs (linked
statically!), try

    cd /usr/src
    realpath ../..

In case there is (by chance) a slash at the -1 index position of the
'resolved' string buffer this sequence will return an empty string
instead of a single slash.

>Fix:
Code that removes the rightmost component of the path string exists in
two locations, that outlined above and also in case we follow a relative
symlink (line 156).  The fix is to leave the slash preceding the component
to be removed intact.  This doesn't hurt since it otherwise gets
re-appended with the next loop iteration, anyway.  The difference is that
it does the right thing in case only one component is left.

Here is the patch I suggest.  It fixes the problem for me.

----------------------- cut here ----------------------
--- src/lib/libc/stdlib/realpath.c.orig	Sat May 24 15:20:37 2003
+++ src/lib/libc/stdlib/realpath.c	Sat May 24 15:20:53 2003
@@ -117,7 +117,7 @@
 			 */
 			if (resolved_len > 1) {
 				resolved[resolved_len - 1] = '\0';
-				q = strrchr(resolved, '/');
+				q = strrchr(resolved, '/') + 1;
 				*q = '\0';
 				resolved_len = q - resolved;
 			}
@@ -156,7 +156,7 @@
 			} else if (resolved_len > 1) {
 				/* Strip the last path component. */
 				resolved[resolved_len - 1] = '\0';
-				q = strrchr(resolved, '/');
+				q = strrchr(resolved, '/') + 1;
 				*q = '\0';
 				resolved_len = q - resolved;
 			}
----------------------- cut here ----------------------

>Release-Note:
>Audit-Trail:
>Unformatted:


More information about the freebsd-bugs mailing list