the problem is, you can't really avoid this kind of race condition. Not in a pre-emptive system. You can't stat a file and open it at the same time: the whole point of the stat is to see if you SHOULD open it. The best you could do is maybe stat the file after you open it. But I'm not sure what other problems that would cause.
Obtain a "session ID" through stat and always use that to access the file:
id := stat("/a")
read(id)
write(id, ..) // doesn't protect race access to a file,
// but at least you're not using the
// pathname twice, which already solves a
// lot of problems
Use transactions:
transid := ftransopen()
f := stat("/a")
read("/a")
write("/a", ..)
ftransend(transid) // fails if clashes with other fs changes
Have "last modification ID" counters that change for every modification to a file, and allow CAS-style operations:
mod := stat_mod("/a")
open_mod("/a", mod)
read_mod("/a", mod)
mod2 := write_mod("/a", ..., mod) // fails if /a modified since mod
mod3 := chown_mod("/a", ..., mod2) // fails if modified since mod2
etc.
All with their own pros and cons, of course, but definitely possible. I'm just curious if there's a specific implementation that's gaining momentum.
What's so strange about stat() on the file after you open it? It's a security check against a known and abused exploit and it's not even hard to do. This seems like a simple fix to the problem.
Just make sure you fstat() the file descriptor and not stat() the file to avoid a second race condition where a malicious actor undoes their attack immediately after you open() the file to hide the evidence.
fstat is a more recent system call than stat, so old code bases do not use it, and mail.local is quite ancient.
Although a quick lookup tells me that it appeared in 4.3BSD-Tahoe (June 1988) and SysVR4 (October 1988), so one would have expected all reasonable distributions to have gotten with the program by now.
A little digging suggests that mail.local appeared in Version 7 Unix from 1979, so it is not a surprise that it doesn't include a syscall invented 9 years later.
Still, that syscall is 28 years old now. It's kind of embarrassing that nobody has gone though and checked for ancient and obvious privilege escalation issues like this. Or I guess they have, but on different OSes. This is one big downside to fragmentation, getting fixes distributed to all of the fragments.
fstat() isn't the complete fix here - it doesn't protect you against opening/creating an unintended file through a symlink, for which you need O_NOFOLLOW (which is a bit more recent).
I could think of multiple ways of fixing this problem changing the API. For instance they could have an api that opens the file only if it is not a synlink.