[prev in list] [next in list] [prev in thread] [next in thread] 

List:       best-of-security
Subject:    BoS: Re: [linux-security] Things NOT to put in root's crontab
From:       Mark Whitis <whitis () dbd ! com>
Date:       1996-05-24 16:56:05
[Download RAW message or body]

On Tue, 21 May 1996, Zygo Blaxell wrote:

> >From Redhat's /etc/crontab file:
> ># Remove /var/tmp files not accessed in 10 days
> >43 02 * * * root find /var/tmp/* -atime +3 -exec rm -f {} \; 2> /dev/null
> >

There is another aspect of this that I find scary, the use of the "*"
wildcard where there is absolutely no need (assuming you dont have
a symbolic link - if you do, use "/var/tmp/." instead of "/var/tmp/*").   
Due to the order in which the shell processes things, it is not as 
dangerous as I thought at first but consider the implications of:

[637]% mkdir /tmp/junk
[638]% cd /tmp/junk
[639]% dir
total 4
lrwxrwxrwx   1 root     other           4 May 23 17:07 +foo -> /etc/
-rw-r--r--   1 root     other           0 May 23 16:58 -follow
-rw-r--r--   1 root     other           0 May 23 17:09 -name
-rw-r--r--   1 root     other           0 May 23 17:10 hosts.deny
[640]% echo *
+foo -follow -name hosts.deny 
[641]% find * -print
+foo/hosts.deny
[641]% find * -exec rm {} \;

Now it turns out that it will not do the same thing if the
path name preceeds the wildcard, fortunately.  But if the
entry had read:
43 02 * * * root cd /var/tmp;  find * -atime +3 -exec rm -f {} \; 2> /dev/null
then you wouldn't need race conditions to exploit symbolic links.

> Folks, do NOT use 'find' on a public directory with '-exec rm -f' as root
> Period.  Ever.  Delete it from your crontab *now* and finish reading the
> rest of this message later.

I am convinced of the danger of this.  I would also be very careful
about uses of chmod and chown commands, which don't even need
race conditions to be exploited on many systems:

For example:
   user# ln -s /etc/hosts.allow /home/user/foo/bar/baz
later:
   root# chown -R user /home/user


> The next easiest way to fix this is to replace 'rm' with a program that
> does not follow symlinks.  It must check that each filename component in
> turn by doing an lstat() of the directory, chdir() into the directory, 
> and further lstat()s to check that the device/inode number of '.' is 
> the same as the directory's device/inode number before chdir().  The 
> parameter of the 'unlink' or 'rmdir' system call must not contain a 
> slash; if it does, then the directory name before the slash can be 
> replaced by a symlink to a different directory between verification of 
> path components and the actual unlink() call. 

> Another way to fix this is with a smarter version of find.  A smart
> find does the chdir() and lstat() checks to make sure that it never

> Note that the 'smart' find without the post-chdir symlink tests won't
> work.  While smart-find is processing:
[...]
> and eventually smart-find will 'cd ..', but since the current directory
> of find has moved, '..' will move as well, and eventually smart-find
> will be one level too high and can start descending into other
> subdirectories of '/'.  To help this along you may need to create:

It is a bad idea for a program traversing a directory tree to
ever issue "cd .." (or more accurately chdir("..")) as part of that 
traversal.  Instead, use the fchdir() system call.
   - open() the current directory and store the descriptor in an array  
     with an index corresponding to the level of nesting if it
     is not already stored  (or instead of an array, you can
     use automatic variables in a self-recursive function).
   - fstat() the current direcory
   - open the next level directory, store value in the array
   - fchdir() to this directory
   - stat() the parent directory, compare with previous fstat()
     to see if you have crossed a symbolic link
   - do whatever you actually wanted to do here, including
     recursively descending directories.  Check files
     with  lstat() to see if they are symbolic links before
     processing.  open() file and compare inode numbers from
     fstat() and lstat().
     Use the open file descriptor to process the file if at
     all possible.
     Note there is still a race condition here if you are
     doing operations on files by name and not by file descriptor.
   - fchdir() to the previously stored parent directory
   - close() the child directory file descriptor
   - close() the parent file descriptor, if appropriate
If the directory is deep enough, you might fun out of file descriptors.

unfortunately, unix systems lack a flstat() and funlink()
calls (these would be to lstat() and unlink() as fchdir() is
informationto chdir()

Note that mandantory locks on systems which support them could probably
be used to exploit some of these race conditions deterministicly without 
actually needing to create thousands or millions of files if you can
lock directories:
   create /tmp/a/etc/file
   mandantory lock /tmp/a/etc
   wait for access time on /tmp/a to change
   wait for 60 seconds more, to give victom program time to continue
   replace /tmp/a with symbolic l
   release lock
 
I do not think the security precautions I give here or the ones
in Zygo's message eliminate race conditions for most
system calls other than unlink() (which does not follow
symbolic links - it deletes the link).
Race conditions will exist unless 
the operating system allows you to do all necessary operations,
including chmod(), chmod(), etc, on file descriptors or
with link proof versions of the commands instead of path names 
and you make use of that capability and
you do an lstat() before and an fstat() after an open and
compare the results.

consider, the following sequence of system calls, between
two programs.
     -- victim --                      -- attacker--
                                      chdir("/tmp/foo");
   chdir("/tmp/foo");
   any checks on current directory
   lstat("bar");                     
                                     unlink("bar");
                                     symlink("/etc/hosts.deny","bar");
   unlink("bar");

This is okay for the unlink() because it will delete the symbolic
link instead of the parent.  But what about a chmod() or
chown() call instead of unlink?  "chown -R" or
"chmod -R" commands as root may be subject to race conditions,
not matter what precautions you take, unless they use file
descriptor or link suppressing forms of these commands.

It simply doesn't matter how carefully you verify the path
if the last component can be changed in many situations.

Here is a comparison of some related calls between various OSes:

           Linux*    AIX 3.2   Sunos 4.1.3   Solaris 2.5

chown()      Y          Y          Y            Y                    
fchown()     Y          Y          Y            Y
lchown()     N          N          N            Y

chmod()      Y          Y          Y            Y
fchmod()     Y          Y          Y            Y
lchmod()     N          N          N            N

stat()       Y          Y          Y            Y
lstat()      Y          Y          Y            Y
fstat()      Y          Y          Y            Y
fstatx()     N          Y          N            N
flstat()     N          N          N            N

access()     Y          Y          Y            Y
faccess()    N          Y          N            N
faccessx()   N          Y          N            N

Personally, I think you should be able to issue any system call
on a file or directory using a file descriptor; in fact, I would
make that the primary interface - old fashioned calls using a pathname
could even be emulated (either in the kernel or in libc) based
on the file descriptor calls combined with an open().

Linux man pages were from the WWW site http://www.ssc.com/linux/man.html
which does not specify a version number.

fstatx() allows you to obtain information on symbolic links
given a file descriptor.  accessx() allows you to check
whether a different user has access.

When squashing symbolic links, you should have the option to
follow symbolic links if either of the following conditions
is true:
   - the link and its destination share the same owner
   - the link is owned by root. 
This is necessary for situations such as:
   ~whitis = /home/whitis
   /home/whitis --> /disk0/home/whitis   (owner root)
   ~whitis/public_html/index.html
which exist at most large sites.

--
Mark Whitis <whitis@dbd.com>;  804-962-4268
428-B Moseley Drive; Charlottesville, VA 22903

[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic