Search code examples
c++c++11race-conditioncoveritycoverity-prevent

Avoid TOCTOU (time-of-check, time-of-use) race condition between stat and rename


How to avoid TOCTOU(time-of-check, time-of-use) race condition for race condition between stat and rename for LOGFILE ?

Required to move the log file after its size value exceeds the max size.

result = stat(LOGFILE, & data);
if (result != 0) {
  // stat failed
  // file probably does not exist
} else if (data.st_size > MAX_LOGSIZE) {
  unlink(PREV_LOGFILE);
  (void) rename(LOGFILE, PREV_LOGFILE);
}

Solution

  • The standard way to avoid TOCTTOU on file operations is to open the file once and then do everything that you need through the file descriptor rather than the file name.

    However, both renaming and unlinking a file require its path (because they need to know what link to rename or remove), so you can't use that approach here. An alternative might be to copy the file's contents elsewhere and then truncate it to zero bytes, although your scenario with log files probably requires that the operation be atomic, which may be difficult to achieve. Another approach is to require tight access controls on the directory: if an attacker cannot write to the directory, then it cannot play TOCTTOU games with your process. You can use unlinkat and renameat to restrict your paths to a specific directory's file descriptor so that you don't need to worry about the directory itself changing.

    Something like this untested code might do the job, assuming a POSIX-like platform:

    dirfd = open(LOGDIR, O_DIRECTORY);
    // check for failure
    res = fstatat(dirfd, LOGFILE, statbuf, AT_SYMLINK_NOFOLLOW);
    if ((0 == res) && (S_ISREG(statbuf) && (data.st_size > MAX_LOGSIZE)) {
        unlinkat(dirfd, PREV_LOGFILE, 0);
        renameat(dirfd, LOGFILE, dirfd, PREV_LOGFILE);
    }
    close(dirfd);