Bir dosyayı ancak ve ancak python ile yoksa güvenli bir şekilde oluşturun


94

Bir dosyaya, o dosyanın zaten var olup olmadığına göre yazmak istiyorum, sadece halihazırda mevcut değilse yazıyorum (pratikte, var olmayan bir dosyayı bulana kadar dosyaları denemeye devam etmek istiyorum).

Aşağıdaki kod, potansiyel olarak bir saldırganın, bu yazıda önerildiği gibi , dosya için bir test ile yazılan dosya arasında bir sembolik bağ ekleyebileceği bir yolu gösterir . Kod yeterince yüksek izinlerle çalıştırılırsa, bu rastgele bir dosyanın üzerine yazabilir.

Bu sorunu çözmenin bir yolu var mı?

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')


@Mikko Bu burada yardımcı olmuyor.
Konrad Rudolph

Ah tamam. Sorunun ne olduğunu anladım ... SADECE dosya varsa yazarsın?
Mikko Ohtamaa

Dosyayı geçici bir yere yazıp, ardından üzerine yazmaya izin vermeden bir kopyalama komutu verebilir misiniz?
Eric

Yanıtlar:


95

Düzenleme : Ayrıca Dave Jones'un cevabına da bakınız : Python 3.3'ten bu işlevi sağlamak için xbayrağı kullanabilirsiniz open().

Aşağıdaki orijinal cevap

Evet, ancak Python'un standart open()çağrısını kullanmıyor . Bunun os.open()yerine kullanmanız gerekecek , bu da temel C koduna bayrakları belirlemenize izin verir.

Özellikle kullanmak istiyorsunuz O_CREAT | O_EXCL. İçin adam sayfasından open(2)under O_EXCLmy Unix sistemi üzerinde:

Bu çağrının dosyayı oluşturduğundan emin olun: bu bayrak ile birlikte belirtilirse O_CREATve yol adı zaten varsa, open()başarısız olur. Belirtilmezse davranışı O_EXCLtanımsızdır O_CREAT.

Bu iki bayrak belirtildiğinde, sembolik bağlar izlenmez: yol adı sembolik bir bağsa, sembolik bağın open()nereye işaret ettiğinden bağımsız olarak başarısız olur.

O_EXCL yalnızca çekirdek 2.6 veya sonraki sürümlerde NFSv3 veya üzeri kullanıldığında NFS'de desteklenir. NFS O_EXCLdesteğinin sağlanmadığı ortamlarda , kilitleme görevlerini gerçekleştirmek için ona dayanan programlar bir yarış koşulu içerecektir.

Yani mükemmel değil, ancak AFAIK, bu yarış koşulundan kaçınmak için en yakın olanı.

Düzenleme: kullanmanın diğer kurallar os.open()yerine open()hala geçerlidir. Özellikle, döndürülen dosya tanımlayıcısını okumak veya yazmak için kullanmak istiyorsanız O_RDONLY, O_WRONLYveya O_RDWRişaretlerinden birine de ihtiyacınız olacaktır .

Tüm O_*bayraklar Python'un osmodülünde, bu yüzden import oskullanmanız ve kullanmanız gerekecek os.O_CREAT.

Misal:

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!")

1
Açıkça doğru cevap için +1. Ben şahsen kaç kişinin NFS uyarısıyla ilgili sorunları olduğunu merak ediyorum - (belki de umursamadan) kodumun asla çalıştırılmaması gereken eski bir ortam olarak görmezden geliyorum.
zigg

3
@zigg: NFSv3 1995'ten beri, bu nedenle eski sürümleri eski olarak görmek adil görünüyor.
Fred Foo

1
Kişisel olarak çekirdek sürümü hakkında daha çok endişelenirdim. Güncel bir sisteme benzeyen, belli belirsiz bir şey çalıştırıyorsanız, sorun yaşamazsınız, ancak örneğin RHEL 3 (hala genişletilmiş destek aşamasında) bir 2.4 çekirdek çalıştırıyor. Ayrıca, potansiyel olarak büyük bir sınırlama olan FAT veya NTFS üzerinde Windows'ta atomik yazmalar sağlayıp sağlamadıklarını araştırmadım.
me_ve

1
@me_and Açık bayrak sabitleri üzerindeki python sayfası, bunun Windows ile iyi çalıştığını gösteriyor. Birazdan deneyeceğim!
Henry Gomersall

1
Doğru, ancak bu bayrakların atomik dosya oluşturma sağladığını açıkça söyleyen hiçbir yerde ( MSDN dahil ) görmedim . Muhtemelen aşırı derecede paranoyak davranıyorum, ancak güvenlik açısından kritik olan herhangi bir şey için buna güvenmeden önce bu "atomik" anahtar kelimeyi görmek isterim.
me_ ve

72

Referans olarak, Python 3.3, bu kullanım durumunu kapsayacak 'x'şekilde open()işlevde yeni bir mod uygular (yalnızca oluştur, dosya varsa başarısız olur). 'x'Modun kendi kendine belirlendiğini unutmayın . Kullanılması 'wx'Bir sonuçları ValueErrorolarak 'w'gereksiz (çağrı başarılı olursa Yapabileceğiniz tek şey zaten dosyaya yazma olduğu; çağrı başarılı olursa o var olamaz):

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

Python 3.2 ve aşağısı (Python 2.x dahil) için lütfen kabul edilen yanıta bakın .


İyi bir öneri. Maalesef bu yalnızca POSIX gibi görünüyor (Windows'ta çalışmıyor):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
Python 3.2 kullanıyorsunuz; 'x' modu 3.3 ve üstüdür ancak çapraz platformdur. Bu arada, "wx" yerine yalnızca "x" kullanıyorsunuz - dosyayla yapabileceğiniz tek şey zaten ona yazmak olduğu için yazma modu gereksizdir
Dave Jones

Python 3.6:ValueError: must have exactly one of create/read/write/append mode
Szabolcs Dombi

1
Olacak - yine de biraz sonra bir bilgisayarın başına dönene kadar beklemesi gerekecek.
Dave Jones

2
Varolan bir dosyayı yazmak için açmak mantıklıdır, ancak 'x' modunun tüm amacı, dosyayı ancak ve ancak zaten mevcut değilse açmak, dosya mevcut olduğunda bir hata ile başarısız olmaktır . Bu nedenle 'w' bayrağı ile gereksizdir; başarılı olursa, dosyanın boş olması garanti edilir (dolayısıyla ondan çok az nokta okunmaktadır :).
Dave Jones

0

Bu kod, yoksa kolayca bir DOSYA oluşturacaktır.

import os
if not os.path.exists('file'):
    open('file', 'w').close() 

16
Evet olacak. Soruyla ilgili önemli olan nokta güvenlik yönüydü. Sorun, dosyanın varlığını tanımlamak ile onu kullanmak veya oluşturmak arasında, kötü bir sonuçla sonuçlanan bir şeyin değişebilmesidir (orijinal soruda olduğu gibi).
Henry Gomersall

5
Bu doğru. Adı TOCTOU!
Rad

İfadeden sonra başka bir işlem dosya oluşturup yazarsa if, bu kod dosyayı boşaltacaktır.
Peter Wood
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.