Bug 90687

Summary: [patch] side effect of -delete option of find(1)
Product: Base System Reporter: Anatoli Klassen <anatoli>
Component: binAssignee: Andriy Gapon <avg>
Status: Closed FIXED    
Severity: Affects Only Me    
Priority: Normal    
Version: 5.4-RELEASE   
Hardware: Any   
OS: Any   
Attachments:
Description Flags
file.diff none

Description Anatoli Klassen 2005-12-20 15:00:12 UTC
	If -delete option is used it cancel -L option silently.
	So find does something different as it does with just -print option.
	The result could be deleting of all symbolic links instead of broken ones only even
	if user has already validated this with previous -print run.

Fix: A patch to remove the side effect. It does not allow to delete the broken links because
there is once more check in the code with comment "sanity check", but at least it will
show error message and not delete correct links.
How-To-Repeat: 	Create a file, symbolic link to it and a broken symbolic link:
	touch a && ln -s a b && ln -s c d

	Now detect all broken links:
	find -L . -type l
	- works fine, shows that "d" is broken.

	Then try to delete the broken links
	find -L . -type l -delete
	- all links are deleted, not only broken ones.
Comment 1 freebsd 2005-12-20 23:29:42 UTC
in message <20051220145417.1B52D1DA14@26th.net>,
wrote Anatoli Klassen thusly...
>
> 
> >Environment:
> System: FreeBSD mercury.26th.net 5.4-RELEASE-p6 FreeBSD 5.4-RELEASE-p6 #0: Wed Jul 27 12:58:07 CEST 2005 root@mercury.26th.net:/usr/obj/usr/src/sys/MERCURY i386
> 
> >Description:
> If -delete option is used it cancel -L option silently.  So find does
> something different as it does with just -print option.  The result could be
> deleting of all symbolic links instead of broken ones only even if user has
> already validated this with previous -print run.
> 
> >How-To-Repeat:
> Create a file, symbolic link to it and a broken symbolic link:
> touch a && ln -s a b && ln -s c d
> 
> Now detect all broken links:
> find -L . -type l
> - works fine, shows that "d" is broken.
> 
> Then try to delete the broken links
> find -L . -type l -delete
> - all links are deleted, not only broken ones.

In your example above ...

  - "b" & "d" are found due to "-type l" criteria,
  - information about "a" is returned, instead of "b", due to "-L"
    option
  - file information about "a" is suppressed as it is not a link,
  - information about "d" is returned as it is a link and and the
    linked file does not exist.
  - in the end, all you have is "d" in the output.


Refer to "-L" option description ...

  -L      Cause the file information and file type (see stat(2))
          returned for each symbolic link to be those of the file
          referenced by the link, not the link itself.  If the
          referenced file does not exist, the file information and
          type will be for the link itself.


  - Parv

--
Comment 2 Anatoli Klassen 2005-12-21 09:34:26 UTC
> In your example above ...
>
>   - "b" & "d" are found due to "-type l" criteria,
>   - information about "a" is returned, instead of "b", due to "-L"
>     option
>   - file information about "a" is suppressed as it is not a link,
>   - information about "d" is returned as it is a link and and the
>     linked file does not exist.
>   - in the end, all you have is "d" in the output.

Exactly. But not try to add the -delete option - both "b" and "d" will be
deleted, not only "d".
Comment 3 davidt 2005-12-22 23:49:02 UTC
On Tue, 20 Dec 2005, Anatoli Klassen wrote:
> 
> >How-To-Repeat:
> 	Create a file, symbolic link to it and a broken symbolic link:
> 	touch a && ln -s a b && ln -s c d
> 
> 	Now detect all broken links:
> 	find -L . -type l
> 	- works fine, shows that "d" is broken.
> 
> 	Then try to delete the broken links
> 	find -L . -type l -delete
> 	- all links are deleted, not only broken ones.

I'm also seeing this problem, and I just noticed that this is
given as an example in the find(1) manpage:

EXAMPLES
..
     find -L /usr/ports/packages -type l -delete
             Delete all broken symbolic links in /usr/ports/packages.
..

-- 
David Taylor
Comment 4 ntarmos 2007-09-04 04:06:16 UTC
I just stumbled upon this issue while reading the manpage for find(1).
IMHO the fact that -delete turns -follow off is a good thing, so I
consider this more of a bug in the manpage. The included patch removes
the erroneous example and adds a word or two about -delete not honoring
-L. There is a mention of -delete not interacting well with options that
alter the traversal algorithm, but that is somewhat vague in this case.

--- find.1.diff begins here ---
Index: find.1
===================================================================
RCS file: /opt/freebsd/cvs/src/usr.bin/find/find.1,v
retrieving revision 1.82
diff -u -r1.82 find.1
--- find.1	28 Feb 2007 10:19:25 -0000	1.82
+++ find.1	4 Sep 2007 02:56:40 -0000
@@ -306,6 +306,24 @@
 .Dq Pa \&.
 for security reasons.
 Depth-first traversal processing is implied by this option.
+Moreover, beware that
+.Ic -delete
+does
+.Ar not
+honor the semantics of
+.Ic -L
+since it turns off following of symbolic links for security reasons.
+Thus,
+.Pp
+.Dl "find -L /usr/ports/packages -type l -delete"
+.Pp
+would delete
+.Ar all
+symbolic links under
+.Ar /usr/ports/packages
+as if
+.Ic -L
+had not been defined in the command line at all.
 .It Ic -depth
 Always true;
 same as the
@@ -843,9 +861,6 @@
 Use the
 .Xr echo 1
 command to print out a list of all the files.
-.It Li "find -L /usr/ports/packages -type l -delete"
-Delete all broken symbolic links in
-.Pa /usr/ports/packages .
 .It Li "find /usr/src -name CVS -prune -o -depth +6 -print"
 Find files and directories that are at least seven levels deep
 in the working directory
---- find.1.diff ends here ----
Comment 5 Romain Tartière 2007-10-21 19:08:49 UTC
The find manpage says:
> The find utility recursively descends the directory tree for each
> _pathname_ listed, evaluating an _expression_ (composed of the
> ``primaries'' and ``operands'' listed below) in terms of each file in
> the tree.

Most operands return True or False depending on the nature of a found
file (i.e. -nouser: True if the file belongs to an unknown user), other
performs actions (i.e -print: This primary always evaluates to true.  It
prints the pathname of the current file to standard output).

The -delete operand also return true all the time.  In other word, as
-print or -delete are just two operands that return true, we can expect
an expression using the first or the second act on the same files,
displaying filenames for the first case, and deleting files for the
second.


According to me, silently disabling symbolic links following depending
on witch operands are set looks odd.  In fact, if I am not sure of what
I am doing, I may first run find with a -print operand, and if
everything is okay, then replace -print with -delete.  It is exactly
what I did after reading the man page example regarding the "Delete all
broken symbolic links in /usr/ports/packages".  I got 2-3 broken links
as a result and so confidently replaced -print by -delete to actually do
the job. I basically see all this as a ``for'' loop that behaves
differently depending on what is performed in the without-side-effects
loop block.

Moreover, 1.8 revision (introduction of the -delete operand) commit
message states that "the code is extremely paranoid", consider race
condition, etc.  But the behaviour of find is to not traverse symbolic
links if not told otherwise.  In other word, it will have the same
behaviour as rm: considering symbolic links, not referenced files, and
does not fills the requirements for a symbolic link race condition. So
maybe the tool may be less paranoid and only the paranoidness of the
user may be taken into account?

Without the restriction told about here, find would be a good tool to
delete particular files according to random criteria (and then the
-delete operand makes sense).


At least, if for ``security reasons'' we don't want the user to use -L
and -delete at the same time, we'd better raise an error and do nothing:
"find: -L and -delete cannot be used simultaneously"
... or even sort of a -shoot-me-in-the-foot option to force find to do
``risky'' stuff rather than doing something the user probably doesn't
wants.


BTW, for those who reached the PR page after deleting a brunch of
symlinks in /usr/ports/packages, you can resurrect them with:

# for PORT in `pkg_info -q -a -o`; do cd /usr/ports/$PORT && \
                                      make package-links; done


Romain
Comment 6 Andriy Gapon 2009-01-08 16:24:27 UTC
I can still reproduce this issue with 7.1.
I think that this is the most dangerous unexpected behavior that I have
encountered using FreeBSD so far!

-- 
Andriy Gapon
Comment 7 dfilter service freebsd_committer freebsd_triage 2009-05-19 15:24:06 UTC
Author: avg
Date: Tue May 19 14:23:54 2009
New Revision: 192381
URL: http://svn.freebsd.org/changeset/base/192381

Log:
  find: do not silently disable -L when -delete is used
  
  First of all, current behavior is not documented and confusing,
  and it can be very dangerous in the following sequence:
  find -L . -type l
  find -L . -type l -delete
  (the second line is even suggested by find(1)).
  
  Instead simply refuse to proceed when -L and -delete are both used.
  A descriptive error message is provided.
  The following command can be safely used to remove broken links:
  find -L . -type l -print0 | xargs rm -0
  
  To do:		update find(1)
  PR:		bin/90687
  Obtained from:	Anatoli Klassen <anatoli@aksoft.net>
  Approved by:	jhb (mentor)

Modified:
  head/usr.bin/find/function.c

Modified: head/usr.bin/find/function.c
==============================================================================
--- head/usr.bin/find/function.c	Tue May 19 14:10:14 2009	(r192380)
+++ head/usr.bin/find/function.c	Tue May 19 14:23:54 2009	(r192381)
@@ -427,11 +427,13 @@ f_delete(PLAN *plan __unused, FTSENT *en
 
 	/* sanity check */
 	if (isdepth == 0 ||			/* depth off */
-	    (ftsoptions & FTS_NOSTAT) ||	/* not stat()ing */
-	    !(ftsoptions & FTS_PHYSICAL) ||	/* physical off */
-	    (ftsoptions & FTS_LOGICAL))		/* or finally, logical on */
+	    (ftsoptions & FTS_NOSTAT))		/* not stat()ing */
 		errx(1, "-delete: insecure options got turned on");
 
+	if (!(ftsoptions & FTS_PHYSICAL) ||	/* physical off */
+	    (ftsoptions & FTS_LOGICAL))		/* or finally, logical on */
+		errx(1, "-delete: forbidden when symlinks are followed");
+
 	/* Potentially unsafe - do not accept relative paths whatsoever */
 	if (strchr(entry->fts_accpath, '/') != NULL)
 		errx(1, "-delete: %s: relative path potentially not safe",
@@ -462,8 +464,6 @@ c_delete(OPTION *option, char ***argvp _
 {
 
 	ftsoptions &= ~FTS_NOSTAT;	/* no optimise */
-	ftsoptions |= FTS_PHYSICAL;	/* disable -follow */
-	ftsoptions &= ~FTS_LOGICAL;	/* disable -follow */
 	isoutput = 1;			/* possible output */
 	isdepth = 1;			/* -depth implied */
 
_______________________________________________
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"
Comment 8 Brian Somers freebsd_committer freebsd_triage 2009-06-14 07:54:42 UTC
Responsible Changed
From-To: freebsd-bugs->avg

Work is in-progress by avg (r192381)
Comment 9 Ed Schouten freebsd_committer freebsd_triage 2009-09-18 14:40:56 UTC
Responsible Changed
From-To: avg->jilles

Jilles, you'd better have this.
Comment 10 Ed Schouten freebsd_committer freebsd_triage 2009-09-18 14:47:24 UTC
Responsible Changed
From-To: jilles->avg

This is my first day helping out sorting bugs. Looks like I've got a lot 
to learn. This patch has already been taken care of by avg.
Comment 11 dfilter service freebsd_committer freebsd_triage 2009-09-20 17:48:10 UTC
Author: jilles
Date: Sun Sep 20 16:47:56 2009
New Revision: 197363
URL: http://svn.freebsd.org/changeset/base/197363

Log:
  Update find(1) man page for -L/-delete interaction.
  
  It is a bit unfortunate that the example to delete broken symlinks now uses
  rm(1), but allowing this with -delete would require fixing fts(3) to not
  imply FTS_NOCHDIR if FTS_LOGICAL is given (or hacks in the -delete option).
  
  PR:		bin/90687
  MFC after:	2 weeks

Modified:
  head/usr.bin/find/find.1

Modified: head/usr.bin/find/find.1
==============================================================================
--- head/usr.bin/find/find.1	Sun Sep 20 15:47:31 2009	(r197362)
+++ head/usr.bin/find/find.1	Sun Sep 20 16:47:56 2009	(r197363)
@@ -312,6 +312,7 @@ character in its pathname relative to
 .Dq Pa \&.
 for security reasons.
 Depth-first traversal processing is implied by this option.
+Following symlinks is incompatible with this option.
 .It Ic -depth
 Always true;
 same as the
@@ -920,7 +921,7 @@ recent than the current time minus one m
 Use the
 .Xr echo 1
 command to print out a list of all the files.
-.It Li "find -L /usr/ports/packages -type l -delete"
+.It Li "find -L /usr/ports/packages -type l -exec rm -- {} +"
 Delete all broken symbolic links in
 .Pa /usr/ports/packages .
 .It Li "find /usr/src -name CVS -prune -o -depth +6 -print"
_______________________________________________
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"
Comment 12 dfilter service freebsd_committer freebsd_triage 2009-10-04 18:23:04 UTC
Author: jilles
Date: Sun Oct  4 17:22:51 2009
New Revision: 197749
URL: http://svn.freebsd.org/changeset/base/197749

Log:
  MFC r197363: Update find(1) man page for -L/-delete interaction.
  
  It is a bit unfortunate that the example to delete broken symlinks now uses
  rm(1), but allowing this with -delete would require fixing fts(3) to not
  imply FTS_NOCHDIR if FTS_LOGICAL is given (or hacks in the -delete option).
  
  PR:		bin/90687

Modified:
  stable/7/usr.bin/find/   (props changed)
  stable/7/usr.bin/find/find.1

Modified: stable/7/usr.bin/find/find.1
==============================================================================
--- stable/7/usr.bin/find/find.1	Sun Oct  4 17:16:11 2009	(r197748)
+++ stable/7/usr.bin/find/find.1	Sun Oct  4 17:22:51 2009	(r197749)
@@ -306,6 +306,7 @@ character in its pathname relative to
 .Dq Pa \&.
 for security reasons.
 Depth-first traversal processing is implied by this option.
+Following symlinks is incompatible with this option.
 .It Ic -depth
 Always true;
 same as the
@@ -843,7 +844,7 @@ recent than the current time minus one m
 Use the
 .Xr echo 1
 command to print out a list of all the files.
-.It Li "find -L /usr/ports/packages -type l -delete"
+.It Li "find -L /usr/ports/packages -type l -exec rm -- {} +"
 Delete all broken symbolic links in
 .Pa /usr/ports/packages .
 .It Li "find /usr/src -name CVS -prune -o -depth +6 -print"
_______________________________________________
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"
Comment 13 dfilter service freebsd_committer freebsd_triage 2009-10-29 21:25:27 UTC
Author: jilles
Date: Thu Oct 29 21:25:16 2009
New Revision: 198628
URL: http://svn.freebsd.org/changeset/base/198628

Log:
  MFC r197363: Update find(1) man page for -L/-delete interaction.
  
  It is a bit unfortunate that the example to delete broken symlinks now uses
  rm(1), but allowing this with -delete would require fixing fts(3) to not
  imply FTS_NOCHDIR if FTS_LOGICAL is given (or hacks in the -delete option).
  
  PR:		bin/90687

Modified:
  stable/8/usr.bin/find/   (props changed)
  stable/8/usr.bin/find/find.1

Modified: stable/8/usr.bin/find/find.1
==============================================================================
--- stable/8/usr.bin/find/find.1	Thu Oct 29 21:23:44 2009	(r198627)
+++ stable/8/usr.bin/find/find.1	Thu Oct 29 21:25:16 2009	(r198628)
@@ -312,6 +312,7 @@ character in its pathname relative to
 .Dq Pa \&.
 for security reasons.
 Depth-first traversal processing is implied by this option.
+Following symlinks is incompatible with this option.
 .It Ic -depth
 Always true;
 same as the
@@ -920,7 +921,7 @@ recent than the current time minus one m
 Use the
 .Xr echo 1
 command to print out a list of all the files.
-.It Li "find -L /usr/ports/packages -type l -delete"
+.It Li "find -L /usr/ports/packages -type l -exec rm -- {} +"
 Delete all broken symbolic links in
 .Pa /usr/ports/packages .
 .It Li "find /usr/src -name CVS -prune -o -depth +6 -print"
_______________________________________________
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"
Comment 14 Andriy Gapon freebsd_committer freebsd_triage 2010-01-18 15:32:14 UTC
State Changed
From-To: open->closed

I think that the PR is resolved within the scope of 
the original report/request. 
If any further enhancements to find(1) in this area are 
wanted or proposed, then they should be filed under a new PR.