You are viewing an old version of this page. View the current version.

Compare with Current View Page History

Version 1 Next »

@ define TOCTOU race condition.

A program that performs a file operation on a filename or path twice creates a race window between the two file operations. This race window comes from the assumption that the filename refers to the same file both times. If an attacker can modify the file, remove it, or replace it with a different file, then this assumption will not hold, and the program will behave badly.

Noncompliant Code Example (POSIX read)

This noncompliant code example tries to ensure that an open will succeed by first calling the POSIX access() function, which returns zero if the file exists and can be read. An attacker can exploit the race window between the access and the open to cause fopen() to fail in spite of the check.

Since this code also does not check if fopen() succeeds, it violates @ERR33-C.

#include <stdio.h>
#include <unistd.h> 

int open_some_file() {
  FILE *fd; 
  if (access("./some_file", R_OK | W_OK) != 0) {
    printf("access granted.\n");
    fd = fopen("./some_file", "rb+");
    /* read file */
    fclose(fd);
  }

  return 0;
}

Compliant Solution (read)

This compliant solution dispenses with an access call and merely relies on open() to verify the file.

#include <stdio.h>
#include <unistd.h> 

int open_some_file() {
  FILE *fd; 
  fd = fopen("./some_file", "rb+");
  if (fd != NULL) {
    printf("access granted.\n");
    /* read file */
    fclose(fd);
  }

  return 0;
}

Noncompliant Code Example (POSIX write)

If an existing file is opened for writing, the file's previous contents are destroyed. This noncompliant code example tries to prevent an existing file from being overwritten by first ensuring that a file does not exist before opening it for writing. An attacker can exploit the race window between the access and the open to cause fopen() to overwrite an existing file.

#include <stdio.h>
#include <unistd.h> 

int open_some_file() {
  FILE *fd; 
  if (access("./some_file", R_OK | W_OK) != 0) {
    printf("access granted.\n");
    fd = fopen("./some_file", "wb+");
    /* write to file */
    fclose(fd);
  }

  return 0;
}

Compliant Solution (C11 write)

This compliant solution uses the x mode of fopen(), which was added to C11. This mode causes fopen() to fail if the file exists. This check and subsequent open is done without creating a race window. Note that the x mode provides exclusive access to the file if the operating platform provides this support.

#include <stdio.h>
#include <unistd.h> 

int open_some_file() {
  FILE *fd; 
  fd = fopen("./some_file", "wb+x");
  if (fd == NULL) {
    printf("access granted.\n");
    /* write to the file */
    fclose(fd);
  }

  return 0;
}

Compliant Solution (POSIX write)

This compliant solution uses the O_CREAT and O_EXCL flags of POSIX's open() system call. These flags cause open() to fail if the file exists.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open_some_file() {
  int file = open("./some_file", O_CREAT | O_EXCL | O_RDWR);
  if (file != -1) {
    FILE *fd; 
    fd = fdopen(file, "rw+");
    if (fd != NULL) {
      printf("access granted.\n");
      /* write to file */
      fclose(fd);
    }
    close(file);
  }

  return 0;
}

Exceptions

FIO45-EX1: Accessing a path multiple times is permitted if it is requested specifically by the users. A program that accepts commands from a user to read or write a specific filename does not violate this standard. Example programs would include file managers or text editors.

FIO45-EX2: Accessing a path multiple times is permitted if the path can not be modified by an attacker. This could occur, for example, if the path refers to a secure directory (for more information, see FIO15-C. Ensure that file operations are performed in a secure directory).

FIO45-EX3: Accessing a path multiple times is permitted if the program is able to verify that every operation indeed operates on the same file.

This code example demonstrates how to verify that two accesses are indeed the same file in POSIX. In POSIX, every file can be uniquely identified by using its device and i-node attributes. This code example checks that a filename does not refer to a symbolic link, using lstat(); the call also retrives its device and i-node. The file is subsequently opened. Finally, the program verifies that the file trhat was opened is the same one (matching device and inodes) as the file that was verified not to be a symbolic link.

fd* open_real_file(char *filename, int flags) {
  struct stat lstat_info;
  struct stat fstat_info;
  int fd;

  if (lstat(filename, &lstat_info) == -1) {
    /* file does not exist, handle error */
  }

  if (!S_ISLNK(lstat_info.st_mode)) {
    /* file is a symlink, handle error */
  }

  fd = open(filename, flags);
  if (fd == -1) {
    /* file has disappeared, handle error */
  }

  if (fstat(fd, &fstat_info) == -1) {
    /* handle error */
  }

  if (!(lstat_info.st_ino == fstat_info.st_ino  &&
       (lstat_info.st_dev == fstat_info.st_dev) {
    /* open file is not non-symlink file, handle error */
  }

  /* fd is true open file, and file was not symlink */
  return fd;
}

Risk Assessment

@

  • No labels