This issue of correctly performing atomic operations
particularly comes up when creating temporary files.
Temporary files in Unix-like systems are traditionally
created in the /tmp or /var/tmp directories,
which are shared by all users.
A common trick by attackers is to create symbolic links in the
temporary directory to some other file (e.g., /etc/passwd)
while your secure program is running.
The attacker's goal is to create
a situation where the secure program determines that
a given filename doesn't exist, the attacker then creates the symbolic
link to another file, and then the secure program performs some operation
(but now it actually opened an unintended file).
Often important files can be clobbered or modified this way.
There are many variations to this attack, such as creating normal files,
all based on the
idea that the attacker can create (or sometimes
otherwise access) file system objects
in the same directory used by the secure program for temporary files.
The general problem when creating files in these shared directories is that
you must guarantee that the filename you plan to use doesn't already
exist at time of creation.
Checking ``before'' you create the file doesn't work, because after the check
occurs, but before creation, another process can create that file with
that filename.
Using an ``unpredictable'' or ``unique'' filename doesn't work in
general, because another process can often repeatedly guess until it succeeds.
Fundamentally, to create a temporary file in a shared (sticky) directory,
you must repetitively: (1) create a ``random'' filename, (2) open it using
O_CREAT | O_EXCL and very narrow permissions,
and (3) stop repeating when the open succeeds.
According to the 1997 ``Single Unix Specification'', the preferred
method for creating an arbitrary temporary file is tmpfile(3).
The tmpfile(3) function creates a temporary file
and opens a corresponding stream, returning that stream (or NULL if it didn't).
Unfortunately, the specification doesn't make any
guarantees that the file will be created securely.
In earlier versions of this book, I stated that I was concerned because
I could not assure myself that all implementations do this securely.
I've since found that older System V systems
have an insecure implementation of tmpfile(3) (as well as insecure
implementations of tmpnam(3) and tempnam(3)).
Library implementations of tmpfile(3) should securely create such files,
of course, but users don't always realize that their system libraries
have this security flaw, and sometimes they can't do anything about it.
Kris Kennaway recommends using mkstemp(3) for making temporary files
in general.
His rationale is that you should use well-known library functions to perform
this task instead of rolling your own functions, and that this function
has well-known semantics.
This is certainly a reasonable position.
I would add that, if you use mkstemp(3), be sure to use umask(2) to limit
the resulting temporary file permissions to only the owner.
This is because
some implementations of mkstemp(3) (basically older ones) make such
files readable and writable by all,
creating a condition in which an attacker can read or
write private data in this directory.
A minor nuisance is that mkstemp(3) doesn't directly support the
environment variables TMP or TMPDIR (as discussed below), so
if you want to support them you have to add code to do so.
Here's a program in C that demonstrates how to use mkstemp(3)
for this purpose, both directly and when adding support for TMP and TMPDIR:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
void failure(msg) {
fprintf(stderr, "%s\n", msg);
exit(1);
}
/*
* Given a "pattern" for a temporary filename
* (starting with the directory location and ending in XXXXXX),
* create the file and return it.
* This routines unlinks the file, so normally it won't appear in
* a directory listing.
* The pattern will be changed to show the final filename.
*/
FILE *create_tempfile(char *temp_filename_pattern)
{
int temp_fd;
mode_t old_mode;
FILE *temp_file;
old_mode = umask(077); /* Create file with restrictive permissions */
temp_fd = mkstemp(temp_filename_pattern);
(void) umask(old_mode);
if (temp_fd == -1) {
failure("Couldn't open temporary file");
}
if (!(temp_file = fdopen(temp_fd, "w+b"))) {
failure("Couldn't create temporary file's file descriptor");
}
if (unlink(temp_filename_pattern) == -1) {
failure("Couldn't unlink temporary file");
}
return temp_file;
}
/*
* Given a "tag" (a relative filename ending in XXXXXX),
* create a temporary file using the tag. The file will be created
* in the directory specified in the environment variables
* TMPDIR or TMP, if defined and we aren't setuid/setgid, otherwise
* it will be created in /tmp. Note that root (and su'd to root)
* _will_ use TMPDIR or TMP, if defined.
*
*/
FILE *smart_create_tempfile(char *tag)
{
char *tmpdir = NULL;
char *pattern;
FILE *result;
if ((getuid()==geteuid()) && (getgid()==getegid())) {
if (! ((tmpdir=getenv("TMPDIR")))) {
tmpdir=getenv("TMP");
}
}
if (!tmpdir) {tmpdir = "/tmp";}
pattern = malloc(strlen(tmpdir)+strlen(tag)+2);
if (!pattern) {
failure("Could not malloc tempfile pattern");
}
strcpy(pattern, tmpdir);
strcat(pattern, "/");
strcat(pattern, tag);
result = create_tempfile(pattern);
free(pattern);
return result;
}
main() {
int c;
FILE *demo_temp_file1;
FILE *demo_temp_file2;
char demo_temp_filename1[] = "/tmp/demoXXXXXX";
char demo_temp_filename2[] = "second-demoXXXXXX";
demo_temp_file1 = create_tempfile(demo_temp_filename1);
demo_temp_file2 = smart_create_tempfile(demo_temp_filename2);
fprintf(demo_temp_file2, "This is a test.\n");
printf("Printing temporary file contents:\n");
rewind(demo_temp_file2);
while ( (c=fgetc(demo_temp_file2)) != EOF) {
putchar(c);
}
putchar('\n');
printf("Exiting; you'll notice that there are no temporary files on exit.\n");
} |
Kennaway also notes that if you can't use mkstemp(3),
then make yourself a directory using mkdtemp(3), which is protected
from the outside world.
Finally, if you really have to use the insecure mktemp(3), use lots of
X's - he suggests 10 (if your libc allows it) so that the filename can't
easily be guessed (using only 6 X's means that 5 are taken up by the
PID, leaving only one random character and allowing an attacker to
mount an easy race condition).
I add that you should avoid tmpnam(3) as well -
some of its uses aren't reliable when threads are present, and
it doesn't guarantee that it will work correctly after
TMP_MAX uses (yet most practical uses must be inside a loop).
In general, you should avoid using the insecure functions
such as mktemp(3) or tmpnam(3), unless you take specific measures to
counter their insecurities or test for a secure library implementation
as part of your installation routines.
If you ever want to make a file in /tmp or a world-writable directory
(or group-writable, if you don't trust the group) and don't want to
use mk*temp() (e.g. you intend for the file to be predictably named),
then always use the O_CREAT and O_EXCL flags to
open() and check the return value.
If you fail the open() call, then recover gracefully (e.g. exit).
The GNOME programming guidelines recommend the following C code when
creating filesystem objects in shared (temporary) directories
to security open temporary files [Quintero 2000]:
char *filename;
int fd;
do {
filename = tempnam (NULL, "foo");
fd = open (filename, O_CREAT | O_EXCL | O_TRUNC | O_RDWR, 0600);
free (filename);
} while (fd == -1); |
Note that, although the insecure function tempnam(3) is being used, it
is wrapped inside a loop using O_CREAT and O_EXCL to counteract its
security weaknesses.
Note that you need to free() the filename.
You should close() and unlink() the file after you are done.
If you want to use the Standard C I/O library,
you can use fdopen() with mode "w+b"
to transform the file descriptor into a FILE *.
Note that this approach won't work over
NFS version 2 (v2) systems, because older
NFS doesn't correctly support O_EXCL.
Note that one minor disadvantage to this approach is that, since
tempnam can be used insecurely, various compilers and security scanners
may give you spurious warnings about its use.
This isn't a problem with mkstemp(3).
If you need a temporary file in a shell script, you're probably
best off using pipes, using a local directory (e.g., something inside the
user's home directory), or in some cases using the current directory.
That way, there's no sharing unless the user permits it.
If you really want/need the temporary file
to be in a shared directory like /tmp, do
not use the traditional shell
technique of using the process id in a template and just creating the file
using normal operations like ">".
Shell scripts can use "$$" to indicate the PID, but the
PID can be easily determined or guessed by an attacker,
who can then pre-create files or links with the same name.
Thus the following "typical" shell script is unsafe:
echo "This is a test" > /tmp/test$$ # DON'T DO THIS. |
If you need a temporary file or directory
in a shell script, and you want it in /tmp,
the solution is probably mktemp(1), which is intended for use in shell scripts.
Note that mktemp(1) and mktemp(3) are different things - it's
mktemp(1) that is safe.
To be honest, I'm not enamored of shell scripts
creating temporary files in shared directories;
creating such files in private directories or using pipes instead is
generally preferable.
However, if you really need it, use it; mktemp(1) takes a template, then
creates a file or directory using O_EXCL and returns the resulting name;
since it uses O_EXCL, it's safe on shared directories like /tmp
(unless the directory uses NFS version 2).
Here are some examples of correct use of mktemp(1) in Bourne shell scripts;
these examples are straight from the mktemp(1) man page:
# Simple use of mktemp(1), where the script should quit
# if it can't get a safe temporary file:
TMPFILE=`mktemp /tmp/$0.XXXXXX` || exit 1
echo "program output" >> $TMPFILE
# Simple example, if you want to catch the error:
TMPFILE=`mktemp -q /tmp/$0.XXXXXX`
if [ $? -ne 0 ]; then
echo "$0: Can't create temp file, exiting..."
exit 1
fi |
Don't reuse a temporary filename (i.e. remove and recreate it),
no matter how you obtained the ``secure'' temporary filename in the
first place.
An attacker can observe the original filename
and hijack it before you recreate it the second time.
And of course, always use appropriate file permissions.
For example, only allow world/group access
if you need the world or a group to access the file, otherwise
keep it mode 0600 (i.e., only the owner can read or write it).
Clean up after yourself, either by using an exit handler, or making
use of UNIX filesystem semantics and unlink()ing the file immediately
after creation so the directory entry goes away but the file itself
remains accessible until the last file descriptor pointing to it is
closed. You can then continue to access it within your program by
passing around the file descriptor.
Unlinking the file has a lot of advantages for code maintenance:
the file is automatically deleted, no matter how your program crashes.
The one minor problem with immediate unlinking is that it makes it slightly
harder for administrators to see how disk space is being used, since
they can't simply look at the file system by name.
You might consider ensuring that your code for Unix-like systems
respects the environment variables TMP or TMPDIR
if the provider of these variable values is trusted.
By doing so, you make it possible for users to move their temporary
files into an unshared directory (and eliminating the problems discussed here),
such as a subdirectory inside their home directory.
Recent versions of Bastille can set these variables to reduce the sharing
between users.
Unfortunately, many users set TMP or TMPDIR to a shared directory
(say /tmp), so your secure program must still
correctly create temporary files even if these environment variables
are set.
This is one advantage of the GNOME approach, since at least on some
systems tempnam(3) automatically uses TMPDIR, while
the mkstemp(3) approach requires more code to do this.
Please don't create yet more environment variables for temporary directories
(such as TEMP), and in particular don't create a different environment
name for each application (e.g., don't use "MYAPP_TEMP").
Doing so greatly complicates managing systems,
and users wanting a special temporary directory for a specific
application can just set the environment variable specially
when running that particular application.
Of course, if these environment variables might have been set by an
untrusted source, you should ignore them - which you'll do anyway
if you follow the advice in
Section 4.2.3.
These techniques don't work if the temporary directory is remotely
mounted using NFS version 2 (NFSv2), because NFSv2 doesn't properly
support O_EXCL.
See Section 6.10.2.1 for more information.
NFS version 3 and later properly support O_EXCL; the simple solution
is to ensure that temporary directories are either local or, if mounted
using NFS, mounted using NFS version 3 or later.
There is a technique for safely creating temporary files on NFS v2,
involving the use of link(2) and stat(2), but it's complex; see
Section 6.10.2.1 which has more information about this.
As an aside, it's worth noting that
FreeBSD has recently changed the mk*temp() family to get rid of
the PID component of the filename and replace the entire thing with
base-62 encoded randomness. This drastically raises the number of
possible temporary files for the "default" usage of 6 X's, meaning
that even mktemp(3) with 6 X's is reasonably (probabilistically) secure
against guessing, except under very frequent usage.
However, if you also follow the guidance here, you'll eliminate the
problem they're addressing.
Much of this information on temporary files was derived from
Kris Kennaway's
posting to Bugtraq about temporary files on December 15, 2000.