Original in fr Frédéric Raynal, Christophe Blaess, Christophe Grenier
fr to en Georges Tarbouriech
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.
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.
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 #
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 :
nice -n 20
prefix;
while (1);
);
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.
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.
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.
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.
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 :
O_CREAT | O_EXCL
, with the most
restrictive permissions;
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.
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.