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. | [reply] [d/l] |
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
| [reply] [d/l] |
# 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
Or you opened the symlink, someone replaced it with a regular file, and you statted that. Again, you can make it pretty hard to exploit, but you can't get rid of it entirely without either doing it atomically at the time of the open (O_NOFOLLOW) or can check on the filehandle you actually have open (and not what that name points to now).
What might work (but I'm not at all certain) would be to stat the filehandle you have open, stat the file you thought you opened, and confirm that they have the same inode and that it isn't a symlink.
| [reply] [d/l] [select] |