Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Perform file operations in a secure directory. In most cases, a secure directory is a directory no one other than the user, or possibly the administrator, has the ability to write, create, moverename, delete files or otherwise manipulate files. (Other users may read or search the directory, but generally may not modify the directory's contents in any way.) Also other users must not be able to delete or rename files they do not own in the parent of the secure directory and all higher directories, although creating new files and deleting or renaming files they own are permissible.

Performing file operations in a secure directory eliminates the possibility that an attacker might tamper with the files or file system to exploit a file system vulnerability in a program. These vulnerabilities often exist because there is a loose binding between the file name and the actual file (see FIO01-A. Be careful using functions that use file names for identification). In some cases, file operations can be performed securely (and should be). In other cases, the only way to ensure secure file operations is to perform the operation within a secure directory.

...

An attacker can replace the file object identified by file_name with a link to an arbitrary file before the call to fopen(). It is also possible that the file object identified by file_name in the call to remove() is not the same file object identified by file_name in the call to fopen(). If the file is not in a secure directory, for example, /tmp/app/tmpdir/passwd, then an attacker can manipulate the location of the file as follows:

...

This example implementation of a secure_dir() function will ensure that path and all directories above it are owned either by the user or the superuser, that path does not have write access for any other users, and that directories above path may not be modified deleted or renamed by any other users. When checking directories, it is important to traverse from the root to the top leaf in order to avoid a dangerous race condition where an attacker who has privileges to at least one of the directories can delete rename and recreate a directory after the privilege verification.

This function uses the realpath() function to canonicalize the input path (The path name passed to this function must be canonicalized (see FIO02-A. Canonicalize path names originating from untrusted sources for more information on realpath()). It then , otherwise there may be directories above it which do not get checked because they are bypassed by following a symbolic link. The function checks every directory in the canonical path, ensuring that every directory is owned by the current user or by root. Furthermore, that the topmost leaf directory must disallow disallows write access to everyone but the owner. All , and that all other directories in the path must forbid other users from deleting or renaming files , (either by turning off group write access and world write access, or by turning on the sticky bit).

Code Block
bgColor#ccccff
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <libgen.h>
#include <sys/stat.h>
#include <string.h>

/* Returns nonzero if directory is secure, zero otherwise */
int secure_dir(const char* pathfullpath) {
  char *fullpath = NULL;
  char *path_copy = NULL;
  char *dirname_res = NULL;
  char ** dirs = NULL;
  int num_of_dirs = 0;
  int secure = 1;
  int i;
  struct stat buf;
  uid_t my_uid = geteuid();

  /* 
   * Canonicalize path and store the result in fullpath
   * see FIO02-A. Canonicalize path names originating from
   * untrusted sources
   */

  if (!(path_copy = strdup(fullpath))) {
    /* Handle error */
  }

  dirname_res = path_copy;
  /* Figure out how far it is to the root */
  while (1) {
    dirname_res = dirname(dirname_res);

    num_of_dirs++;

    if ((strcmp(dirname_res, "./") == 0) ||
	(strcmp(dirname_res, "//") == 0)) {
      break;
    }
  }
  free(path_copy);
  path_copy = NULL;

  /* Now allocate and fill the dirs array */
  if (!(dirs = (char **)malloc(num_of_dirs*sizeof(*dirs)))) {
    /* Handle error */
  }
  if (!(dirs[num_of_dirs - 1] = strdup(fullpath))) {
    /* Handle error */
  }

  if(!(path_copy = strdup(fullpath))) {
    /* Handle error */
  }

  dirname_res = path_copy;
  for (i = 1; i < num_of_dirs; i++) {
    dirname_res = dirname(dirname_res);

    dirs[num_of_dirs - i - 1] = strdup(dirname_res);

  }
  free(path_copy);
  path_copy = NULL;

  /* Traverse from the root to the topleaf, checking
   * permissions along the way */
  for (i = 0; i < num_of_dirs; i++) {
    if (stat(dirs[i], &buf) != 0) {
       /* Handle error */
    }
    if ((buf.st_uid != my_uid) && (buf.st_uid != 0)) {
      /* Directory is owned by someone besides user or root */
      secure = 0;
    } else if ((buf.st_mode & (S_IWGRP | S_IWOTH))
      && ((i == 0 num_of_dirs - 1) || !(buf.st_mode & S_ISVTX))) {
        /* Others have permissions to the topmostleaf directory
         * or are able to delete or rename files along the way */
        secure = 0;
     }
            
    free(dirs[i]);
    dirs[i] = NULL;
  }

  free(dirs);
  dirs = NULL;

  return secure;
}

This compliant solution uses the secure_dir() function above to ensure that an attacker may not tamper with the file to be opened and subsequently removed. Note that once the path name has been canonicalized and checked using secure_dir(), all further file operations must be done using the canonicalized path.

Code Block
bgColor#ccccff
char *dir_name;
char *canonical_dir_name;
char const *file_name = "passwd"; /* file name within the secure directory */
FILE *fp;

/* initialize file_name */ dir_name */

canonical_dir_name = realpath(dir_name, NULL);
if (canonical_dir_name == NULL) {
  /* Handle error */
}

if (!secure_dir(filecanonical_dir_name)) {
  /* Handle error */
}

if (chdir(canonical_dir_name) == -1) {
  /* Handle error */
}

fp = fopen(file_name, "w");
if (fp == NULL) {
  /* Handle error */
}

/*... Process file ...*/

if (fclose(fp) != 0) {
  /* Handle error */
}

if (remove(file_name) != 0) {
  /* Handle error */
}

...