From gemini@geminix.org  Mon May 26 01:58:18 2003
Return-Path: <gemini@geminix.org>
Received: from mx1.FreeBSD.org (mx1.freebsd.org [216.136.204.125])
	by hub.freebsd.org (Postfix) with ESMTP id CB24637B401
	for <FreeBSD-gnats-submit@freebsd.org>; Mon, 26 May 2003 01:58:18 -0700 (PDT)
Received: from geminix.org (gen129.n001.c02.escapebox.net [213.73.91.129])
	by mx1.FreeBSD.org (Postfix) with ESMTP id 0B26F43F75
	for <FreeBSD-gnats-submit@freebsd.org>; Mon, 26 May 2003 01:58:18 -0700 (PDT)
	(envelope-from gemini@geminix.org)
Received: from gemini by geminix.org with local (Exim 3.36 #1)
	id 19KDo7-0001cb-00
	for FreeBSD-gnats-submit@freebsd.org; Mon, 26 May 2003 10:58:15 +0200
Message-Id: <E19KDo7-0001cb-00@geminix.org>
Date: Mon, 26 May 2003 10:58:15 +0200
From: Uwe Doering <gemini@geminix.org>
Reply-To: Uwe Doering <gemini@geminix.org>
To: FreeBSD-gnats-submit@freebsd.org
Cc:
Subject: Problem with realpath(3) when traversing /
X-Send-Pr-Version: 3.113
X-GNATS-Notify:

>Number:         52686
>Category:       bin
>Synopsis:       Problem with realpath(3) when traversing /
>Confidential:   no
>Severity:       serious
>Priority:       high
>Responsible:    fjoe
>State:          closed
>Quarter:        
>Keywords:       
>Date-Required:  
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Mon May 26 02:00:28 PDT 2003
>Closed-Date:    Mon Jun 02 06:32:18 PDT 2003
>Last-Modified:  Mon Jun 02 06:32:18 PDT 2003
>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@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:
Responsible-Changed-From-To: freebsd-bugs->fjoe 
Responsible-Changed-By: mbr 
Responsible-Changed-When: Mon May 26 11:12:55 PDT 2003 
Responsible-Changed-Why:  
Max did commit the new realpath() implementation. 

http://www.freebsd.org/cgi/query-pr.cgi?pr=52686 
State-Changed-From-To: open->patched 
State-Changed-By: fjoe 
State-Changed-When: Wed May 28 01:45:41 PDT 2003 
State-Changed-Why:  
Fix committed to HEAD, MFC after 1 day 

http://www.freebsd.org/cgi/query-pr.cgi?pr=52686 
State-Changed-From-To: patched->closed 
State-Changed-By: fjoe 
State-Changed-When: Mon Jun 2 06:31:33 PDT 2003 
State-Changed-Why:  
MFC'ed to RELENG_4 

http://www.freebsd.org/cgi/query-pr.cgi?pr=52686 
>Unformatted:
