1. ホーム
  2. python

[解決済み] Pythonでファイルが存在しない場合のみ、安全にファイルを作成する

2023-01-19 01:51:21

質問

ファイルがすでに存在するかどうかに基づいてファイルに書き込み、ファイルがまだ存在しない場合にのみ書き込むことを希望します (実際には、存在しないファイルを見つけるまでファイルを試し続けたいのです)。

で提案されているように、次のコードは潜在的な攻撃者がシンボリックリンクを挿入する方法を示しています。 この投稿 で提案されているように、ファイルに対するテストと書き込まれるファイルの間にシンボリックリンクを挿入する方法を示しています。コードが十分に高い権限で実行される場合、これは任意のファイルを上書きすることができます。

この問題を解決する方法はあるのでしょうか?

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 3.3 以降は x フラグを open() を追加することで、この機能を提供することができます。

以下、回答原文

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

特に、あなたが使いたいのは O_CREAT | O_EXCL . の man ページから open(2) の下にある O_EXCL を私の Unix システム上で実行します。

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

これら二つのフラグが指定された場合、シンボリックリンクは追跡されない: パス名がシンボリックリンクの場合 open() は、シンボリックリンクがどこを指しているかに関わらず、失敗します。

O_EXCL は、カーネル 2.6 以降で NFSv3 以降を使用している場合のみ、NFS でサポートされます。 NFS がサポートされている環境では O_EXCL がサポートされていない環境では、ロックタスクを実行するためにこれに依存するプログラムにはレースコンディションが含まれます。

というわけで、完璧ではありませんが、AFAIKではこのレースコンディションを回避するのに一番近い方法だと思います。

編集: 他の使用ルールとして os.open() の代わりに open() がまだ適用されます。特に、返されたファイルディスクリプタを読み書きするために使いたい場合は、 ファイルディスクリプタを指定するための O_RDONLY , O_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!")