Programmers frequently create temporary files in directories that are writable by everyone (examples are /tmp
and /var/tmp
on UNIX and C:\TEMP
on Windows) and may be purged regularly (for example, every night or during reboot).
Temporary files are commonly used for auxiliary storage for data that does not need to, or otherwise cannot, reside in memory and also as a means of communicating with other processes by transferring data through the file system. For example, one process will create a temporary file in a shared directory with a well-known name, or a temporary name that is communicated to collaborating processes. The file then can be used to share information among these collaborating processes.
This is a dangerous practice, because a well-known file in a shared directory can be easily hijacked or manipulated by an attacker. Mitigation strategies include the following:
1. Use other low-level IPC mechanisms such as sockets or shared memory.
2. Use higher level IPC mechanisms such as remote procedure calls.
3. Use a secure directory or a jail that can only be accessed by application instances (making sure that multiple instances of the application running on the same platform do not compete).
There are many different interprocess communication (IPC) mechanisms, some of which require the use of temporary files, while others do not. An example of an IPC mechanism that uses temporary files is the POSIX mmap()
function. Berkley Sockets, POSIX Local IPC Sockets, and System V Shared Memory do not require temporary files. Because the multiuser nature of shared directories poses an inherent security risk, the use of shared temporary files for IPC is discouraged.
When two or more users, or a group of users, have write permission to a directory, the potential for deception is far greater than it is for shared access to a few files.
Consequently, temporary files in shared directories must be:
- created with unique and unpredictable file names,
- opened with exclusive access,
- removed before the program exits, and
- opened with appropriate permissions.
The following table lists common temporary file functions and their respective conformance to these criteria:
Conformance of file functions to criteria for temporary files
| | | | | | |
---|---|---|---|---|---|---|
Unpredictable Name | Not portably | Yes | Not portably | Yes | Not portably | Not portably |
Unique Name | Yes | Yes | Yes | Yes | Yes | Yes |
Atomic | No | No | Yes | Yes | No | Yes |
Exclusive Access | Possible | Possible | No | If supported by OS | Possible | If supported by OS |
Appropriate Permissions | Possible | Possible | No | If supported by OS | Possible | Not portably |
File Removed | No | No | Yes* | Yes* | No | No |
* If the program terminates abnormally, this behavior is implementation-defined.
Securely creating temporary files is error prone and dependent on the version of the C runtime library used, the operating system, and the file system. Code that works for a locally mounted file system, for example, may be vulnerable when used with a remotely mounted file system. Moreover, none of these functions are without problems. The only secure solution is not to create temporary files in shared directories.
Unique and Unpredictable File Names
Wiki Markup |
---|
Privileged programs that create temporary files in world-writable directories can be exploited to overwrite protected system files. An attacker who can predict the name of a file created by a privileged program can create a symbolic link (with the same name as the file used by the program) to point to a protected system file. Unless the privileged program is coded securely, the program will follow the symbolic link instead of opening or creating the file that it is supposed to be using. As a result, a protected system file to which the symbolic link points can be overwritten when the program is executed \[[HP 03|AA. C References#HP 03]\]. Unprivileged programs can be similarly exploited to overwrite protected user files. |
Exclusive Access
Wiki Markup |
---|
Exclusive access grants unrestricted file access to the locking process while denying access to all other processes and eliminates the potential for a race condition on the locked region (see \[[Seacord 05a|AA. C References#Seacord 05]\] Chapter 7). |
Files, or regions of files, can be locked to prevent two processes from concurrent access. Windows supports two types of file locks:
- shared locks, provided by
LockFile()
, prohibit all write access to the locked file region while allowing concurrent read access to all processes. - exclusive locks, provided by
LockFileEx()
, grant unrestricted file access to the locking process while denying access to all other processes.
In both cases, the lock is removed by calling UnlockFile()
.
Both shared locks and exclusive locks eliminate the potential for a race condition on the locked region. The exclusive lock is similar to a mutual exclusion solution, and the shared lock eliminates race conditions by removing the potential for altering the state of the locked file region (one of the required properties for a race).
These Windows file-locking mechanisms are called mandatory locks because every process attempting access to a locked file region is subject to the restriction. Linux implements mandatory locks and advisory locks. An advisory lock is not enforced by the operating system, which severely diminishes its value from a security perspective. Unfortunately, the mandatory file lock in Linux is also largely impractical for the following reasons:
- Mandatory locking works only on local file systems and does not extend to network file systems (such as NFS or AFS)
- File systems must be mounted with support for mandatory locking, and this is disabled by default
- Locking relies on the group ID bit that can be turned off by another process (thereby defeating the lock)
Removal Before Termination
Removing temporary files when they are no longer required allows file names and other resources (such as secondary storage) to be recycled. In the case of abnormal termination, there is no sure method that can guarantee the removal of orphaned files. For this reason, temporary file cleaner utilities, which are invoked manually by a system administrator or periodically run by a daemon to sweep temporary directories and remove old files, are widely used. However, these utilities are themselves vulnerable to file-based exploits and often require the use of shared directories. During normal operation, it is the responsibility of the program to ensure that temporary files are removed either explicitly or through the use of library routines such as tmpfile_s
which guarantee temporary file deletion upon program termination.
Noncompliant Code Example (fopen()/open()
with tmpnam()
)
This noncompliant code example creates a file with a hard-coded file_name
(presumably in a shared directory such as /tmp
or C:\Temp
).
Code Block | ||
---|---|---|
| ||
char file_name[] = /* hard coded string */; FILE *fp = fopen(file_name, "wb+"); if (fp == NULL) { /* Handle error */ } |
Because the name is hard coded and consequently neither unique nor unpredictable, an attacker need only replace a file with a symbolic link, and the target file referenced by the link is opened and truncated.
Wiki Markup |
---|
The following noncompliant code example attempts to remedy the problem by generating the file name at runtime using {{tmpnam()}}. The C99 {{tmpnam()}} function generates a string that is a valid file name and that is not the same as the name of an existing file \[[ISO/IEC 9899:1999|AA. C References#ISO/IEC 9899-1999]\]. Files created using strings generated by the {{tmpnam()}} function are temporary in that their names should not collide with those generated by conventional naming rules for the [implementation|BB. Definitions#implementation]. The function is potentially capable of generating {{TMP_MAX}} different strings, but any or all of them may already be in use by existing files. |
Code Block | ||
---|---|---|
| ||
char file_name[L_tmpnam]; FILE* fp; if (!tmpnam(file_name)) { /* Handle error */ } /* A TOCTOU race condition exists here */ fp = fopen(file_name, "wb+"); if (fp == NULL) { /* Handle error */ } |
Because tmpnam()
does not guarantee a unique name and fopen()
does not provide a facility for an exclusive open, this code is still vulnerable.
Wiki Markup |
---|
This next noncompliant code example attempts to remedy the problem by using the POSIX {{open()}} function, and providing a mechanism to indicate whether an existing file has been opened for writing or a new file has been created \[[Open Group 04|AA. C References#Open Group 04]\]. If the {{O_CREAT}} and {{O_EXCL}} flags are used together, the {{open()}} function fails when the file specified by {{file_name}} already exists. To prevent an existing file from being opened and truncated, include the flags {{O_CREAT}} and {{O_EXCL}} when calling {{open()}}. |
Code Block | ||
---|---|---|
| ||
char file_name[L_tmpnam]; int fd; if (!(tmpnam(file_name))) { /* Handle error */ } /* A TOCTOU race condition exists here */ fd = open(file_name, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, 0600); if (fd < 0) { /* Handle error */ } |
This call to open()
fails whenever file_name
already exists, including when it is a symbolic link, but a temporary file is presumably still required. Additionally, the method used by tmpnam()
to generate file names is not guaranteed to be unpredictable, which leaves room for an attacker to guess the file name ahead of time.
Wiki Markup |
---|
Care should be observed when using {{O_EXCL}} with remote file systems, as it does not work with NFS version 2. NFS version 3 added support for {{O_EXCL}} mode in {{open()}}; see IETF RFC 1813 \[[Callaghan 95|AA. C References#Callaghan 95]\], in particularly the {{EXCLUSIVE}} value to the {{mode}} argument of {{CREATE}}. |
Wiki Markup |
---|
Moreover, the {{open()}} function, as specified by the Open Group Base Specifications Issue 6 \[[Open Group 04|AA. C References#Open Group 04]\], does not include support for shared or exclusive locks. However, BSD systems support two additional flags that allow you to obtain these locks: |
O_SHLOCK
Atomically obtain a shared lock.O_EXLOCK
Atomically obtain an exclusive lock.
Noncompliant Code Example (tmpnam_s()
, ISO/IEC TR 24731-1)
Wiki Markup |
---|
The TR 24731-1 {{tmpnam_s()}} function generates a string that is a valid file name and that is not the same as the name of an existing file \[[ISO/IEC TR 24731-1:2007|AA. C References#SO/IEC TR 24731-1-2007]\]. It is almost identical to the {{tmpnam()}} function except with an added {{maxsize}} argument for the supplied buffer. |
Code Block | ||
---|---|---|
| ||
char file_name[L_tmpnam_s]; int fd; if (tmpnam_s(file_name, L_tmpnam_s) != 0) { /* Handle error */ } /* A TOCTOU race condition exists here */ fd = open(file_name, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, 0600); if (fd < 0) { /* Handle error */ } |
Nonnormative text in TR 24731-1 also recommends the following:
Implementations should take care in choosing the patterns used for names returned by
tmpnam_s
. For example, making a thread id part of the names avoids the race condition and possible conflict when multiple programs run simultaneously by the same user generate the same temporary file names.
If implemented, this reduces the space for unique names and increases the predictability of the resulting names. In general, TR 24731-1 does not establish any criteria for the predictability of names. For example, the name generated by the tmpnam_s
function from Microsoft Visual Studio 2005 consists of a program-generated file name and, after the first call to tmpnam_s()
, a file extension of sequential numbers in base 32 (.1-.1vvvvvu).
Noncompliant Code Example (mktemp()/open()
, POSIX)
The POSIX function mktemp()
takes a given file name template and overwrites a portion of it to create a file name. The template may be any file name with some number of X's appended to it (for example, /tmp/temp.XXXXXX
). The trailing X's are replaced with the current process number and/or a unique letter combination. The number of unique file names mktemp()
can return depends on the number of X's provided.
Code Block | ||
---|---|---|
| ||
char file_name[] = "tmp-XXXXXX"; int fd; if (!mktemp(file_name)) { /* Handle error */ } /* A TOCTOU race condition exists here */ fd = open(file_name, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, 0600); if (fd < 0) { /* Handle error */ } |
The mktemp()
function has been marked "LEGACY" in the Open Group Base Specifications Issue 6. The man page for mktemp()
gives more detail:
Never use
mktemp()
. Some implementations follow BSD 4.3 and replaceXXXXXX
by the current process id and a single letter, so that at most 26 different names can be returned. Since on the one hand the names are easy to guess, and on the other hand there is a race between testing whether the name exists and opening the file, every use ofmktemp()
is a security risk. The race is avoided bymkstemp(3)
.
Noncompliant Code Example (tmpfile()
)
The C99 tmpfile()
function creates a temporary binary file that is different from any other existing file and that is automatically removed when it is closed or at program termination.
Wiki Markup |
---|
It should be possible to open at least {{TMP_MAX}} temporary files during the lifetime of the program (this limit may be shared with {{tmpnam()}}). C99, Section 7.19.4.4, allows for the value of the macro {{TMP_MAX}} to be as small as 25 \[[ISO/IEC 9899:1999|AA. C References#ISO/IEC 9899-1999]\]. |
Most historic implementations provide only a limited number of possible temporary file names (usually 26) before file names are recycled.
Code Block | ||
---|---|---|
| ||
FILE* fp = tmpfile(); if (fp == NULL) { /* Handle error */ } |
Noncompliant Code Example (tmpfile_s()
, ISO/IEC TR 24731-1)
The ISO/IEC TR 24731-1 function tmpfile_s()
creates a temporary binary file that is different from any other existing file and that is automatically removed when it is closed or at program termination. If the program terminates abnormally, whether an open temporary file is removed is implementation-defined.
The file is opened for update with "wb+"
mode, which means "truncate to zero length or create binary file for update." To the extent that the underlying system supports the concepts, the file is opened with exclusive (nonshared) access and has a file permission that prevents other users on the system from accessing the file.
Wiki Markup |
---|
It should be possible to open at least {{TMP_MAX_S}} temporary files during the lifetime of the program (this limit may be shared with {{tmpnam_s()}}). The value of the macro {{TMP_MAX_S}} is only required to be 25 \[[ISO/IEC TR 24731-1:2007|AA. C References#ISO/IEC TR 24731-1-2007]\]. |
Wiki Markup |
---|
TR 24731-1 notes the following regarding the use of {{tmpfile_s()}} instead of {{tmpnam_s()}} \[[ISO/IEC TR 24731-1:2007|AA. C References#ISO/IEC TR 24731-1-2007]\]: |
After a program obtains a file name using the
tmpnam_s
function and before the program creates a file with that name, the possibility exists that someone else may create a file with that same name. To avoid this race condition, thetmpfile_s
function should be used instead oftmpnam_s
when possible. One situation that requires the use of thetmpnam_s
function is when the program needs to create a temporary directory rather than a temporary file.
Code Block | ||
---|---|---|
| ||
if (tmpfile_s(&fp)) { /* Handle error */ } |
The TR24731-1 tmpfile_s()
function should not be used with implementations that create temporary files in shared directory such as /tmp
or C:
because the function does not allow the user to specify a directory in which the temporary file should be created.
Compliant Solution (mkstemp()
, POSIX)
The mkstemp()
algorithm for selecting file names has shown to be immune to attacks. The mkstemp()
function is available on systems that support the Open Group Base Specifications Issue 4, Version 2 or later.
A call to mkstemp()
replaces the six X's in the template string with six randomly selected characters and returns a file descriptor for the file (opened for reading and writing), as in this compliant solution.
Code Block | ||
---|---|---|
| ||
const char *sdn = "/home/usr1/"; char sfn[] = "/home/usr1/temp-XXXXXX"; FILE *sfp; if (!secure_dir(sdn)) { /* Handle error */ } int fd = mkstemp(sfn); if (fd == -1) { /* Handle error */ } /* * Unlink immediately to hide the file name. * The race condition here is inconsequential if the file * is created with exclusive permissions (glibc >= 2.0.7) */ if (unlink(sfn) == -1) { /* Handle error */ } sfp = fdopen(fd, "w+"); if (sfp == NULL) { close(fd); /* Handle error */ } /* Use temporary file */ fclose(sfp); /* also closes fd */ |
This solution is not serially reusable, however, because the mkstemp()
function replaces the "XXXXXX"
in template
the first time it is invoked. This is not a problem as long as template
is reinitialized before calling mkstemp()
again. If template
is not reinitialized, the mkstemp()
function will return -1
and leave template
unmodified because template
did not contain six X's.
Wiki Markup |
---|
The Open Group Base Specification Issue 6 \[[Open Group 04|AA. C References#Open Group 04]\] does not specify the permissions the file is created with, so these are [implementation-defined|BB. Definitions#implementation-defined behavior]. However, Issue 7 (POSIX.1-2008) specifies them as {{S_IRUSR\|S_IWUSR}} (0600) \[[Austin Group 08|AA. C References#Austin Group 08]\]. |
This compliant solution invokes an implementation-specific secure_dir(
} function (such as the one defined in FIO15-C. Ensure that file operations are performed in a secure directory) to ensure the temporary file resides in a secure directory.
Implementation Details
For glibc versions 2.0.6 and earlier, the file is created with permissions 0666; for glibc versions 2.0.7 and later, the file is created with permissions 0600. On NetBSD, the file is created with permissions 0600. This creates a security risk in that an attacker will have write access to the file immediately after creation. Consequently, programs need a private version of the mkstemp()
function in which this issue is known to be fixed.
Wiki Markup |
---|
In many older [implementations|BB. Definitions#implementation], the name is a function of process ID and time, so it is possible for the attacker to predict the name and create a decoy in advance. FreeBSD changed the {{mk*temp()}} family to eliminate the PID component of the file name and replace the entire field with base-62 encoded randomness. This raises the number of possible temporary files for the typical use of six X's significantly, meaning that even {{mktemp()}} with six X's is reasonably (probabilistically) secure against guessing, except under frequent usage \[[Kennaway 00|AA. C References#Kennaway 00]\]. |
Exceptions
FIO43-EX1: The TR24731-1 tmpfile_s()
function can be used if all the targeted implementations create temporary files; in secure directories.
Risk Assessment
Insecure temporary file creation can lead to a program accessing unintended files and permission escalation on local systems.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
FIO43-C | high | probable | medium | P12 | L1 |
The
unlink()
function doesn't follow symlinks, and doesn't really have much of an affect on hard links. So, I guess your options for attacking something like that would be:
*SIGSTOP
orSIGTSTP
it before the unlink, maybe unlink it yourself and wait (a while) until something created something with the same name, or try to use that name somehow. Probably not that useful, but maybe in a specific attack it could work with a lot of effort.
*You could sorta do a symlink attack with an intermediate path component, for example, if it was/tmp/tmp2/ed.XXXXXX
, you couldrm tmp2
and then symlink it to/etc
or something. It would thenrm /etc/ed.XXXXXX
, but that probably wouldn't buy you much.John McDonald
Automated Detection
Compass/ROSE can detect violations of this recommendation. Specifically, Rose reports use of tmpnam()
, tmpnam_s()
, tmpfile()
, and mktemp()
.
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 FIO43-CPP. Do not create temporary files in shared directories.
This rule appears in the Java Secure Coding Standard as FIO37-J. Create and delete temporary files safely.
References
Wiki Markup |
---|
\[[Austin Group 08|AA. C References#Austin Group 08]\] \[[HP 03|AA. C References#HP 03]\] \[[ISO/IEC 9899:1999|AA. C References#ISO/IEC 9899-1999]\] Section 7.19.4.4, "The {{tmpnam}} function," 7.19.4.3, "The {{tmpfile}} function," and Section 7.19.5.3, "The {{fopen}} function" \[[ISO/IEC PDTR 24772|AA. C References#ISO/IEC PDTR 24772]\] "EWR Path Traversal" \[[ISO/IEC TR 24731-1:2007|AA. C References#ISO/IEC TR 24731-1-2007]\] Section 6.5.1.2, "The {{tmpnam_s}} function," 6.5.1.1, "The {{tmpfile_s}} function," and Section 6.5.2.1, "The {{fopen_s}} function" \[[Kennaway 00|AA. C References#Kennaway 00]\] \[[MITRE 07|AA. C References#MITRE 07]\] [CWE ID 379|http://cwe.mitre.org/data/definitions/379.html], "Creation of Temporary File in Directory with Insecure Permissions" \[[Open Group 04|AA. C References#Open Group 04]\] [{{mktemp()}}|http://www.opengroup.org/onlinepubs/000095399/functions/mktemp.html], [{{mkstemp()}}|http://www.opengroup.org/onlinepubs/009695399/functions/mkstemp.html], [{{open()}}|http://www.opengroup.org/onlinepubs/009695399/functions/open.html] \[[Seacord 05a|AA. C References#Seacord 05a]\] Chapter 3, "File I/O", Chapter 7 \[[Viega 03|AA. C References#Viega 03]\] Section 2.1, "Creating Files for Temporary Use" \[[Wheeler 03|AA. C References#Wheeler 03]\] [Chapter 7, "Structure Program Internals and Approach"|http://www.dwheeler.com/secure-programs/Secure-Programs-HOWTO/avoid-race.html#TEMPORARY-FILES] |
FIO42-C. Ensure files are properly closed when they are no longer needed 09. Input Output (FIO)