I would like to safely be able to simulate open
with O_CREAT | O_WRONLY | O_TRUNC | O_NOFOLLOW
and O_CREAT | O_WRONLY | O_APPEND | O_NOFOLLOW
on systems that do not support O_NOFOLLOW
. I can somewhat achieve what I am asking for with:
struct stat lst;
if (lstat(filename, &lst) != -1 && S_ISLNK(lst.st_mode)) {
errno = ELOOP;
return -1;
}
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC | O_NOFOLLOW, mode);
but then I introduce a race condition and possibly a security problem.
I thought about maybe creating a dummy file with only the user being able to write, kind of like touch
ing filename
, doing the lstat
check, and then using chmod
after I finish writing (to correct the file mode bits), but I could be overlooking something major (e.g. if the file at filename
exists, is not a regular file, or is already a symbolic link).
What do you think?
Your proposal still has a race condition:
- Mallory creates the link he wants you to follow;
- You
open()
the link with O_CREAT
;
- Mallory replaces the link with a regular file;
- You do your
lstat()
test, which passes (not a link);
- Mallory replaces the regular file with the link again.
You can fix this for the non-O_TRUNC
case by calling fstat()
on your open file descriptor as well as lstat()
on the path, and ensuring that the .st_dev
and .st_ino
members are the same.
However, this doesn't work if you're using O_TRUNC
- by the time you've discovered the deception, it's too late - Mallory has already induced you to truncate one of your important files.
I believe the traditional way to eliminate the hole without O_NOFOLLOW
support is:
- Create a temporary directory with mode
700
. Error (or retry) if mkdir()
fails due to existing directory;
- Create your new file within the temporary directory;
- Use
rename()
to atomically move the temporary file to the target name;
- Remove the temporary directory.