Versions Compared

Key

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

...

The path name passed to this function must be canonicalized absolute, but need not be canonical (see FIO02-C. Canonicalize path names originating from untrusted sources), otherwise there may be directories above it which do not get checked because they are bypassed by following a symbolic link. If the path contains a symbolic link, this routine will recursively invoke itself on the linked-to directory and ensure it is also secure. A symlinked directory may be secure if both its source and linked-to directory are secure. The function checks every directory in the canonical path, ensuring that every directory is owned by the current user or by root, that the leaf directory disallows write access to everyone but the owner, and that all other directories in the path 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 *fullpath) {
  char *path_copy = NULL;
  char *dirname_res = NULL;
  char **dirs = NULL;
  int num_of_dirs = 01;
  int secure = 1;
  int i;
  struct stat buf;
  uid_t my_uid = geteuid();
  size_t linksize = buf.st_size+1;
  char* link;

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

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

    num_of_dirs++;

; ((strcmp(path_copy, "/") != 0) &&
         if ((strcmp(dirnamepath_rescopy, "//") !== 0)) ||
	(strcmp(dirname_res, "//") == 0;
       path_copy = dirname(path_copy)) {
      break;
    }
  }num_of_dirs++;
  } // now num_of_dirs indicates # of dirs we must check
  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;Now fill the dirs array */
  for (i = 1num_of_dirs - 2; i < num_of_dirs>= 0; i++--) {
    dirnamepath_rescopy = dirname(dirnamepath_rescopy);

    if (!(dirs[num_of_dirs - i - 1] = strdup(dirnamepath_res);
copy))) {
      /* handle error */
    }
  }
  free(path_copy);
  path_copy = NULL;

  /* Traverse from the root to the leaffullpath, checking
   * checking permissions along the way */
  for (i = 0; i < num_of_dirs; i++) {
    if (statlstat(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 */
      }
      if (!secure_dir(link)) {
        secure = 0;
        break;
      }
      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;
    }
   else if (i == num_of_dirs - 1) { /* leaf dir */
      if (buf.st_mode & (S_IWGRP | S_IWOTH)) { /* dir is writable by others */
       && ((i == num_of_dirs - 1) || !secure = 0;
        break;
      }
    } else { /* parent dirs */
      if ((buf.st_mode & (S_ISVTXIWGRP | S_IWOTH))) {||
        /* Others have permissions to the leaf directory
      (buf.st_mode & S_ISVTX)) { /* dir has sticky bet off */
    * or are able tosecure delete= or0;
 rename files along the way */
  break;
      secure = 0;}
     }
            
    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 has been canonicalized and checked using secure_dir(), all further file operations must be performed using the canonicalized path.

Code Block
bgColor#ccccff

char *dir_name;
char *canonical_dir_name;
const char *file_name = "passwd"; /* file name within the secure directory */
FILE *fp;

/* initialize dir_name */

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

if (!secure_dir(canonical_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 */
}

...