Subject: security/7060: find and rm
To: None <gnats-bugs@gnats.netbsd.org>
From: Chris Jones <cjones@rupert.honors.montana.edu>
List: netbsd-bugs
Date: 02/27/1999 23:42:17
>Number:         7060
>Category:       security
>Synopsis:       find and rm
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    security-officer (NetBSD Security Officer)
>State:          open
>Class:          change-request
>Submitter-Id:   net
>Arrival-Date:   Sat Feb 27 22:50:00 1999
>Last-Modified:
>Originator:     Chris Jones
>Organization:
-----------------------------------------------------cjones@math.montana.edu
Chris Jones                                          cjones@honors.montana.edu
           Mad scientist at large                    cjones@nervana.montana.edu
"Is this going to be a stand-up programming session, sir, or another bug hunt?"
>Release:        Feb 23, 1999
>Environment:
	
System: NetBSD rupert.honors.montana.edu 1.3.2 NetBSD 1.3.2 (RUPERT) #2: Thu Jun 4 11:36:25 MDT 1998 tnkrbell@rupert.honors.montana.edu:/usr/src/sys/arch/i386/compile/RUPERT i386


>Description:
NetBSD has had a problem with find and rm in the daily script for a
while.  As I understand it, the issue is a race condition:  Find is
trying to remove files that match certain criteria, but there is a
race condition between the time when it runs stat on a file and when
it (or rm) unlinks the file.

>How-To-Repeat:
In order to exploit this, a user can create a directory under /tmp
which contains a file that would be targeted by find.  After the file
is targeted, but before rm is run, the user renames the directory and
puts a symlink in its place.  Then rm will be fooled into deleting the
target file in whatever directory the symlink points to.

As far as I know, the above is the only way to reproduce this problem.
Please correct me if my understanding is false.

>Fix:
One possibility would be a system call which takes a stat buffer and a
pathname as arguments.  It does a stat on the file indicated by
pathname, and compares the result to the stat buffer that gets passed
in to it.  If they're the same, it deletes the file.  Doesn't seem
like a very clean solution, though.

A preferred (IHO) solution is to have an unlink(2) call which refuses
to traverse symlinks.  So, the attacker can substitute a different
directory for the intended one, but it still has to be a directory
owned by the attacker, or the parent directory has to be owned by the
attacker.  (If I own /tmp/cjones, and find wants to delete
/tmp/cjones/foo/bar, then I can rename some other directory
/tmp/cjones/joe to /tmp/cjones/foo, and a file named bar will be
deleted in that directory.)  I don't see this as a problem, but maybe
someone else will see a security hole that it opens.

There should be an accompanying system call that does the same thing
for rmdir(2).  In addition, it might make sense to give the same
treatment to a few calls like chmod(2) and chown(2).  There should be
a naming scheme for the "non-symlink" versions of these calls.  The
best thing I came up with was unlinkd and rmdird, with the "d"
indicating that it traverses directories only.

In fact, I now realize that there will still be a race condition open,
though the time window will be much smaller:  Between the time when
find identifies a directory to recurse into, and the time when it
issues an open(2) on that directory, the directory could be replaced
with a symlink.  In order to fix this, there should be a opend(2) call
which refuses to traverse symlinks, including a symlink in the last
component of the pathname.

Here are patches to implement the unlinkd and rmdird system calls.
Hopefully I haven't missed anything else really obvious, and these
will actually work.

*** sys/kern/syscalls.master.orig	Sat Feb 27 09:32:35 1999
--- sys/kern/syscalls.master	Sat Feb 27 20:07:24 1999
***************
*** 1,4 ****
! 	$NetBSD: syscalls.master,v 1.88 1999/02/10 18:02:28 kleink Exp $
  
  ;	@(#)syscalls.master	8.2 (Berkeley) 1/13/94
  
--- 1,4 ----
! 	$NetBSD: syscalls.master,v 1.88 1999/02/10 18:02:28 kleink Exp  $
  
  ;	@(#)syscalls.master	8.2 (Berkeley) 1/13/94
  
***************
*** 564,566 ****
--- 564,568 ----
  			    sigset_t *oset); }
  294	STD		{ int sys___sigsuspend14(const sigset_t *set); }
  295	STD		{ int sys___sigreturn14(struct sigcontext *sigcntxp); }
+ 296	STD		{ int sys_unlinkd(const char *path); }
+ 297	STD		{ int sys_rmdird(const char *path); }
*** sys/kern/vfs_syscalls.c.orig	Sat Feb 27 09:32:43 1999
--- sys/kern/vfs_syscalls.c	Sat Feb 27 20:07:24 1999
***************
*** 1,4 ****
! /*	$NetBSD: vfs_syscalls.c,v 1.127 1998/12/10 15:09:19 christos Exp $	*/
  
  /*
   * Copyright (c) 1989, 1993
--- 1,4 ----
! /*	$NetBSD: vfs_syscalls.c,v 1.127 1998/12/10 15:09:19 christos Exp  $	*/
  
  /*
   * Copyright (c) 1989, 1993
***************
*** 1195,1200 ****
--- 1195,1261 ----
  }
  
  /*
+  * Delete a name from the filesystem, refusing to follow symlinks.
+  */
+ /* ARGSUSED */
+ int
+ sys_unlinkd(p, v, retval)
+ 	struct proc *p;
+ 	void *v;
+ 	register_t *retval;
+ {
+ 	struct sys_unlinkd_args /* {
+ 		syscallarg(const char *) path;
+ 	} */ *uap = v;
+ 	register struct vnode *vp;
+ 	int error;
+ 	struct nameidata nd;
+ 
+ 	NDINIT(&nd, DELETE, LOCKPARENT | LOCKLEAF, UIO_USERSPACE,
+ 	    SCARG(uap, path), p);
+ 	if ((error = namei(&nd)) != 0)
+ 		return (error);
+ 	vp = nd.ni_vp;
+ 
+ 	if(nd.ni_loopcnt > 0) {
+ 		VOP_ABORTOP(nd.ni_dvp, &nd.ni_cnd);
+ 		if(nd.ni_dvp == vp)
+ 			vrele(nd.ni_dvp);
+ 		else
+ 			vput(nd.ni_dvp);
+ 		vput(vp);
+ 		error = ELOOP;
+ 		goto out;
+ 	}
+ 
+ 	/*
+ 	 * The root of a mounted filesystem cannot be deleted.
+ 	 */
+ 	if (vp->v_flag & VROOT) {
+ 		VOP_ABORTOP(nd.ni_dvp, &nd.ni_cnd);
+ 		if (nd.ni_dvp == vp)
+ 			vrele(nd.ni_dvp);
+ 		else
+ 			vput(nd.ni_dvp);
+ 		vput(vp);
+ 		error = EBUSY;
+ 		goto out;
+ 	}
+ 
+ #if defined(UVM)
+ 	(void)uvm_vnp_uncache(vp);
+ #else
+ 	(void)vnode_pager_uncache(vp);
+ #endif
+ 
+ 	VOP_LEASE(nd.ni_dvp, p, p->p_ucred, LEASE_WRITE);
+ 	VOP_LEASE(vp, p, p->p_ucred, LEASE_WRITE);
+ 	error = VOP_REMOVE(nd.ni_dvp, nd.ni_vp, &nd.ni_cnd);
+ out:
+ 	return (error);
+ }
+ 
+ /*
   * Reposition read/write file offset.
   */
  int
***************
*** 2437,2442 ****
--- 2498,2561 ----
  	 */
  	if (nd.ni_dvp == vp) {
  		error = EINVAL;
+ 		goto out;
+ 	}
+ 	/*
+ 	 * The root of a mounted filesystem cannot be deleted.
+ 	 */
+ 	if (vp->v_flag & VROOT)
+ 		error = EBUSY;
+ out:
+ 	if (!error) {
+ 		VOP_LEASE(nd.ni_dvp, p, p->p_ucred, LEASE_WRITE);
+ 		VOP_LEASE(vp, p, p->p_ucred, LEASE_WRITE);
+ 		error = VOP_RMDIR(nd.ni_dvp, nd.ni_vp, &nd.ni_cnd);
+ 	} else {
+ 		VOP_ABORTOP(nd.ni_dvp, &nd.ni_cnd);
+ 		if (nd.ni_dvp == vp)
+ 			vrele(nd.ni_dvp);
+ 		else
+ 			vput(nd.ni_dvp);
+ 		vput(vp);
+ 	}
+ 	return (error);
+ }
+ 
+ /*
+  * Remove a directory file, refusing to follow symlinks.
+  */
+ /* ARGSUSED */
+ int
+ sys_rmdird(p, v, retval)
+ 	struct proc *p;
+ 	void *v;
+ 	register_t *retval;
+ {
+ 	struct sys_rmdird_args /* {
+ 		syscallarg(const char *) path;
+ 	} */ *uap = v;
+ 	register struct vnode *vp;
+ 	int error;
+ 	struct nameidata nd;
+ 
+ 	NDINIT(&nd, DELETE, LOCKPARENT | LOCKLEAF, UIO_USERSPACE,
+ 	    SCARG(uap, path), p);
+ 	if ((error = namei(&nd)) != 0)
+ 		return (error);
+ 	vp = nd.ni_vp;
+ 	if (vp->v_type != VDIR) {
+ 		error = ENOTDIR;
+ 		goto out;
+ 	}
+ 	/*
+ 	 * No rmdir "." please.
+ 	 */
+ 	if (nd.ni_dvp == vp) {
+ 		error = EINVAL;
+ 		goto out;
+ 	}
+ 	if (nd.ni_loopcnt > 0) {
+ 		error = ELOOP;
  		goto out;
  	}
  	/*
*** lib/libc/sys/Makefile.inc.orig	Sat Feb 27 19:45:24 1999
--- lib/libc/sys/Makefile.inc	Sat Feb 27 20:07:24 1999
***************
*** 1,4 ****
! #	$NetBSD: Makefile.inc,v 1.89 1999/02/11 20:42:49 carrel Exp $
  #	@(#)Makefile.inc	8.3 (Berkeley) 10/24/94
  
  # sys sources
--- 1,4 ----
! #	$NetBSD: Makefile.inc,v 1.89 1999/02/11 20:42:49 carrel Exp  $
  #	@(#)Makefile.inc	8.3 (Berkeley) 10/24/94
  
  # sys sources
***************
*** 47,60 ****
  	nfssvc.o ntp_adjtime.o ntp_gettime.o open.o pathconf.o \
  	poll.o profil.o quotactl.o read.o \
  	readlink.o readv.o reboot.o recvfrom.o recvmsg.o rename.o revoke.o \
! 	rmdir.o select.o semconfig.o semget.o semop.o sendmsg.o sendto.o \
! 	setegid.o seteuid.o setgid.o setgroups.o setitimer.o \
  	setpgid.o setpriority.o setregid.o setreuid.o setrlimit.o \
  	setsid.o setsockopt.o settimeofday.o setuid.o shmat.o \
  	shmctl.o shmdt.o shmget.o shutdown.o __sigaction14.o \
  	__sigaltstack14.o __sigpending14.o __sigprocmask14.o __sigsuspend14.o \
  	socket.o socketpair.o __stat13.o statfs.o \
! 	swapctl.o symlink.o sysarch.o umask.o undelete.o unlink.o \
  	unmount.o utimes.o vadvise.o wait4.o write.o writev.o \
  	__semctl.o __syscall.o __sysctl.o \
  	__posix_chown.o __posix_fchown.o __posix_lchown.o __posix_rename.o
--- 47,60 ----
  	nfssvc.o ntp_adjtime.o ntp_gettime.o open.o pathconf.o \
  	poll.o profil.o quotactl.o read.o \
  	readlink.o readv.o reboot.o recvfrom.o recvmsg.o rename.o revoke.o \
! 	rmdir.o rmdird.o select.o semconfig.o semget.o semop.o sendmsg.o \
! 	sendto.o setegid.o seteuid.o setgid.o setgroups.o setitimer.o \
  	setpgid.o setpriority.o setregid.o setreuid.o setrlimit.o \
  	setsid.o setsockopt.o settimeofday.o setuid.o shmat.o \
  	shmctl.o shmdt.o shmget.o shutdown.o __sigaction14.o \
  	__sigaltstack14.o __sigpending14.o __sigprocmask14.o __sigsuspend14.o \
  	socket.o socketpair.o __stat13.o statfs.o \
! 	swapctl.o symlink.o sysarch.o umask.o undelete.o unlink.o unlinkd.o \
  	unmount.o utimes.o vadvise.o wait4.o write.o writev.o \
  	__semctl.o __syscall.o __sysctl.o \
  	__posix_chown.o __posix_fchown.o __posix_lchown.o __posix_rename.o
***************
*** 216,221 ****
--- 216,222 ----
  MLINKS+=pathconf.2 fpathconf.2
  MLINKS+=read.2 readv.2 read.2 pread.2 read.2 preadv.2
  MLINKS+=recv.2 recvfrom.2 recv.2 recvmsg.2
+ MLINKS+=rmdir.2 rmdird.2
  MLINKS+=send.2 sendmsg.2 send.2 sendto.2
  MLINKS+=setpgid.2 setpgrp.2
  MLINKS+=setuid.2 setegid.2 setuid.2 seteuid.2 setuid.2 setgid.2
***************
*** 224,229 ****
--- 225,231 ----
  MLINKS+=statfs.2 fstatfs.2
  MLINKS+=syscall.2 __syscall.2
  MLINKS+=truncate.2 ftruncate.2
+ MLINKS+=unlink.2 unlinkd.2
  MLINKS+=utimes.2 futimes.2 utimes.2 lutimes.2
  MLINKS+=wait.2 wait3.2 wait.2 wait4.2 wait.2 waitpid.2
  MLINKS+=write.2 writev.2 write.2 pwrite.2 write.2 pwritev.2
*** include/unistd.h.orig	Sat Feb 27 19:14:51 1999
--- include/unistd.h	Sat Feb 27 20:07:25 1999
***************
*** 1,4 ****
! /*	$NetBSD: unistd.h,v 1.69 1998/11/30 20:36:27 thorpej Exp $	*/
  
  /*-
   * Copyright (c) 1998 The NetBSD Foundation, Inc.
--- 1,4 ----
! /*	$NetBSD: unistd.h,v 1.69 1998/11/30 20:36:27 thorpej Exp  $	*/
  
  /*-
   * Copyright (c) 1998 The NetBSD Foundation, Inc.
***************
*** 132,137 ****
--- 132,138 ----
  int	 pipe __P((int *));
  ssize_t	 read __P((int, void *, size_t));
  int	 rmdir __P((const char *));
+ int	 rmdird __P((const char *));
  int	 setgid __P((gid_t));
  int	 setpgid __P((pid_t, pid_t));
  pid_t	 setsid __P((void));
***************
*** 142,147 ****
--- 143,149 ----
  int	 tcsetpgrp __P((int, pid_t));
  __aconst char *ttyname __P((int));
  int	 unlink __P((const char *));
+ int	 unlinkd __P((const char *));
  ssize_t	 write __P((int, const void *, size_t));
  
  
*** lib/libc/sys/rmdir.2.orig	Sat Feb 27 19:29:29 1999
--- lib/libc/sys/rmdir.2	Sat Feb 27 20:07:25 1999
***************
*** 1,4 ****
! .\"	$NetBSD: rmdir.2,v 1.10 1998/08/29 08:32:41 lukem Exp $
  .\"
  .\" Copyright (c) 1983, 1991, 1993
  .\"	The Regents of the University of California.  All rights reserved.
--- 1,4 ----
! .\"	$NetBSD: rmdir.2,v 1.10 1998/08/29 08:32:41 lukem Exp  $
  .\"
  .\" Copyright (c) 1983, 1991, 1993
  .\"	The Regents of the University of California.  All rights reserved.
***************
*** 37,48 ****
  .Dt RMDIR 2
  .Os BSD 4.2
  .Sh NAME
! .Nm rmdir
  .Nd remove a directory file
  .Sh SYNOPSIS
  .Fd #include <unistd.h>
  .Ft int
  .Fn rmdir "const char *path"
  .Sh DESCRIPTION
  .Fn rmdir
  removes a directory file
--- 37,51 ----
  .Dt RMDIR 2
  .Os BSD 4.2
  .Sh NAME
! .Nm rmdir ,
! .Nm rmdird
  .Nd remove a directory file
  .Sh SYNOPSIS
  .Fd #include <unistd.h>
  .Ft int
  .Fn rmdir "const char *path"
+ .Ft int
+ .Fn rmdird "const char *path"
  .Sh DESCRIPTION
  .Fn rmdir
  removes a directory file
***************
*** 53,58 ****
--- 56,68 ----
  .Ql \&.
  and
  .Ql \&.. .
+ .Pp
+ .Fn rmdird
+ is similar, except the file will not be removed if any component of
+ .Fa path
+ is a symlink.  Useful to avoid a race condition between the time
+ .Fn stat
+ is run on a file and the time it is removed.
  .Sh RETURN VALUES
  A 0 is returned if the remove succeeds; otherwise a -1 is
  returned and an error code is stored in the global location
***************
*** 72,77 ****
--- 82,91 ----
  The named directory does not exist.
  .It Bq Er ELOOP
  Too many symbolic links were encountered in translating the pathname.
+ .It Bq Er ELOOP
+ .Fn rmdird
+ encountered a symbolic link in
+ .Fa path .
  .It Bq Er ENOTEMPTY
  The named directory contains files other than
  .Ql \&.
*** lib/libc/sys/unlink.2.orig	Sat Feb 27 19:29:34 1999
--- lib/libc/sys/unlink.2	Sat Feb 27 20:07:25 1999
***************
*** 1,4 ****
! .\"	$NetBSD: unlink.2,v 1.11 1998/08/29 08:32:43 lukem Exp $
  .\"
  .\" Copyright (c) 1980, 1991, 1993
  .\"	The Regents of the University of California.  All rights reserved.
--- 1,4 ----
! .\"	$NetBSD: unlink.2,v 1.11 1998/08/29 08:32:43 lukem Exp  $
  .\"
  .\" Copyright (c) 1980, 1991, 1993
  .\"	The Regents of the University of California.  All rights reserved.
***************
*** 37,48 ****
  .Dt UNLINK 2
  .Os BSD 4
  .Sh NAME
! .Nm unlink
  .Nd remove directory entry
  .Sh SYNOPSIS
  .Fd #include <unistd.h>
  .Ft int
  .Fn unlink "const char *path"
  .Sh DESCRIPTION
  The
  .Fn unlink
--- 37,51 ----
  .Dt UNLINK 2
  .Os BSD 4
  .Sh NAME
! .Nm unlink ,
! .Nm unlinkd
  .Nd remove directory entry
  .Sh SYNOPSIS
  .Fd #include <unistd.h>
  .Ft int
  .Fn unlink "const char *path"
+ .Ft int
+ .Fn unlinkd "const char *path"
  .Sh DESCRIPTION
  The
  .Fn unlink
***************
*** 58,63 ****
--- 61,75 ----
  If one or more process have the file open when the last link is removed,
  the link is removed, but the removal of the file is delayed until
  all references to it have been closed.
+ .Pp
+ .Fn unlinkd
+ behaves similarly, but will also refuse to remove the file if any
+ component of
+ .Fa path ,
+ other than the last (filename) portion, is a symbolic link.  Useful
+ to avoid a race condition between the time when
+ .Fn stat
+ is run on a file and the time when it is removed.
  .Sh RETURN VALUES
  Upon successful completion, a value of 0 is returned.
  Otherwise, a value of -1 is returned and
***************
*** 85,90 ****
--- 97,105 ----
  to be removed.
  .It Bq Er ELOOP
  Too many symbolic links were encountered in translating the pathname.
+ .It Bq Er ELOOP
+ .Fn unlinkd
+ encountered a symbolic link.
  .It Bq Er EPERM
  The named file is a directory and the effective user ID
  of the process is not the super-user, or the file system
*** bin/rm/rm.c.orig	Sat Feb 27 19:19:18 1999
--- bin/rm/rm.c	Sat Feb 27 20:07:25 1999
***************
*** 1,4 ****
! /*	$NetBSD: rm.c,v 1.24 1998/07/28 11:41:51 mycroft Exp $	*/
  
  /*-
   * Copyright (c) 1990, 1993, 1994
--- 1,4 ----
! /*	$NetBSD: rm.c,v 1.24 1998/07/28 11:41:51 mycroft Exp  $	*/
  
  /*-
   * Copyright (c) 1990, 1993, 1994
***************
*** 62,68 ****
  #include <pwd.h>
  #include <grp.h>
  
! int dflag, eval, fflag, iflag, Pflag, Wflag, stdin_ok;
  
  int	check __P((char *, char *, struct stat *));
  void	checkdot __P((char **));
--- 62,68 ----
  #include <pwd.h>
  #include <grp.h>
  
! int dflag, eval, fflag, iflag, Pflag, sflag, Wflag, stdin_ok;
  
  int	check __P((char *, char *, struct stat *));
  void	checkdot __P((char **));
***************
*** 80,85 ****
--- 80,90 ----
  #define NONEXISTENT(x) \
      ((x) == ENOENT || (x) == ENAMETOOLONG || (x) == ENOTDIR)
  
+ #define RMDIR(x) \
+      (sflag ? rmdird(x) : rmdir(x))
+ #define UNLINK(x) \
+      (sflag ? unlinkd(x) : unlink(x))
+ 
  /*
   * rm --
   *	This rm is different from historic rm's, but is expected to match
***************
*** 97,103 ****
  	(void)setlocale(LC_ALL, "");
  
  	Pflag = rflag = 0;
! 	while ((ch = getopt(argc, argv, "dfiPRrW")) != -1)
  		switch(ch) {
  		case 'd':
  			dflag = 1;
--- 102,108 ----
  	(void)setlocale(LC_ALL, "");
  
  	Pflag = rflag = 0;
! 	while ((ch = getopt(argc, argv, "dfiPRrsW")) != -1)
  		switch(ch) {
  		case 'd':
  			dflag = 1;
***************
*** 117,122 ****
--- 122,130 ----
  		case 'r':			/* Compatibility. */
  			rflag = 1;
  			break;
+ 		case 's':
+ 			sflag = 1;
+ 			break;
  		case 'W':
  			Wflag = 1;
  			break;
***************
*** 226,232 ****
  		switch (p->fts_info) {
  		case FTS_DP:
  		case FTS_DNR:
! 			if (!rmdir(p->fts_accpath) ||
  			    (fflag && errno == ENOENT))
  				continue;
  			break;
--- 234,240 ----
  		switch (p->fts_info) {
  		case FTS_DP:
  		case FTS_DNR:
! 			if (!RMDIR(p->fts_accpath) ||
  			    (fflag && errno == ENOENT))
  				continue;
  			break;
***************
*** 240,246 ****
  		default:
  			if (Pflag)
  				rm_overwrite(p->fts_accpath, NULL);
! 			if (!unlink(p->fts_accpath) ||
  			    (fflag && NONEXISTENT(errno)))
  				continue;
  		}
--- 248,254 ----
  		default:
  			if (Pflag)
  				rm_overwrite(p->fts_accpath, NULL);
! 			if (!UNLINK(p->fts_accpath) ||
  			    (fflag && NONEXISTENT(errno)))
  				continue;
  		}
***************
*** 291,301 ****
  		if (S_ISWHT(sb.st_mode))
  			rval = undelete(f);
  		else if (S_ISDIR(sb.st_mode))
! 			rval = rmdir(f);
  		else {
  			if (Pflag)
  				rm_overwrite(f, &sb);
! 			rval = unlink(f);
  		}
  		if (rval && (!fflag || !NONEXISTENT(errno))) {
  			warn("%s", f);
--- 299,309 ----
  		if (S_ISWHT(sb.st_mode))
  			rval = undelete(f);
  		else if (S_ISDIR(sb.st_mode))
! 			rval = RMDIR(f);
  		else {
  			if (Pflag)
  				rm_overwrite(f, &sb);
! 			rval = UNLINK(f);
  		}
  		if (rval && (!fflag || !NONEXISTENT(errno))) {
  			warn("%s", f);
***************
*** 438,444 ****
  usage()
  {
  
! 	(void)fprintf(stderr, "usage: rm [-dfiPRrW] file ...\n");
  	exit(1);
  	/* NOTREACHED */
  }
--- 446,452 ----
  usage()
  {
  
! 	(void)fprintf(stderr, "usage: rm [-dfiPRrsW] file ...\n");
  	exit(1);
  	/* NOTREACHED */
  }
*** bin/rm/rm.1.orig	Sat Feb 27 19:25:23 1999
--- bin/rm/rm.1	Sat Feb 27 20:07:25 1999
***************
*** 45,51 ****
  .Sh SYNOPSIS
  .Nm
  .Op Fl f | Fl i
! .Op Fl dPRrW
  .Ar file ...
  .Sh DESCRIPTION
  The
--- 45,51 ----
  .Sh SYNOPSIS
  .Nm
  .Op Fl f | Fl i
! .Op Fl dPRrsW
  .Ar file ...
  .Sh DESCRIPTION
  The
***************
*** 101,106 ****
--- 101,110 ----
  .It Fl r
  Equivalent to
  .Fl R .
+ .It Fl s
+ Do not unlink the file if any pathname component (except the filename
+ part) is a symlink.  This avoids certain race condition security
+ problems, such as when rm is used with find.
  .It Fl W
  Attempts to undelete the named files.
  Currently, this option can only be used to recover
*** bin/rmdir/rmdir.c.orig	Sat Feb 27 19:55:16 1999
--- bin/rmdir/rmdir.c	Sat Feb 27 20:07:25 1999
***************
*** 1,4 ****
! /*	$NetBSD: rmdir.c,v 1.16 1998/07/28 05:31:27 mycroft Exp $	*/
  
  /*-
   * Copyright (c) 1992, 1993, 1994
--- 1,4 ----
! /*	$NetBSD: rmdir.c,v 1.16 1998/07/28 05:31:27 mycroft Exp  $	*/
  
  /*-
   * Copyright (c) 1992, 1993, 1994
***************
*** 55,60 ****
--- 55,65 ----
  #include <locale.h>
  #include <unistd.h>
  
+ #define RMDIR(x) \
+         (sflag ? rmdird(x) : rmdir(x))
+ 
+ int sflag;
+ 
  int rm_path __P((char *));
  void usage __P((void));
  int main __P((int, char *[]));
***************
*** 69,80 ****
  
  	(void)setlocale(LC_ALL, "");
  
! 	pflag = 0;
! 	while ((ch = getopt(argc, argv, "p")) != -1)
  		switch(ch) {
  		case 'p':
  			pflag = 1;
  			break;
  		case '?':
  		default:
  			usage();
--- 74,88 ----
  
  	(void)setlocale(LC_ALL, "");
  
! 	pflag = sflag = 0;
! 	while ((ch = getopt(argc, argv, "ps")) != -1)
  		switch(ch) {
  		case 'p':
  			pflag = 1;
  			break;
+ 		case 's':
+ 			sflag = 1;
+ 			break;
  		case '?':
  		default:
  			usage();
***************
*** 94,100 ****
  			;
  		*++p = '\0';
  
! 		if (rmdir(*argv) < 0) {
  			warn("%s", *argv);
  			errors = 1;
  		} else if (pflag)
--- 102,108 ----
  			;
  		*++p = '\0';
  
! 		if (RMDIR(*argv) < 0) {
  			warn("%s", *argv);
  			errors = 1;
  		} else if (pflag)
***************
*** 117,123 ****
  			;
  		*++p = '\0';
  
! 		if (rmdir(path) < 0) {
  			warn("%s", path);
  			return (1);
  		}
--- 125,131 ----
  			;
  		*++p = '\0';
  
! 		if (RMDIR(path) < 0) {
  			warn("%s", path);
  			return (1);
  		}
***************
*** 130,136 ****
  usage()
  {
  
! 	(void)fprintf(stderr, "usage: rmdir [-p] directory ...\n");
  	exit(1);
  	/* NOTREACHED */
  }
--- 138,144 ----
  usage()
  {
  
! 	(void)fprintf(stderr, "usage: rmdir [-ps] directory ...\n");
  	exit(1);
  	/* NOTREACHED */
  }
*** bin/rmdir/rmdir.1.orig	Sat Feb 27 19:55:25 1999
--- bin/rmdir/rmdir.1	Sat Feb 27 20:07:26 1999
***************
*** 1,4 ****
! .\"	$NetBSD: rmdir.1,v 1.11 1997/10/20 08:53:22 enami Exp $
  .\"
  .\" Copyright (c) 1990, 1993
  .\"	The Regents of the University of California.  All rights reserved.
--- 1,4 ----
! .\"	$NetBSD: rmdir.1,v 1.11 1997/10/20 08:53:22 enami Exp  $
  .\"
  .\" Copyright (c) 1990, 1993
  .\"	The Regents of the University of California.  All rights reserved.
***************
*** 44,50 ****
  .Nd remove directories
  .Sh SYNOPSIS
  .Nm
! .Op Fl p
  .Ar directory ...
  .Sh DESCRIPTION
  The rmdir utility removes the directory entry specified by
--- 44,50 ----
  .Nd remove directories
  .Sh SYNOPSIS
  .Nm
! .Op Fl ps
  .Ar directory ...
  .Sh DESCRIPTION
  The rmdir utility removes the directory entry specified by
***************
*** 60,66 ****
  .Nm
  tries to remove it.
  .Pp
! The following option is available:
  .Bl -tag -width Ds
  .It Fl p
  Each
--- 60,66 ----
  .Nm
  tries to remove it.
  .Pp
! The following options are available:
  .Bl -tag -width Ds
  .It Fl p
  Each
***************
*** 71,76 ****
--- 71,83 ----
  (See
  .Xr rm 1
  for fully non-discriminant recursive removal.)
+ .It Fl s
+ Do not remove the file if any component of the specified pathname is a
+ symbolic link.  This can avoid certain race conditions caused by
+ running
+ .Nm
+ with
+ .Xr find 1 .
  .El
  .Pp
  The
***************
*** 85,91 ****
  An error occurred.
  .El
  .Sh SEE ALSO
! .Xr rm 1
  .Sh STANDARDS
  The
  .Nm
--- 92,99 ----
  An error occurred.
  .El
  .Sh SEE ALSO
! .Xr rm 1 ,
! .Xr rmdir 2
  .Sh STANDARDS
  The
  .Nm
*** etc/daily.orig	Sat Feb 27 19:48:01 1999
--- etc/daily	Sat Feb 27 20:07:26 1999
***************
*** 1,6 ****
  #!/bin/sh -
  #
! #	$NetBSD: daily,v 1.29 1999/01/06 03:24:06 abs Exp $
  #	@(#)daily	8.2 (Berkeley) 1/25/94
  #
  
--- 1,6 ----
  #!/bin/sh -
  #
! #	$NetBSD: daily,v 1.29 1999/01/06 03:24:06 abs Exp  $
  #	@(#)daily	8.2 (Berkeley) 1/25/94
  #
  
***************
*** 32,68 ****
  echo ""
  echo "Uptime: " `uptime`
  
! # Uncommenting any of the finds below would open up a race condition attack
! # based on symlinks, potentially allowing removal of any file on the system.
! #
! #echo ""
! #echo "Removing scratch and junk files:"
! #if [ -d /tmp -a ! -h /tmp ]; then
! #	cd /tmp && {
! #	find . -type f -atime +3 -exec rm -f -- {} \;
! #	find . ! -name . -type d -mtime +1 -exec rmdir -- {} \; \
! #	    >/dev/null 2>&1; }
! #fi
  
! #if [ -d /var/tmp -a ! -h /var/tmp ]; then
! #	cd /var/tmp && {
! #	find . ! -name . -atime +7 -exec rm -f -- {} \;
! #	find . ! -name . -type d -mtime +1 -exec rmdir -- {} \; \
! #	    >/dev/null 2>&1; }
! #fi
  
  # Additional junk directory cleanup would go like this:
  #if [ -d /scratch -a ! -h /scratch ]; then
  #	cd /scratch && {
! #	find . ! -name . -atime +1 -exec rm -f -- {} \;
! #	find . ! -name . -type d -mtime +1 -exec rmdir -- {} \; \
  #	    >/dev/null 2>&1; }
  #fi
  
! #if [ -d /var/rwho -a ! -h /var/rwho ] ; then
! #	cd /var/rwho && {
! #	find . ! -name . -mtime +7 -exec rm -f -- {} \; ; }
! #fi
  
  TMPDIR=/tmp/_daily.$$
  
--- 32,65 ----
  echo ""
  echo "Uptime: " `uptime`
  
! echo ""
! echo "Removing scratch and junk files:"
! if [ -d /tmp -a ! -h /tmp ]; then
! 	cd /tmp && {
! 	find . -type f -atime +3 -exec rm -fs -- {} \;
! 	find . ! -name . -type d -mtime +1 -exec rmdir -s -- {} \; \
! 	    >/dev/null 2>&1; }
! fi
  
! if [ -d /var/tmp -a ! -h /var/tmp ]; then
! 	cd /var/tmp && {
! 	find . ! -name . -atime +7 -exec rm -fs -- {} \;
! 	find . ! -name . -type d -mtime +1 -exec rmdir -s -- {} \; \
! 	    >/dev/null 2>&1; }
! fi
  
  # Additional junk directory cleanup would go like this:
  #if [ -d /scratch -a ! -h /scratch ]; then
  #	cd /scratch && {
! #	find . ! -name . -atime +1 -exec rm -fs -- {} \;
! #	find . ! -name . -type d -mtime +1 -exec rmdir -s -- {} \; \
  #	    >/dev/null 2>&1; }
  #fi
  
! if [ -d /var/rwho -a ! -h /var/rwho ] ; then
! 	cd /var/rwho && {
! 	find . ! -name . -mtime +7 -exec rm -fs -- {} \; ; }
! fi
  
  TMPDIR=/tmp/_daily.$$
  
>Audit-Trail:
>Unformatted: