Leser wie Sie helfen, MUO zu unterstützen. Wenn Sie über Links auf unserer Website einen Kauf tätigen, erhalten wir möglicherweise eine Affiliate-Provision.

Eine Racebedingung tritt auf, wenn zwei Operationen in einer bestimmten Reihenfolge ausgeführt werden müssen, aber möglicherweise in der entgegengesetzten Reihenfolge ausgeführt werden.

Beispielsweise könnten in einer Multithread-Anwendung zwei separate Threads auf eine gemeinsame Variable zugreifen. Wenn also ein Thread den Wert der Variablen ändert, verwendet der andere möglicherweise immer noch die ältere Version und ignoriert den neuesten Wert. Dies führt zu unerwünschten Ergebnissen.

Um dieses Modell besser zu verstehen, wäre es gut, den Prozessumschaltprozess des Prozessors genau zu untersuchen.

Wie ein Prozessor Prozesse wechselt

Moderne Betriebssysteme kann mehr als einen Prozess gleichzeitig ausführen, was als Multitasking bezeichnet wird. Wenn Sie sich diesen Prozess im Hinblick auf die Ausführungszyklus der CPU, stellen Sie möglicherweise fest, dass Multitasking nicht wirklich existiert.

instagram viewer

Stattdessen wechseln Prozessoren ständig zwischen Prozessen, um sie gleichzeitig auszuführen oder zumindest so zu tun, als würden sie dies tun. Die CPU kann einen Prozess unterbrechen, bevor er abgeschlossen ist, und einen anderen Prozess fortsetzen. Das Betriebssystem steuert die Verwaltung dieser Prozesse.

Der Round-Robin-Algorithmus, einer der einfachsten Schaltalgorithmen, funktioniert beispielsweise wie folgt:

Im Allgemeinen ermöglicht dieser Algorithmus, dass jeder Prozess nur für sehr kurze Zeitabschnitte ausgeführt wird, wie es das Betriebssystem festlegt. Beispielsweise könnte dies ein Zeitraum von zwei Mikrosekunden sein.

Die CPU übernimmt jeden Prozess der Reihe nach und führt Befehle aus, die zwei Mikrosekunden lang ausgeführt werden. Es fährt dann mit dem nächsten Prozess fort, unabhängig davon, ob der aktuelle beendet ist oder nicht. Aus Sicht eines Endbenutzers scheint also mehr als ein Prozess gleichzeitig zu laufen. Wenn Sie jedoch hinter die Kulissen schauen, erledigt die CPU die Dinge immer noch in Ordnung.

Wie das obige Diagramm zeigt, fehlt dem Round-Robin-Algorithmus übrigens jede Vorstellung von Optimierung oder Verarbeitungspriorität. Infolgedessen handelt es sich um eine eher rudimentäre Methode, die in realen Systemen selten verwendet wird.

Um dies alles besser zu verstehen, stellen Sie sich nun vor, dass zwei Threads laufen. Wenn die Threads auf eine gemeinsame Variable zugreifen, kann es zu einer Racebedingung kommen.

Eine beispielhafte Webanwendung und Race Condition

Schauen Sie sich die einfache Flask-App unten an, um ein konkretes Beispiel für alles zu reflektieren, was Sie bisher gelesen haben. Der Zweck dieser Anwendung ist die Verwaltung von Geldtransaktionen, die im Internet stattfinden. Speichern Sie Folgendes in einer Datei mit dem Namen Geld.py:

aus Flasche importieren Flasche
aus Flask.ext.sqlalchemy importieren SQLAlchemie

app = Flasche (__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy (App)

KlasseKonto(db. Modell):
id = db. Spalte (db. Ganzzahl, primary_key = WAHR)
Betrag = db. Spalte (db. Zeichenfolge (80), einzigartig = WAHR)

def__drin__(selbst, zählen):
self.amount = Betrag

def__repr__(selbst):
zurückkehren '' % Eigenbetrag

@app.route("/")
defHi():
Konto = Konto.query.get(1) # Es gibt nur eine Geldbörse.
zurückkehren "Total Money = {}".format (Konto.Betrag)

@app.route("/senden/")
defschicken(Menge):
Konto = Konto.query.get(1)

Wenn int (konto.betrag) < betrag:
zurückkehren "Mangelhaftes Gleichgewicht. Geld zurücksetzen mit /reset!)"

account.amount = int (account.amount) - Betrag
db.session.commit()
zurückkehren "Gesendeter Betrag = {}".format (Betrag)

@app.route("/reset")
defzurücksetzen():
Konto = Konto.query.get(1)
Konto.Betrag = 5000
db.session.commit()
zurückkehren "Geld zurücksetzen."

Wenn __name__ == "__main__":
app.secret_key = 'heLLoTHisIsSeCReTKey!'
app.run()

Um diesen Code auszuführen, müssen Sie einen Datensatz in der Kontotabelle erstellen und die Transaktionen über diesen Datensatz fortsetzen. Wie Sie im Code sehen können, handelt es sich hier um eine Testumgebung, sodass Transaktionen mit dem ersten Datensatz in der Tabelle durchgeführt werden.

aus Geld importieren db
db.create_all()
aus Geld importieren Konto
Konto = Konto (5000)
db.Sitzung.hinzufügen(Konto)
db.Sitzung.begehen()

Sie haben jetzt ein Konto mit einem Guthaben von 5.000 $ erstellt. Führen Sie schließlich den obigen Quellcode mit dem folgenden Befehl aus, vorausgesetzt, Sie haben die Pakete Flask und Flask-SQLAlchemy installiert:

PythonGeld.py

Sie haben also die Flask-Webanwendung, die einen einfachen Extraktionsprozess durchführt. Diese Anwendung kann die folgenden Operationen mit GET-Anforderungslinks ausführen. Da Flask standardmäßig auf dem 5000-Port läuft, lautet die Adresse, unter der Sie darauf zugreifen 127.0.0.1:5000/. Die App bietet die folgenden Endpunkte:

  • 127.0.0.1:5000/ zeigt den aktuellen Kontostand an.
  • 127.0.0.1:5000/senden/{Betrag} zieht den Betrag vom Konto ab.
  • 127.0.0.1:5000/Zurücksetzen setzt das Konto auf 5.000 $ zurück.

In diesem Stadium können Sie nun untersuchen, wie die Schwachstelle der Race-Bedingung auftritt.

Wahrscheinlichkeit einer Rennbedingungs-Schwachstelle

Die obige Webanwendung enthält eine mögliche Race-Condition-Schwachstelle.

Stellen Sie sich vor, Sie haben zu Beginn 5.000 US-Dollar und erstellen zwei verschiedene HTTP-Anforderungen, die 1 US-Dollar senden. Dazu können Sie zwei verschiedene HTTP-Requests an den Link senden 127.0.0.1:5000/senden/1. Gehen Sie davon aus, sobald der Webserver die erste Anforderung verarbeitet, stoppt die CPU diesen Prozess und verarbeitet die zweite Anforderung. Beispielsweise kann der erste Prozess anhalten, nachdem die folgende Codezeile ausgeführt wurde:

Konto.Betrag = int(Konto.Betrag) - Betrag

Dieser Code hat eine neue Summe berechnet, aber den Datensatz noch nicht in der Datenbank gespeichert. Wenn die zweite Anfrage beginnt, führt sie die gleiche Berechnung durch, subtrahiert 1 $ von dem Wert in der Datenbank – 5.000 $ – und speichert das Ergebnis. Wenn der erste Prozess fortgesetzt wird, speichert er seinen eigenen Wert – 4.999 $ – der nicht den letzten Kontostand widerspiegelt.

Somit wurden zwei Anfragen abgeschlossen, und jede hätte 1 $ vom Kontostand abziehen müssen, was zu einem neuen Saldo von 4.998 $ geführt hätte. Abhängig von der Reihenfolge, in der der Webserver sie verarbeitet, kann der endgültige Kontostand jedoch 4.999 US-Dollar betragen.

Stellen Sie sich vor, Sie senden in einem Zeitrahmen von fünf Sekunden 128 Anfragen für eine Überweisung von 1 $ an das Zielsystem. Als Ergebnis dieser Transaktion beträgt der erwartete Kontoauszug 5.000 $ - 128 $ = 4.875 $. Aufgrund der Rennbedingungen kann der Endbetrag jedoch zwischen 4.875 und 4.999 US-Dollar liegen.

Programmierer sind eine der wichtigsten Sicherheitskomponenten

In einem Softwareprojekt hat man als Programmierer einiges an Verantwortung. Das obige Beispiel war für eine einfache Geldtransferanwendung. Stellen Sie sich vor, Sie arbeiten an einem Softwareprojekt, das ein Bankkonto oder das Backend einer großen E-Commerce-Website verwaltet.

Sie müssen mit solchen Schwachstellen vertraut sein, damit das Programm, das Sie zu ihrem Schutz geschrieben haben, frei von Schwachstellen ist. Dies erfordert eine starke Verantwortung.

Eine Race-Condition-Schwachstelle ist nur eine davon. Unabhängig davon, welche Technologie Sie verwenden, müssen Sie auf Schwachstellen in dem von Ihnen geschriebenen Code achten. Eine der wichtigsten Fähigkeiten, die Sie sich als Programmierer aneignen können, ist die Vertrautheit mit Softwaresicherheit.