安全地创建一个文件,前提是它不存在于python中

Safely create a file if and only if it does not exist with python

我希望根据文件是否已经存在写入文件,仅在文件不存在时写入(在实践中,我希望继续尝试文件,直到找到不存在的文件)。

下面的代码显示了一种可能的攻击者可以插入符号链接的方法,如本文中在文件测试和正在写入的文件之间所建议的。如果代码以足够高的权限运行,则可能覆盖任意文件。

有没有办法解决这个问题?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import os
import errno

file_to_be_attacked = 'important_file'

with open(file_to_be_attacked, 'w') as f:
    f.write('Some important content!
'
)

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


编辑:另请参见Dave Jones的回答:从python 3.3,您可以使用x标志到open()来提供此功能。

以下为原始答案

是的,但不使用Python的标准open()调用。您将需要使用os.open(),它允许您为基础C代码指定标志。

特别是,您希望使用O_CREAT | O_EXCL。从我的Unix系统中O_EXCL下的open(2)的手册页:

Ensure that this call creates the file: if this flag is specified in conjunction with O_CREAT, and pathname already exists, then open() will fail. The behavior of O_EXCL is undefined if O_CREAT is not specified.

When these two flags are specified, symbolic links are not followed: if pathname is a symbolic link, then open() fails regardless of where the symbolic link points to.

O_EXCL is only supported on NFS when using NFSv3 or later on kernel 2.6 or later. In environments where NFS O_EXCL support is not provided, programs that rely on it for performing locking tasks will contain a race condition.

所以这并不完美,但是阿法克,这是你最接近避免这种比赛条件。

编辑:其他使用os.open()而不是open()的规则仍然适用。特别是,如果要使用返回的文件描述符进行读写,还需要一个O_RDONLYO_WRONLYO_RDWR标志。

所有的O_*标志都在python的os模块中,所以您需要import os和使用os.O_CREAT等。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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!")


作为参考,python 3.3在open()函数中实现了一个新的'x'模式来覆盖这个用例(仅创建,如果文件存在则失败)。注意,'x'模式是单独指定的。使用'wx'会导致ValueError,因为'w'是冗余的(如果调用成功,您唯一能做的就是写入文件;如果调用成功,它就不可能存在):

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

对于python 3.2及以下版本(包括python 2.x),请参考接受的答案。


如果文件不存在,此代码将很容易创建文件。

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