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) {
closedir(start);
return -1;
}
uid = geteuid( );
do {
if (chdir(dir) = = -1) break;
if (!(fd = opendir("."))) break;
if (fstat(dirfd(fd), &f) = = -1) {
closedir(fd);
break;
}
closedir(fd);
if (l.st_mode != f.st_mode || l.st_ino != f.st_ino || l.st_dev != f.st_dev)
break;
if ((f.st_mode & (S_IWOTH | S_IWGRP)) || (f.st_uid && f.st_uid != uid)) {
rc = 0;
break;
}
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;
fchdir(dirfd(start));
closedir(start);
return rc;
}
|