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それはここでは役に立ちません。
Konrad Rudolph

ああ。何が問題か理解しました...ファイルが存在する場合にのみ書き込みますか?
Mikko Ohtamaa

ファイルを一時的な場所に書き込んでから、上書きを許可せずにコピーコマンドを実行できますか?
Eric

回答:


93

編集Dave Jonesの回答も参照してください。Python3.3以降では、xフラグを使用してopen()この機能を提供できます。

以下の元の答え

はい、ただしPythonの標準open()呼び出しは使用しません。os.open()代わりにを使用する必要があります。これにより、基になるCコードにフラグを指定できます。

特に、使用したいO_CREAT | O_EXCL。Unixシステムのopen(2)under のmanページからO_EXCL

この呼び出しでファイルが作成されることを確認してください。このフラグをと組み合わせて指定しO_CREAT、パス名がすでに存在する場合open()は失敗します。が指定されていO_EXCLない場合の動作は未定義ですO_CREAT

これらの2つのフラグが指定されている場合、シンボリックリンクは追跡されません。パス名がシンボリックリンクopen()の場合、シンボリックリンクが指す場所に関係なく失敗します。

O_EXCL カーネル2.6以降でNFSv3以降を使用している場合、NFSでのみサポートされます。NFS O_EXCLサポートが提供されていない環境では、NFS サポートに依存してロックタスクを実行するプログラムには、競合状態が含まれます。

だから完璧ではありませんが、この競合状態を回避するために取得できる最も近いのはAFAIKです。

編集:os.open()代わりに使用する他のルールがopen()適用されます。あなたが読み取りまたは書き込みのために返されたファイル記述子を使用したい場合は特に、あなたは次のいずれかが必要ですO_RDONLYO_WRONLYまたはO_RDWR同様のフラグを。

すべてのO_*フラグはPythonのosモジュールにあるので、必要に応じて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カーネルを実行しています。また、FATまたはNTFS上のWindowsでアトミックな書き込みを提供するかどうかは調査していません。これは、潜在的に大きな制限です。
me_and

1
@me_and オープンフラグ定数のpythonページは、これがWindowsで正常に機能することを示唆しています。まもなくやってみます!
Henry Gomersall 2012年

1
確かに、しかしこれらのフラグがアトミックファイルの作成を与えると明示的に言っているところ(MSDNを含む)はどこにも見たことがない。おそらく私は過度に偏執的であるかもしれませんが、セキュリティ上重要な何かについてこれを信頼する前に、その「アトミック」キーワードを見たいと思います。
me_and

69

参考までに、Python 3.3 'x'open()関数に新しいモードを実装して、このユースケースをカバーします(作成のみ、ファイルが存在する場合は失敗)。'x'モードは独自に指定されることに注意してください。asの'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'
ダンLenski

5
Python 3.2を使用しています。「x」モードは3.3以降ですが、クロスプラットフォームです。ちなみに、「wx」の代わりに「x」のみを使用します
Dave Jones

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

1
やります-少し遅れてコンピュータの前に戻るまで待つ必要があります。
デイブジョーンズ

2
書き込みのために既存のファイルを開くことは妥当ですが、「x」モードの全体のポイントは、ファイルがまだ存在しない場合に限りファイルを開くことであり、ファイルが存在する場合はエラーで失敗します。これが、「w」フラグで冗長になる理由です。成功した場合、ファイルは空であることが保証されます(したがって、そこから読み取るポイントはほとんどありません:)。
デイブジョーンズ

0

このコードは、FILEが存在しない場合に簡単に作成します。

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

15
はい、そうです。質問についての重要な点は安全面でした。問題は、ファイルの存在を特定してから使用または作成するまでの間に、何かが変更されて、悪い結果が生じる可能性があることです(元の質問のように)。
Henry Gomersall 2013年

5
それは本当だ。それはTOCTOUと呼ばれています!
Rad

ifステートメントの後に別のプロセスがファイルを作成して書き込む場合、このコードはファイルをブランクにします。
Peter Wood
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.