Des lecteurs comme vous aident à soutenir MUO. Lorsque vous effectuez un achat en utilisant des liens sur notre site, nous pouvons gagner une commission d'affiliation.

Une condition de concurrence se produit lorsque deux opérations doivent se produire dans un ordre spécifique, mais elles peuvent s'exécuter dans l'ordre opposé.

Par exemple, dans une application multithread, deux threads distincts peuvent accéder à une variable commune. Par conséquent, si un thread modifie la valeur de la variable, l'autre peut toujours utiliser l'ancienne version, en ignorant la valeur la plus récente. Cela entraînera des résultats indésirables.

Pour mieux comprendre ce modèle, il serait bon d'examiner de près le processus de commutation de processus du processeur.

Comment un processeur change de processus

Systèmes d'exploitation modernes peut exécuter plus d'un processus simultanément, appelé multitâche. Lorsque vous examinez ce processus en termes de Cycle d'exécution du CPU, vous constaterez peut-être que le multitâche n'existe pas vraiment.

instagram viewer

Au lieu de cela, les processeurs basculent constamment entre les processus pour les exécuter simultanément ou du moins agissent comme s'ils le faisaient. Le processeur peut interrompre un processus avant qu'il ne soit terminé et reprendre un processus différent. Le système d'exploitation contrôle la gestion de ces processus.

Par exemple, l'algorithme Round Robin, l'un des algorithmes de commutation les plus simples, fonctionne comme suit :

Généralement, cet algorithme permet à chaque processus de s'exécuter pendant de très petites tranches de temps, comme le détermine le système d'exploitation. Par exemple, cela pourrait être une période de deux microsecondes.

Le CPU prend chaque processus à tour de rôle et exécute des commandes qui dureront deux microsecondes. Il passe ensuite au processus suivant, que le processus en cours soit terminé ou non. Ainsi, du point de vue d'un utilisateur final, plusieurs processus semblent s'exécuter simultanément. Cependant, lorsque vous regardez dans les coulisses, le processeur fait toujours les choses dans l'ordre.

Soit dit en passant, comme le montre le schéma ci-dessus, l'algorithme Round Robin est dépourvu de toute notion d'optimisation ou de priorité de traitement. Par conséquent, il s'agit d'une méthode plutôt rudimentaire qui est rarement utilisée dans les systèmes réels.

Maintenant, pour mieux comprendre tout cela, imaginez que deux threads fonctionnent. Si les threads accèdent à une variable commune, une condition de concurrence peut survenir.

Un exemple d'application Web et de condition de concurrence

Découvrez l'application simple Flask ci-dessous pour réfléchir à un exemple concret de tout ce que vous avez lu jusqu'à présent. Le but de cette application est de gérer les transactions monétaires qui auront lieu sur le web. Enregistrez ce qui suit dans un fichier nommé argent.py:

depuis ballon importer Ballon
depuis flacon.ext.sqlalchimie importer SQLAlchimie

app = flacon (__nom__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy (application)

classeCompte(db. Modèle):
id = db. Colonne (db. Entier, clé_primaire = Vrai)
montant = db. Colonne (db. Chaîne(80), uniques = Vrai)

définitivement__init__(soi-même, compter):
self.amount = montant

définitivement__repr__(soi):
retour '' % self.mount

@app.route("/")
définitivementSalut():
compte = Compte.query.get(1) # Il n'y a qu'un seul portefeuille.
retour "Argent total = {}".format (compte.montant)

@app.route("/send/")
définitivementenvoyer(montant):
compte = Compte.query.get(1)

si entier (compte.montant) < montant :
retour "Solde insuffisant. Réinitialiser l'argent avec /reset!)"

compte.montant = int (compte.montant) - montant
db.session.commit()
retour "Montant envoyé = {}".format (montant)

@app.route("/reset")
définitivementréinitialiser():
compte = Compte.query.get(1)
compte.montant = 5000
db.session.commit()
retour "Réinitialisation de l'argent."

si __name__ == "__main__":
app.secret_key = 'helloTHisIsSeCReTKey !'
app.run()

Pour exécuter ce code, vous devrez créer un enregistrement dans la table des comptes et continuer les transactions sur cet enregistrement. Comme vous pouvez le voir dans le code, il s'agit d'un environnement de test, il effectue donc des transactions sur le premier enregistrement de la table.

depuis argent importer db
db.create_all()
depuis argent importer Compte
compte = Compte (5000)
db.session.ajouter(compte)
db.session.commettre()

Vous avez maintenant créé un compte avec un solde de 5 000 $. Enfin, exécutez le code source ci-dessus à l'aide de la commande suivante, à condition que les packages Flask et Flask-SQLAlchemy soient installés :

pythonargent.py

Vous avez donc l'application Web Flask qui effectue un processus d'extraction simple. Cette application peut effectuer les opérations suivantes avec des liens de requête GET. Étant donné que Flask s'exécute sur le port 5000 par défaut, l'adresse à laquelle vous y accédez est 127.0.0.1:5000/. L'application fournit les points de terminaison suivants :

  • 127.0.0.1:5000/ affiche le solde actuel.
  • 127.0.0.1:5000/envoi/{montant} soustrait le montant du compte.
  • 127.0.0.1:5000/réinitialiser réinitialise le compte à 5 000 $.

Maintenant, à ce stade, vous pouvez examiner comment la vulnérabilité de condition de concurrence se produit.

Probabilité d'une vulnérabilité de condition de course

L'application Web ci-dessus contient une possible vulnérabilité de condition de concurrence.

Imaginez que vous disposiez de 5 000 $ pour commencer et créez deux requêtes HTTP différentes qui enverront 1 $. Pour cela, vous pouvez envoyer deux requêtes HTTP différentes au lien 127.0.0.1:5000/envoi/1. Supposons que, dès que le serveur web traite la première requête, le CPU arrête ce processus et traite la deuxième requête. Par exemple, le premier processus peut s'arrêter après avoir exécuté la ligne de code suivante :

compte.montant = entier(compte.montant) - montant

Ce code a calculé un nouveau total mais n'a pas encore enregistré l'enregistrement dans la base de données. Lorsque la deuxième requête commence, elle effectue le même calcul, en soustrayant 1 $ de la valeur dans la base de données - 5 000 $ - et en stockant le résultat. Lorsque le premier processus reprendra, il stockera sa propre valeur - 4 999 $ - qui ne reflétera pas le solde du compte le plus récent.

Ainsi, deux demandes ont été effectuées et chacune aurait dû soustraire 1 $ du solde du compte, ce qui donne un nouveau solde de 4 998 $. Mais, selon l'ordre dans lequel le serveur Web les traite, le solde final du compte peut être de 4 999 $.

Imaginez que vous envoyez 128 requêtes pour effectuer un transfert de 1 $ vers le système cible dans un laps de temps de cinq secondes. À la suite de cette transaction, le relevé de compte prévu sera de 5 000 $ - 128 $ = 4 875 $. Cependant, en raison de la condition de concurrence, le solde final peut varier entre 4 875 $ et 4 999 $.

Les programmeurs sont l'un des composants les plus importants de la sécurité

Dans un projet logiciel, en tant que programmeur, vous avez pas mal de responsabilités. L'exemple ci-dessus concernait une simple application de transfert d'argent. Imaginez travailler sur un projet logiciel qui gère un compte bancaire ou le backend d'un grand site de commerce électronique.

Vous devez être familiarisé avec ces vulnérabilités afin que le programme que vous avez écrit pour les protéger soit exempt de vulnérabilités. Cela demande une forte responsabilité.

Une vulnérabilité aux conditions de concurrence n'est que l'une d'entre elles. Quelle que soit la technologie que vous utilisez, vous devez faire attention aux vulnérabilités du code que vous écrivez. L'une des compétences les plus importantes que vous puissiez acquérir en tant que programmeur est la familiarité avec la sécurité des logiciels.