![[image of the authors]](../../common/images/FredCrisBCrisG.jpg) 
 
    Original in fr Frédéric Raynal, Christophe Blaess, Christophe Grenier
fr to en Georges Tarbouriech
en to en Lorne Bailey
en to de Guido Socher
![[article illustration]](../../common/images/illustration183.gif) 
 
    Das Prinzip einer Race Condition ist wie folgt: Ein Prozeß möchte Exklusivrechte für einen Teil des Systems haben. Er überprüft, daß noch kein anderer Prozeß mit diesem Teil des Systems arbeitet, danach bearbeitet er diesen Teil des Systems. Die Race Condition tritt auf, wenn ein anderer Prozeß versucht, in dem kurzen Intervall, in dem der erste Prozeß geprüft hat, daß niemand darauf zugreift, aber den Teil noch nicht für sich reserviert hat, auf dasselbe Teil zuzugreifen. Das Ergebnis kann sehr unterschiedlich sein. Der klassische Fall aus der Betriebssystemtheorie ist ein deadlock für beide Prozesse, das heißt, jeder Prozeß wartet auf den anderen und nichts passiert. Viel häufiger führt es zu "nicht reproduzierbarem" Fehlverhalten des Systems. Ausschalten, wieder einschalten und es geht plötzlich. Viel schlimmer ist, daß sich daraus ein Sicherheitsproblem ergeben kann.
Race Conditions werden oft im Kernel selbst gefunden
    und behoben und es handelt sich dabei meist um Probleme beim
    Zugriff auf Speicher. In diesem Artikel werden wir jedoch mehr auf
    Race Conditions beim Zugriff auf Dateien (Filesystem
    Nodes) eingehen. Das betrifft nicht nur normale Dateien, sondern
    auch Device Dateien aus /dev/.
Im allgemeinen werden immer Set-UID Programme
    angegriffen, wenn versucht wird, die Systemsicherheit zu
    kompromittieren. Das liegt daran, daß der Angreifer dann die
    Privilegien der Set-UID Applikation erben kann. Jedoch
    erlaubt im Gegensatz zu früher besprochenen
    Sicherheitslöchern (buffer overflow, format strings...), die
    Race Conditions es nicht, fremden Code auszuführen.
    Der Angriff kann auch gegen normale Programme (nicht Set-UID)
    laufen. Der Angreifer lauert einem anderen Benutzer auf (oft dem User
    root) und versucht auf Dateien zuzugreifen, die sonst nur root
    lesen und schreiben kann. Schafft man es z.B ein
    "+ +" in die Datei ~/.rhost zu
    schreiben, dann kann man sich auf dem Rechner von einem anderen
    Rechner aus ohne Passwort einloggen. Man kann auch geheime Dateien
    lesen (sensitive kommerzielle Daten, medizinische Daten, Passwort
    Datei, ...)
Betrachten wir das Verhalten eines Set-UID Programmes, das Daten in eine Datei schreiben muß die einem Benutzer gehört. Dieses ist z.B bei dem Mail Transport Programm sendmail der Fall. Die Applikation muß prüfen, ob die Datei auch wirklich dem Benutzer gehört und es kein Verweis (symlink) auf eine Systemdatei ist. Wir sollten nicht vergessen, das das Programm mit Set-UID root läuft und damit jede beliebige Datei auf dem Rechner modifizieren könnte. Diese checks machen also Sinn. Unser Programm könnte z.B so aussehen:
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    }
    Wie wir in dem ersten Artikel erklärt haben, wäre es besser für die Set-UID Applikation zeitweise die Privilegien aufzugeben und die Dateien unter der Identität des Benutzers zu öffnen. Wir bleiben jedoch bei unserem Beispiel, da es dann leichter ist, das Problem Race Condition zu verstehen.
Wie wir sehen, führt das Programm alle nötigen checks
    durch. Als nächstes öffnet es die Datei und schreibt
    einen kurzen Text. Da liegt das Sicherheitsproblem. Oder genauer
    gesagt, liegt es in dem Zeitintervall zwischen den
    stat() und dem fopen(). Diese Zeit ist
    extrem kurz, aber nicht Null. Um den Angriff zu Testzwecken für
    uns einfacher zu machen, erhöhen wir den Zeitraum etwas und
    fügen ein sleep ein. In Zeile 30 schreiben wir:
30 sleep (20);
Hier ist der Probelauf: Wir setzen das Programm auf Set-UID
    root und machen eine Sicherheitskopie der Passwort Datei
    /etc/shadow ( sehr wichtig):
$ 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 $
Alles ist fertig für den Angriff. Wir sind in einem
    Verzeichnis, das uns gehört, wir haben eine Set-UID
    root Utility (hier ex_01) mit einem
    Sicherheitsloch und wir würden gerne den Eintrag für root
    in der Datei /etc/shadow durch ein leeres Password
    ersetzen.
Zuerst erzeugen wir eine Datei namens fic, die uns
    gehört:
$ rm -f fic $ touch fic
Als nächstes starten wir unser Programm in den Hintergrund (&) und bitten es einen String in die Datei fic zu schreiben. Das Programm führt seine checks durch und schläft dann, bevor es wirklich auf die Datei zugreift.
$ ./ex_01 fic "root::1:99999:::::" & [1] 4426
Diesen String hier haben wir in der shadow(5) man
    page nachgelesen. Das zweite Feld ist leer (kein Password). Solange
    der Prozess schläft, wir haben ca. 20 Sekunden Zeit,
    löschen wir die Datei fic und ersetzen sie durch
    einen Link auf /etc/shadow. Wir wir wissen, können
    wir einen Link erzeugen, da uns das Verzeichnis in dem
    fic liegt für uns schreibar ist. Dieses ist auch dann
    der Fall, wenn wir das Ziel des Links, die Datei
    /etc/shadow, nicht lesen können. Es ist jedoch
    nicht möglich, eine Kopie von /etc/shadow zu
    machen.
$ rm -f fic $ ln -s /etc/shadow ./fic
Nun bitten wir die shell den ex_01 Prozess wieder
    in den Vordergrund zu holen, in dem wir fg eingeben
    und warten, bis der Prozess fertig ist.
$ fg ./ex_01 fic "root::1:99999:::::" Write Ok $
Voilà ! Es ist geschehen. Die Datei
    /etc/shadow enthält jetzt genau eine Zeile und
    dort steht, daß root kein Password hat. Du glaubst
    es nicht?
$ su # whoami root # cat /etc/shadow root::1:99999::::: #
Wir beenden das Experiment, indem wir die Sicherheitskopie der Datei /etc/shadow wieder zurückspielen:
# cp /etc/shadow.bak /etc/shadow cp: replace `/etc/shadow'? y #
Wir haben es geschafft, eine Race Condition in einem Set-UID root Programm auszunutzen. Natürlich war das Programm sehr hilfsbereit und wartete 20 Sekunden. In einer echten Applikation ist das nur ein extern kurzer Zeitraum. Wie können wir dann die Race Condition ausnutzen?
Normalerweise probiert es der Angreifer einfach 100, 1000, vielleicht 10000 mal und automatisiert die Sache mit Scripten. Man kann außerdem versuchen, das Programm langsamer zu machen:
nice -n 20 reduzieren.while
      (1);)Das Sicherheitsproblem entsteht aus dem Zeitabstand zwischen dem
    Prüfen der Datei und dem Öffnen der Datei zum Schreiben.
    Ein normaler Benutzer könnte die Datei weder lesen noch
    schreiben, die Datei /etc/shadow selbst hat also nichts
    mit dem Problem zu tun. Die meisten Systembefehle
    (rm, mv, ln, u.s.w.)
    benutzen einen Dateinamen, um auf einen file node im Dateisystem
    zuzugreifen. Eine Datei wird aber wirklich nur gelöscht (rm,
    unlink() system call), wenn der letzte Verweis auf
    eine Datei gelöscht ist. Das wiederum hat nichts mit dem Namen
    der Datei zu tun.
Der Fehler in dem Programm ist die Annahme, daß die
    Assoziation zwischen dem Dateiinhalt und dem Namen konstant sei
    zwischen dem ersten stat() und dem
    fopen(). Das Beispiel eines hardlinks sollte reichen,
    um zu zeigen, daß die Assoziation zwischen Name und
    physikalischer Datei nicht permanent ist. In einem Verzeichnis,
    das uns gehört, erzeugen wir einen neuen Verweis (link) auf
    eine Systemdatei. Natürlich bleiben Eigentümer und
    Dateirechte erhalten:
$ 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 $
Der Befehl /bin/ls -i zeigt die Dateisystem inode
    number am Anfang der Zeile. 
    
    
Was wir also brauchen, sind Funktionen, die die Zugriffsrechte
    prüfen und nicht den Namen der Datei benutzen, sondern die
    inode Nummer. Das ist möglich. Der Kernel selbst managed diese
    Assoziation, wenn er uns einen Filedescriptor gibt. Wenn wir eine
    Datei zum Lesen öffnen, gibt der open() Aufruf
    einen Integer Wert zurück. Dieser Wert wird in einer internen
    Tabelle verwaltet und zeigt immer auf denselben Inhalt, egal was mit
    dem Namen der Datei passiert, während wir die Datei lesen.
Um das nochmal zu betonen: Sobald eine Datei geöffnet wird,
    hat jede Operation, die mit dem Dateinamen arbeitet, keinen Effekt
    mehr. Selbst wenn jemand die Datei (den Namen) löscht, sorgt
    der Kernel dafür, das wir sie in Ruhe zu Ende lesen dürfen.
    Der Kernel erhält also die Assoziation zwischen Inhalt und dem
    Filedescriptor, den wir mit dem open() system call
    erhalten haben, bis wir den Filedescriptor mit close()
    wieder freigeben oder unser Programm beenden.
Da haben wir die Lösung! Beim Check der Rechte und
    Dateieigentümer benutzen wir den Filedescriptor und nicht den
    Namen. Der System Call ist dann fstat() Anstelle von
    stat() und fdopen() benutzen wir, wenn
    wir die Datei lesen möchten. Damit sieht unser Programm so
    aus:
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    }
    Dieses Mal wird nach Zeile 20 kein Verändern des Dateinamens (löschen, umbenennen, Link setzen) Einfluß auf das Programm haben.
Wenn man eine Datei verändert, ist es wichtig, sicherzustellen, daß die Assoziation zwischen interner Darstellung im Programm und dem wirklichem Inhalt konstant bleibt. Man sollte folgende Befehle benutzen und nicht ihre Äquivalente, die nur mit dem Dateinamen arbeiten:
| System call | Use | 
| fchdir (int fd) | Geht in das Verzeichnis, das durch fd repräsentiert wird. | 
| fchmod (int fd, mode_t mode) | Ändert die Dateizugriffsrechte. | 
| fchown (int fd, uid_t uid, gid_t gif) | Ändert den Dateieigentümer. | 
| fstat (int fd, struct stat * st) | Liest verschiedene Parameter, die die physikalische Datei beschreiben. | 
| ftruncate (int fd, off_t length) | Schneidet eine Datei ab. | 
| fdopen (int fd, char * mode) | Inizialisiert die Ein- Ausgabe einer schon geöffneten Datei. Es ist eine stdio Bibliotheksroutine und kein system call. | 
Natürlich muß man die Datei in dem gewünschten
    Mode öffnen, wenn man open() aufruft.
Es ist wichtig, die Rückgabewerte der Systemcalls zu
    prüfen. Das hat nichts mit Race Conditions zu tun,
    kann aber auch zu Sicherheitsproblemen führen. Eine
    ältere Implementation von /bin/login führte
    zu einem Sicherheitsproblem, weil ein Fehlercode nicht geprüft
    wurde. Login gab automatisch root Rechte frei, wenn die Datei
    /etc/passwd nicht gefunden wurde. Das Verhalten mag
    hilfreich bei einem beschädigten Dateisystem sein, wenn
    dadurch /etc/passwd nicht lesbar ist, es ist aber auch
    ein Sicherheitsloch. Nachdem die maximale Anzahl möglicher
    geöffneter Filedescriptoren geöffnet war, mußte man nur
    /bin/login aufrufen und man war ... root ...
Ein Programm bei dem es um Systemsicherheit geht, sollte sich nicht auf exklusive Zugriffsrechte verlassen. Das Hauptproblem entsteht, wenn ein Benutzer mehrere Instanzen eines Set-UID root Programmes laufen läßt.
Um die Probleme zu vermeiden, sollte man einen Exklusiv Zugriffsmechanismus für Dateien benutzen. Ähnliche Mechanismen findet man in Datenbanken, wenn mehrere Benutzer eine Tabelle modifizieren. Man bezeichnet das als Locking.
Wenn ein Prozess Daten exklusiv schreiben/lesen möchte, dann muß er den Kernel bitten, die ganze Datei oder Teile davon zu locken. Solange der Prozess dann im Besitz des Locks (Schloß) ist, kann kein anderer Prozess ein Lock erhalten oder zumindest kein Lock für denselben Teil der Datei.
Es gibt unterschiedliche Locks für Prozesse, die nur schreiben oder nur lesen möchten. Viele Prozesse können ein Lock zum Lesen besitzen, aber nur einer kann eines zum Schreiben haben.
Es gibt zwei unterschiedliche Lock Mechanismen, die nicht
    kompatibel zueinander sind. Das eine kommt von BSD und benutzt den
    Systemcall flock(). Das erste Argument für flock
    ist ein Filedescriptor der Datei, auf die man zugreifen
    möchte. Das zweite Argument ist eine symbolische Konstante, die
    folgende Werte haben kann: LOCK_SH (Lock zum Lesen),
    LOCK_EX (Lock zum Schreiben). Zusätzlich kann man
    diese Konstanten über ein binäres oder (|) mit
    LOCK_NB verknüpfen, um zu bestimmern, ob der eigene
    Prozess blocken (=warten) soll, bis das Lock frei ist, oder ob der
    flock() mit einem Fehlercode zurückkommen soll, falls das Lock
    nicht verfügbar ist.
Der zweite Typ von Lock kommt aus System V und benutzt den
    fcntl() Systemcall, dessen Aufruf etwas komplizierter
    ist. Es gibt eine Bibliotheksfunktion lockf(), die den
    fcntl() Aufruf benutzt, jedoch nicht so schnell ist
    wie die ursprüngliche fcntl() Funktion. Das erste
    Argument für fcntl() ist ein Filedescriptor. Das
    zweite repräsentiert die Operation, die ausgeführt werden
    soll: F_SETLK und F_SETLKW.
    F_SETLKW wartet bis das Lock erhalten werden kann
    wohingegen die andere mit einem Fehlercode zurückkommt. Mit
    F_GETLK kann man den Zustand des Locks abfragen. Das
    dritte Argument ist ein Pointer auf struct flock der
    das Lock beschreibet:
| Name | Typ | Bedeutung | 
| l_type | int | Was zu tun ist : F_RDLCK(lock zum Lesen),F_WRLCK(lock zum Schreiben) undF_UNLCK(lock freigeben). | 
| l_whence | int | l_start= Field origin (normalerweiseSEEK_SET). | 
| l_start | off_t | Position, bei der das Lock beginnt (normalerweise 0). | 
| l_len | off_t | Länge des Locks. 0 = bis zum Ende der Datei | 
Wie wir sehen, kann fcntl() auch Teile einer Datei
    locken. Hier ist ein kleines Beispielprogramm, das eine Datei lockt
    und dann den Benutzer bittet, Return zu drücken und das Lock
    wieder frei gibt.
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   }
    Wir starten das Programm aus dem ersten xterm Fenster, wo es dann auf die Eingabe wartet.
$ cc -Wall ex_03.c -o ex_03 $ ./ex_03 myfile Press Enter to release the lock(s)>in dem zweiten xterm Fenster...
    $ ./ex_03 myfile
    Can't lock myfile
    $
    Wenn wir Enter in dem ersten Xterm Fenster
    drücken, geben wir das Lock frei. 
    Mit diesem Mechanismus kann man Race Conditions verhindern. Der
    lpd daemon benutzt ein flock() lock auf
    /var/lock/subsys/lpd, um zu erreichen, daß nur
    eine Instanz von lpd läuft. Die pam library benutzt
    fcntl(), um /etc/passwd zu lesen.
Leider schützt dieser Mechanismus nur vor Applikationen, die sich korrekt verhalten. Das heißt, sie fragen den Kernel zuerst nach einem Lock, bevor sie wichtige Daten lesen oder schreiben. Wir sprechen hier von sogenannten kooperativen Locks. Ein schlecht geschriebenes Programm kann die Datei immer noch änderen selbst, wenn ein gutes Programm ein Lock für die Datei besitzt. Hier ist ein Beispiel. Wir schreiben ein paar Zeichen in eine Datei, die gelockt ist:
$ echo "FIRST" > myfile $ ./ex_03 myfile Press Enter to release the lock(s)>In dem anderem Xterm ändern wir die Datei einfach :
    $ echo "SECOND" > myfile
    $
    Zurück in dem ersten xterm überprüfen wir den
    Schaden: 
(Enter) $ cat myfile SECOND $
Um dieses Problem zu lösen, bietet der Linux Kernel dem
    Sysadmin noch einen weiteren Mechanismus, der das Problem
    löst. Er kommt aus System V und kann deshalb nur mit
    fcntl() und nicht mit flock() benutzt
    werden. Der Systemadministrator kann dem Kernel sagen, daß
    die fcntl() locks streng sind. Das geht mit einer
    bestimmten Set-GID Bit Kombination, bei der das X-Bit
    entfernt ist für die Gruppe. Gesetzt wird das über
    chmod:
$ chmod g+s-x myfile $Das ist jedoch noch nicht genug. Zusätzlich muß man sicherstellen, daß das mandatory Attribut für die Partition aktiviert ist, in der sich die Datei befindet. Normalerweise muß man dazu den
/etc/fstab
    Eintrag ändern und die mand Option in der vierten
    Spalte einfügen oder die Option dem Kommando mount direkt
    übergeben: 
# mount /dev/hda5 on / type ext2 (rw) [...] # mount / -o remount,mand # mount /dev/hda5 on / type ext2 (rw,mand) [...] #Nun probieren wir das nochmal:
$ ./ex_03 myfile Press Enter to release the lock(s)>aus dem zweiten xterm ...:
    $ echo "THIRD" > myfile
    bash: myfile: Resource temporarily not available
    $  
    Der Systemadministrator und nicht der Programmierer entscheidet,
    ob Locks streng sind für bestimmte Dateien (z.B.
    /etc/passwd, oder /etc/shadow). Der
    Programmierer muß kontrollieren, wann auf die Daten zugegriffen
    werden soll und locks richtig handhaben.
Sehr oft besteht die Notwendigkeit, in einem Programm Daten
    temporär in eine Datei zu speichern. Wenn man z.B in der Mitte
    einer Datei etwas einfügen möchte liest man das
    Original und schreibt die entsprechend geänderten Daten in
    eine temporäre Datei. Anschließend kann man das Original
    löschen (unlink()) und die temporäre Datei
    in die Original Datei umbenennen (rename()).
Das Öffnen einer temporären Datei, wenn falsch angelegt, ist oft der Startpunkt einer Race Condition, die von einem boshaften Benutzer ausgenutzt werden kann. Sicherheitslöcher basierend auf temporären Dateien wurden kürzlich in Programmen wie Apache, Linuxconf, getty_ps, wu-ftpd, rdist, gpm, inn, etc... entdeckt. Es gibt einige Regeln, die man beachten muß, um solche Probleme zu vermeiden.
Temporäre Dateien werden im allgemeinen in
    /tmp erzeugt. Der Systemadministrator kann dann
    periodisch ein Programm (mit Hilfe von crontab) laufen lassen, das
    alte temporären Dateien löscht. Das Verzeichnis für
    temporäre Dateien ist in <paths.h> und
    <stdio.h> festgelegt über die symbolischen
    Konstanten _PATH_TMP und P_tmpdir. GlibC
    erlaubt es auch über die Environment Variable
    TMPDIR festzulegen, wo temporäre Dateien
    geschrieben werden sollen.
Das Verzeichnis /tmp ist etwas besonderes wegen
    seiner speziellen Zugriffsrechte:
$ ls -ld /tmp drwxrwxrwt 7 root root 31744 Feb 14 09:47 /tmp $
Das Sticky-Bit hier als t dargestellt,
    oktal 01000, hat eine besondere Bedeutung, wenn es auf Verzeichnisse
    angewendet wird: Nur der Eigentümer (root) des
    Verzeichnisses und der Eigentümer der Datei können
    Dateien löschen, da das Verzeichnis aber ansonsten volle
    Schreibrechte hat, kann jeder dort schreiben.
Trotzdem kann es hier zu Problemen kommen. Nehmen wir z.B ein
    Mail Transport Programm. Wenn es ein Signal SIGTERM oder
    SIGQUIT während des shutdown des Rechners
    erhält, kann es versuchen, Dateien schnell zu speichern. In
    älteren Programmen wurde das in /tmp/dead.letter
    gemacht. Ein böswilliger Benutzer brauchte nur einen Link in
    /tmp mit dem Namen dead.letter zu erzeugen und diesen
    auf /etc/passwd zeigen zu lassen. Da das Mail
    Transport Programm mit root Rechten läuft, schrieb es die noch
    nicht fertige Mail, die zufällig die Zeile
    "root::1:99999:::::" enthielt in
    /etc/passwd.
Das erste Problem ist der vorhersehbare Name. Man braucht solch
    eine Applikation nur einmal zu beobachten und man weiß, daß
    die Datei /tmp/dead.letter heißen wird. Der
    erste Schritt ist daher, einen Namen zu benutzen, der nicht konstant
    ist. Verschiedene Bibliotheksfunktionen sind dazu in der Lage.
Jetzt ist die Sache jedoch nur schwieriger geworden. Der Name wird immer noch berechenbar sein, speziell wenn der Sourcecode der Bibliotheksfunktionen vorliegt und man studieren kann, wie der Name erzeugt wird (z.B. PID + Zeit). Man muß also prüfen, ob die Datei schon vorhanden ist. Naiverweise könnte man folgendes schreiben:
  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);
  ...
    Offensichtlich ist das eine typische Race Condition, da
    die Zeit zwischen den zwei open Aufrufen nie null ist. Das
    Überprüfen der Existenz der Datei und das Öffnen
    muß atomar sein. Das ist möglich, wenn man
    open() mit den Optionen O_EXCL und
    O_CREAT benutzt. Damit schlägt open() fehl,
    wenn die Datei schon existiert, aber der Check der Existenz ist
    atomar an ihr Erzeugen gebunden.
Übrigens bietet die Option-'x' in der Gnu Erweiterung von
    fopen() die gleichen Möglichkeiten atomar zu
    testen und eine Datei zu erzeugen:
  FILE * fp;
  if ((fp = fopen (filename, "r+x")) == NULL) {
    perror ("Can't create the file.");
    exit (EXIT_FAILURE);
  }
    Die Rechte der temporären Datei sind auch sehr wichtig. Wenn man geheime Daten in eine Datei mit Mode 644 (lesen für alle) schreibt, kann jeder sehen, was darin steht. Mit der umask Funktion kann man festlegen, welche Rechte eine Datei beim Erzeugen erhält.
    #include <sys/types.h>
    #include <sys/stat.h>
        mode_t umask(mode_t mask);
    Mit umask(077) wird die Datei im Mode 600 erzeugt und
    nur der Eigentümer kann lesen und schreiben. Normalerweise sind 3 Schritte zum erzeugen temporärer Dateien nötig:
O_CREAT | O_EXCL, und einer
      umask von 077;Wie erzeugt man nun einen temporären Namen? Die Funktionen
      #include <stdio.h>
      char *tmpnam(char *s);
      char *tempnam(const char *dir, const char *prefix);
    geben einen Pointer auf einen zufällig erzeugten
    temporären Namen zurück. Die erste Funktion akzeptiert ein NULL Argument und
    gibt dann eine Adresse eines statischen Buffers zurück, in dem
    der Name steht. Sein Inhalt wird sich beim nächsten Aufruf von
    tmpnam(NULL) wieder ändern. Wenn man tmpnam die
    Adresse eines schon allokierten Strings gibt, dann wird der Name
    dahin kopiert. Das erfordert eine Stringlänge von mindestens
    L-tmpnam Bytes. Vorsicht mit buffer overflows! Die
    manpage sagt einiges zu Problemen, wenn die Funktion mit
    NULL Argument benutzt wird und gleichzeitig
    _POSIX_THREADS oder
    _POSIX_THREAD_SAFE_FUNCTIONS definiert sind.
Die tempnam(dir,prefix) Funktion gibt einen Pointer
    auf einen String zurück. Dabei muß dir ein
    geeignetes Verzeichnis sein (die manpage beschreibt was
    "geeignetes" meint). Die Funktion überprüft auch, daß der
    Name nicht existiert, bevor sie ihn zurück gibt, aber die
    manpage sagt, daß man sich (wegen Race Conditions) darauf nicht
    verlassen sollte. Das Gnome Projekt empfiehlt die Funktion so zu
    benutzen:
  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);
    Die hier benutzte Schleife reduziert das Risiko, erzeugt aber neue
    Probleme. Was passiert, wenn das Dateisystem voll ist oder schon die
    maximale Anzahl geöffneter Dateien erreicht ist... Die Funktion
       #include <stdio.h>
       FILE *tmpfile (void);
    erzeugt einen neuen Namen und öffnet die Datei. Sie wird
    automatisch beim Schließen gelöscht. In GlibC-2.1.3 benutzt diese Funktion einen ähnlichen
    Mechanismus wie tmpnam().
  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 */
    Im Normalfall braucht man nicht wissen, wo die Datei erzeugt wird
    und was der Name ist. Hier ist tmpfile() genau
    richtig.
Die man Page sagt nichts, aber das
    Secure-Programs-HOWTO empfiehlt die Funktion nicht. Der Autor meint,
    daß die Spezifikation nicht garantiert, daß die Datei erzeugt wird
    und er konnte bisher nicht alle Implementationen
    überprüfen. Trotzdem ist diese Funktion die
    effizienteste.
Zuletzt noch:
       #include <stdlib.h>
       char *mktemp(char *template);
       int mkstemp(char *template);
    Diese Funktion erzeugt einen eindeutigen Namen basierend auf einem
    vorgegebenen String, der in "XXXXXX" enden muß.
    Diese X werden dann durch neue und eindeutige Buchstaben und
    Zahlenkombinationen ersetzt. mktemp() ersetzt die ersten 5 X mit der Process
    ID (PID) und nur das letzte X ist zufällig. Einige
    Versionen erlauben mehr als 6 X.
mkstemp() ist die empfohlene Funktion in der
    Secure-Programs-HOWTO:
#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;
}
    Diese Funktionen zeigen die Probleme von Portierbarkeit und 
    Abstraktion. Standard Bibliotheksfunktionen sollten gewisse
    "Features" zur Verfügung stellen (Abstraktion) ... aber die
    Art wie sie implementiert sind, variiert von System zu System
    (Portierbarkeit). Die Funktion tmpfile() öffnet
    z.B temporäre Dateien auf verschiedene Art. Einige Versionen
    benutzen O_EXCL nicht. mkstemp() nimmt
    eine unterschiedliche Anzahl von 'X', je nach Implementation.
Race Conditions haben immer eine Ursache: Zwei abhängige
    Operationen sind nicht atomar. Man darf niemals annehmen, daß
    aufeinander folgende Anweisungen auch wirklich in dieser
    Reihenfolge in der CPU bearbeitet werden. Das ist so, weil in einem
    Multitaskingsystem mehrere Dinge gleichzeitig geschehen. Wenn
    Race Conditions Sicherheitsprobleme nachsichziehen, so muß
    man erst recht bei threads und shared variables , shared memory
    segments mit shmget() aufpassen. Hier sind auch locks
    wie z.B semaphores nötig, um schwer zu findende Fehler zu
    vermeiden.