101 votes

Créer en toute sécurité un fichier si et seulement s'il n'existe pas avec Python

Je souhaite écrire dans un fichier en me basant sur le fait que ce fichier existe déjà ou non, en écrivant uniquement s'il n'existe pas déjà (en pratique, je souhaite continuer à essayer les fichiers jusqu'à ce que je trouve un fichier qui n'existe pas).

Le code suivant montre une façon dont un attaquant potentiel pourrait insérer un lien symbolique, comme suggéré dans le document ce poste entre un test pour le fichier et le fichier en cours d'écriture. Si le code est exécuté avec des permissions suffisamment élevées, cela pourrait écraser un fichier arbitraire.

Existe-t-il un moyen de résoudre ce problème ?

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

101voto

me_and Points 6090

Editar : Voir aussi Réponse de Dave Jones : à partir de Python 3.3, vous pouvez utiliser la fonction x pour open() pour assurer cette fonction.

Réponse originale ci-dessous

Oui, mais pas en utilisant la norme Python open() appel. Vous aurez besoin d'utiliser os.open() à la place, qui vous permet de spécifier des drapeaux pour le code C sous-jacent.

En particulier, vous voulez utiliser O_CREAT | O_EXCL . Extrait de la page de manuel de open(2) sous O_EXCL sur mon système Unix :

Assurez-vous que cet appel crée le fichier : si ce drapeau est spécifié en conjonction avec O_CREAT et le nom du chemin existe déjà, alors open() échouera. Le comportement de O_EXCL est indéfini si O_CREAT n'est pas spécifié.

Lorsque ces deux drapeaux sont spécifiés, les liens symboliques ne sont pas suivis : si le chemin est un lien symbolique, alors open() échoue quel que soit l'endroit vers lequel pointe le lien symbolique.

O_EXCL n'est pris en charge sur NFS que si l'on utilise NFSv3 ou une version ultérieure sur le noyau 2.6 ou une version ultérieure. Dans les environnements où NFS O_EXCL n'est pas fournie, les programmes qui s'appuient sur elle pour effectuer des tâches de verrouillage contiendront une condition de course.

Ce n'est donc pas parfait, mais je pense que c'est ce qui se rapproche le plus de cette condition de course.

Edit : les autres règles d'utilisation os.open() au lieu de open() s'appliquent toujours. En particulier, si vous souhaitez utiliser le descripteur de fichier retourné pour la lecture ou l'écriture, vous aurez besoin de l'une des méthodes suivantes O_RDONLY , O_WRONLY o O_RDWR également des drapeaux.

Tous les O_* se trouvent dans le langage Python os vous devrez donc import os et utiliser os.O_CREAT etc.

Exemple :

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

87voto

Dave Jones Points 231

À titre de référence, Python 3.3 implémente une nouvelle fonction 'x' dans le open() pour couvrir ce cas d'utilisation (création seulement, échec si le fichier existe). Notez que la fonction 'x' est spécifié seul. Utilisation de 'wx' donne lieu à un ValueError comme le 'w' est redondant (la seule chose que vous pouvez faire si l'appel réussit est d'écrire dans le fichier de toute façon ; il ne peut pas avoir existé si l'appel réussit) :

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

Pour Python 3.2 et inférieur (y compris Python 2.x), veuillez vous référer à la réponse acceptée .

0voto

Henry Gomersall Points 2916

Sur la base des commentaires, voici ma solution provisoire. N'hésitez pas à souligner tout problème !

import os
import errno
import tempfile

file_to_be_attacked = 'important_file'

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

test_file = 'testfile'

import os, errno
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 tempfile.NamedTemporaryFile(
                'w', dir=os.getcwd(), delete=False) as f:
            f.write('Hello, kthxbye!\n')
            temp_name = f.name

        os.rename(temp_name, test_file)

-2voto

user2033758 Points 119

Ce code permettra de créer facilement un fichier s'il n'en existe pas.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X