TranslationInfo:

Original in fr Frédéric Raynal, Christophe Blaess, Christophe Grenier

fr to en Georges Tarbouriech

AboutTheAuthor:

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, it doesn't use hormones, neither GMO nor animal fat-flour... only sweat and tricks.

Abstract

This fifth article of our series is dedicated to security problems related to the way of working of a multitasking OS. The race conditions allow various processes to use the same resource at once (file, device, memory), though each one "believes" it has an exclusive use. This leads to bugs difficult to detect but also to real security holes able to compromise the global security of a system.

Introduction

The general principle of race conditions is the following : a process wants to access a system resource in an exclusive way. It checks the resource is not already used by another process, then it takes over and uses it as it wants. The problem appears when another process tries to benefit from the lapse of time between the check and the true access to take over the same resource. The results may vary. The classical case in the OS theory is the definitive lock of both processes. In more practical cases, this leads to applications misfunction, or to true security holes when a process wrongfully benefits from the privileges of the other.

What we previously called resource can have different aspect. Most of the race condition problems often discovered and corrected in the kernel itself, rely on competitive access to memory areas. Here, we will focus on system applications and we'll consider that the concerned resources are filesytem nodes. This not only means usual files but also direct access to devices through special entry points from the /dev/ directory.

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

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

First example

Let's have a look at the behavior of a Set-UID program having to save data into 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, what is plausible under some circumstances. The application must then check the file belongs to the person having run the program. It also will check that the file is not a symlink to a system file. Don't we forget, the program being Set-UID root, it is allowed to modify any file in 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 loose its privileges and to open the file using the real UID of the user having called it. As a matter of fact, the above situation rather corresponds to the one of a daemon, providing services to every user. Always running under the root ID, it would check comparing to the UID instead of its own real UID. Nevertheless, we do keep that scheme, even if it isn't that realistic, since it allows to understand the problem while easily "exploiting" the security hole.

As we can see, the program starts doing all the needed controls, checking that the file exists, that it belongs to the user and that it's a normal file. Next, it really opens the file and writes the message. And, that is where lies the security hole ! 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 it isn't null, then an attacker can benefit from it to change the file's characteristics. To make our attack even easier, let's add a line making the process sleeping 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 second field to be empty (no password). While the process is asleep, we have about 20 seconds to remove the fic file and to replace it with a link (symbolic or physical, both work) to the /etc/shadow file. Let's remind, that every user can create a link to a file even if he can't read the content, in a directory belonging to him (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 /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 putting the old password file back :

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

Let's be more realist

We did succeed in exploiting a race condition in a Set-UID root utility. Of course, this program was very "helpful" waiting for 20 seconds we finish to modify the files behind its back. Within a real application, the race condition only applies during very short lapses of time. How to benefit from that ?

Usually, the principle relies on a brutal attack, renewing the attempts hundred, thousand 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 more easily the delay preceding the file modification. Different approaches can be conceivable to reach our goal :

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

Possible improvement

The above discussed problem relies on the ability to change an object characteristics during the lapse of time between two operations concerning this object, 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 node in the name tree and the file itself as a physical entity. Let's remind 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 therefore to consider as unchanging the association between the name of the file and its content, or at least, constant during the lapse of time between stat() and fopen() operations. Thus, enough to take the example of a physical link to check this association is not at all a permanent one. 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 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 allows to display the inode number at the beginning of the line. Thus we can see the same name points to two differents physical inodes. By the way, it's obvious that both "cat" commands, while working on the same filename, display two completely different contents, nevertheless no change happened to these files between the two operations.

In fact, we would like the functions used to check and to 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 to the physical file within an internal table. All the reading we'll do next will concern this file content, whatever happens to the name used for the file opening.

Let's insist on that point : once a file has been opened, every operation concerning the filename, including removing it, will have no effect on the file content. As soon as there is still a process having a descriptor for a file, the file content isn't removed from the disk, even if its name disappeared from the directory where it was stored. The kernel ensures to keep the association to the file content during the lapse of time between the open() system call providing a file descriptor and the release of this descriptor using close() or when the process ends.

But then we got our solution ! Enough to start opening the file and then check the permissions 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 get next an IO flow around the descriptor we'll use the fdopen() function (working 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 concerning the filename (deleting, renaming, linking) will affect our program's behavior; the content of the original physical file will be kept.

Generalization

Thus, it's important, when manipulating a file, to ensure the association between the internal representation and the real content stays constant. Preferently, we'll use the following system calls, these last manipulating 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) Gets an IO flow around the already open descriptor. It's an stdio library routine, not a system call.

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

We must insist in how 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 of neglecting the 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. Enough to call /bin/login after opening the maximum number of allowed descriptors for an user to get a root access... Let's finish with this digression insisting in how it's important to check, not only the system calls success or failure, but the error codes too, before taking any action about system security.

Race conditions to the file content

A program concerning the system security shouldn't work relying on the exlusive 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 an user running simultaneously multiple instances of a Set-UID root application or establishing various connexions 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 files locking principle allows to solve 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 far 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 locking before reading the content of a file, what ensures no changes will be done as far as the lock is kept.

As a matter of fact, the system is more clever than that : the kernel makes a difference between the locks required for file reading and those for file writing. Various processes simultaneously can benefit from a lock for reading since no one will attempt to change the file content. However, only one process can benefit from 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 lock (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 stays locked as long as the requested operation remains impossible. However, you can add (using a binary OR |) 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 which invocation is a bit complicated. There's a library function called lockf() close to the system call but not so performing. The fcntl() first argument is the descriptor of the file to lock. The second one represents the operation to be done : F_SETLK and F_SETLKW manage a lock, the second command stays locked till the operation becomes possible, while the first immediately returns in case of failure. F_GETLK allows to consult the lock state of a file (what 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 origine (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 from race conditions to directories and print queues, like does the lpd daemon, using a flock() lock on the /var/lock/subsys/lpd file, thus allowing to only run one instance. You can also manage in a secure way the access to a system file like /etc/passwd, locked using fcntl() from the pam library when changing an user's data.

However, this only protects from interferences with applications having a correct behavior, that is, asking the kernel to reserve the proper access before reading or writing to an important system file. We then talk about cooperative lock, what shows the application liability towards data access. Unfortunately, a badly written program is able to replace a 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 stricts, 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 stricts 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 stricts 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 manage coherent data when reading and it is not dangerous for other processes when writing, as soon 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, what implies to 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 remind a few principles to avoid this sort of trouble.

Usually, temporary files creation is done in the /tmp directory. This allows the sysadmin to know where short time data storage is done. Thus, it's also possible to program a periodic cleaning (using cron), the use of an independant 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 the 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 an 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. Enough to watch only once such an application to deduct it will use the /tmp/dead.letter 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, we are here in a typical case of race condition, where a security hole opens following the action from an 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 to fix the permissions for 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 get 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. Careful to 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, neither 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' 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;

  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;
}

These functions show the problems concerning abstraction and portability. That is, the standard libraries 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 a same resource. Let's remind you must never consider that two operations in a row are always linked unless the kernel manages this. If race conditions generate security holes, you must not neglect the holes relying on other resources, such as common variables with different threads, or memory segments shared from shmget(). Selection access mechanisms (semaphore, for example) must be used to avoid bugs hard to discover.

Links