สร้างไฟล์อย่างปลอดภัยถ้าไม่มีอยู่ใน python


94

ฉันต้องการเขียนลงในไฟล์โดยขึ้นอยู่กับว่าไฟล์นั้นมีอยู่แล้วหรือไม่เพียงเขียนเมื่อไม่มีอยู่แล้วเท่านั้น (ในทางปฏิบัติฉันต้องการลองไฟล์ต่อไปจนกว่าฉันจะพบไฟล์ที่ไม่มีอยู่)

โค้ดต่อไปนี้แสดงวิธีที่ผู้โจมตีอาจแทรก symlink ตามที่แนะนำในโพสต์นี้ระหว่างการทดสอบไฟล์และไฟล์ที่กำลังเขียน หากรหัสถูกเรียกใช้โดยมีสิทธิ์สูงเพียงพอสิ่งนี้อาจเขียนทับไฟล์โดยพลการ

มีวิธีใดในการแก้ปัญหานี้หรือไม่?

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 Ohtamaa

@ มิกโกะนั่นไม่ได้ช่วยตรงนี้
Konrad Rudolph

อาโอเค. ฉันเข้าใจว่าเกิดอะไรขึ้น ... คุณเขียนเฉพาะเมื่อมีไฟล์อยู่?
Mikko Ohtamaa

คุณสามารถเขียนไฟล์ในตำแหน่งชั่วคราวจากนั้นทำคำสั่งคัดลอกโดยไม่อนุญาตให้เขียนทับได้หรือไม่?
Eric

คำตอบ:


95

แก้ไข : ดูคำตอบของ Dave Jones ด้วย : จาก Python 3.3 คุณสามารถใช้xแฟล็กopen()เพื่อจัดเตรียมฟังก์ชันนี้

คำตอบเดิมด้านล่าง

ใช่ แต่ไม่ได้ใช้การopen()เรียกมาตรฐานของ Python คุณจะต้องใช้os.open()แทนซึ่งช่วยให้คุณระบุแฟล็กให้กับรหัส C ที่อยู่ข้างใต้

O_CREAT | O_EXCLโดยเฉพาะอย่างยิ่งที่คุณต้องการใช้ จากหน้าคนสำหรับopen(2)ภายใต้O_EXCLระบบ Unix ของฉัน:

ตรวจสอบให้แน่ใจว่าการเรียกนี้สร้างไฟล์: หากระบุแฟล็กนี้ร่วมกับO_CREATและมีชื่อพา ธ อยู่แล้วopen()จะล้มเหลว ลักษณะการทำงานของO_EXCLis undefined ถ้าO_CREATไม่ได้ระบุ

เมื่อระบุแฟล็กทั้งสองนี้จะไม่มีการติดตามลิงก์สัญลักษณ์: หากชื่อพา ธ เป็นลิงก์สัญลักษณ์จะopen()ล้มเหลวไม่ว่าลิงก์สัญลักษณ์จะชี้ไปที่ใด

O_EXCL ได้รับการสนับสนุนบน NFS เท่านั้นเมื่อใช้ NFSv3 หรือใหม่กว่าบนเคอร์เนล 2.6 หรือใหม่กว่า ในสภาพแวดล้อมที่ไม่มีO_EXCLการสนับสนุนNFS โปรแกรมที่ใช้ในการดำเนินการล็อกจะมีเงื่อนไขการแข่งขัน

ดังนั้นมันจึงไม่สมบูรณ์แบบ แต่ AFAIK เป็นสิ่งที่ใกล้เคียงที่สุดที่คุณจะหลีกเลี่ยงสภาพการแข่งขันนี้ได้

แก้ไข: กฎอื่น ๆ ในการใช้os.open()แทนที่จะopen()ยังคงมีผลบังคับใช้ โดยเฉพาะอย่างยิ่งถ้าคุณต้องการใช้อธิบายไฟล์กลับมาอ่านหรือเขียนคุณจะต้องเป็นหนึ่งในO_RDONLY, O_WRONLYหรือO_RDWRธงเช่นกัน

O_*แฟล็กทั้งหมดอยู่ในosโมดูลของ Python ดังนั้นคุณจะต้อง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

3
@zigg: NFSv3 มาจากปี 1995 ดังนั้นจึงดูเหมือนจะยุติธรรมที่จะถือว่าเวอร์ชันเก่าล้าสมัย
Fred Foo

1
ฉันกังวลมากขึ้นเกี่ยวกับเวอร์ชันเคอร์เนลเป็นการส่วนตัว หากคุณกำลังเรียกใช้สิ่งใด ๆ ที่ดูคลุมเครือแม้กระทั่งระบบที่ทันสมัยคุณก็ไม่น่ามีปัญหา แต่ RHEL 3 (ยังอยู่ในระยะการสนับสนุนเพิ่มเติม) กำลังเรียกใช้เคอร์เนล 2.4 เช่น นอกจากนี้ฉันยังไม่ได้ตรวจสอบว่ามีการเขียนอะตอมบน Windows บน FAT หรือ NTFS หรือไม่ซึ่งอาจเป็นข้อ จำกัด ที่สำคัญ
me_and

1
@me_and หน้า python บนค่าคงที่ของค่าสถานะแบบเปิดแสดงให้เห็นว่าสิ่งนี้ใช้ได้ดีกับ Windows ฉันจะลองใช้ในไม่ช้า!
Henry Gomersall

1
จริง แต่ฉันไม่เคยเห็นที่ไหน (รวมถึงMSDN ) ที่บอกอย่างชัดเจนว่าแฟล็กเหล่านี้ให้การสร้างไฟล์อะตอม อาจเป็นไปได้ว่าฉันหวาดระแวงมากเกินไป แต่ฉันต้องการเห็นคีย์เวิร์ด "อะตอม" นั้นก่อนที่จะเชื่อถือสิ่งนี้สำหรับสิ่งที่สำคัญด้านความปลอดภัย
me_and

72

สำหรับการอ้างอิง Python 3.3 จะใช้'x'โหมดใหม่ในopen()ฟังก์ชันเพื่อให้ครอบคลุมกรณีการใช้งานนี้ (สร้างเท่านั้นล้มเหลวหากมีไฟล์อยู่) โปรดทราบว่า'x'โหมดถูกระบุด้วยตัวมันเอง การใช้'wx'ผลลัพธ์ในValueErroras 'w'ซ้ำซ้อน (สิ่งเดียวที่คุณสามารถทำได้หากการโทรประสบความสำเร็จคือการเขียนลงไฟล์อย่างไรก็ตามจะไม่สามารถมีอยู่ได้หากการโทรสำเร็จ):

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

สำหรับงูหลาม 3.2 และด้านล่าง (รวมทั้งงูหลาม 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

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

1
จะทำ - แม้ว่าจะต้องรอจนกว่าฉันจะกลับมาที่หน้าคอมพิวเตอร์ในภายหลัง
Dave Jones

2
มันสมเหตุสมผลที่จะเปิดไฟล์ที่มีอยู่เพื่อเขียน แต่ประเด็นทั้งหมดของโหมด 'x' คือการเปิดไฟล์ถ้าไฟล์นั้นไม่มีอยู่แล้วเท่านั้นโดยจะเกิดข้อผิดพลาดเมื่อไม่มีไฟล์ ด้วยเหตุนี้จึงซ้ำซ้อนกับแฟล็ก 'w' หากประสบความสำเร็จไฟล์จะรับประกันว่าจะว่างเปล่า (ดังนั้นจึงมีจุดอ่านน้อยมากจากมัน :)
Dave Jones

0

รหัสนี้จะสร้าง FILE ได้อย่างง่ายดายหากไม่มีอยู่

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

16
ใช่! ฉันจะ. ประเด็นสำคัญเกี่ยวกับคำถามคือประเด็นด้านความปลอดภัย ปัญหาคือระหว่างการระบุสถานะของไฟล์และการใช้งานหรือสร้างไฟล์บางอย่างอาจเปลี่ยนแปลงไปซึ่งส่งผลให้เกิดผลลัพธ์ที่ไม่ดี (เช่นเดียวกับคำถามเดิม)
Henry Gomersall

5
นั่นคือเรื่องจริง เรียกว่า TOCTOU!
ราด

หากกระบวนการอื่นสร้างและเขียนไปยังไฟล์หลังifคำสั่งรหัสนี้จะทำให้ไฟล์ว่างเปล่า
Peter Wood
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.