From nobody@FreeBSD.org  Wed Jan  1 19:32:30 2014
Return-Path: <nobody@FreeBSD.org>
Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:1900:2254:206a::19:1])
	(using TLSv1 with cipher ADH-AES256-SHA (256/256 bits))
	(No client certificate requested)
	by hub.freebsd.org (Postfix) with ESMTPS id 3E0B1801
	for <freebsd-gnats-submit@FreeBSD.org>; Wed,  1 Jan 2014 19:32:30 +0000 (UTC)
Received: from oldred.freebsd.org (oldred.freebsd.org [IPv6:2001:1900:2254:206a::50:4])
	(using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
	(No client certificate requested)
	by mx1.freebsd.org (Postfix) with ESMTPS id 2A0BC1EA6
	for <freebsd-gnats-submit@FreeBSD.org>; Wed,  1 Jan 2014 19:32:30 +0000 (UTC)
Received: from oldred.freebsd.org ([127.0.1.6])
	by oldred.freebsd.org (8.14.5/8.14.7) with ESMTP id s01JWT0O088043
	for <freebsd-gnats-submit@FreeBSD.org>; Wed, 1 Jan 2014 19:32:29 GMT
	(envelope-from nobody@oldred.freebsd.org)
Received: (from nobody@localhost)
	by oldred.freebsd.org (8.14.5/8.14.5/Submit) id s01JWTSj088033;
	Wed, 1 Jan 2014 19:32:29 GMT
	(envelope-from nobody)
Message-Id: <201401011932.s01JWTSj088033@oldred.freebsd.org>
Date: Wed, 1 Jan 2014 19:32:29 GMT
From: Ben Reser <ben@reser.org>
To: freebsd-gnats-submit@FreeBSD.org
Subject: find -lname buffer read overflow bug
X-Send-Pr-Version: www-3.1
X-GNATS-Notify:

>Number:         185393
>Category:       bin
>Synopsis:       find(1): -lname buffer read overflow bug
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    jilles
>State:          patched
>Quarter:        
>Keywords:       
>Date-Required:  
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Wed Jan 01 19:40:00 UTC 2014
>Closed-Date:    
>Last-Modified:  Tue Jan 14 21:40:01 UTC 2014
>Originator:     Ben Reser
>Release:        9.1
>Organization:
>Environment:
FreeBSD freebsd9.1 9.1-RELEASE FreeBSD 9.1-RELEASE #0 r243825: Tue Dec  4 09:23:10 UTC 2012     root@farrell.cse.buffalo.edu:/usr/obj/usr/src/sys/GENERIC  amd64
>Description:
The implementation of -lname and -ilname improperly use readlink() by not setting a null character before using the string.  readlink() is documented as not doing this for you and returns the length of the link string, requiring the caller to set the null character.

In particular this is implemented in the usr.bin/find/function.c in the f_name() function.  The function uses an automatic buffer which gets reused through multiple calls, resulting in link names that are shorter than the preceding values stored in the buffer to fail to match properly.

This could cause the program to read past the end of the buffer.  In practice this doesn't seem to happen because the buffer seems to always end up in zeroed memory the first time it is used (though there's no requirement for it to do so).  This would result in a crash of the find command.

You can force reading past the end of the buffer by creating a link that points at a path of PATH_MAX length on the path being searched.  Presumably it's not possible to create a link that points at a path longer than that but if possible that would also allow reading past the end of the buffer.  I haven't bothered to exercise this.

It might be possible to view this as a minor security issue if someone is using find to try and find link with -lname for auditing purposes, since they might not reliably find what they are looking for.  The read past the end of the buffer doesn't seem particularly useful.  For one it'd only ever be a read, which isn't particularly useful and for another find doesn't run with escalated privileges.  So all in all I think it'd be a stretch to call this anything other than an ordinary bug.

This bug was introduced in r176497 (committed 5 years 10 months ago), so any releases of FreeBSD that contain this change would contain the same issue.  I actually happened to find the issue in OS X's fork of your find command.  But successfully duplicated the issue in a VM of 9.1 that I had laying around.
>How-To-Repeat:
The following shell script should demonstrate the issue:

#!/usr/bin/env bash

set -e

# Demonstration of -lname bug with FreeBSD and OS X find.

# find stops output matching links as soon as it passes a link
# that points at a path that is longer than the path we are trying
# to match.  Note that file system ordering of results may change
# when this happens.  OS X seems to return readdir results in
# alphabetical sorted order (HFS+) and FreeBSD (UFS) seems to return
# them in creation order (though there does seem to be some variation
# on this).  So the below example has both the creation
# order and the alphabetical sort order such that it should reliably
# reproduce the issue.  However, I've not tested this with other
# supported file systems so they may have different behavior, possibly
# even non-deterministic behavior that makes this harder to demonstrate.

# Expected behavior will have no output and a zero exit value.

test_dir=`mktemp -d find-test.XXXXXXX`
cd "$test_dir" > /dev/null
ln -s /usr/bin/gcc a
ln -s /usr/bin/touch b
ln -s /usr/bin/gcc c
ln -s /usr/bin/gcc d
ln -s /usr/bin/gcc e

echo './a' > expected
echo './c' >> expected
echo './d' >> expected
echo './e' >> expected

"${FIND:-find}" . -lname /usr/bin/gcc | sort > received
set +e
diff -u expected received
rv=$?
set -e
cd - > /dev/null
rm -rf "$test_dir"
exit $rv

>Fix:
Set a null character at fn[len] (where len is the return of the readlink() call) as implemented in the attached patch.

Patch attached with submission follows:

Index: usr.bin/find/function.c
===================================================================
--- usr.bin/find/function.c	(revision 260159)
+++ usr.bin/find/function.c	(working copy)
@@ -1124,9 +1124,11 @@ f_name(PLAN *plan, FTSENT *entry)
 	const char *name;
 
 	if (plan->flags & F_LINK) {
+		int len = readlink(entry->fts_path, fn, sizeof(fn));
+		if (len == -1)
+			return 0;
+		fn[len] = '\0';
 		name = fn;
-		if (readlink(entry->fts_path, fn, sizeof(fn)) == -1)
-			return 0;
 	} else
 		name = entry->fts_name;
 	return !fnmatch(plan->c_data, name,


>Release-Note:
>Audit-Trail:

From: Ben Reser <ben@reser.org>
To: bug-followup@FreeBSD.org
Cc:  
Subject: Re: bin/185393: find -lname buffer read overflow bug
Date: Wed, 01 Jan 2014 12:03:04 -0800

 This is a multi-part message in MIME format.
 --------------080101030108030107080503
 Content-Type: text/plain; charset=ISO-8859-1
 Content-Transfer-Encoding: 7bit
 
 Correction on the patch.  Forgot to subtract one byte from the buffer to allow
 for the NULL character to be set.  Updated patch attached.
 
 --------------080101030108030107080503
 Content-Type: text/plain; charset=UTF-8;
  name="fbsd-find-lname.patch.txt"
 Content-Transfer-Encoding: 7bit
 Content-Disposition: attachment;
  filename="fbsd-find-lname.patch.txt"
 
 Index: usr.bin/find/function.c
 ===================================================================
 --- usr.bin/find/function.c	(revision 260159)
 +++ usr.bin/find/function.c	(working copy)
 @@ -1124,9 +1124,11 @@ f_name(PLAN *plan, FTSENT *entry)
  	const char *name;
  
  	if (plan->flags & F_LINK) {
 +		int len = readlink(entry->fts_path, fn, sizeof(fn) - 1);
 +		if (len == -1)
 +			return 0;
 +		fn[len] = '\0';
  		name = fn;
 -		if (readlink(entry->fts_path, fn, sizeof(fn)) == -1)
 -			return 0;
  	} else
  		name = entry->fts_name;
  	return !fnmatch(plan->c_data, name,
 
 --------------080101030108030107080503--
Responsible-Changed-From-To: freebsd-bugs->jilles 
Responsible-Changed-By: jilles 
Responsible-Changed-When: Thu Jan 2 21:41:09 UTC 2014 
Responsible-Changed-Why:  
I'm working on this. 

http://www.freebsd.org/cgi/query-pr.cgi?pr=185393 

From: Jilles Tjoelker <jilles@stack.nl>
To: bug-followup@FreeBSD.org, ben@reser.org
Cc:  
Subject: Re: bin/185393: find(1): -lname buffer read overflow bug
Date: Sun, 5 Jan 2014 19:04:37 +0100

 In PR bin/185393, you wrote:
 > The implementation of -lname and -ilname improperly use readlink() by
 > not setting a null character before using the string. readlink() is
 > documented as not doing this for you and returns the length of the
 > link string, requiring the caller to set the null character.
 
 > In particular this is implemented in the usr.bin/find/function.c in
 > the f_name() function. The function uses an automatic buffer which
 > gets reused through multiple calls, resulting in link names that are
 > shorter than the preceding values stored in the buffer to fail to
 > match properly.
 
 Thanks for the report.
 
 Your patch works for me. I have changed the 'len' variable from 'int' to
 'ssize_t' matching the prototype of readlink().
 
 I am not entirely happy with the hard-coded PATH_MAX, since the limit is
 higher on some other operating systems and an overlong symlink might be
 encountered on a shared filesystem. For example, a Linux machine can
 create a symlink target of 1024 bytes plus '\0' which ls -l and Python
 os.readlink() display correctly but readlink and the patched find do
 not. In the case of ls -l it only works because the target is only one
 byte "too long". I guess that is a "fringe case" though, unlike the
 problems fixed by the patch.
 
 -- 
 Jilles Tjoelker

From: Ben Reser <ben@reser.org>
To: Jilles Tjoelker <jilles@stack.nl>, bug-followup@FreeBSD.org
Cc:  
Subject: Re: bin/185393: find(1): -lname buffer read overflow bug
Date: Sun, 05 Jan 2014 12:41:29 -0800

 On 1/5/14, 10:04 AM, Jilles Tjoelker wrote:
 > Your patch works for me. I have changed the 'len' variable from 'int' to
 > 'ssize_t' matching the prototype of readlink().
 
 In general I followed the other uses of readlink() in usr.bin.  For example see
 printlink under find/ls.c.  I noticed the difference between int and ssize_t as
 well.  Given the lengths we're dealing with I don't think any automatic
 conversion will lose anything even if int happens to be shorter than ssize_t so
 I went ahead and stayed consistent with the other uses of readlink().  I don't
 think using ssize_t should present a problem for portability so no objection to
 that change, you might want to make the change in lots of other places though.
 
 > I am not entirely happy with the hard-coded PATH_MAX, since the limit is
 > higher on some other operating systems and an overlong symlink might be
 > encountered on a shared filesystem. For example, a Linux machine can
 > create a symlink target of 1024 bytes plus '\0' which ls -l and Python
 > os.readlink() display correctly but readlink and the patched find do
 > not. In the case of ls -l it only works because the target is only one
 > byte "too long". I guess that is a "fringe case" though, unlike the
 > problems fixed by the patch.
 
 On further thought I don't think PATH_MAX is the appropriate constant to use.
 Rather it should be MAXPATHLEN (see /usr/include/sys/param.h) which exists for
 this purpose.  If people run into the problem you're suggesting then that's the
 constant they are likely to change.  Right now that constant is just set to
 PATH_MAX so there isn't any functional change in the limits by using that
 constant but it seems to be the correct one.
 
 It would be possible to code this to use a dynamic buffer that is sized
 properly.  There's two ways to go about that:
 
 1) Increase the buffer size and retry (a limited number of times) the call to
 readlink() when readlink() returns -1 and errno is ENAMETOOLONG.  The cost over
 the current behavior here would be that you'd need a malloc()/realloc() prior
 to each readlink() call.
 
 2) You can do an lstat() on the link, I believe the st_size member is the
 length of the path the link points at.  However, this would have the cost of a
 lstat() and a malloc() before each readlink().
 
 Given that looking around at other utilities in FreeBSD, this seems to be a
 common limitation I wouldn't bother to do anything about it until it creates a
 problem for someone.  Since the fixes are not entirely free and you could just
 as easily just increase MAXPATHLEN.

From: dfilter@FreeBSD.ORG (dfilter service)
To: bug-followup@FreeBSD.org
Cc:  
Subject: Re: bin/185393: commit references a PR
Date: Sun,  5 Jan 2014 21:44:20 +0000 (UTC)

 Author: jilles
 Date: Sun Jan  5 21:44:04 2014
 New Revision: 260336
 URL: http://svnweb.freebsd.org/changeset/base/260336
 
 Log:
   find: Fix -lname and -ilname.
   
   The code did not take into account that readlink() does not add a
   terminating '\0', and therefore did not work reliably.
   
   As before, symlinks of length PATH_MAX or more are not handled correctly.
   (These can only be created on other operating systems.)
   
   PR:		bin/185393
   Submitted by:	Ben Reser (original version)
   MFC after:	1 week
 
 Modified:
   head/usr.bin/find/function.c
 
 Modified: head/usr.bin/find/function.c
 ==============================================================================
 --- head/usr.bin/find/function.c	Sun Jan  5 21:35:07 2014	(r260335)
 +++ head/usr.bin/find/function.c	Sun Jan  5 21:44:04 2014	(r260336)
 @@ -1122,11 +1122,14 @@ f_name(PLAN *plan, FTSENT *entry)
  {
  	char fn[PATH_MAX];
  	const char *name;
 +	ssize_t len;
  
  	if (plan->flags & F_LINK) {
 -		name = fn;
 -		if (readlink(entry->fts_path, fn, sizeof(fn)) == -1)
 +		len = readlink(entry->fts_path, fn, sizeof(fn) - 1);
 +		if (len == -1)
  			return 0;
 +		fn[len] = '\0';
 +		name = fn;
  	} else
  		name = entry->fts_name;
  	return !fnmatch(plan->c_data, name,
 _______________________________________________
 svn-src-all@freebsd.org mailing list
 http://lists.freebsd.org/mailman/listinfo/svn-src-all
 To unsubscribe, send any mail to "svn-src-all-unsubscribe@freebsd.org"
 
State-Changed-From-To: open->patched 
State-Changed-By: jilles 
State-Changed-When: Mon Jan 6 23:00:04 UTC 2014 
State-Changed-Why:  
Fixed in head. 
Note that I fixed additional problems with -lname in r260355. 

http://www.freebsd.org/cgi/query-pr.cgi?pr=185393 

From: dfilter@FreeBSD.ORG (dfilter service)
To: bug-followup@FreeBSD.org
Cc:  
Subject: Re: bin/185393: commit references a PR
Date: Sun, 12 Jan 2014 23:18:06 +0000 (UTC)

 Author: jilles
 Date: Sun Jan 12 23:17:56 2014
 New Revision: 260579
 URL: http://svnweb.freebsd.org/changeset/base/260579
 
 Log:
   MFC r260336,r260355: find: Fix -lname and -ilname:
   
   * Take into account that readlink() does not add a terminating '\0'.
   
   * Do not match symlinks that are followed because of -H or -L. This is
     explicitly documented in GNU find's info file and is like -type l.
   
   * Fix matching symlinks in subdirectories when fts changes directories.
   
   As before, symlinks of length PATH_MAX or more are not handled correctly.
   (These can only be created on other operating systems.)
   
   Also, avoid some readlink() calls on files that are obviously not symlinks
   (because of fts(3) restrictions, not all of them).
   
   PR:		bin/185393
   Submitted by:	Ben Reser (parts, original version)
 
 Modified:
   stable/10/usr.bin/find/find.1
   stable/10/usr.bin/find/function.c
 Directory Properties:
   stable/10/   (props changed)
 
 Modified: stable/10/usr.bin/find/find.1
 ==============================================================================
 --- stable/10/usr.bin/find/find.1	Sun Jan 12 22:17:56 2014	(r260578)
 +++ stable/10/usr.bin/find/find.1	Sun Jan 12 23:17:56 2014	(r260579)
 @@ -31,7 +31,7 @@
  .\"	@(#)find.1	8.7 (Berkeley) 5/9/95
  .\" $FreeBSD$
  .\"
 -.Dd November 18, 2012
 +.Dd January 5, 2014
  .Dt FIND 1
  .Os
  .Sh NAME
 @@ -520,6 +520,8 @@ Like
  .Ic -name ,
  but the contents of the symbolic link are matched instead of the file
  name.
 +Note that this only matches broken symbolic links
 +if symbolic links are being followed.
  This is a GNU find extension.
  .It Ic -ls
  This primary always evaluates to true.
 
 Modified: stable/10/usr.bin/find/function.c
 ==============================================================================
 --- stable/10/usr.bin/find/function.c	Sun Jan 12 22:17:56 2014	(r260578)
 +++ stable/10/usr.bin/find/function.c	Sun Jan 12 23:17:56 2014	(r260579)
 @@ -1122,11 +1122,24 @@ f_name(PLAN *plan, FTSENT *entry)
  {
  	char fn[PATH_MAX];
  	const char *name;
 +	ssize_t len;
  
  	if (plan->flags & F_LINK) {
 -		name = fn;
 -		if (readlink(entry->fts_path, fn, sizeof(fn)) == -1)
 +		/*
 +		 * The below test both avoids obviously useless readlink()
 +		 * calls and ensures that symlinks with existent target do
 +		 * not match if symlinks are being followed.
 +		 * Assumption: fts will stat all symlinks that are to be
 +		 * followed and will return the stat information.
 +		 */
 +		if (entry->fts_info != FTS_NSOK && entry->fts_info != FTS_SL &&
 +		    entry->fts_info != FTS_SLNONE)
 +			return 0;
 +		len = readlink(entry->fts_accpath, fn, sizeof(fn) - 1);
 +		if (len == -1)
  			return 0;
 +		fn[len] = '\0';
 +		name = fn;
  	} else
  		name = entry->fts_name;
  	return !fnmatch(plan->c_data, name,
 _______________________________________________
 svn-src-all@freebsd.org mailing list
 http://lists.freebsd.org/mailman/listinfo/svn-src-all
 To unsubscribe, send any mail to "svn-src-all-unsubscribe@freebsd.org"
 

From: dfilter@FreeBSD.ORG (dfilter service)
To: bug-followup@FreeBSD.org
Cc:  
Subject: Re: bin/185393: commit references a PR
Date: Tue, 14 Jan 2014 21:35:34 +0000 (UTC)

 Author: jilles
 Date: Tue Jan 14 21:35:25 2014
 New Revision: 260651
 URL: http://svnweb.freebsd.org/changeset/base/260651
 
 Log:
   MFC r260336,r260355: find: Fix -lname and -ilname:
   
   * Take into account that readlink() does not add a terminating '\0'.
   
   * Do not match symlinks that are followed because of -H or -L. This is
     explicitly documented in GNU find's info file and is like -type l.
   
   * Fix matching symlinks in subdirectories when fts changes directories.
   
   As before, symlinks of length PATH_MAX or more are not handled correctly.
   (These can only be created on other operating systems.)
   
   Also, avoid some readlink() calls on files that are obviously not symlinks
   (because of fts(3) restrictions, not all of them).
   
   PR:		bin/185393
   Submitted by:	Ben Reser (parts, original version)
 
 Modified:
   stable/9/usr.bin/find/find.1
   stable/9/usr.bin/find/function.c
 Directory Properties:
   stable/9/usr.bin/find/   (props changed)
 
 Modified: stable/9/usr.bin/find/find.1
 ==============================================================================
 --- stable/9/usr.bin/find/find.1	Tue Jan 14 21:20:51 2014	(r260650)
 +++ stable/9/usr.bin/find/find.1	Tue Jan 14 21:35:25 2014	(r260651)
 @@ -31,7 +31,7 @@
  .\"	@(#)find.1	8.7 (Berkeley) 5/9/95
  .\" $FreeBSD$
  .\"
 -.Dd September 9, 2012
 +.Dd January 5, 2014
  .Dt FIND 1
  .Os
  .Sh NAME
 @@ -502,6 +502,8 @@ Like
  .Ic -name ,
  but the contents of the symbolic link are matched instead of the file
  name.
 +Note that this only matches broken symbolic links
 +if symbolic links are being followed.
  This is a GNU find extension.
  .It Ic -ls
  This primary always evaluates to true.
 
 Modified: stable/9/usr.bin/find/function.c
 ==============================================================================
 --- stable/9/usr.bin/find/function.c	Tue Jan 14 21:20:51 2014	(r260650)
 +++ stable/9/usr.bin/find/function.c	Tue Jan 14 21:35:25 2014	(r260651)
 @@ -1082,11 +1082,24 @@ f_name(PLAN *plan, FTSENT *entry)
  {
  	char fn[PATH_MAX];
  	const char *name;
 +	ssize_t len;
  
  	if (plan->flags & F_LINK) {
 -		name = fn;
 -		if (readlink(entry->fts_path, fn, sizeof(fn)) == -1)
 +		/*
 +		 * The below test both avoids obviously useless readlink()
 +		 * calls and ensures that symlinks with existent target do
 +		 * not match if symlinks are being followed.
 +		 * Assumption: fts will stat all symlinks that are to be
 +		 * followed and will return the stat information.
 +		 */
 +		if (entry->fts_info != FTS_NSOK && entry->fts_info != FTS_SL &&
 +		    entry->fts_info != FTS_SLNONE)
 +			return 0;
 +		len = readlink(entry->fts_accpath, fn, sizeof(fn) - 1);
 +		if (len == -1)
  			return 0;
 +		fn[len] = '\0';
 +		name = fn;
  	} else
  		name = entry->fts_name;
  	return !fnmatch(plan->c_data, name,
 _______________________________________________
 svn-src-all@freebsd.org mailing list
 http://lists.freebsd.org/mailman/listinfo/svn-src-all
 To unsubscribe, send any mail to "svn-src-all-unsubscribe@freebsd.org"
 
>Unformatted:
