Buat file dengan aman jika dan hanya jika tidak ada dengan python

93

Saya ingin menulis ke file berdasarkan apakah file itu sudah ada atau tidak, hanya menulis jika belum ada (dalam praktiknya, saya ingin terus mencoba file sampai saya menemukan file yang tidak ada).

Kode berikut menunjukkan cara di mana penyerang yang berpotensi dapat menyisipkan symlink, seperti yang disarankan dalam posting ini di antara pengujian untuk file dan file yang sedang ditulis. Jika kode dijalankan dengan izin yang cukup tinggi, ini dapat menimpa file arbitrer.

Apakah ada cara untuk mengatasi masalah ini?

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
sumber
Periksa penulisan atom dengan Python stackoverflow.com/questions/2333872/…
Mikko Ohtamaa
@Mikko Itu tidak membantu di sini.
Konrad Rudolph
Ah ok. Saya mengerti ada apa ... Anda menulis HANYA jika file itu ada?
Mikko Ohtamaa
Bisakah Anda menulis file di lokasi sementara, lalu melakukan perintah salin tanpa mengizinkan penimpaan?
Eric

Jawaban:

94

Edit : Lihat juga jawaban Dave Jones : dari Python 3.3, Anda dapat menggunakan xflag to open()untuk menyediakan fungsi ini.

Jawaban asli di bawah

Ya, tetapi tidak menggunakan open()panggilan standar Python . Anda harus menggunakan os.open()sebagai gantinya, yang memungkinkan Anda menentukan bendera ke kode C yang mendasarinya.

Secara khusus, Anda ingin menggunakan O_CREAT | O_EXCL. Dari halaman manual untuk di open(2)bawah O_EXCLpada sistem Unix saya:

Pastikan panggilan ini membuat file: jika tanda ini ditentukan sehubungan dengan O_CREAT, dan nama jalur sudah ada, maka open()akan gagal. Perilaku O_EXCLtidak ditentukan jika O_CREATtidak ditentukan.

Ketika dua bendera ini ditentukan, tautan simbolis tidak diikuti: jika nama jalur adalah tautan simbolis, maka open()gagal terlepas dari ke mana tautan simbolis menunjuk.

O_EXCL hanya didukung di NFS saat menggunakan NFSv3 atau yang lebih baru pada kernel 2.6 atau yang lebih baru. Di lingkungan di mana O_EXCLdukungan NFS tidak tersedia, program yang mengandalkannya untuk melakukan tugas penguncian akan berisi kondisi balapan.

Jadi memang tidak sempurna, tapi AFAIK paling dekat bisa Anda hindari untuk menghindari kondisi balapan ini.

Sunting: aturan penggunaan lain os.open()daripada open()tetap berlaku. Secara khusus, jika Anda ingin menggunakan deskriptor file yang dikembalikan untuk membaca atau menulis, Anda memerlukan salah satu tanda O_RDONLY, O_WRONLYatau O_RDWRjuga.

Semua O_*bendera ada di osmodul Python , jadi Anda harus import osdan menggunakan os.O_CREATdll.

Contoh:

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!")
saya dan
sumber
1
1 untuk jawaban yang jelas benar. Saya secara pribadi ingin tahu berapa banyak orang yang sebenarnya memiliki masalah dengan peringatan NFS — saya (mungkin sembrono) menolaknya sebagai lingkungan usang kode saya tidak boleh dijalankan.
zigg
2
@zigg: NFSv3 adalah dari tahun 1995, jadi tampaknya adil untuk menganggap versi lama sebagai usang.
Fred Foo
1
Saya pribadi lebih khawatir tentang versi kernel. Jika Anda menjalankan sesuatu yang bahkan samar-samar menyerupai sistem terbaru, Anda seharusnya tidak memiliki masalah, tetapi RHEL 3 (masih dalam fase dukungan yang diperpanjang) menjalankan kernel 2.4, misalnya. Juga, saya belum menyelidiki apakah mereka menyediakan penulisan atom pada Windows pada FAT atau NTFS, yang merupakan batasan utama yang potensial.
saya_dan
1
@me_and Halaman python pada konstanta bendera terbuka menunjukkan bahwa ini berfungsi dengan baik dengan Windows. Saya akan segera mencobanya!
Henry Gomersall
1
Benar, tetapi saya belum pernah melihat di mana pun (termasuk MSDN ) yang secara eksplisit mengatakan bahwa bendera ini memberikan pembuatan file atom . Mungkin saya terlalu paranoid, tapi saya ingin melihat kata kunci "atom" sebelum memercayai ini untuk apa pun yang penting bagi keamanan.
me_and
70

Sebagai referensi, Python 3.3 mengimplementasikan 'x'mode baru dalam open()fungsi untuk mencakup kasus penggunaan ini (hanya buat, gagal jika file ada). Perhatikan bahwa 'x'mode ditentukan sendiri. Menggunakan 'wx'hasil dalam a ValueErrorsebagai 'w'redundan (satu-satunya hal yang dapat Anda lakukan jika panggilan berhasil adalah tetap menulis ke file; itu tidak akan ada jika panggilan berhasil):

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

Untuk Python 3.2 dan yang lebih lama (termasuk Python 2.x), lihat jawaban yang diterima .

Dave Jones
sumber
Saran yang bagus. Sayangnya ini tampaknya hanya POSIX (tidak berfungsi pada 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
Anda menggunakan python 3.2; mode 'x' ada di 3.3 ke atas tetapi ini adalah lintas platform. Kebetulan, Anda hanya menggunakan 'x' dan bukan 'wx' - mode tulis berlebihan karena satu-satunya hal yang dapat Anda lakukan dengan file tersebut adalah tetap menulis padanya
Dave Jones
Python 3.6:ValueError: must have exactly one of create/read/write/append mode
Szabolcs Dombi
1
Akan dilakukan - meskipun harus menunggu sampai saya kembali ke depan komputer nanti.
Dave Jones
2
Masuk akal untuk membuka file yang sudah ada untuk ditulis, tetapi inti dari mode 'x' adalah membuka file jika dan hanya jika belum ada , gagal dengan kesalahan saat file memang ada. Inilah mengapa itu berlebihan dengan bendera 'w'; jika berhasil, file tersebut dijamin kosong (dan karenanya tidak ada gunanya membaca darinya :).
Dave Jones
0

Kode ini akan dengan mudah membuat FILE jika tidak ada.

import os
if not os.path.exists('file'):
    open('file', 'w').close() 
pengguna2033758
sumber
16
Ya, tentu saja. Poin penting dari pertanyaan tersebut adalah aspek keamanan. Masalahnya adalah antara mengidentifikasi keberadaan file dan menggunakannya atau membuatnya, sesuatu mungkin berubah yang menghasilkan hasil yang buruk (seperti dalam pertanyaan awal).
Henry Gomersall
5
Itu benar. Ini disebut TOCTOU!
Rad
Jika proses lain membuat dan menulis ke file setelah ifpernyataan, kode ini akan mengosongkan file.
Peter Wood