Versions Compared

Key

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

...

In this noncompliant code example, the file identified by file_name is opened, processed, closed, and removed.

Code Block
bgColor#FFCCCC
langc
char *file_name;
FILE *fp;

/* initialize file_name */

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 */
}

...

Note that this function is only effective on filesystems that are fully compatible with UNIX permissions, and it may not behave normally for filesystems with other permission mechanisms, such as AFS.

Code Block
bgColor#ccccff
langc
#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 *fullpath) {
  char *path_copy = NULL;
  char **dirs = NULL;
  int num_of_dirs = 1;
  int secure = 1;
  int i;
  struct stat buf;
  uid_t my_uid = geteuid();
  size_t linksize;
  char* link;

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

  /* Figure out how far it is to the root */
  for (; ((strcmp(path_copy, "/") != 0) &&
          (strcmp(path_copy, "//") != 0));
       path_copy = dirname(path_copy)) {
    num_of_dirs++;
  } // now num_of_dirs indicates # of dirs we must check
  free(path_copy);
  path_copy = NULL;

  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 */
  }

  /* Now fill the dirs array */
  for (i = num_of_dirs - 2; i >= 0; i--) {
    path_copy = dirname(path_copy);
    if (!(dirs[i] = strdup(path_copy))) {
      /* handle error */
    }
  }
  free(path_copy);
  path_copy = NULL;

  /* Traverse from the root to the fullpath,
   * checking permissions along the way */
  for (i = 0; i < num_of_dirs; i++) {
    if (lstat(dirs[i], &buf) != 0) {
      /* Handle error */
    }
    if (S_ISLNK(buf.st_mode)) { // symlink, test linked-to file
      linksize = buf.st_size+1;
      if (!(link = (char *)malloc(linksize))) {
        /* Handle error */
      }
      if (readlink( dirs[i], link, linksize) == -1) {
        /* Handle error */
      }
      link[linksize-1] = '\0';
      if (!secure_dir(link)) {
        secure = 0;
      }
      free(link);
      link = NULL;
      break;
    }
    if (!S_ISDIR( buf.st_mode)) { // not a directory
      secure = 0;
      break;
    }
    if ((buf.st_uid != my_uid) && (buf.st_uid != 0)) {
      /* Directory is owned by someone besides user or root */
      secure = 0;
      break;
    }
    if (i == num_of_dirs - 1) { /* leaf dir */
      if (buf.st_mode & (S_IWGRP | S_IWOTH)) { /* dir is writable by others */
        secure = 0;
        break;
      }
    } else { /* parent dirs */
      if ((buf.st_mode & (S_IWGRP | S_IWOTH)) ||
          (buf.st_mode & S_ISVTX)) { /* dir has sticky bit off */
        secure = 0;
        break;
      }
    }
            
    free(dirs[i]);
    dirs[i] = NULL;
  }

  free(dirs);
  dirs = NULL;

  return secure;
}

This compliant solution uses this secure_dir() function to ensure that an attacker may not tamper with the file to be opened and subsequently removed. Note that once the path name of a directory has been checked using secure_dir(), all further file operations on that directory must be performed using the same path.

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

/* initialize dir_name */

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

if (chdir(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 */
}

...