Erstellen Sie eine Datei nur dann sicher, wenn sie mit Python nicht vorhanden ist

93

Ich möchte in eine Datei schreiben, basierend darauf, ob diese Datei bereits vorhanden ist oder nicht, und nur schreiben, wenn sie noch nicht vorhanden ist (in der Praxis möchte ich weiterhin versuchen, Dateien zu finden, bis ich eine finde, die nicht vorhanden ist).

Der folgende Code zeigt eine Möglichkeit, wie ein potenzieller Angreifer einen Symlink einfügen kann, wie in diesem Beitrag zwischen einem Test für die Datei und der zu schreibenden Datei vorgeschlagen. Wenn der Code mit ausreichend hohen Berechtigungen ausgeführt wird, kann dies eine beliebige Datei überschreiben.

Gibt es eine Möglichkeit, dieses Problem zu lösen?

import os
import errno

file_to_be_attacked = 'important_file'

with open(file_to_be_attacked, 'w') as f:
    f.write('Some important content!\n')

test_file = 'testfile'

try:
    with open(test_file) as f: pass
except IOError, e:

    # symlink created here
    os.symlink(file_to_be_attacked, test_file)

    if e.errno != errno.ENOENT:
        raise
    else:
        with open(test_file, 'w') as f:
            f.write('Hello, kthxbye!\n')
Henry Gomersall
quelle
Überprüfen Sie das atomare Schreiben mit Python stackoverflow.com/questions/2333872/…
Mikko Ohtamaa
@Mikko Das hilft hier nicht.
Konrad Rudolph
Ah ok. Ich habe verstanden, was los ist ... Sie schreiben NUR, wenn die Datei existiert?
Mikko Ohtamaa
Könnten Sie die Datei an einem temporären Speicherort schreiben und dann einen Kopierbefehl ausführen, ohne das Überschreiben zuzulassen?
Eric

Antworten:

93

Bearbeiten : Siehe auch Dave Jones 'Antwort : Ab Python 3.3 können Sie das xFlag verwenden open(), um diese Funktion bereitzustellen.

Ursprüngliche Antwort unten

Ja, aber nicht mit Pythons Standardaufruf open(). Sie müssen os.open()stattdessen verwenden, um Flags für den zugrunde liegenden C-Code anzugeben.

Insbesondere möchten Sie verwenden O_CREAT | O_EXCL. Von der Manpage für open(2)unter O_EXCLauf meinem Unix-System:

Stellen Sie sicher, dass dieser Aufruf die Datei erstellt: Wenn dieses Flag in Verbindung mit angegeben O_CREATwird und der Pfadname bereits vorhanden ist, schlägt dies open()fehl. Das Verhalten von O_EXCList undefiniert, wenn O_CREATnichts angegeben ist.

Wenn diese beiden Flags angegeben werden, werden symbolische Verknüpfungen nicht befolgt: Wenn der Pfadname eine symbolische Verknüpfung ist, open()schlägt dies fehl, unabhängig davon, wohin die symbolische Verknüpfung zeigt.

O_EXCL wird unter NFS nur unterstützt, wenn NFSv3 oder höher unter Kernel 2.6 oder höher verwendet wird. In Umgebungen, in denen O_EXCLkeine NFS- Unterstützung bereitgestellt wird, enthalten Programme, die für die Ausführung von Sperraufgaben darauf angewiesen sind, eine Race-Bedingung.

Es ist also nicht perfekt, aber AFAIK ist das Beste, was Sie tun können, um diese Rennbedingung zu vermeiden.

Bearbeiten: Die anderen Regeln für die Verwendung os.open()anstelle von open()weiterhin gelten. Insbesondere dann , wenn Sie den zurück Dateideskriptor zum Lesen oder Schreiben verwenden wollen, müssen Sie eine der benötigen O_RDONLY, O_WRONLYoder O_RDWRFahnen als auch.

Alle O_*Flags befinden sich im Python- osModul, Sie müssen also usw. import osverwenden os.O_CREAT.

Beispiel:

import os
import errno

flags = os.O_CREAT | os.O_EXCL | os.O_WRONLY

try:
    file_handle = os.open('filename', flags)
except OSError as e:
    if e.errno == errno.EEXIST:  # Failed as the file already exists.
        pass
    else:  # Something unexpected went wrong so reraise the exception.
        raise
else:  # No exception, so the file must have been created successfully.
    with os.fdopen(file_handle, 'w') as file_obj:
        # Using `os.fdopen` converts the handle to an object that acts like a
        # regular Python file object, and the `with` context manager means the
        # file will be automatically closed when we're done with it.
        file_obj.write("Look, ma, I'm writing to a new file!")
ich und
quelle
1
+1 für die offensichtlich richtige Antwort. Ich persönlich bin neugierig zu wissen, wie viele Leute tatsächlich Probleme mit der NFS-Einschränkung haben - ich lehne sie (vielleicht rücksichtslos) als veraltete Umgebung ab, in der mein Code niemals ausgeführt werden sollte.
Zigg
2
@zigg: NFSv3 stammt aus dem Jahr 1995, daher scheint es fair, ältere Versionen als veraltet anzusehen.
Fred Foo
1
Ich persönlich würde mir mehr Sorgen um die Kernel-Version machen. Wenn Sie etwas ausführen, das auch nur vage einem aktuellen System ähnelt, sollten Sie kein Problem haben, aber RHEL 3 (noch in der erweiterten Support-Phase) führt beispielsweise einen 2.4-Kernel aus. Außerdem habe ich nicht untersucht, ob sie unter Windows unter FAT oder NTFS atomare Schreibvorgänge bereitstellen, was eine potenziell große Einschränkung darstellt.
me_and
1
@me_and Die Python-Seite mit Open-Flag-Konstanten legt nahe, dass dies unter Windows einwandfrei funktioniert. Ich werde es in Kürze versuchen!
Henry Gomersall
1
Stimmt, aber ich habe nirgendwo (einschließlich MSDN ) gesehen, dass diese Flags explizit die Erstellung atomarer Dateien ermöglichen. Möglicherweise bin ich übermäßig paranoid, aber ich möchte dieses "atomare" Schlüsselwort sehen, bevor ich ihm für alles vertraue, was sicherheitskritisch ist.
me_and
69

Als Referenz implementiert Python 3.3 einen neuen 'x'Modus in der open()Funktion, um diesen Anwendungsfall abzudecken (nur erstellen, fehlschlagen, wenn eine Datei vorhanden ist). Beachten Sie, dass der 'x'Modus eigenständig angegeben wird. Verwenden von 'wx'Ergebnissen in a ValueErrorals 'w'redundant (das einzige, was Sie tun können, wenn der Aufruf erfolgreich ist, ist das Schreiben in die Datei; es kann nicht existiert haben, wenn der Aufruf erfolgreich ist):

>>> f1 = open('new_binary_file', 'xb')
>>> f2 = open('new_text_file', 'x')

Informationen zu Python 3.2 und niedriger (einschließlich Python 2.x) finden Sie in der akzeptierten Antwort .

Dave Jones
quelle
Guter Vorschlag. Leider scheint dies nur POSIX zu sein (funktioniert nicht unter Windows):Python 3.2 (r32:88445, Feb 20 2011, 21:30:00) [MSC v.1500 64 bit (AMD64)] on win32 >>> open("c:/temp/foo.csv","wx") ValueError: invalid mode: 'wx'
Dan Lenski
5
Sie verwenden Python 3.2; Der 'x'-Modus ist in 3.3 und höher, aber plattformübergreifend. Übrigens verwenden Sie nur 'x' anstelle von 'wx' - der Schreibmodus ist redundant, da Sie mit der Datei ohnehin nur schreiben können
Dave Jones
Python 3.6:ValueError: must have exactly one of create/read/write/append mode
Szabolcs Dombi
1
Wird es tun - obwohl es warten muss, bis ich etwas später wieder vor einem Computer stehe.
Dave Jones
2
Es ist sinnvoll, eine vorhandene Datei zum Schreiben zu öffnen, aber der gesamte Sinn des 'x'-Modus besteht darin, die Datei genau dann zu öffnen, wenn sie noch nicht vorhanden ist. Wenn die Datei vorhanden ist, tritt ein Fehler auf. Aus diesem Grund ist es mit dem Flag 'w' überflüssig. Wenn dies gelingt, ist die Datei garantiert leer (und daher macht es wenig Sinn, daraus zu lesen :).
Dave Jones
0

Dieser Code erstellt leicht eine DATEI, wenn keine vorhanden ist.

import os
if not os.path.exists('file'):
    open('file', 'w').close() 
user2033758
quelle
15
Ja, es wird. Der wichtige Punkt bei der Frage war der Sicherheitsaspekt. Das Problem besteht darin, dass sich zwischen dem Erkennen des Vorhandenseins der Datei und dem Verwenden oder Erstellen der Datei möglicherweise etwas ändert, das zu einem schlechten Ergebnis führt (wie in der ursprünglichen Frage).
Henry Gomersall
5
Das ist richtig. Es heißt TOCTOU!
Rad
Wenn ein anderer Prozess nach der ifAnweisung eine Datei erstellt und in diese schreibt, wird die Datei durch diesen Code ausgeblendet.
Peter Wood