Temporary Files


Applications often make use of temporary files to store data that is in some intermediate format, or to channel data between related processes. This practice has proved dangerous, however; innumerable local UNIX security vulnerabilities are related to temporary file use. Public temporary directories can be an extremely hostile environment for programs attempting to make use of them.

On most UNIX systems, there's a public temporary directory in /tmp and one in /var/tmp. Programs are free to create files in those directories for the purpose of temporary storage. The temporary directories are marked as sticky directories, which means only the file owner can delete or rename that file. These directories are usually mode octal 1777, granting everyone full read, write, and search permissions.

Programs typically use temporary directories in two ways. Most programs want to create a new, unique temporary file they can use once and then discard. Some programs, however, want to open an existing temporary file, which they expect to have been created by a related program in the past. The following sections describe issues in both uses of temporary directories.

Unique File Creation

Many applications want to create a unique temporary file, use it, and then delete it or hand it off to another program. In general, you should check for all the file creation issues outlined earlier and the creation-related issues with symbolic links and race conditions. Several library calls, described in the following sections, are designed to assist in obtaining these unique temporary files. Unfortunately, the majority of them are fairly broken, as you will see.

The mktemp() Function

The mktemp() function takes a template for a filename and fills it out so that it represents a unique, unused filename. The template the user provides has XXX characters as placeholders for random data. However, that data is fairly easy to predict because it's based on the process ID of the program that calls mktemp() plus a simple static pattern. Here's some code that uses mktemp():

char temp[1024]; int fd; strcpy(temp, "/tmp/tmpXXXX"); if (!mktemp(temp))     die("mktemp"); fd=open(temp, O_CREAT | O_RDWR, 0700); if (fd<0) {     perror("open");     exit(1); } ...


The problem with this code, and the problem with all nearly uses of mktemp(), is a race condition between when the file is verified as unique and when the file is opened. If attackers can create a symbolic link after the call to mktemp() but before the call to open(), the program opens that symbolic link, potentially creating a file wherever it points, and starts writing to it. If the program is running with sufficient privileges, it could be coerced into overwriting sensitive system files with data that could lead to an exploitable situation.

Here's a real-world example of a vulnerability resulting from the use of mktemp(). Michael Zalewski observed that the GNU C Compiler (GCC) uses temporary files during its compilation process. The following slightly edited code is from a vulnerable version of gcc:

#define TEMP_FILE "ccXXXXXX" char * choose_temp_base () {   char *base = 0;   char *temp_filename;   int len;   static char tmp[] = { DIR_SEPARATOR, 't', 'm', 'p', 0 };   static char usrtmp[] = { DIR_SEPARATOR, 'u', 's', 'r',       DIR_SEPARATOR, 't', 'm', 'p', 0 };   base = try (getenv ("TMPDIR"), base);   base = try (getenv ("TMP"), base);   base = try (getenv ("TEMP"), base);   /* Try /usr/tmp, then /tmp. */   base = try (usrtmp, base);   base = try (tmp, base);   /* If all else fails, use the current directory! */   if (base == 0)     base = ".";   len = strlen (base);   temp_filename = xmalloc (len + 1 /*DIR_SEPARATOR*/                + strlen (TEMP_FILE) + 1);   strcpy (temp_filename, base);   if (len != 0       && temp_filename[len-1] != '/'       && temp_filename[len-1] != DIR_SEPARATOR)     temp_filename[len++] = DIR_SEPARATOR;   strcpy (temp_filename + len, TEMP_FILE);   mktemp (temp_filename);   if (strlen (temp_filename) == 0)     abort ();   return temp_filename; }


As you can see, gcc uses mktemp() to create temporary files in a public temporary directory. When you compile a program, gcc first creates an intermediate file in /tmp/ccXXXXXX.i. The X characters are filled in by mktemp(). When gcc goes to create other files, such as the assembly file (.s) and the object file (.o), it reuses that same ccXXXXXX base that was used for the intermediate file. Attackers can simply watch /tmp and look for .i files. As soon as they find one, they can create links to other files with the name gcc attempts to use for other temporary compilation files, and then gcc overwrites the linked files with the contents of the intermediate compilation file. If attackers wait for root to compile something, they can obtain root privileges by tricking root into overwriting a sensitive file.

Note

mktemp() almost always indicates a potential race condition because the unique filename it returns can often be predicted and taken before it's actually used by an application.


The tmpnam() and tempnam() Functions

The tmpnam() and tempnam() functions are similar to mktemp(), in that they're used to return the name of a temporary file available for use. tmpnam() looks for files in the system temporary directory, and tempnam() lets users specify the directory and file prefix to use for creating a temporary filename. Both functions have the same race condition issues as mktemp(), so you can consider them similar in terms of security.

Here's a real-world example from xpdf-0.90, which contains a vulnerable use of tmpnam():

  tmpnam(tmpFileName);   if (!(f = fopen(tmpFileName, "wb"))) { error(-1, "Couldn't open temporary Type 1 font file '%s'",       tmpFileName); return -1;   }


If attackers create a symbolic link to a sensitive file after the call to tmpnam() but before the call to fopen(), xpdf creates or opens that file with the privileges of the user running xpdf.

In addition, Eric Raymond's cstrings utility was vulnerable to a race condition involving the use of tempnam() (documented at www.securityfocus.com/bid/9391/info):

    if (argv[optind][0] != '/')         (void) getcwd(buf, BUFSIZ);     else         buf[0] = '\0';     (void) strcat(buf, argv[optind]);     if (cp = strrchr(buf, '/'))         *cp = '\0';     if ((tf = tempnam(buf, "cstr")) == (char *)NULL)     {         perror("cstrings, making tempfile");         exit(1);     }     if ((ofp = fopen(tf, "w")) == (FILE *)NULL)     {         perror("cstrings, making output file");         exit(1);     }


Again, if attackers create a symbolic link to a sensitive file after the call to tempnam() but before the call to fopen(), the process opens the symbolic link target as the user running cstrings and writes font information to it.

The mkstemp() Function

The library function mkstemp() is much safer than mktemp(), assuming it's used correctly. It finds a unique filename, like mktemp(), but then proceeds to create the file and return a file descriptor to the program that has read and write access to the file. It does all this in a safe fashion. However, it is still possible for a developer to misuse mkstemp() in other ways, as shown in Listing 9-6.

Listing 9-6. Reopening a Temporary File

char g_mytempfile[1024]; void init_prog(void) {     int fd;     strcpy(g_mytempfile, "/tmp/tmpXXXXXX");     fd = mkstemp(g_mytempfile);     if (fd==-1)         die("mkstemp");     initialize_tmpfile(fd);     close(fd); } void main_loop(void) {     FILE *fp; ...     /* open temporary file */     if ((fp=fopen(g_mytempfile,"rw"))==NULL)         die("fopen"); ...

You might see this code if a programmer tries to fix a program using mktemp() so that it uses mkstemp() instead. The init_prog() function creates a temporary file and initializes it to contain a default set of contents. The path to this temporary file is stored in g_mytempfile. Later in the application, the temporary file is reopened for further processing. The problem is that, although the initial creation of the temporary file was done safely, it's reopened later in an unsafe fashion. Malicious users might be able to manipulate that temporary file if they have sufficient permissions in the directory. If they could delete or rename the file and replace it with a symbolic link to a sensitive file, the program could potentially manipulate that sensitive file in an exploitable way. If users didn't have permissions for that kind of manipulation, they might still be able to place the process in a suspended state long enough that the temporary directory would be cleaned out by an administrative daemon. They could then re-create the file so that it points to a sensitive system file.

Keep in mind that some System V UNIX implementations might honor the umask when creating a temporary file with mkstemp(), so it's a good idea for programs to set it properly beforehand.

The tmpfile() and mkdtemp() Functions

The tmpfile() function is similar to mkstemp(); its purpose is to create a unique file in the system's temporary directory and return a stream pointer to the file. This function is generally implemented in a safe, atomic fashion, often using mkstemp(). According to Casper Dik, Solaris versions before 2.6 have a tmpfile() function that's vulnerable to race conditions, and IRIX and AIX are probably also vulnerable.

mkdtemp() is used to create a unique directory. It takes a template similar to mkstemp() and creates a directory with mode 0700. This directory is then a safe place in which the program can operate.

The O_CREAT | O_EXCL Flags

Say that attackers can predict the filename an application uses, or the application uses a predetermined filename such as /tmp/.ps_data. In general, unless an application does something like the following, it's probably vulnerable to an attack:

    fd = open(filename, O_RDWR | O_CREAT | O_EXCL, FMODE);     if (fd < 0)         abort();


The call to open() specifies the O_CREAT | O_EXCL flag, which means the file is created only if a file with the same name does not already exist. If a file exists with that name, the open() call returns an error, which the application should expect in case of attack. Using O_CREAT | O_EXCL also means that if the last path component of the filename is a symbolic link, the kernel won't follow it. These flags make sure the file is created safely, as long as the application is ready for open() to return a failure condition in case of any funny business.

File Reuse

So far, you've focused on the creation of temporary files that are unique and don't already exist on the file system. Applications also might have a requirement to open temporary files that already exist in a temporary directory. These files might have a known, fixed filename, or they might have a unique filename that's explicitly passed along to program components that need to open the file. Programs might use these files to share information as a simple form of IPC or to cache processing results for use by a subsequent execution of a program.

Opening these files safely is difficult. First, you want to make sure you aren't opening a symbolic link or hard link to a sensitive file. If you try to use lstat() to determine whether the file is a symbolic link, you introduce a race condition before the call to open(). If you call open() and then fstat() on the file, you end up following symbolic links unless your open() call supports the nonstandard Linux O_NOFOLLOW flag (and even then, O_NOFOLLOW only ensures that the last component of the pathname isn't a symbolic link).

If you try to prevent a hard link attack, you can run into trouble. If you use lstat() and then check the link count, you introduce a race condition before the call to open(). If you open the file first and then use fstat() to check the link count, you're again exposed to symbolic link attacks. If attackers can delete the link they made you open, the result of the fstat() might indicate a link count of one, even though you opened a sensitive file.

Cryogenic Sleep Attacks

Olaf Kirch, a well known security researcher, published an interesting vulnerability related to reusing temporary files. The following code, which is slightly modified from Olaf's Bugtraq post (available at http://seclists.org/bugtraq/2000/Jan/0016.html), represents an idiom for a safe way to open a persistent temporary file:

    if (lstat(fname, &stb1) >= 0)     {         if (!S_ISREG(stb1.st_mode) ||             (stb1.st_nlink>1))             raise_big_stink();         fd = open(fname, O_RDWR);         if (fd < 0 || fstat(fd, &stb2) < 0)             raise_big_stink();         if (stb1.st_ino  != stb2.st_ino  ||             stb1.st_dev  != stb2.st_dev  ||             stb2.st_nlink>1)             raise_big_stink();     }     else     {         fd = open(fname, O_RDWR | O_CREAT | O_EXCL, FMODE);         if (fd < 0)             raise_big_stink();     }


This code represents a reasonably safe idiom for opening a potentially existing file in a public directory. The code first checks the file with lstat() and stores the results in the stat buffer structure stb1. If the lstat() fails, indicating that the file doesn't exist, the code attempts to create the file by using open() with the O_CREAT | O_EXCL flags. This open() doesn't follow symbolic links in the last path component, and it succeeds only if it's successful in creating the file.

So if the file doesn't exist, the open() call attempts to create it in a safe fashion. If the file does exist, it's first analyzed with lstat() to make sure it's not a symbolic link or hard link. Naturally, attackers could delete or rename the file and replace it with another file, device file, hard link, or symbolic link immediately after the lstat() security check. So the program opens the file and uses fstat(), and then uses the inode and device numbers from the fstat() and lstat() calls to check that the pathname hasn't been manipulated in the time that has elapsed since the program first called lstat(). If the pathname hasn't been tampered with, lstat() and fstat() should both indicate that the file has the same device and inode numbers. Note that the call to open() in the first block uses the O_RDWR flag, but not O_CREAT, ensuring that it doesn't create a file accidentally.

This solution seems fairly robust, assuming the application can deal with the file open failing if tampering is detected. Kirch observed that in some situations, the inode and device check might be circumvented. Say that attackers create a regular file in the temporary directory with the filename the program is expecting. This program would call lstat() on the regular file and learn that it existed and wasn't a symbolic link. Say attackers then manage to send a job control signal, such as a SIGSTOP, to the application immediately after the lstat() but before the call to open(). This would be possible if the program is a setuid root program users had started in their terminal session.

At this point, attackers would make note of the inode and device of the temporary file they created. They would then delete that file and wait for a sensitive file to be created with the same inode and device number. They could simply wait for something to happen, or they could call other privileged programs in ways designed to get them to create sensitive files.

As soon as a sensitive file is created with an inode and device number equal to that of the original file, attackers would create a symbolic link to that file and resume the program. The program would perform the open() call, which would follow the symbolic link and open the sensitive file. However, when it analyzes the file, it would find that the inode and device numbers hadn't changed, so it wouldn't suspect anything odd was afoot.

Temporary Directory Cleaners

Michael Zalewski described an interesting class of attacks that can undermine the security of mkstemp() in certain environments (available at www.bindview.com/Services/Razor/Papers/2002/mkstemp.cfm). Many UNIX systems have a daemon that runs periodically to clean out public temporary directories, such as /tmp and /var/tmp. The program Zalewski analyzed, tmpwatch, is a popular program that performs this task. It goes through each file in the temporary directory and uses lstat() to determine the age of the file. If the file is old enough, the cleaning daemon uses unlink() on the file to delete it.

Say you have a program that creates a temporary file securely by using mkstemp(), but later it uses the file in a potentially unsafe fashion by reopening the file or performing operations such as chmod() and chown() that work with filenames rather than file descriptors. If the temporary file is created properly, with the correct umask, ownership, and permissions, usually this isn't a problem in a sticky directory, as only the file's owner is able to rename or unlink the file. You've already looked at a code snippet with these characteristics in Listing 9-6.

If you could get a temporary file to be unlinked after it was created but before an application used it again, you could potentially create an exploitable condition. Zalewski outlined two attacks that could do just this. The simplest attack is to start a privileged setuid program, let it create its temporary file, and then suspend the program with a SIGSTOP signal. Then simply wait the requisite number of days for the cleaning daemon to decide that the temporary file is old enough to be purged. After the daemon purges the file, create a symbolic link in its place and resume the privileged program.

Zalewski outlined a more complex attack that requires considerably more delicate timing. The cleaning daemons are implemented so that there's a race condition between lstat() and unlink(). If you let the cleaner daemon use lstat() on a file and decide to unlink it, you could unlink it preemptively out from under the daemon. If another application creates a file with that name right before the cleaning daemon uses unlink(), that program's file would be deleted right out from under it.




The Art of Software Security Assessment. Identifying and Preventing Software Vulnerabilities
The Art of Software Security Assessment: Identifying and Preventing Software Vulnerabilities
ISBN: 0321444426
EAN: 2147483647
Year: 2004
Pages: 194

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net