Hi all, when you're dealing with files in /tmp that are supposed to be re-opened (rather than opened once and then discarded) there's an established way to do it which goes like this: if (lstat(fname, &stb1) >= 0 && S_ISREG(stb1.st_mode)) { fd = open(fname, O_RDWR); if (fd < 0 || fstat(fd, &stb2) < 0 || ino_or_dev_mismatch(&stb1, &stb2)) raise_big_stink() } else { /* do the O_EXCL thing */ } Accepted wisdom has it that this protects you from symlink attacks. When trying to explain this to someone, I noticed that this is not quite what it does. It protects your application against symlinks to files that exist *before the call to lstat*. This sounds like I'm nitpicking, and my first reaction also was to try to find some convincing handwaving argument to dispel my concerns. However, consider an average setuid root application, written by a good-intentioned author like yours truly, using the above kind of code. Assume you want to perform a symlink attack on this application, and you've got lots of time on your hands. You create a regular file in /tmp (the one your targetted application is going to look at). When the application reaches the critical section of code between the lstat and the open, you stop it by sending it a SIGSTOP. You record the device and inode number of your /tmp file, remove it, and wait. Seconds, days or maybe even weeks later, somebody creates an interesting file with exactly the same inode (and device) number as the one you used with my setuid program. You now create a symlink in /tmp, pointing to that interesting file, and send my setuid application a SIGCONT. Zap, there goes the file. Sounds silly? Here are some reasons why this attack may not be that esoteric at all: - All symlink attacks can be improved by running them on an NFS mounted directory (easy for applications that heed the TMPDIR environment variable). In terms of file system race conditions, NFS acts as a kind of slo-mo glue. - Just like you can improve your chances of racing a particular /tmp file access by running unlink/symlink in a tight loop, you can `step' through an application by sending it SIGSTOP/SIGCONT in a tight loop. - It's not that hard to detect whether the targetted application is in the critical section of code. On Linux, it's very easy because /proc/$pid/stat will give you the instruction pointer. On other OSes it may be harder, but not impossible--for instance a lookup of /tmp/foo (as done by lstat()) will change the directory's atime. - If you have no, or a very large quota, you can increase the likelihood of a certain inode number being reused by first claiming as many inodes as you get, and then free the one you want someone interesting to allocate. - There are network services whose main job it is to create and remove interesting files--e.g. the NIS yppasswd daemon will create a temporary file, copy most of /etc/shadow to it, update your password entry, and replace the original /etc/shadow with it. Repeat until it uses an inode number you like. All of this doesn't make it a practical attack yet, but it surely demonstrates that the supposedly secure code shown above is far from secure. Comments? Suggestions? A happy new year to everyone, Olaf -- Olaf Kirch | --- o --- Nous sommes du soleil we love when we play okirat_private | / | \ sol.dhoop.naytheet.ah kin.ir.samse.qurax okirat_private +-------------------- Why Not?! ----------------------- UNIX, n.: Spanish manufacturer of fire extinguishers.
This archive was generated by hypermail 2b30 : Fri Apr 13 2001 - 15:25:40 PDT