Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: REM Cost Reform

File operations should be performed in a secure directory. In most cases, a secure directory is a directory in which no one other than the user, or possibly the administrator, has the ability to create, rename, delete, 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 in the parent of the secure directory and all higher directories, although creating new files , or deleting or renaming files they own is 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 See FIO01-C. Be careful using functions that use file names for identification.) . In some cases, file operations can be performed securely anywhere. In other cases, the only way to ensure secure file operations is to perform the operation within a secure directory.

Ensuring that file systems are configured in a safe manner is typically a system administration function. However, programs can often check that a file system is securely configured before performing file operations that may potentially lead to security vulnerabilities if the system is misconfigured. There is a slight possibility that file systems will be reconfigured in an insecure manner while a process is running and after the check has been made. As a result, it is always advisable to implement your code in a secure manner (that is, consistent with the other rules and recommendations in this section) even when running in a secure directory.

...

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;

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

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:

Code Block

% cd /tmp/app/ 
% rm -rf tmpdir
% ln -s /etc tmpdir

There is not Not much that can be done 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.

Compliant Solution (POSIX)

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

The path name passed to this function must be absolute, but fullpath need not be canonical canonicalized (see FIO02-C. Canonicalize path names originating from untrusted tainted sources). If the path contains a symbolic link, this routine will recursively invoke invokes itself on the linked-to directory and ensure ensures it is also secure. A symlinked symbolically linked 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

Note that this function is effective only on file systems that are fully compatible with UNIX permissions, and it may not behave normally for file systems with other permission mechanisms, such as AFS (Andrew File System).

Code Block
bgColor#ccccff
langc

#include <stdlib.h>
#include <unistd<limits.h>
#include <limits<string.h>
#include <libgen.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string<sys/types.h>

/* enum { MAX_SYMLINKS = 5 };

/* Returns nonzero if directory is secure, zero otherwise */
int secure_dir(const char *fullpath) {
  static unsigned int num_symlinks = 0;
  char *path_copy = NULL;
  char **dirs = NULL;
  int num_of_dirs = 1;
  int secure = 1;
  int i, r;
  struct stat buf;
  uid_t my_uid = geteuid();
  size_t linksize;
  char* link;
   
  if (!(path_copy = strdup(fullpath))) {fullpath || fullpath[0] != '/') {
    /* Handle error */
  }
   
  if (num_symlinks > MAX_SYMLINKS) {  /* Figure out how far it is to the rootCould be a symlink loop */
    /* Handle error */
  for}
 (; ((strcmp
  if (!(path_copy, "/") != 0strdup(fullpath))) &&{
    /* Handle     (strcmp(path_copy, "//") != 0));error */
  }
  
  /* Figure out path_copyhow = 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_copyfar it is to the root */
  char* path_parent = path_copy;
  for (; ((strcmp(path_parent, "/") != 0) &&
          (strcmp(path_parent, "//") != 0) &&
          (strcmp(path_parent, ".") != 0));
       path_parent = dirname(path_parent)) {
    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(char *)))) {
      /* handleHandle error */
    }
  } 
  if free(path_copy);
  path_copy = NULL;

(!(dirs[num_of_dirs - 1] = strdup(fullpath))) {
    /* TraverseHandle fromerror the*/
 root to}
 the fullpath,
   * checking permissions along the wayif (!(path_copy = strdup(fullpath))) {
    /* Handle error */
  }
 for 
 (i =/* 0;Now ifill <the num_of_dirs; i++) {array */
  path_parent = if (lstat(dirs[i], &buf) != 0) {
      /* Handle error */
    }path_copy;
  for (i = num_of_dirs - 2; i >= 0; i--) {
    path_parent = dirname(path_parent);
    if (S_ISLNK(buf.st_mode!(dirs[i] = strdup(path_parent))) {
 // symlink, test linked-to file
 /* Handle error */
  linksize = buf.st_size+1; }
  }
    if (!(link = (char *)malloc(linksize))) {free(path_copy);
  path_copy = NULL;
  
  /*
 Handle error */
 Traverse from the root to the }fullpath,
   * checking permissions ifalong (readlink( dirs[i], link, linksize) == -1) {the way.
        /* Handle error */
      }
  */
 for (i = 0; i < num_of_dirs; i++) {
    if (!secure_dir(link)lstat(dirs[i], &buf) != 0) {
      /* Handle secure = 0;error */
        break;
      }
      break;
    }
    if (!S_ISDIRISLNK( buf.st_mode)) { // not a directory* Symlink, test linked-to file */
      securelinksize = 0buf.st_size + 1;
      break;
    }if (!(link = (char *)malloc(linksize))) {
    if ((buf.st_uid != my_uid) && (buf.st_uid != 0)) { /* Handle error */
      /* Directory is owned by someone besides user or root */}
       
      securer = 0;
      breakreadlink(dirs[i], link, linksize);
    }
    if (ir == num_of_dirs - 1) {
        /* leafHandle direrror */
      if (buf.st_mode & (S_IWGRP | S_IWOTH)) { /* dir is writable by others */
  } else if (r >= linksize) {
        /* Handle truncation error */
      }
      securelink[r] = '\0';
 
       breaknum_symlinks++;
      }
r = secure_dir(link);
  } else { /* parent dirs */
 num_symlinks--;
       
  if ((buf.st_mode & (S_IWGRP | S_IWOTH)) ||
if (!r) {
        secure =  (buf.st_mode & S_ISVTX)) { /* dir has sticky bit off */0;
 
        free(link);
        securelink = 0NULL;
        break;
      }
    }
       
     
    free(dirs[i]link);
      dirs[i]link = NULL;
     }

  free(dirs);
  dirs  = NULL continue;
    }
  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

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

Risk Assessment

Failing to perform file I/O operations in a secure directory that cannot otherwise be securely performed can result in a broad range of file system vulnerabilities.

Recommendation

Severity

Likelihood

Remediation Cost

Priority

Level

FIO15-C

medium

probable

high

P4

L3

Related Vulnerabilities

Search for vulnerabilities resulting from the violation of this rule on the CERT website.

Other Languages

This rule appears in the C++ Secure Coding Standard as FIO15-CPP. Ensure that file operations are performed in a secure directory.

References

Wiki Markup
\[[ISO/IEC 9899:1999|AA. References#ISO/IEC 9899-1999]\]
\[[MITRE 07|AA. References#MITRE 07]\] [CWE ID 552|http://cwe.mitre.org/data/definitions/552.html], "Files or Directories Accessible to External Parties," [CWE ID 379|http://cwe.mitre.org/data/definitions/379.html], and "Creation of Temporary File in Directory with Insecure Permissions"
\[[Open Group 04|AA. References#Open Group 04]\] [{{dirname()}}|http://www.opengroup.org/onlinepubs/009695399/functions/dirname.html], [{{realpath()}}|http://www.opengroup.org/onlinepubs/009695399/functions/realpath.html]
\[[Viega 03|AA. References#Viega 03]\] Section 2.4, "Determining Whether a Directory Is Secure"


    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 (buf.st_mode & (S_IWGRP | S_IWOTH)) { /* dir is writable by others */
      secure = 0;
      break;
    }
  }
   
  for (i = 0; i < num_of_dirs; i++) {
    free(dirs[i]);
    dirs[i] = NULL;
  }
  
  free(dirs);
  dirs = NULL;
  
  return secure;
}

This compliant solution uses the 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 */
}

Risk Assessment

Failing to perform file I/O operations in a secure directory that cannot otherwise be securely performed can result in a broad range of file system vulnerabilities.

Recommendation

Severity

Likelihood

Detectable

Repairable

Priority

Level

FIO15-C

Medium

Probable

No

No

P4

L3

Related Vulnerabilities

Search for vulnerabilities resulting from the violation of this rule on the CERT website.

Related Guidelines

SEI CERT C++ Coding StandardVOID FIO15-CPP. Ensure that file operations are performed in a secure directory
MITRE CWECWE-379, Creation of temporary file in directory with insecure permissions
CWE-552, Files or directories accessible to external parties

Bibliography

[IEEE Std 1003.1:2013]XSH, System Interfaces, dirname
XSH, System Interfaces, realpath
[Viega 2003]Section 2.4, "Determining Whether a Directory Is Secure"


...

Image Added Image Added Image AddedImage Removed      09. Input Output (FIO)      FIO16-C. Limit access to files by creating a jail