in reply to opening files: link checking and race conditions

A general comment on various posts above. People just don't seem to be getting what a race condition is. Imagine I run the following in the background:
while (1) { symlink '/tmp/foo', '/home/user/.rhosts'; unlink '/tmp/foo'; }
Then when I run the OP's suid script, there's a good chance that between the test and the open, the symlink may appear; thus I get to create or truncate any file owned by that user. Even checking afterwards that the just-opened file is not a link wont necessarily work.

Dave.

Replies are listed 'Best First'.
Re^2: opening files: link checking and race conditions
by danderson (Beadle) on Aug 03, 2005 at 11:12 UTC
    dave_the_m: O_NOFOLLOW is a great start. Since, as a rule, suid/guid only matters on *nix scripts, and in fact this one is designed specifically for *nix (it uses /home/username in it) that'll do nicely. I'll put a warning in the header to ensure that it's only run on systems that correctly implement O_NOFOLLOW; I'm suprised I hadn't seen that flag in my wanderings. Thanks!

    Tanktalus: sudo doesn't help; I don't care who's running it, in fact anyone should be able to (that's the point of suid, right? Run as one user when you're really another). Also, writing via a symlink /is/ often a Very Bad Idea; see the example in dave_the_m's reply below. If the user is suid root, then you've appended to rhosts, and suddenly there are people being trusted who shouldn't be.

    graff, sk: I know it looks that simple, but bear in mind that processes run "concurrently" (not really, unless you're on a multiprocessor system, but they fake it). So looking at dave_the_m's example, all you need is for the timing to be just right. Since as a rule a malicious attacker doesn't mind running something (probably nice'd) a few thousand times to get an attack to work, testing before and after isn't enough. Google "atomicity" for more info.

    So: a general solution? Seems like no. One that will work for this situation? Yes. Suid isn't common on most systems that don't support the O_NOFOLLOW flag (believe it or not, suid exists to a degree on windows - eg. run 'at' as admin, have it open cmd.exe - bam you've escalated to Machine and can overwrite/modify/delete absolutely anything), so hopefully my incessant curiosity won't spend any more of my time on the topic, until it's a neccessary question again.

    Thanks for all your replies, I really appreciate them! Oh monks, you have yet again shown this follower a better path.
Re^2: opening files: link checking and race conditions
by graff (Chancellor) on Aug 03, 2005 at 13:59 UTC
    Then when I run the OP's suid script, there's a good chance that between the test and the open, the symlink may appear; thus I get to create or truncate any file owned by that user. (emphasis added)

    But,  open( O, ">>foo" ) does not truncate the output file (and dying before writing leaves it unmodified). So why would there still be a problem with the code suggested in my second reply?

      Because you're still vulnerable to the race condition, it's just very timing dependent. Here's how the timeline falls out. At first, the file doesn't exist.
      unless ( -l "foo" ) { # we get here fine #*now* the link is created open( FH, ">>foo" ) or die "foo: $!"; #and now it's removed again } die "Link attack detected" if ( -l "foo" ); #too late, the link is gone already, so we didn't die.
      Now, it's really, really hard to get the timing to work perfectly for that, but that doesn't mean that it won't happen sometimes. So if you start a program creating and removing the link very quickly, and at the same time run the other program again and again, sooner or later you're going to get unlucky.
        Thank you. The problem with my approach is now clear -- and in the timing scenario you laid out, the script will have no problem writing to that file handle after the second symlink check passes. Once the "open" has succeeded, FH is pointing directly to the actual target file, and deletion of the symlink has no impact on the ability to write successfully to the file.

        Well then, here's one last try:

        unless ( -l "foo" ) { # we get here fine #*now* the link is created open( FH, ">>foo" ) or die "foo: $!"; #and now it's removed again } die "Link attack detected" if ( -l "foo" or ! -e _ ); # we have checked for both existence and "type == symlink" on the same + stat call, # so either it's a link, or it's non-existent, or it's safe to write o +utput