Czytelnicy tacy jak ty pomagają wspierać MUO. Kiedy dokonujesz zakupu za pomocą linków na naszej stronie, możemy otrzymać prowizję partnerską.

Sytuacja wyścigu ma miejsce, gdy dwie operacje muszą zostać przeprowadzone w określonej kolejności, ale mogą one przebiegać w odwrotnej kolejności.

Na przykład w aplikacji wielowątkowej dwa oddzielne wątki mogą uzyskiwać dostęp do wspólnej zmiennej. W rezultacie, jeśli jeden wątek zmieni wartość zmiennej, drugi może nadal używać starszej wersji, ignorując najnowszą wartość. Spowoduje to niepożądane rezultaty.

Aby lepiej zrozumieć ten model, dobrze byłoby przyjrzeć się bliżej procesowi przełączania procesów procesora.

Jak procesor przełącza procesy

Nowoczesne systemy operacyjne może uruchamiać więcej niż jeden proces jednocześnie, zwany wielozadaniowością. Kiedy spojrzysz na ten proces pod kątem tzw Cykl wykonania procesora, może się okazać, że wielozadaniowość tak naprawdę nie istnieje.

Zamiast tego procesory nieustannie przełączają się między procesami, aby uruchamiać je jednocześnie lub przynajmniej zachowywać się tak, jakby to robiły. Procesor może przerwać proces przed jego zakończeniem i wznowić inny proces. System operacyjny steruje zarządzaniem tymi procesami.

instagram viewer

Na przykład algorytm Round Robin, jeden z najprostszych algorytmów przełączania, działa w następujący sposób:

Ogólnie rzecz biorąc, ten algorytm pozwala każdemu procesowi działać przez bardzo małe fragmenty czasu, zgodnie z ustaleniami systemu operacyjnego. Na przykład może to być okres dwóch mikrosekund.

Procesor po kolei bierze każdy proces i wykonuje polecenia, które będą działać przez dwie mikrosekundy. Następnie przechodzi do następnego procesu, niezależnie od tego, czy bieżący zakończył się, czy nie. Tak więc z punktu widzenia użytkownika końcowego wydaje się, że jednocześnie działa więcej niż jeden proces. Jednak gdy spojrzysz za kulisy, procesor nadal robi wszystko w porządku.

Nawiasem mówiąc, jak pokazuje powyższy diagram, algorytm Round Robin nie ma żadnych pojęć dotyczących optymalizacji ani priorytetów przetwarzania. W rezultacie jest to dość prymitywna metoda, rzadko stosowana w rzeczywistych systemach.

Teraz, aby lepiej to wszystko zrozumieć, wyobraź sobie, że działają dwa wątki. Jeśli wątki uzyskują dostęp do wspólnej zmiennej, może wystąpić sytuacja wyścigu.

Przykładowa aplikacja internetowa i sytuacja wyścigu

Sprawdź prostą aplikację Flask poniżej, aby zastanowić się nad konkretnym przykładem wszystkiego, co do tej pory przeczytałeś. Celem tej aplikacji jest zarządzanie transakcjami pieniężnymi, które będą miały miejsce w sieci. Zapisz następujące elementy w pliku o nazwie pieniądze.py:

z kolba import Kolba
z flask.ext.sqlalchemy import SQLAlchemy

aplikacja = Kolba (__nazwa__)
app.config ['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy (aplikacja)

klasaKonto(db. Model):
identyfikator = baza danych. Kolumna (db. Liczba całkowita, klucz_podstawowy = PRAWDA)
kwota = db. Kolumna (db. Strunowy(80), unikalny = PRAWDA)

pok__w tym__(ja, liczyć):
kwota.własna = kwota

pok__repr__(samego siebie):
powrót '' % własnej kwoty

@app.route("/")
pokCześć():
konto = konto.zapytanie.get(1) # Jest tylko jeden portfel.
powrót „Całkowita suma pieniędzy = {}”.format (konto.kwota)

@app.route("/wyślij/")
pokwysłać(kwota):
konto = konto.zapytanie.get(1)

Jeśli int (konto.kwota) < kwota:
powrót "Niewystarczająca ilość środków. Zresetuj pieniądze z /reset!)"

kwota.konta = int (kwota.konta) - kwota
db.sesja.zatwierdzenie()
powrót „Wysłana kwota = {}”.format (kwota)

@app.route("/resetuj")
pokResetowanie():
konto = konto.zapytanie.get(1)
kwota.konta = 5000
db.sesja.zatwierdzenie()
powrót „Resetowanie pieniędzy”.

Jeśli __nazwa__ == "__główny__":
app.secret_key = 'heLLoTHisIsSeCReTKey!'
aplikacja.uruchom()

Aby uruchomić ten kod, musisz utworzyć rekord w tabeli kont i kontynuować transakcje na tym rekordzie. Jak widać w kodzie, jest to środowisko testowe, więc dokonuje transakcji na podstawie pierwszego rekordu w tabeli.

z pieniądze import baza danych
baza danych.utwórz_wszystko()
z pieniądze import Konto
konto = konto (5000)
baza danych.sesja.dodać(konto)
baza danych.sesja.popełniać()

Właśnie utworzyłeś konto z saldem 5000 $. Na koniec uruchom powyższy kod źródłowy za pomocą następującego polecenia, pod warunkiem, że masz zainstalowane pakiety Flask i Flask-SQLAlchemy:

pytonpieniądze.py

Masz więc aplikację internetową Flask, która wykonuje prosty proces ekstrakcji. Ta aplikacja może wykonywać następujące operacje z łączami żądania GET. Ponieważ Flask domyślnie działa na porcie 5000, adres, z którego uzyskujesz do niego dostęp, to 127.0.0.1:5000/. Aplikacja udostępnia następujące punkty końcowe:

  • 127.0.0.1:5000/ wyświetla aktualne saldo.
  • 127.0.0.1:5000/wyślij/{kwota} odejmuje kwotę z konta.
  • 127.0.0.1:5000/zresetuj resetuje konto do 5000 $.

Teraz na tym etapie możesz zbadać, w jaki sposób powstaje podatność na wyścig.

Prawdopodobieństwo wystąpienia podatności na warunki wyścigu

Powyższa aplikacja internetowa zawiera możliwą lukę związaną z wyścigiem.

Wyobraź sobie, że masz na początek 5000 USD i tworzysz dwa różne żądania HTTP, które wyślą 1 USD. W tym celu możesz wysłać dwa różne żądania HTTP do łącza 127.0.0.1:5000/wyślij/1. Załóżmy, że tak szybko, jak serwer WWW przetwarza pierwsze żądanie, CPU zatrzymuje ten proces i przetwarza drugie żądanie. Na przykład pierwszy proces może zostać zatrzymany po uruchomieniu następującego wiersza kodu:

kwota.konta = int(konto.kwota) - kwota

Ten kod obliczył nową sumę, ale nie zapisał jeszcze rekordu w bazie danych. Kiedy rozpocznie się drugie żądanie, wykona te same obliczenia, odejmując 1 USD od wartości w bazie danych — 5000 USD — i zapisze wynik. Gdy pierwszy proces zostanie wznowiony, zapisze swoją własną wartość — 4 999 USD — która nie będzie odzwierciedlać ostatniego salda konta.

Tak więc dwa żądania zostały zrealizowane i każdy powinien był odjąć 1 USD od salda konta, co dałoby nowe saldo w wysokości 4 998 USD. Ale w zależności od kolejności, w jakiej serwer WWW je przetwarza, ostateczne saldo konta może wynieść 4 999 USD.

Wyobraź sobie, że wysyłasz 128 żądań wykonania przelewu w wysokości 1 USD do systemu docelowego w ciągu pięciu sekund. W wyniku tej transakcji oczekiwany wyciąg z konta wyniesie 5000 USD - 128 USD = 4875 USD. Jednak ze względu na warunki wyścigu ostateczne saldo może wahać się od 4 875 USD do 4 999 USD.

Programiści są jednym z najważniejszych elementów bezpieczeństwa

W projekcie oprogramowania jako programista masz sporo obowiązków. Powyższy przykład dotyczył prostej aplikacji do przesyłania pieniędzy. Wyobraź sobie, że pracujesz nad projektem oprogramowania, który zarządza kontem bankowym lub zapleczem dużej witryny e-commerce.

Musisz być zaznajomiony z takimi lukami, aby program, który napisałeś w celu ich ochrony, był wolny od luk. To wymaga dużej odpowiedzialności.

Luka w zabezpieczeniach związana z wyścigiem to tylko jedna z nich. Bez względu na to, jakiej technologii używasz, musisz uważać na luki w pisanym kodzie. Jedną z najważniejszych umiejętności, jakie możesz zdobyć jako programista, jest znajomość zagadnień związanych z bezpieczeństwem oprogramowania.