仅当与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')


@Mikko在这里没有帮助。
Konrad Rudolph 2012年

喔好吧。我知道这是怎么回事...仅在文件存在的情况下才写?
Mikko Ohtamaa 2012年

您能否在一个临时位置写入文件,然后执行复制命令而不允许覆盖?
埃里克

Answers:


93

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

下面的原始答案

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

特别是您要使用O_CREAT | O_EXCL。从该名男子页open(2)O_EXCL我的Unix系统:

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

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

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

因此它并不完美,但是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
+1为显然正确的答案。我个人很想知道实际上有多少人对NFS提出了警告-我(可能是鲁ck地)将其视为过时的环境而忽略了,我的代码永远都不应在上面运行。
zigg 2012年

2
@zigg:NFSv3是1995年发布的,因此将旧版本视为过时的版本似乎很公平。
Fred Foo 2012年

1
我个人会更担心内核版本。如果您正在运行的东西甚至看起来像是最新的系统,都应该没有问题,但是RHEL 3(仍处于扩展支持阶段)正在运行2.4内核。另外,我还没有研究它们是否在Windows上的FAT或NTFS上提供原子写操作,这是一个潜在的主要限制。
me_and 2012年

1
@me_and有关打开标志常量的python页面建议在Windows上可以正常使用。我会尽快尝试!
亨利·戈默索尔

1
没错,但是我没有看到任何地方(包括MSDN)明确表示这些标志可以创建原子文件。可能我过于偏执,但是在对安全性至关重要的任何事物信任此关键字之前,我希望先看到“ atomic”关键字。
me_and 2012年

69

作为参考,Python 3.3 'x'open()函数中实现了一种新模式以涵盖此用例(仅创建,如果文件存在则失败)。请注意,'x'模式是单独指定的。使用'wx'结果中的ValueErroras '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'
Dan Lenski 2015年

5
您正在使用python 3.2; “ x”模式在3.3及更高版本中,但它是跨平台的。顺便说一句,您只使用'x'而不是'wx'–写模式是多余的,因为您只能对文件执行写操作
Dave Jones

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

1
会做的-尽管它必须等到稍后再回到电脑前。
戴夫·琼斯

2
打开现有文件进行写入是合理的,但是'x'模式的全部要点是当且仅当文件尚不存在时才打开文件,否则当文件确实存在时会失败。这就是为什么它与'w'标志是多余的;如果成功,则保证文件为空(因此,从中读取的内容很少:)。
戴夫·琼斯

0

如果不存在此代码,则可以轻松创建一个FILE。

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

15
是的,它会的。关于这个问题的重点是安全方面。问题在于,在识别文件的存在与使用或创建之间,某些内容可能会更改,从而导致不良的结果(与原始问题相同)。
亨利·戈默索尔

5
确实如此。叫TOCTOU!
Rad

如果if语句之后有另一个进程创建并写入文件,则此代码将清空文件。
Peter Wood
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.