Безпечно створюйте файл тоді і лише тоді, коли він не існує з python


93

Я хочу писати у файл, виходячи з того, чи існує цей файл чи ні, лише якщо він ще не існує (на практиці я хочу продовжувати пробувати файли, поки не знайду такого, який не існує).

Наступний код показує спосіб, яким потенційний зловмисник може вставити символічне посилання, як пропонується в цьому дописі між тестом для файлу та файлом, який пишеться. Якщо код запускається з достатньо високими дозволами, це може перезаписати довільний файл.

Чи є спосіб вирішити цю проблему?

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

Перевірте атомне лист з Python stackoverflow.com/questions/2333872 / ...
Мікко Ohtamaa

@Mikko Це тут не допомагає.
Конрад Рудольф

Ну гаразд. Я зрозумів, у чому справа ... ви пишете ТІЛЬКИ, якщо файл існує?
Мікко Отамаа

Чи можете ви написати файл у тимчасовому місці, а потім виконати команду копіювання, не дозволяючи перезаписувати?
Ерік,

Відповіді:


93

Редагувати : Див. Також відповідь Дейва Джонса : з Python 3.3 ви можете використовувати xпрапор open()для надання цієї функції.

Оригінальна відповідь нижче

Так, але не використовуючи стандартний open()виклик Python . Вам потрібно буде використовувати os.open()замість цього, що дозволяє вказати прапори для базового коду C.

Зокрема, ви хочете використовувати O_CREAT | O_EXCL. На сторінці людини для open(2)Under O_EXCLна моїй системі Unix:

Переконайтеся, що цей виклик створює файл: якщо цей прапорець вказаний у поєднанні з O_CREATі ім'я шляху вже існує, тоді open()він не вдасться. Поведінка параметра O_EXCLне визначено, якщо O_CREATне вказано.

Коли вказані ці два прапори, символічні посилання не виконуються: якщо ім'я шляху є символічним посиланням, воно open()не працює незалежно від того, куди вказує символічне посилання.

O_EXCL підтримується лише на NFS при використанні NFSv3 або пізнішої версії на ядрі 2.6 або пізнішої версії. У середовищах, де O_EXCLпідтримка NFS не надається, програми, які покладаються на неї для виконання завдань блокування, містять умову перегонів.

Тож це не ідеально, але AFAIK - це найближче, що ви можете досягти, щоб уникнути цього стану перегонів.

Редагувати: інші правила використання os.open()замість того, щоб open()все ще застосовуватися. Зокрема, якщо ви хочете використовувати повернений дескриптор файлу для читання або запису, вам буде потрібно одна з O_RDONLY, O_WRONLYабо O_RDWRпрапори , а також.

Всі O_*прапори знаходяться в osмодулі Python , тому вам потрібно буде import osвикористовувати os.O_CREATі т.д.

Приклад:

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
+1 за очевидно правильну відповідь. Мені особисто цікаво дізнатись, скільки людей насправді має проблеми із застереженням NFS - я (можливо, необдумано) сприймаю це як застаріле середовище, на якому ніколи не слід працювати.
zigg

2
@zigg: NFSv3 походить з 1995 року, тому здається справедливим вважати застарілі версії застарілими.
Fred Foo

1
Мене б більше хвилювала версія ядра, особисто. Якщо у вас запущено щось, що навіть смутно нагадує сучасну систему, у вас не повинно виникнути проблем, але RHEL 3 (все ще перебуває у фазі розширеної підтримки), наприклад, використовує ядро ​​2.4. Крім того, я не досліджував, чи забезпечують вони атомарні записи у Windows на FAT або NTFS, що є потенційно серйозним обмеженням.
me_and

1
@me_and Сторінка python на відкритих константах прапора свідчить про те, що це добре працює з Windows. Я спробую це незабаром!
Генрі Гомерсалл,

1
Правда, але я ніде не бачив (включаючи MSDN ), що прямо говорить, що ці прапори дають атомний файл. Можливо, я занадто параноїчний, але я хотів би побачити це "атомне" ключове слово, перш ніж довіряти йому все, що є критичним для безпеки.
me_and

69

Для довідки, Python 3.3 реалізує новий 'x'режим у open()функції, що охоплює цей випадок використання (лише створення, помилка, якщо файл існує). Зверніть увагу, що 'x'режим вказується самостійно. Використання 'wx'результатів у ValueErrorяк 'w'надлишкове (єдине, що ви можете зробити, якщо виклик успішний, це запис у файл у будь-якому випадку; він не міг існувати, якщо виклик вдався):

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

Щодо Python 3.2 і нижче (включаючи Python 2.x), будь ласка, зверніться до прийнятої відповіді .


Гарна пропозиція. На жаль, це, здається, лише POSIX (не працює в 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'
Ден Ленскі

5
Ви використовуєте python 3.2; режим "x" знаходиться у версії 3.3 і вище, але він є крос-платформним. До речі, ви використовуєте лише "x" замість "wx" - режим запису надлишковий, оскільки єдине, що ви можете зробити з файлом, це все одно написати в нього
Дейв Джонс,

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

1
Зробить - хоча доведеться почекати, поки я трохи пізніше повернусь до комп’ютера.
Дейв Джонс,

2
Доцільно відкрити існуючий файл для запису, але вся суть режиму "х" полягає у тому, щоб відкрити файл тоді і лише тоді, коли він ще не існує , і помилка, коли файл існує. Ось чому він зайвий із позначкою 'w'; якщо це вдасться, файл гарантовано буде порожнім (а отже, дуже мало сенсу читати з нього :).
Дейв Джонс,

0

Цей код легко створить ФАЙЛ, якщо такий не існує.

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

15
Так це буде. Важливим моментом у питанні був аспект безпеки. Проблема полягає в тому, що між виявленням наявності файлу та його використанням або створенням може щось змінитися, що призведе до поганого результату (як у вихідному питанні).
Генрі Гомерсалл,

5
Це правда. Це називається TOCTOU!
Рад

Якщо інший процес створює файл і записує його у файл після ifвиписки, цей код видалить файл.
Пітер Вуд,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.