Versions Compared

Key

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

...

There is not much that can be programmatically done to ensure the file removed is the same file that was opened, processed, and closed except to make sure that the file is opened in a secure directory with privileges that would prevent the file from being manipulated by an untrusted user.

Non-Compliant Code Example

This code example attempts to save a secret in a file specified by path. While path is assumed to come from a trusted source (see FIO02-A. Canonicalize path names originating from untrusted sources), this does not imply that an attacker will not be able to read the secret after it is written out to file.

Code Block
bgColor#ffcccc

void write_secret(const char* path, secret_t my_secret) {
  /* save my_secret to the file specified by path */
}

Compliant Solution (POSIX)

...

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* path) {
  char *realpath_res = realpath(path, 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();

  if (realpath_res == NULL) {
    /* Handle Error */
  }

  if (!(path_copy = strdup(realpath_res))) {
    /* 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(realpath_res))) {
    /* Handle Error */
  }

  if(!(path_copy = strdup(realpath_res))) {
    /* 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 top, 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_ISVTX) &&
	       (buf.st_mode & (S_IWGRP | S_IWOTH))) {
      /* Others have permission to rename or remove files here */
      secure = 0;
    }
    free(dirs[i]);
    dirs[i] = NULL;
  }

  free(dirs);
  dirs = NULL;

  return secure;
}

This compliant solution uses the secure_dir() function above to verify that my_secret is written to a secure file.

Code Block
bgColor#ccccff

void write_secret(const char* path, secret_t my_secret) {
  if (!secure_dir(path)) {
    /* Handle Error */
  }
  /* save my_secret to the file specified by path */
}

This compliant solution uses the secure_dir() function above to ensure that the file that is opened and written to is the intended file, and also the file that is eventually removed.

Code Block
bgColor#ccccff
char *file_name;
FILE *f_ptr;

/* initialize file_name */

if (!secure_dir(path)) {
  /* Handle Error */
}

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

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

if (fclose(f_ptr) != 0) {
  /* Handle Error */
}

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

...

Risk Assessment

Failing to ensure proper permissions in a directory may lead to sensitive data getting saved to (or critical configuration or other input files being read from) public directories to which an attacker has access.

...