Pytanie w sprawie python – Bezpiecznie utwórz plik tylko wtedy, gdy nie istnieje z pythonem

82

Chcę napisać do pliku na podstawie tego, czy ten plik już istnieje, czy nie, pisząc tylko wtedy, gdy nie istnieje (w praktyce chcę nadal próbować plików, dopóki nie znajdę pliku, który nie istnieje).

Poniższy kod pokazuje sposób, w jaki potencjalny atakujący może wstawić dowiązanie symboliczne, jak sugerowano wten post pomiędzy testem pliku a zapisywanym plikiem. Jeśli kod jest uruchamiany z wystarczająco dużymi uprawnieniami, może to zastąpić dowolny plik.

Czy jest jakiś sposób na rozwiązanie tego problemu?

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')
Czy możesz napisać plik w tymczasowej lokalizacji, a następnie wykonać polecenie kopiowania bez zezwolenia na zastąpienie? Eric
Sprawdź pisanie atomowe za pomocą Pythonastackoverflow.com/questions/2333872/… Mikko Ohtamaa
@Mikko To nie pomaga tutaj. Konrad Rudolph
Ah ok. Zrozumiałem o co chodzi ... piszesz TYLKO jeśli plik istnieje? Mikko Ohtamaa

Twoja odpowiedź

2   odpowiedź
81

Edytować: Zobacz teżOdpowiedź Dave'a Jonesa: z Pythona 3.3 możesz użyćx flaga doopen() aby zapewnić tę funkcję.

Oryginalna odpowiedź poniżej

Tak, ale nie używając standardu Pythonaopen() połączenie. Musisz użyćos.open() zamiast tego pozwala określić flagi do podstawowego kodu C.

W szczególności chcesz użyćO_CREAT | O_EXCL. Ze strony man dlaopen(2) podO_EXCL w moim systemie Unix:

Upewnij się, że to wywołanie tworzy plik: jeśli ta flaga jest określona w połączeniu zO_CREAT, a wtedy ścieżka już istniejeopen() zawiedzie. ZachowanieO_EXCL jest niezdefiniowane, jeśliO_CREAT nie jest określony.

Gdy podane są te dwie flagi, nie są stosowane dowiązania symboliczne: jeśli nazwa ścieżki jest dowiązaniem symbolicznymopen() kończy się niepowodzeniem, niezależnie od tego, gdzie wskazuje dowiązanie symboliczne.

O_EXCL jest obsługiwany tylko w NFS podczas używania NFSv3 lub nowszego na jądrze 2.6 lub nowszym. W środowiskach, w których NFSO_EXCL wsparcie nie jest zapewnione, programy, które polegają na tym, aby wykonywać zadania blokowania, będą zawierać warunki wyścigu.

Więc to nie jest idealne, ale AFAIK jest najbliżej tego, co można uniknąć, aby uniknąć tego wyścigu.

Edytuj: inne zasady używaniaos.open() zamiastopen() nadal obowiązują. W szczególności, jeśli chcesz użyć zwróconego deskryptora pliku do odczytu lub zapisu, będziesz potrzebować jednego z nichO_RDONLY, O_WRONLY lubO_RDWR również flagi.

WszystkieO_* flagi są w Pythonieos moduł, więc musiszimport os I użyćos.O_CREAT itp.

Przykład:
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!")
@pinhead Witamy w przepełnieniu stosu! Lepiej zadawaj pytania w nowych pytaniach, ponieważ są one bardziej widoczne i nie jesteś ograniczony przez formatowanie komentarzy lub limity znaków. W każdym razie twój kod nie rozwiąże problemu, gdzie jawnie chcemy otworzyć plik, jeślii tylko jeśli to już nie istnieje. me_and
(Chociaż OP mówi o tymos.symlink(), który jest tylko Unix, więc Windows prawdopodobnie nie jest dla nich problemem.) me_and
Osobiście bardziej martwię się o wersję jądra. Jeśli używasz czegoś, co przypomina nieco aktualny system, nie powinieneś mieć problemu, ale RHEL 3 (wciąż w rozszerzonej fazie wsparcia) uruchamia na przykład jądro 2.4. Ponadto nie zbadałem, czy dostarczają atomowych zapisów w systemie Windows na FAT lub NTFS, co jest potencjalnie dużym ograniczeniem. me_and
+1 dla oczywiście poprawnej odpowiedzi. Osobiście jestem ciekawy, ile osób faktycznie ma problemy z zastrzeżeniem NFS - ja (być może lekkomyślnie) odrzucam to jako przestarzałe środowisko, w którym mój kod nigdy nie powinien być uruchamiany. zigg
52

Dla odniesienia Python 3.3 implementuje nowy'x' tryb wopen() funkcja obejmująca ten przypadek użycia (tylko tworzenie, niepowodzenie, jeśli plik istnieje). Zauważ, że'x' tryb jest określony sam. Za pomocą'wx' skutkuje aValueError jak'w' jest zbędny (jedyną rzeczą, którą możesz zrobić, jeśli wywołanie się powiedzie, jest zapis do pliku; nie może istnieć, jeśli połączenie się powiedzie):

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

W przypadku Pythona 3.2 i poniżej (w tym Pythona 2.x) prosimy o zapoznanie się zzaakceptowana odpowiedź.

Jak są duplikaty „w” i „x”? Jest całkowicie rozsądne, aby otworzyć istniejący plik do zapisu (który go zastępuje). Dubslow
tak masz rację. czy możesz rozszerzyć odpowiedź za pomocą 2 przykładów? Szabolcs Dombi
Rozsądne jest otwarcie istniejącego pliku do zapisu, ale cały punkt trybu „x” polega na otwarciu plikujeśli i tylko wtedy, gdy już nie istnieje, błąd z błędem, gdy plik istnieje. Dlatego jest zbędny z flagą „w”; jeśli się powiedzie, plik jest gwarantowany, że jest pusty (i dlatego jest bardzo mało czytania z niego :). Dave Jones
Używasz Pythona 3.2; tryb „x” jest w wersji 3.3 i wyższej, ale jest to platforma krzyżowa. Nawiasem mówiąc, używasz tylko „x” zamiast „wx” - tryb zapisu jest zbędny, ponieważ jedyną rzeczą, jaką możesz zrobić z tym plikiem, jest zapisanie go Dave Jones

Powiązane pytania