2.4 Determining Whether a Directory Is Secure

2.4.1 Problem

Your application needs to store sensitive information on disk, and you want to ensure that the directory used cannot be modified by any other entity on the system besides the current user and the administrator. That is, you would like a directory where you can modify the contents at will, without having to worry about future permission checks.

2.4.2 Solution

Check the entire directory tree above the one you intend to use for unsafe permissions. Specifically, you are looking for the ability for users other than the owner and the superuser (the Administrator account on Windows) to modify the directory. On Windows, the required directory traversal cannot be done without introducing race conditions and a significant amount of complex path processing. The best advice we can offer, therefore, is to consider home directories (typically x:\Documents and Settings\User, where x is the boot drive and User is the user's account name) the safest directories. Never consider using temporary directories to store files that may contain sensitive data.

2.4.3 Discussion

Storing sensitive data in files requires extra levels of protection to ensure that the data is not compromised. An often overlooked aspect of protection is ensuring that the directories that contain files (which, in turn, contain sensitive data) are safe from modification.

This may appear to be a simple matter of ensuring that the directory is protected against any other users writing to it, but that is not enough. All the directories in the path must also be protected against any other users writing to them. This means that the same user who will own the file containing the sensitive data also owns the directories, and that the directories are all protected against other users modifying them.

The reason for this is that when a directory is writable by a particular user, that user is able to rename directories and files that reside within that directory. For example, suppose that you want to store sensitive data in a file that will be placed into the directory /home/myhome/stuff/securestuff. If the directory /home/myhome/stuff is writable by another user, that user could rename the directory securestuff to something else. The result would be that your program would no longer be able to find the file containing its sensitive data.

Even if the securestuff directory is owned by the user who owns the file containing the sensitive data, and the permissions on the directory prevent other users from writing to it, the permissions that matter are on the parent directory, /home/myhome/stuff. This same problem exists for every directory in the path, right up to the root directory.

In this recipe we present a function, spc_is_safedir( ), for checking all of the directories in a path specification on Unix. It traverses the directory tree from the bottom back up to the root, ensuring that only the owner or superuser have write access to each directory.

The spc_is_safedir( ) function requires a single argument specifying the directory to check. The return value from the function is -1 if some kind of error occurs while attempting to verify the safety of the path specification, 0 if the path specification is not safe, or 1 if the path specification is safe.

On Unix systems, a process has only one current directory; all threads within a process share the same working directory. The code presented here changes the working directory as it works; therefore, the code is not thread-safe!

#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int spc_is_safedir(const char *dir) {
  DIR         *fd, *start;
  int         rc = -1;
  char        new_dir[PATH_MAX + 1];
  uid_t       uid;
  struct stat f, l;
  if (!(start = opendir("."))) return -1;
  if (lstat(dir, &l) =  = -1) {
    return -1;
  uid = geteuid(  );
  do {
    if (chdir(dir) =  = -1) break;
    if (!(fd = opendir("."))) break;
    if (fstat(dirfd(fd), &f) =  = -1) {
    if (l.st_mode != f.st_mode || l.st_ino != f.st_ino || l.st_dev != f.st_dev)
    if ((f.st_mode & (S_IWOTH | S_IWGRP)) || (f.st_uid && f.st_uid != uid)) {
      rc = 0;
    dir = "..";
    if (lstat(dir, &l) =  = -1) break;
    if (!getcwd(new_dir, PATH_MAX + 1)) break;
  } while (new_dir[1]); /* new_dir[0] will always be a slash */
  if (!new_dir[1]) rc = 1;
  return rc;