From whitis@dbd.com Sat May 25 02:55:58 1996 Received: from top.dbd.com (whitis@top.dbd.com [206.205.40.1]) by suburbia.net (8.7.4/Proff-950810) with ESMTP id CAA21026 for ; Sat, 25 May 1996 02:54:59 +1000 Received: (from whitis@localhost) by top.dbd.com (8.7.3/8.7.3) id MAA04427; Fri, 24 May 1996 12:56:08 -0400 (EDT) Date: Fri, 24 May 1996 12:56:05 -0400 (EDT) From: Mark Whitis To: Zygo Blaxell cc: linux-security@tarsier.cv.nrao.edu, best-of-security@suburbia.net Subject: Re: [linux-security] Things NOT to put in root's crontab In-Reply-To: <199605211710.NAA25637@myrus.com> Message-ID: MIME-Version: 1.0 Content-Type: TEXT/PLAIN; charset=US-ASCII 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 ; 804-962-4268 428-B Moseley Drive; Charlottesville, VA 22903