[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Help testing a race condition on OpenBSD



A paper at USENIX Security 2004 described a method for avoiding the
classic access()/open() race condition:

[Dean, Hu] http://www.usenix.org/events/sec04/tech/dean.html

Nikita Borisov, Naveen Sastry and I have found a way to defeat the
technique described in this paper, and I've tested it on Linux 2.4,
Linux 2.6, FreeBSD 4.10-PRERELEASE, and Solaris 9.

I would like to test it on OpenBSD, too.  If anyone has a lightly loaded
machine running OpenBSD (3.5 or the current prerelease) that they can
give me two accounts on, then I would be very grateful.  We'd certainly
give you an acknowledgement in the paper we're writing on this attack.

Note that this is just an attack on the _idea_ presented in the above
paper.  We don't have an attack on any real software.  All our testing
involves just attacking our own example "victim" program.  Your system's
security will not be affected by our experiments.

Thanks in advance,
Rob


P.S. Here's some info on the attack:

Sometimes a setuid program needs to test whether the invoking user could
open a file.  The access() system call was introduced to answer this
question, but there's a race condition

  if (access("path/file", R_OK))
    exit(1);
  /* If attacker gets to run here, he can change some symlinks and 
     trick the setuid victim into opening a different file. */
  fd = open("path/file", O_RDONLY);

The Dean/Hu defense basically does:

  access();
  open();
  fstat();
  <save inode and device numbers>
  for (i = 0; i < K; i++) {
    access();
    open();
    fstat();
    <check that inode and device numbers are the same>
  }

Our attack creates a "maze" of 2*K+1 directories, each of which contains
a large (~100-1000) number of nested subdirectories and carefully
arranged symlinks.  We then force all these directories out of the
buffer cache (by, e.g. reading a bunch of files from another part of the
disk), so that when the victim performs the first call to access(), it
will sleep on IO.  Meanwhile, the attacker watches the atime on the
first directory in the maze to detect when the victim begins the
access() call.  At that point, the attacker switches around the symlinks
in the maze so that 
1. The access call will succeed
2. The open call will open a file the user doesn't have access to
3. The victim will again have to perform IO when it makes the open()
call.  
By repeating this process, we can ensure that the victim sleeps on every
access() and open() call, and the attacker will have the chance to do
whatever work it needs to do to trick the victim into opening a file it
shouldn't.

A more sophisticated version of the Dean/Hu defense randomly performs an
access() or open() on each iteration of the loop.  This defeats the
above attack, since we don't know whether to make the victim see a
"good" or "bad" file.  However, if we can distinguish whether the victim
is performing an access() or open(), then we can defeat this defense,
too.  On Linux and FreeBSD access() is implemented by temporarily
changing the caller's real uid, which can be observed via proc, so the
attacker can tell whether the victim is doing access() or open() by
looking at it's uid.  Solaris makes the current syscall number of every
process available in proc, so we can just look at that.

I've tested all this on Linux 2.4, 2.6, FreeBSD, and Solaris, and it all
works.  From my reading of the OpenBSD source code, it looks like it
behaves just like Linux and FreeBSD, but I'd like to check.  That's why
I need two accounts on an OpenBSD box.  Thanks again for reading, and
for any help you can offer.



Visit your host, monkey.org