Search code examples
csecurityrace-conditionsymlinkposix-api

What is a good way to simulate O_NOFOLLOW on systems without this flag?


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 touching 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?


Solution

  • 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.