【问题标题】:Safely create a file if and only if it does not exist with Python当且仅当 Python 不存在文件时,才安全地创建文件
【发布时间】:2012-06-14 06:50:40
【问题描述】:

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

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

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

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/…
  • @Mikko 这在这里没有帮助。
  • 嗯,好的。我明白这是怎么回事......你只在文件存在时才写?
  • 能否将文件写入临时位置,然后执行复制命令且不允许覆盖?

标签: python


【解决方案1】:

编辑:另见Dave Jones' answer:从 Python 3.3 开始,您可以使用 x 标志到 open() 来提供此功能。

原答案如下

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

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

确保此调用创建文件:如果此标志与O_CREAT 一起指定,并且路径名已存在,则open() 将失败。如果未指定 O_CREAT,则 O_EXCL 的行为未定义。

当指定这两个标志时,不遵循符号链接:如果路径名是符号链接,则无论符号链接指向何处,open() 都会失败。

O_EXCL 仅在内核 2.6 或更高版本上使用 NFSv3 或更高版本时在 NFS 上受支持。在不提供 NFS O_EXCL 支持的环境中,依赖它来执行锁定任务的程序将包含竞争条件。

所以它并不完美,但 AFAIK 是您可以避免这种竞争条件的最接近的方法。

编辑:使用os.open() 而不是open() 的其他规则仍然适用。特别是,如果您想使用返回的文件描述符进行读取或写入,您还需要 O_RDONLYO_WRONLYO_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 显然是正确的答案。我个人很想知道有多少人实际上对 NFS 警告有问题——我(也许是鲁莽地)认为它是一个过时的环境,我的代码永远不应该在其上运行。
  • @zigg:NFSv3 是 1995 年的,因此认为旧版本已过时似乎是公平的。
  • 我个人更担心内核版本。如果您正在运行任何与最新系统类似的东西,那么您应该没有问题,但是例如 RHEL 3(仍处于扩展支持阶段)正在运行 2.4 内核。此外,我还没有调查他们是否在 FAT 或 NTFS 上的 Windows 上提供原子写入,这是一个潜在的主要限制。
  • @me_and open flag constants 上的 python 页面表明这适用于 Windows。我会尽快尝试!
  • 是的,但我没有看到任何地方(包括MSDN)明确表示这些标志可以创建 atomic 文件。可能我过于偏执了,但我想先看看那个“原子”关键字,然后再相信任何对安全至关重要的东西。
【解决方案2】:

如果文件不存在,此代码将轻松创建一个文件

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

【讨论】:

  • 会的。关于这个问题的重点是安全方面。问题在于,在识别文件的存在和使用它或创建它之间,可能会发生一些变化,从而导致不好的结果(如原始问题所示)。
  • 确实如此。它叫 TOCTOU!
  • 如果另一个进程在if 语句之后创建并写入文件,此代码将清空文件。
【解决方案3】:

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

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

对于 Python 3.2 及以下版本(包括 Python 2.x)请参考the accepted answer

【讨论】:

  • 好建议。不幸的是,这似乎仅限于 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'
  • 您使用的是 python 3.2; 'x' 模式在 3.3 及更高版本中,但它是跨平台的。顺便说一句,您只使用 'x' 而不是 'wx' - 写入模式是多余的,因为您可以对文件做的唯一事情就是写入它
  • “w”和“x”如何重复?打开现有文件进行写入(覆盖它)是完全合理的。
  • 打开现有文件进行写入是合理的,但'x'模式的全部意义在于打开文件当且仅当文件不存在时,当文件确实存在时失败并出现错误。这就是为什么它与 'w' 标志是多余的;如果成功,则文件保证为空(因此从中读取的意义很小:)。
猜你喜欢
  • 1970-01-01
  • 2012-10-05
  • 1970-01-01
  • 1970-01-01
  • 2014-05-12
  • 2018-04-03
  • 2016-09-05
  • 2016-09-11
相关资源
最近更新 更多