NetBSD Problem Report #7060
Received: (qmail 28070 invoked from network); 28 Feb 1999 06:42:18 -0000
Message-Id: <199902280642.XAA22222@rupert.honors.montana.edu>
Date: Sat, 27 Feb 1999 23:42:17 -0700 (MST)
From: Chris Jones <cjones@rupert.honors.montana.edu>
Reply-To: cjones@netbsd.org
To: gnats-bugs@gnats.netbsd.org
Subject: find and rm
X-Send-Pr-Version: 3.95
>Number: 7060
>Category: security
>Synopsis: find and rm
>Confidential: no
>Severity: serious
>Priority: low
>Responsible: security-officer
>State: closed
>Class: change-request
>Submitter-Id: net
>Arrival-Date: Sat Feb 27 22:50:00 +0000 1999
>Closed-Date: Tue Feb 06 13:26:01 +0000 2007
>Last-Modified: Wed Feb 07 06:20:01 +0000 2007
>Originator: Chris Jones
>Release: Feb 23, 1999
>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?"
>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.$$
>Release-Note:
>Audit-Trail:
From: "Erik E. Fair" <fair@clock.org>
To: cjones@rupert.honors.montana.edu
Cc: gnats-bugs@gnats.netbsd.org
Subject: Re: security/7060: find and rm
Date: Sun, 28 Feb 1999 01:54:21 -0800
I don't think that a new system call for this is a good idea.
How big is the window that the attacker has to hit?
Can an attacker deterministically hit the window?
I'd like to see more evidence that this is a real problem.
Erik <fair@clock.org>
From: Richard Earnshaw <rearnsha@arm.com>
To: cjones@rupert.honors.montana.edu
Cc: gnats-bugs@gnats.netbsd.org, current-users@netbsd.org
Subject: Re: security/7060: find and rm
Date: Mon, 01 Mar 1999 10:50:05 +0000
cjones@rupert.honors.montana.edu said:
> >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.
Have I missed something? I can't see the problem here. rm will remove
the symlink not the file pointed to (unless some magic options are being
used with rm).
From: Jaromir Dolecek <dolecek@ics.muni.cz>
To: richard.earnshaw@arm.com
Cc: cjones@rupert.honors.montana.edu, gnats-bugs@gnats.netbsd.org
Subject: Re: security/7060: find and rm
Date: Mon, 1 Mar 1999 13:10:39 +0100 (MET)
Richard Earnshaw wrote:
>
> cjones@rupert.honors.montana.edu said:
> > >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.
>
> Have I missed something? I can't see the problem here. rm will remove
> the symlink not the file pointed to (unless some magic options are being
> used with rm).
Problem:
1. mkdir /tmp/foo
2. touch /tmp/foo/passwd
3. let daily scan fs
4. in right time do:
rm -r /tmp/foo
ln -s /etc /tmp/foo
5. guess what happens when daily would issue ``rm /tmp/foo/passwd''
--
Jaromir Dolecek <dolecek@ics.muni.cz> http://www.ics.muni.cz/~dolecek/
"The only way how to get rid temptation is to yield to it." -- Oscar Wilde
From: Manuel Bouyer <bouyer@antioche.lip6.fr>
To: richard.earnshaw@arm.com
Cc: cjones@rupert.honors.montana.edu, gnats-bugs@gnats.netbsd.org,
current-users@netbsd.org
Subject: Re: security/7060: find and rm
Date: Mon, 1 Mar 1999 13:47:08 +0100
On Mar 1, Richard Earnshaw wrote
> Have I missed something? I can't see the problem here. rm will remove
> the symlink not the file pointed to (unless some magic options are being
> used with rm).
>
The problem is if you remplace a directory with a symlink to another directory.
rm may then remove entries from the target directory, before removing
the symlink.
--
Manuel Bouyer, LIP6, Universite Paris VI. Manuel.Bouyer@lip6.fr
--
From: Chris Jones <cjones@rupert.honors.montana.edu>
To: "Erik E. Fair" <fair@clock.org>
Cc: gnats-bugs@gnats.netbsd.org
Subject: Re: security/7060: find and rm
Date: 01 Mar 1999 09:10:19 -0700
>>>>> "Erik" == Erik E Fair <fair@clock.org> writes:
Erik> I don't think that a new system call for this is a good idea.
I'm not convinced it is, either. But it's the only decent solution I
could come up with, and nobody else had come up with anything in the
year or so that this problem has been around.
Erik> How big is the window that the attacker has to hit?
I guess it would depend on the VM activity of the system. If it's
busy, the window's going to be larger. Practically speaking, it's
enough time to fork, do a path search, exec, load a new binary, and
then run unlink.
Erik> Can an attacker deterministically hit the window?
I don't know.
Erik> I'd like to see more evidence that this is a real problem.
Apparently somebody thinks it is, because the find | rm thing has
been disabled in /etc/daily for quite some time. And I'd like to go
with a deterministic solution rather than a probabilistic one, based
on the idea that, "It's unlikely that anybody'll exploit this bug."
Chris
--
-----------------------------------------------------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?"
State-Changed-From-To: open->analyzed
State-Changed-By: david
State-Changed-When: Sat Aug 10 20:22:21 PDT 2002
State-Changed-Why:
Added some discussion of how to implement a fix.
Responsible-Changed-From-To: security-officer->elad
Responsible-Changed-By: elad@netbsd.org
Responsible-Changed-When: Sun, 18 Sep 2005 20:42:19 +0000
Responsible-Changed-Why:
I'll handle it.
Responsible-Changed-From-To: elad->security-officer
Responsible-Changed-By: elad@netbsd.org
Responsible-Changed-When: Sun, 22 Jan 2006 19:55:23 +0000
Responsible-Changed-Why:
sink
From: Elad Efrat <elad@NetBSD.org>
To: gnats-bugs@NetBSD.org
Cc: netbsd-bugs@netbsd.org
Subject: re: security/7060
Date: Mon, 05 Feb 2007 22:59:14 +0200
I recommend to close this pr now that it is possible to use per-user
/tmp directories.
-e.
From: SODA Noriyuki <soda@sra.co.jp>
To: gnats-bugs@NetBSD.org
Cc:
Subject: Re: security/7060 (find and rm)
Date: Tue, 6 Feb 2007 10:53:20 +0900
David wrote:
> One solution would be adding an option to find, which could then
> perform the unlink,
FreeBSD already has -delete option in find(1).
How about importing their change?
--
soda
State-Changed-From-To: analyzed->closed
State-Changed-By: elad@netbsd.org
State-Changed-When: Tue, 06 Feb 2007 13:26:01 +0000
State-Changed-Why:
as suggested by david@ & soda@, I imported a -delete for our find(1) from freebsd. personally I think perusertmp can be used in this case, but we'll let the admin decide. :)
From: matthew green <mrg@eterna.com.au>
To: gnats-bugs@NetBSD.org
Cc: security-officer@netbsd.org, security-alert@netbsd.org,
gnats-admin@netbsd.org, elad@netbsd.org, cjones@netbsd.org
Subject: re: security/7060 (find and rm)
Date: Wed, 07 Feb 2007 09:08:57 +1100
cool. find ... -delete will save me typing. :-)
however, i don't understand how per user /tmp solves this problem.
i also don't see how the implemented "-delete" option solves the
problem, merely shortens the race. it probably could do so more
correctly if it chdir()'d to the right directly and checked that
the file in that dir was the same stat() as the one i wants to
delete...
i also wonder about the automatic stripping of append/immutable
bits by find's "-delete" option. to me, that seems counter
intuitive. i would expect it to fail by default, and require
either manual changing or an explicit option.
.mrg.
From: Elad Efrat <elad@NetBSD.org>
To: matthew green <mrg@eterna.com.au>
Cc: gnats-bugs@NetBSD.org
Subject: Re: security/7060 (find and rm)
Date: Wed, 07 Feb 2007 03:48:19 +0200
matthew green wrote:
> however, i don't understand how per user /tmp solves this problem.
">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."
> i also don't see how the implemented "-delete" option solves the
> problem, merely shortens the race.
I have mentioned that already. I was asked to implement freebsd's
-delete because it made sense to those who asked me to. given it
can be used to save typing :) I figured why not.
> it probably could do so more
> correctly if it chdir()'d to the right directly and checked that
> the file in that dir was the same stat() as the one i wants to
> delete...
comparing stat() is incorrect and not needed. (not to say ugly :)
> i also wonder about the automatic stripping of append/immutable
> bits by find's "-delete" option. to me, that seems counter
> intuitive. i would expect it to fail by default, and require
> either manual changing or an explicit option.
I merely imported the same behavior from freebsd. you may change
this function as you see fit, given others don't mind, of course...
-e.
From: matthew green <mrg@eterna.com.au>
To: Elad Efrat <elad@NetBSD.org>
Cc: gnats-bugs@NetBSD.org
Subject: re: security/7060 (find and rm)
Date: Wed, 07 Feb 2007 14:50:31 +1100
> however, i don't understand how per user /tmp solves this problem.
">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."
so when the daily find runs as root over the /private/*/tmp how
does this PR get solved now? AFAICT, the is no safe way to do
this without changing find "-delete" to do the chdir combo i
suggested previously. if you run find over writeable-by-users
directories, this problem exists.
per-user-tmp solves a bunch of problems, but not this one.
.mrg.
From: Elad Efrat <elad@NetBSD.org>
To: matthew green <mrg@eterna.com.au>
Cc: gnats-bugs@NetBSD.org
Subject: Re: security/7060 (find and rm)
Date: Wed, 07 Feb 2007 06:10:28 +0200
matthew green wrote:
>
> > however, i don't understand how per user /tmp solves this problem.
>
> ">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."
>
>
> so when the daily find runs as root over the /private/*/tmp how
> does this PR get solved now? AFAICT, the is no safe way to do
> this without changing find "-delete" to do the chdir combo i
> suggested previously. if you run find over writeable-by-users
> directories, this problem exists.
>
> per-user-tmp solves a bunch of problems, but not this one.
okay. I still think that the current race happens because there is
only one /tmp dir. given a per-user tmp dir, and quotas, a daily
check don't even need to care about cleaning scartch files for
the various users on the system.
about what you suggested (comparing struct stat), iirc it was
shown that in some cases an attacker can achieve the same data.
personally, as the commands in question are commented out and not
even mentioned outside the /etc/daily file (or so I'm guessing),
and given my opinion above, this issue is not something I'm worried
about.
-e.
From: matthew green <mrg@eterna.com.au>
To: Elad Efrat <elad@NetBSD.org>
Cc: gnats-bugs@NetBSD.org
Subject: re: security/7060 (find and rm)
Date: Wed, 07 Feb 2007 15:29:23 +1100
matthew green wrote:
>
> > however, i don't understand how per user /tmp solves this problem.
>
> ">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."
>
>
> so when the daily find runs as root over the /private/*/tmp how
> does this PR get solved now? AFAICT, the is no safe way to do
> this without changing find "-delete" to do the chdir combo i
> suggested previously. if you run find over writeable-by-users
> directories, this problem exists.
>
> per-user-tmp solves a bunch of problems, but not this one.
okay. I still think that the current race happens because there is
only one /tmp dir. given a per-user tmp dir, and quotas, a daily
check don't even need to care about cleaning scartch files for
the various users on the system.
i'm not so sure. i'd rather fix the tools to work properly than
have them only work securely with a lot of setup.
about what you suggested (comparing struct stat), iirc it was
shown that in some cases an attacker can achieve the same data.
do you have a pointer for this? thanks.
personally, as the commands in question are commented out and not
even mentioned outside the /etc/daily file (or so I'm guessing),
and given my opinion above, this issue is not something I'm worried
about.
i just wanted it to be clear that this doesn't solve the problem
that this PR was logged for, given that i expect that cleaning
these private tmp's will be wanted.
.mrg.
From: Elad Efrat <elad@NetBSD.org>
To: matthew green <mrg@eterna.com.au>
Cc: gnats-bugs@NetBSD.org
Subject: Re: security/7060 (find and rm)
Date: Wed, 07 Feb 2007 06:43:33 +0200
matthew green wrote:
> i'm not so sure. i'd rather fix the tools to work properly than
> have them only work securely with a lot of setup.
how is it a lot of setup? security(8) states:
# /etc/rc.d/perusertmp start
> about what you suggested (comparing struct stat), iirc it was
> shown that in some cases an attacker can achieve the same data.
>
> do you have a pointer for this? thanks.
I would have to search the lists (not even sure it was on a netbsd
list) and as I already mentioned, afaic, this is a non-issue.
> i just wanted it to be clear that this doesn't solve the problem
> that this PR was logged for, given that i expect that cleaning
> these private tmp's will be wanted.
well, let's see:
- the relevant commands (again, iiuc) were commented out in 1996
(rev 1.17 of daily) and a comment was added as to why they are
commented out in 1999 (rev 1.29 of daily).
- the pr was debated since its arrival in feb 1999 up to march
of the same year. in 2002 it seems the audit trail was appended.
- in sep 2005 I took it over to handle it, but I had other netbsd
work, I withdrew "ownership".
- few days ago (feb 2007) we implemented per-user-/tmp, and I
commented that I think this pr can be closed.
- someone disagreed, and suggested to import freebsd's -delete
option to find(1). I mentioned it doesn't solve the problem,
and that the issue is really a subset of a much larger problem
that if the admin cares about he'll have to take further measures
that can also mitigate this one.
I imported it anyway, though, to make people happy. :)
- now we have per-user-/tmp and find -delete. the commands seem to
be commented out since 1996. to me, this is a non-issue.
you may do whatever it is you like with this pr, but do not cc me
on further mails on this issue, please. if anyone considered it a
real problem, than other people than myself would have done something
in the course of 8 (11?) years.
-e.
From: matthew green <mrg@eterna.com.au>
To: Elad Efrat <elad@NetBSD.org>
Cc: gnats-bugs@NetBSD.org
Subject: re: security/7060 (find and rm)
Date: Wed, 07 Feb 2007 17:07:56 +1100
> about what you suggested (comparing struct stat), iirc it was
> shown that in some cases an attacker can achieve the same data.
>
> do you have a pointer for this? thanks.
I would have to search the lists (not even sure it was on a netbsd
list) and as I already mentioned, afaic, this is a non-issue.
i can't find anything. i'm quite curious what this attack vector
looks like.
.mrg.
From: Elad Efrat <elad@NetBSD.org>
To: matthew green <mrg@eterna.com.au>
Cc: gnats-bugs@NetBSD.org
Subject: Re: security/7060 (find and rm)
Date: Wed, 07 Feb 2007 08:17:23 +0200
matthew green wrote:
>
> > about what you suggested (comparing struct stat), iirc it was
> > shown that in some cases an attacker can achieve the same data.
> >
> > do you have a pointer for this? thanks.
>
> I would have to search the lists (not even sure it was on a netbsd
> list) and as I already mentioned, afaic, this is a non-issue.
>
>
> i can't find anything. i'm quite curious what this attack vector
> looks like.
http://ilja.netric.org/files/Unusual%20bugs%2023c3.pdf
slide 46:
"File race conditions
Reopening in a file in /tmp in a (un)safe manner.
Suggested solutions: ``lstat/open/fstat dance''
–Compare both devices and inodes
–If they match it must be the same file
Not true
Still a race
–File could be deleted
–Create new file with the same inode nr"
slide 47:
"``But if you compare more from the stat struct it's safe''
Not true"
and it continues with an analysis of struct stat. granted, netbsd has
a member that may help in this ugly form of verification (the "birth"
date of the inode) -- but as I have already mentioned numerous times,
this issue does not interest me.
-e.
>Unformatted:
This certainly could be exploitable. This isn't a problem that is
specific to /etc/daily, though it is an issue there. One solution would
be adding an option to find, which could then perform the unlink,
instead of execing rm for the job. Find could then take the proper
precausions to make sure the unlinked file was the file which was stated.
I'm marking this analyzed now, and moving the priority to 'low', since the
/etc/daily issue was addresses years ago but disabling those finds.
David
(Contact us)
$NetBSD: query-full-pr,v 1.39 2013/11/01 18:47:49 spz Exp $
$NetBSD: gnats_config.sh,v 1.8 2006/05/07 09:23:38 tsutsui Exp $
Copyright © 1994-2007
The NetBSD Foundation, Inc. ALL RIGHTS RESERVED.