[LinuxFocus-icon]
Home  |  Map  |  Index  |  Search

News | Archives | Links | About LF
This document is available in: English  Castellano  Deutsch  Francais  Nederlands  Portugues  Russian  Turkce  

convert to palmConvert to GutenPalm
or to PalmDoc

[image of the authors]
by Frédéric Raynal, Christophe Blaess, Christophe Grenier
<pappy(at)users.sourceforge.net, ccb(at)club-internet.fr, grenier(at)nef.esiea.fr>

About the author:

Christophe Blaess is an independent aeronautics engineer. He is a Linux fan and does much of his work on this system. He coordinates the translation of the man pages as published by the Linux Documentation Project.

Christophe Grenier is a 5th year student at the ESIEA, where he works as a sysadmin too. He has a passion for computer security.

Frédéric Raynal has been using Linux for many years because it doesn't pollute, doesn't use hormones, MSG or animal bone-meal... only sweat and craft.



Translated to English by:
Georges Tarbouriech <georges.t(at)linuxfocus.org>
Lorne Bailey [proof read] <sherm_pbody(at)yahoo.com>

Content:

 

Avoiding security holes when developing an application - Part 5: race conditions

[article illustration]

Abstract:

This fifth article of our series is dedicated to security problems related to multitasking. A race condition occurs when different processes use the same resource (file, device, memory) at the same time and each one "believes" it has exclusive access. This leads to difficult to detect bugs and also to security holes that can compromise a system's global security.

_________________ _________________ _________________

 

Introduction

The general principle defining race conditions is the following : a process wants to access a system resource exclusively. It checks that the resource is not already used by another process, then uses it as it pleases. The race condition occurs when another process tries to use the same resource in the time-lag between the first process checking that resource and actually taking it over. The side effects may vary. The classical case in OS theory is the deadlock of both processes. More often it leads to application malfunction or even to security holes when a process wrongfully benefits from the privileges another.

What we previously called a resource can have different aspects. Most notably the race conditions discovered and corrected in the Linux kernel itself due to competitive access to memory areas. Here, we will focus on system applications and we'll deem that the concerned resources are filesystem nodes. This concerns not only regular files but also direct access to devices through special entry points from the /dev/ directory.

Most of the time, an attack aiming to compromise system security is done against Set-UID applications since the attacker can benefit from the privileges of the owner of the executable file. However, unlike previously discussed security holes (buffer overflow, format strings...), race conditions usually don't allow the execution of "customized" code. Rather, they benefit from the resources of a program while it's running. This type of attack is also aimed at "normal" utilities (not Set-UID), the cracker lying in ambush for another user, especially root, to run the concerned application and access its resources. This is also true for writing to a file (i.e, ~/.rhost in which the string "+ +" provides a direct access from any machine without password), or for reading a confidential file (sensitive commercial data, personal medical information, password file, private key...)

Unlike the security holes discussed in our previous articles, this security problem applies to every application and not just to Set-UID utilities and system servers or daemons.

 

First example

Let's have a look at the behavior of a Set-UID program that needs to save data in a file belonging to the user. We could, for instance, consider the case of a mail transport software like sendmail. Let's suppose the user can both provide a backup filename and a message to write into that file, which is plausible under some circumstances. The application must then check if the file belongs to the person who started the program. It also will check that the file is not a symlink to a system file. Let's not forget, the program being Set-UID root, it is allowed to modify any file on the machine. Accordingly, it will compare the file's owner to its own real UID. Let's write something like :

1     /* ex_01.c */
2     #include <stdio.h>
3     #include <stdlib.h>
4     #include <unistd.h>
5     #include <sys/stat.h>
6     #include <sys/types.h>
7
8     int
9     main (int argc, char * argv [])
10    {
11        struct stat st;
12        FILE * fp;
13
14        if (argc != 3) {
15            fprintf (stderr, "usage : %s file message\n", argv [0]);
16            exit(EXIT_FAILURE);
17        }
18        if (stat (argv [1], & st) < 0) {
19            fprintf (stderr, "can't find %s\n", argv [1]);
20            exit(EXIT_FAILURE);
21        }
22        if (st . st_uid != getuid ()) {
23            fprintf (stderr, "not the owner of %s \n", argv [1]);
24            exit(EXIT_FAILURE);
25        }
26        if (! S_ISREG (st . st_mode)) {
27            fprintf (stderr, "%s is not a normal file\n", argv[1]);
28            exit(EXIT_FAILURE);
29        }
30
31        if ((fp = fopen (argv [1], "w")) == NULL) {
32            fprintf (stderr, "Can't open\n");
33            exit(EXIT_FAILURE);
34        }
35        fprintf (fp, "%s\n", argv [2]);
36        fclose (fp);
37        fprintf (stderr, "Write Ok\n");
38        exit(EXIT_SUCCESS);
39    }

As we explained in our first article, it would be better for a Set-UID application to temporarily drop its privileges and open the file using the real UID of the user having called it. As a matter of fact, the above situation corresponds to a daemon, providing services to every user. Always running under the root ID, it would check using the UID instead of its own real UID. Nevertheless, we'll keep this scheme for now, even if it isn't that realistic, since it allows us to understand the problem while easily "exploiting" the security hole.

As we can see, the program starts doing all the needed checks, i.e. that the file exists, that it belongs to the user and that it's a normal file. Next, it actually opens the file and writes the message. That is where the security hole lies! Or, more exactly, it's within the lapse of time between the reading of the file attributes with stat() and its opening with fopen(). This lapse of time is often extremely short but an attacker can benefit from it to change the file's characteristics. To make our attack even easier, let's add a line that causes the process to sleep between the two operations, thus having the time to do the job by hand. Let's change the line 30 (previously empty) and insert :

30        sleep (20);

Now, let's implement it; first, let's make the application Set-UID root. Let's make, it's very important, a backup copy of our password file /etc/shadow :

$ cc ex_01.c -Wall -o ex_01
$ su
Password:
# cp /etc/shadow /etc/shadow.bak
# chown root.root ex_01
# chmod +s ex_01
# exit
$ ls -l ex_01
-rwsrwsr-x 1 root  root    15454 Jan 30 14:14 ex_01
$

Everything is ready for the attack. We are in a directory belonging to us. We have a Set-UID root utility (here ex_01) holding a security hole, and we feel like replacing the line concerning root from the /etc/shadow password file with a line containing an empty password.

First, we create a fic file belonging to us :

$ rm -f fic
$ touch fic

Next, we run our application in the background "to keep the lead". We ask it to write a string into that file. It checks what it has to, sleeps for a while before really accessing the file.

$ ./ex_01 fic "root::1:99999:::::" &
[1] 4426

The content of the root line comes from the shadow(5) man page, the most important being the empty second field (no password). While the process is asleep, we have about 20 seconds to remove the fic file and replace it with a link (symbolic or physical, both work) to the /etc/shadow file. Let's remember, that every user can create a link to a file in a directory belonging to him even if he can't read the content, (or in /tmp, as we'll see a bit later). However it isn't possible to create a copy of such a file, since it would require a full read.

$ rm -f fic
$ ln -s /etc/shadow ./fic

Then we ask the shell to bring the ex_01 process back to the foreground with the fg command, and wait till it finishes :

$ fg
./ex_01 fic "root::1:99999:::::"
Write Ok
$

Voilà ! It's over, the /etc/shadow file only holds one line indicating root has no password. You don't believe it ?

$ su
# whoami
root
# cat /etc/shadow
root::1:99999:::::
#

Let's finish our experiment by putting the old password file back :

# cp /etc/shadow.bak /etc/shadow
cp: replace `/etc/shadow'? y
#
 

Let's be more realistic

We succeeded in exploiting a race condition in a Set-UID root utility. Of course, this program was very "helpful" waiting for 20 seconds giving us time to modify the files behind its back. Within a real application, the race condition only applies for a very short time. How do we take advantage of that ?

Usually, the cracker relies on a brute force attack, renewing the attempts hundreds, thousands or ten thousand times, using scripts to automate the sequence. It's possible to improve the chance of "falling" into the security hole with various tricks aiming at increasing the lapse of time between the two operations that the program wrongly considers as atomically linked. The idea is to slow down the target process to manage the delay preceding the file modification more easily. Different approaches can help us to reach our goal :

The method allowing us to benefit from a security hole based in race condition is boring and repetitive, but it really is usable ! Let's try to find the most effective solutions.

 

Possible improvement

The problem discussed above relies on the ability to change an object's characteristics during the time-lapse between two operations, the whole thing being as continuous as possible. In the previous situation, the change did not concern the file itself. By the way, as a normal user it would have been quite difficult to modify, or even to read, the /etc/shadow file. As a matter of fact, the change relies on the link between the existing file node in the name tree and the file itself as a physical entity. Let's remember most of the system commands (rm, mv, ln, etc.) act on the file name not on the file content. Even when you delete a file (using rm and the unlink() system call), the content is really deleted when the last physical link - the last reference - is removed.

The mistake made in the previous program is considering the association between the name of the file and its content as unchangeable, or at least constant, during the lapse of time between stat() and fopen() operation. Thus, the example of a physical link should suffice to verify that this association is not a permanent one at all. Let's take an example using this type of link. In a directory belonging to us, we create a new link to a system file. Of course, the file's owner and the access mode are kept. The ln command -f option forces the creation, even if that name already exists :

$ ln -f /etc/fstab ./myfile
$ ls -il /etc/fstab myfile
8570 -rw-r--r--   2 root  root  716 Jan 25 19:07 /etc/fstab
8570 -rw-r--r--   2 root  root  716 Jan 25 19:07 myfile
$ cat myfile
/dev/hda5   /                 ext2    defaults,mand   1 1
/dev/hda6   swap              swap    defaults        0 0
/dev/fd0    /mnt/floppy       vfat    noauto,user     0 0
/dev/hdc    /mnt/cdrom        iso9660 noauto,ro,user  0 0
/dev/hda1   /mnt/dos          vfat    noauto,user     0 0
/dev/hda7   /mnt/audio        vfat    noauto,user     0 0
/dev/hda8   /home/ccb/annexe  ext2    noauto,user     0 0
none        /dev/pts          devpts  gid=5,mode=620  0 0
none        /proc             proc    defaults        0 0
$ ln -f /etc/host.conf ./myfile
$ ls -il /etc/host.conf myfile 
8198 -rw-r--r--   2 root  root   26 Mar 11  2000 /etc/host.conf
8198 -rw-r--r--   2 root  root   26 Mar 11  2000 myfile
$ cat myfile
order hosts,bind
multi on
$

The /bin/ls -i option displays the inode number at the beginning of the line. We can see the same name points to two different physical inodes.

In fact, we would like the functions that check and access the file to always point to the same content and the same inode. And it's possible ! The kernel itself automatically manages this association when it provides us with a file descriptor. When we open a file for reading, the open() system call returns an integer value, that is the descriptor, associating it with the physical file by an internal table. All the reading we'll do next will concern this file content, no matter what happens to the name used during the file open operation.

Let's emphasize that point : once a file has been opened, every operation on the filename, including removing it, will have no effect on the file content. As long as there is still a process holding a descriptor for a file, the file content isn't removed from the disk, even if its name disappears from the directory where it was stored. The kernel maintains the association to the file content between the open() system call providing a file descriptor and the release of this descriptor by close() or the process ends.

So there we have our solution ! We can open the file and then check the permissions by examining the descriptor characteristics instead of the filename ones. This is done using the fstat() system call (this last working like stat()), but checking a file descriptor rather than a path. To access the content of the file using the descriptor we'll use the fdopen() function (that works like fopen()) while relying on a descriptor rather than on a filename. Thus, the program becomes :

1    /* ex_02.c */
2    #include <fcntl.h>
3    #include <stdio.h>
4    #include <stdlib.h>
5    #include <unistd.h>
6    #include <sys/stat.h>
7    #include <sys/types.h>
8
9     int
10    main (int argc, char * argv [])
11    {
12        struct stat st;
13        int fd;
14        FILE * fp;
15
16        if (argc != 3) {
17            fprintf (stderr, "usage : %s file message\n", argv [0]);
18            exit(EXIT_FAILURE);
19        }
20        if ((fd = open (argv [1], O_WRONLY, 0)) < 0) {
21            fprintf (stderr, "Can't open %s\n", argv [1]);
22            exit(EXIT_FAILURE);
23        }
24        fstat (fd, & st);
25        if (st . st_uid != getuid ()) {
26            fprintf (stderr, "%s not owner !\n", argv [1]);
27            exit(EXIT_FAILURE);
28        }
29        if (! S_ISREG (st . st_mode)) {
30            fprintf (stderr, "%s not a normal file\n", argv[1]);
31            exit(EXIT_FAILURE);
32        }
33        if ((fp = fdopen (fd, "w")) == NULL) {
34            fprintf (stderr, "Can't open\n");
35            exit(EXIT_FAILURE);
36        }
37        fprintf (fp, "%s", argv [2]);
38        fclose (fp);
39        fprintf (stderr, "Write Ok\n");
40        exit(EXIT_SUCCESS);
41    }

This time, after line 20, no change to the filename (deleting, renaming, linking) will affect our program's behavior; the content of the original physical file will be kept.

 

Guidelines

When manipulating a file it's important to ensure the association between the internal representation and the real content stays constant. Preferably, we'll use the following system calls to manipulate the physical file as an already open descriptor rather than their equivalents using the path to the file :

System call Use
fchdir (int fd) Goes to the directory represented by fd.
fchmod (int fd, mode_t mode) Changes the file access rights.
fchown (int fd, uid_t uid, gid_t gif) Changes the file owner.
fstat (int fd, struct stat * st) Consults the informations stored within the inode of the physical file.
ftruncate (int fd, off_t length) Truncates an existing file.
fdopen (int fd, char * mode) Initializes IO from an already open descriptor. It's an stdio library routine, not a system call.

Then, of course, you must open the file in the wanted mode, calling open() (don't forget the third argument when creating a new file). More on open() later when we discuss the temporary file problem.

We must insist that it is important to check the system calls return codes. For instance, let's mention, even if it has nothing to do with race conditions, a problem found in old /bin/login implementations because it neglected an error code check. This application, automatically provided a root access when not finding the /etc/passwd file. This behavior can seem acceptable as soon as a damaged file system repair is concerned. On the other hand, checking that it was impossible to open the file instead of checking if the file really existed, was less acceptable. Calling /bin/login after opening the maximum number of allowed descriptors allowed any user to get root access... Let's finish with this digression insisting in how it's important to check, not only the system call's success or failure, but the error codes too, before taking any action about system security.

 

Race conditions to the file content

A program dealing with system security shouldn't rely on the exclusive access to a file content. More exactly, it's important to properly manage the risks of race conditions to the same file. The main danger comes from a user running multiple instances of a Set-UID root application simultaneously or establishing multiple connections at once with the same daemon, hoping to create a race condition situation, during which the content of a system file could be modified in an unusual way.

To avoid a program being sensitive to this kind of situation, it's necessary to institute an exclusive access mechanism to the file data. This is the same problem as the one found in databases when various users are allowed to simultaneously query or change the content of a file. The principle of file locking solves this problem.

When a process wants to write into a file, it asks the kernel to lock that file - or a part of it. As long as the process keeps the lock, no other process can ask to lock the same file, or at least the same part of the file. In the same way, a process asks for a lock before reading the file content to ensure no changes will be made while it holds the lock.

As a matter of fact, the system is more clever than that : the kernel distinguishes between the locks required for file reading and those for file writing. Various processes can hold a lock for reading simultaneously since no one will attempt to change the file content. However, only one process can hold a lock for writing at a given time, and no other lock can be provided at the same time, even for reading.

There are two types of locks (mostly incompatible with each other). The first one comes from BSD and relies on the flock() system call. Its first argument is the descriptor of the file you wish to access in an exclusive way, and the second one is a symbolic constant representing the operation to be done. It can have different values : LOCK_SH (lock for reading), LOCK_EX (for writing), LOCK_UN (release of the lock). The system call blocks as long as the requested operation remains impossible. However, you can do a binary OR | of the LOCK_NB constant for the call to fail instead of staying locked.

The second type of lock comes from System V, and relies on the fcntl() system call whose invocation is a bit complicated. There's a library function called lockf() close to the system call but not as fast. fcntl()'s first argument is the descriptor of the file to lock. The second one represents the operation to be performed : F_SETLK and F_SETLKW manage a lock, the second command stays blocks till the operation becomes possible, while the first immediately returns in case of failure. F_GETLK consults the lock state of a file (which is useless for current applications). The third argument is a pointer to a variable of struct flock type, describing the lock. The flock structure important members are the following :

Name Type Meaning
l_type int Expected action : F_RDLCK (to lock for reading), F_WRLCK (to lock for writing) and F_UNLCK (to release the lock).
l_whence int l_start Field origin (usually SEEK_SET).
l_start off_t Position of the beginning of the lock (usually 0).
l_len off_t Length of the lock, 0 to reach the end of the file.

We can see fcntl() can lock limited portions of the file, but it's able to do much more compared to flock(). Let's have a look at a small program asking for a lock for reading concerning files which names are given as an argument, and waiting for the user to press the Enter key before finishing (and thus releasing the locks).

1    /* ex_03.c */
2    #include <fcntl.h>
3    #include <stdio.h>
4    #include <stdlib.h>
5    #include <sys/stat.h>
6    #include <sys/types.h>
7    #include <unistd.h>
8
9    int
10   main (int argc, char * argv [])
11   {
12     int i;
13     int fd;
14     char buffer [2];
15     struct flock lock;
16
17     for (i = 1; i < argc; i ++) {
18       fd = open (argv [i], O_RDWR | O_CREAT, 0644);
19       if (fd < 0) {
20         fprintf (stderr, "Can't open %s\n", argv [i]);
21         exit(EXIT_FAILURE);
22       }
23       lock . l_type = F_WRLCK;
24       lock . l_whence = SEEK_SET;
25       lock . l_start = 0;
26       lock . l_len = 0;
27       if (fcntl (fd, F_SETLK, & lock) < 0) {
28         fprintf (stderr, "Can't lock %s\n", argv [i]);
29         exit(EXIT_FAILURE);
30       }
31     }
32     fprintf (stdout, "Press Enter to release the lock(s)\n");
33     fgets (buffer, 2, stdin);
34     exit(EXIT_SUCCESS);
35   }

We first launch this program from a first console where it waits :

$ cc -Wall ex_03.c -o ex_03
$ ./ex_03 myfile
Press Enter to release the lock(s)
>From another terminal...
    $ ./ex_03 myfile
    Can't lock myfile
    $
Pressing Enter in the first console, we release the locks.

With this locking mechanism, you can prevent race conditions to directories and print queues, like the lpd daemon, using a flock() lock on the /var/lock/subsys/lpd file, thus allowing only one instance. You can also manage the access to a system file in a secure way like /etc/passwd, locked using fcntl() from the pam library when changing a user's data.

However, this only protects from interferences with applications having correct behavior, that is, asking the kernel to reserve the proper access before reading or writing to an important system file. We now talk about cooperative lock, what shows the application liability towards data access. Unfortunately, a badly written program is able to replace file content, even if another process, with good behavior, has a lock for writing. Here is an example. We write a few letters into a file and lock it using the previous program :

$ echo "FIRST" > myfile
$ ./ex_03 myfile
Press Enter to release the lock(s)
>From another console, we can change the file :
    $ echo "SECOND" > myfile
    $
Back to the first console, we check the "damages" :
(Enter)
$ cat myfile
SECOND
$

To solve this problem, the Linux kernel provides the sysadmin with a locking mechanism coming from System V. Therefore you can only use it with fcntl() locks and not with flock(). The administrator can tell the kernel the fcntl() locks are strict, using a particular combination of access rights. Then, if a process locks a file for writing, another process won't be able to write into that file (even as root). The particular combination is to use the Set-GID bit while the execution bit is removed for the group. This is obtained with the command :

$ chmod g+s-x myfile
$
However this is not enough. For a file to automatically benefit from strict cooperative locks, the mandatory attribute must be activated on the partition where it can be found. Usually, you have to change the /etc/fstab file to add the mand option in the 4th column, or typing the command :
# mount
/dev/hda5 on / type ext2 (rw)
[...]
# mount / -o remount,mand
# mount
/dev/hda5 on / type ext2 (rw,mand)
[...]
#
Now, we can check that a change from another console is impossible :
$ ./ex_03 myfile
Press Enter to release the lock(s)
>From another terminal :
    $ echo "THIRD" > myfile
    bash: myfile: Resource temporarily not available
    $
And back to the first console :
(Enter)
$ cat myfile
SECOND
$

The administrator and not the programmer has to decide to make strict file locks (for instance /etc/passwd, or /etc/shadow). The programmer has to control the way the data is accessed, what ensures his application to manages data coherently when reading and it is not dangerous for other processes when writing, as long as the environment is properly administrated.

 

Temporary files

Very often a program needs to temporarily store data in an external file. The most usual case is inserting a record in the middle of a sequential ordered file, which implies that we make a copy of the original file in a temporary file, while adding new information. Next the unlink() system call removes the original file and rename() renames the temporary file to replace the previous one.

Opening a temporary file, if not done properly, is often the starting point of race condition situations for an ill-intentioned user. Security holes based on the temporary files have been recently discovered in applications such as Apache, Linuxconf, getty_ps, wu-ftpd, rdist, gpm, inn, etc. Let's remember a few principles to avoid this sort of trouble.

Usually, temporary file creation is done in the /tmp directory. This allows the sysadmin to know where short term data storage is done. Thus, it's also possible to program a periodic cleaning (using cron), the use of an independent partition formated at boot time, etc. Usually, the administrator defines the location reserved for temporary files in the <paths.h> and <stdio.h> files, in the _PATH_TMP and P_tmpdir symbolic constants definition. As a matter of fact, using another default directory than /tmp is not that good, since it would imply recompiling every application, including the C library. However, let's mention that GlibC routine behavior can be defined using the TMPDIR environment variable. Thus, the user can ask the temporary files to be stored in a directory belonging to him rather than in /tmp. This is sometimes mandatory when the partition dedicated to /tmp is too small to run applications requiring big amount of temporary storage.

The /tmp system directory is something special because of its access rights :

$ ls -ld /tmp
drwxrwxrwt 7 root  root    31744 Feb 14 09:47 /tmp
$

The Sticky-Bit represented by the letter t at the end or the 01000 octal mode, has a particular meaning when applied to a directory : only the directory owner (root ), and the owner of a file found in that directory are able to delete the file. The directory having a full write access, each user can put his files in it, being sure they are protected - at least till the next clean up managed by the sysadmin.

Nevertheless, using the temporary storage directory may cause a few problems. Let's start with the trivial case, a Set-UID root application talking to a user. Let's talk about a mail transport program. If this process receives a signal asking it to finish immediately, for instance SIGTERM or SIGQUIT during a system shutdown, it can try to save on the fly the mail already written but not sent. With old versions, this was done in /tmp/dead.letter. Then, the user just had to create (since he can write into /tmp) a physical link to /etc/passwd with the name dead.letter for the mailer (running under effective UID root) to write to this file the content of the not yet finished mail (incidently containing a line "root::1:99999:::::").

The first problem with this behavior is the foreseeable nature of the filename. You can to watch such an application only once to deduct it will use the /tmp/dead.letter file name. Therefore, the first step is to use a filename defined for the current program instance. There are various library functions able to provide us with a personal temporary filename.

Let's suppose we have such a function providing a unique name for our temporary file. Free software being available with source code (and so for C library), the filename is however foreseeable even if it's rather difficult. An attacker could create a symlink to the name provided by the C library. Our first reaction is to check the file exists before opening it. Naively we could write something like :

  if ((fd = open (filename, O_RDWR)) != -1) {
    fprintf (stderr, "%s already exists\n", filename);
    exit(EXIT_FAILURE);
  }
  fd = open (filename, O_RDWR | O_CREAT, 0644);
  ...

Obviously, this is a typical case of race condition, where a security hole opens following the action from a user succeeding in creating a link to /etc/passwd between the first open() and the second one. These two operations have to be done in an atomic way, without any manipulation able to take place between them. This is possible using a specific option of the open() system call. Called O_EXCL, and used in conjunction with O_CREAT, this option makes the open() fail if the file already exists, but the check of existence is atomically linked to the creation.

By the way, the 'x' Gnu extension for the opening modes of the fopen() function, requires an exclusive file creation, failing if the file already exists :

  FILE * fp;

  if ((fp = fopen (filename, "r+x")) == NULL) {
    perror ("Can't create the file.");
    exit (EXIT_FAILURE);
  }

The temporary files permissions are quite important too. If you have to write confidential information into a mode 644 file (read/write for the owner, read only for the rest of the world) it can be a bit of a nuisance. The

	#include <sys/types.h>
	#include <sys/stat.h>

        mode_t umask(mode_t mask);
function allows us to determine the permissions of a file at creation time. Thus, following a umask(077) call, the file will be open in mode 600 (read/write for the owner, no rights at all for the others).

Usually, the temporary file creation is done in three steps :

  1. unique name creation (random) ;
  2. file opening using O_CREAT | O_EXCL, with the most restrictive permissions;
  3. checking the result when opening the file and reacting accordingly (either retry or quit).

How create a temporary file ? The

      #include <stdio.h>

      char *tmpnam(char *s);
      char *tempnam(const char *dir, const char *prefix);

functions return pointers to randomly created names.

The first function accepts a NULL argument, then it returns a static buffer address. Its content will change at tmpnam(NULL) next call. If the argument is an allocated string, the name is copied there, what requires a string of at least L-tmpnam bytes. Be careful with buffer overflows ! The man page informs about problems when the function is used with a NULL parameter, if _POSIX_THREADS or _POSIX_THREAD_SAFE_FUNCTIONS are defined.

The tempnam() function returns a pointer to a string. The dir directory must be "suitable" (the man page describes the right meaning of "suitable"). This function checks the file doesn't exist before returning its name. However, once again, the man page doesn't recommend its use, since "suitable" can have a different meaning according to the function implementations. Let's mention that Gnome recommends its use in this way :

  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);
The loop used here, reduces the risks but creates new ones. What would happen if the partition where you want to create the temporary file is full, or if the system already opened the maximum number of files available at once...

The

       #include <stdio.h>

       FILE *tmpfile (void);
function creates an unique filename and opens it. This file is automatically deleted at closing time.

With GlibC-2.1.3, this function uses a mechanism similar to tmpnam() to generate the filename, and opens the corresponding descriptor. The file is then deleted, but Linux really removes it when no resources at all use it, that is when the file descriptor is released, using a close() system call.

  FILE * fp_tmp;

  if ((fp_tmp = tmpfile()) == NULL) {
    fprintf (stderr, "Can't create a temporary file\n");
    exit (EXIT_FAILURE);
  }

  /* ... use of the temporary file ... */

  fclose (fp_tmp);  /* real deletion from the system */

The simplest cases don't require filename change nor transmission to another process, but only storage and data re-reading in a temporary area. We therefore don't need to know the name of the temporary file but only to access its content. The tmpfile() function does it.

The man page says nothing, but the Secure-Programs-HOWTO doesn't recommend it. According to the author, the specifications don't guarantee the file creation and he hasn't been able to check every implementation. Despite this reserve, this function is the most efficient.

Last, the

       #include <stdlib.h>

       char *mktemp(char *template);
       int mkstemp(char *template);
functions create an unique name from a template made of a string ending with "XXXXXX". These 'X's are replaced to get an unique filename.

According to versions, mktemp() replaces the first five 'X' with the Process ID (PID) ... what makes the name rather easy to guess : only the last 'X' is random. Some versions allow more than six 'X'.

mkstemp() is the recommended function in the Secure-Programs-HOWTO. Here is the method :

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>

 void failure(msg) {
  fprintf(stderr, "%s\n", msg);
  exit(1);
 }

/*
 * Creates a temporary file and returns it.
 * This routine removes the filename from the filesystem thus
 * it doesn't appear anymore when listing the directory.
 */
FILE *create_tempfile(char *temp_filename_pattern)
{
  int temp_fd;
  mode_t old_mode;
  FILE *temp_file;

  /* Create file with restrictive permissions */
  old_mode = umask(077);
  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;
}

These functions show the problems concerning abstraction and portability. That is, the standard library functions are expected to provide features (abstraction)... but the way to implement them varies according to the system (portability). For instance, the tmpfile() function opens a temporary file in different ways (some versions don't use O_EXCL), or mkstemp() handles a variable number of 'X' according to implementations.  

Conclusion

We flew over most of the security problems concerning race conditions to the same resource. Let's remember you must never assume that two consecutive operations are always sequentially processed in the CPU unless the kernel manages this. If race conditions generate security holes, you must not neglect the holes caused by relying on other resources, such as variables shared between threads or memory segments shared using shmget(). Selection access mechanisms (semaphore, for example) must be used to avoid hard to discover bugs.

 

Links


 

Talkback form for this article

Every article has its own talkback page. On this page you can submit a comment or look at comments from other readers:
 talkback page 

Webpages maintained by the LinuxFocus Editor team
© Frédéric Raynal, Christophe Blaess, Christophe Grenier, FDL
LinuxFocus.org
Translation information:
fr --> -- : Frédéric Raynal, Christophe Blaess, Christophe Grenier <pappy(at)users.sourceforge.net, ccb(at)club-internet.fr, grenier(at)nef.esiea.fr>
fr --> en: Georges Tarbouriech <georges.t(at)linuxfocus.org>
en --> en: Lorne Bailey <sherm_pbody(at)yahoo.com>

2002-09-22, generated by lfparser version 2.31