파이썬과 함께 존재하지 않는 경우에만 안전하게 파일 생성


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

파이썬과 원자 쓰기 확인 stackoverflow.com/questions/2333872/...
미코 Ohtamaa

@Mikko 그것은 여기에 도움이되지 않습니다.
Konrad Rudolph

그래. 문제가 뭔지 이해 했어요 ... 파일이 존재하는 경우에만 작성하나요?
Mikko Ohtamaa 2011 년

임시 위치에 파일을 작성한 다음 덮어 쓰기를 허용하지 않고 복사 명령을 수행 할 수 있습니까?
에릭

답변:


93

편집 : Dave Jones의 답변 참조 : Python 3.3에서 x플래그를 사용 open()하여이 기능을 제공 할 수 있습니다 .

아래의 원래 답변

예,하지만 Python의 표준 open()호출을 사용하지 않습니다 . os.open()대신 사용 하여 기본 C 코드에 플래그를 지정할 수 있습니다.

특히 O_CREAT | O_EXCL. 내 Unix 시스템의 open(2)under O_EXCL에 대한 man 페이지에서 :

이 호출이 파일을 생성하는지 확인합니다.이 플래그가와 함께 지정 O_CREAT되고 경로 이름이 이미 존재 open()하면 실패합니다. 이 지정 O_EXCL되지 않은 경우 의 동작 은 정의되지 O_CREAT않습니다.

이 두 플래그가 지정되면 기호 링크를 따르지 않습니다. 경로 이름이 기호 링크이면 open()기호 링크가 가리키는 위치에 관계없이 실패합니다.

O_EXCL 커널 2.6 이상에서 NFSv3 이상을 사용하는 경우 NFS에서만 지원됩니다. NFS O_EXCL지원이 제공되지 않는 환경에서는 잠금 작업을 수행하기 위해 NFS 지원에 의존하는 프로그램에 경쟁 조건이 포함됩니다.

따라서 완벽하지는 않지만 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!")

1
+1은 분명하게 정답입니다. 저는 개인적으로 얼마나 많은 사람들이 NFS 경고에 문제가 있는지 알고 싶습니다. 저는 (아마 무모하게) 제 코드를 절대 실행해서는 안되는 쓸모없는 환경으로 여기고 있습니다.
zigg

2
@zigg : NFSv3는 1995 년의 것이므로 이전 버전을 구버전으로 간주하는 것이 공정 해 보입니다.
Fred Foo 2011 년

1
개인적으로 커널 버전이 더 걱정됩니다. 예를 들어 최신 시스템과 모호하게 유사한 것을 실행하는 경우 문제가 없지만 RHEL 3 (여전히 확장 지원 단계에 있음)은 2.4 커널을 실행합니다. 또한 잠재적으로 주요 제한 사항 인 FAT 또는 NTFS의 Windows에서 원자 쓰기를 제공하는지 조사하지 않았습니다.
me_and

1
@me_and 열기 플래그 상수 의 파이썬 페이지 는 이것이 Windows에서 잘 작동 함을 제안합니다. 곧 시도하겠습니다!
Henry Gomersall 2012-06-11

1
사실이지만 이러한 플래그가 원자 파일 생성을 제공한다고 명시 적으로 말하는 곳 ( MSDN 포함 )을 본 적이 없습니다 . 내가 지나치게 편집증적일 수 있지만 보안에 중요한 모든 것에 대해 이것을 신뢰하기 전에 "atomic"키워드를보고 싶습니다.
me_and

69

참고로 Python 3.3 은이 사용 사례를 다루기 'x'위해 open()함수에 새 모드를 구현합니다 (만들기 만, 파일이 있으면 실패). 점을 유의 'x'모드가 자체적으로 지정됩니다. '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'
댄 렌스키

5
파이썬 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

1
하지만 나중에 컴퓨터 앞에 돌아올 때까지 기다려야합니다.
Dave Jones

2
쓰기를 위해 기존 파일을 여는 것이 합리적이지만 'x'모드의 전체 요점은 파일 이 이미 존재하지 않는 경우에만 파일을 여는 것이며 파일 이 존재 하면 오류가 발생합니다. 이것이 'w'플래그와 중복되는 이유입니다. 성공하면 파일은 비어 있음이 보장됩니다 (따라서 파일에서 읽는 점이 거의 없습니다.).
Dave Jones

0

이 코드는 FILE이없는 경우 쉽게 생성합니다.

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

15
네, 그렇습니다. 질문의 중요한 점은 안전 측면이었습니다. 문제는 파일의 존재를 식별하고 파일을 사용하거나 생성하는 사이에 무언가 변경되어 잘못된 결과를 초래할 수 있다는 것입니다 (원래 질문에서와 같이).
Henry Gomersall 2013 년

5
사실입니다. TOCTOU라고합니다!
라드

다른 프로세스가 if명령문 다음에 파일을 작성하고 기록하는 경우이 코드는 파일을 비 웁니다.
Peter Wood
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.