Bir yarış koşulu, iki işlemin belirli bir sırada gerçekleşmesi gerektiğinde oluşur, ancak bunlar ters sırada çalışabilir.
Örneğin, çok iş parçacıklı bir uygulamada, iki ayrı iş parçacığı ortak bir değişkene erişebilir. Sonuç olarak, bir iş parçacığı değişkenin değerini değiştirirse, diğeri en yeni değeri yok sayarak eski sürümü kullanmaya devam edebilir. Bu istenmeyen sonuçlara neden olacaktır.
Bu modeli daha iyi anlamak için işlemcinin süreç değiştirme sürecini yakından incelemekte fayda var.
Bir İşlemci Süreçleri Nasıl Değiştirir?
Modern işletim sistemleri , çoklu görev adı verilen birden fazla işlemi aynı anda çalıştırabilir. Bu sürece CPU’nun yürütme döngüsü açısından baktığınızda, çoklu görevin gerçekten var olmadığını görebilirsiniz.
Bunun yerine, işlemciler aynı anda çalıştırmak veya en azından yapıyormuş gibi davranmak için süreçler arasında sürekli geçiş yaparlar. CPU, bir işlemi tamamlanmadan önce kesebilir ve farklı bir işleme devam edebilir. İşletim sistemi bu süreçlerin yönetimini kontrol eder.
Örneğin en basit anahtarlama algoritmalarından biri olan Round Robin algoritması şu şekilde çalışır:
Kuyruğa alınmış, uykuda olan 3 işlemi gösteren bir diyagram, CPU’nun şu anda çalışmakta olduğu 1 aktif işlemi işler
Genel olarak, bu algoritma, işletim sisteminin belirlediği gibi, her işlemin çok küçük zaman dilimlerinde çalışmasına izin verir. Örneğin, bu iki mikrosaniyelik bir periyot olabilir.
CPU, her işlemi sırayla alır ve iki mikrosaniye boyunca çalışacak komutları yürütür. Daha sonra mevcut işlemin bitip bitmediğine bakılmaksızın bir sonraki işleme devam eder. Böylece, bir son kullanıcının bakış açısından, birden fazla süreç aynı anda çalışıyor gibi görünüyor. Ancak, perde arkasına baktığınızda, CPU hala işleri sırayla yapıyor.
Bu arada, yukarıdaki diyagramın gösterdiği gibi, Round Robin algoritması herhangi bir optimizasyon veya işleme önceliği kavramından yoksundur. Sonuç olarak, gerçek sistemlerde nadiren kullanılan oldukça ilkel bir yöntemdir.
Şimdi, tüm bunları daha iyi anlamak için, iki iş parçacığının çalıştığını hayal edin. İplikler ortak bir değişkene erişirse, bir yarış durumu ortaya çıkabilir.
Örnek Bir Web Uygulaması ve Yarış Koşulu
Şimdiye kadar okuduğunuz her şeyin somut bir örneğini yansıtmak için aşağıdaki basit Flask uygulamasına göz atın. Bu uygulamanın amacı, web üzerinde gerçekleşecek para işlemlerini yönetmektir. Aşağıdakileri money.py adlı bir dosyaya kaydedin :
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config[‘SQLALCHEMY_DATABASE_URI’] = ‘sqlite:////tmp/test.db’
db = SQLAlchemy(app)
class Account(db.Model):
id = db.Column(db.Integer, primary_key = True)
amount = db.Column(db.String(80), unique = True)
def __init__(self, count):
self.amount = amount
def __repr__(self):
return ” % self.amount
@app.route(“/”)
def hi():
account = Account.query.get(1) # There is only one wallet.
return “Total Money = {}”.format(account.amount)
@app.route(“/send/”)
def send(amount):
account = Account.query.get(1)
if int(account.amount) < amount:
return “Insufficient balance. Reset money with /reset!)”
account.amount = int(account.amount) – amount
db.session.commit()
return “Amount sent = {}”.format(amount)
@app.route(“/reset”)
def reset():
account = Account.query.get(1)
account.amount = 5000
db.session.commit()
return “Money reset.”
if __name__ == “__main__”:
app.secret_key = ‘heLLoTHisIsSeCReTKey!’
app.run()
Bu kodu çalıştırmak için hesap tablosunda bir kayıt oluşturmanız ve bu kayıt üzerinden işlemlere devam etmeniz gerekir. Kodda da göreceğiniz gibi burası bir test ortamı yani tablodaki ilk kayda karşı işlem yapıyor.
from money import db
db.create_all()
from money import Account
account = Account(5000)
db.session.add(account)
db.session.commit()
Şimdi 5.000 ABD Doları bakiyesi olan bir hesap oluşturdunuz. Son olarak, Flask ve Flask-SQLAlchemy paketlerinin kurulu olması koşuluyla, aşağıdaki komutu kullanarak yukarıdaki kaynak kodunu çalıştırın:
python money.py
Yani basit bir çıkarma işlemi yapan Flask web uygulamasına sahipsiniz. Bu uygulama GET request linkleri ile aşağıdaki işlemleri gerçekleştirebilir. Flask varsayılan olarak 5000 bağlantı noktasında çalıştığından, ona eriştiğiniz adres 127.0.0.1:5000/ şeklindedir . Uygulama aşağıdaki uç noktaları sağlar:
127.0.0.1:5000/ mevcut bakiyeyi görüntüler.
127.0.0.1:5000/send/{amount} tutarı hesaptan çıkarır.
127.0.0.1:5000/sıfırlama , hesabı 5.000 ABD Dolarına sıfırlar.
Şimdi bu aşamada yarış durumu zafiyetinin nasıl oluştuğunu inceleyebilirsiniz.
Bir Yarış Koşulu Güvenlik Açığı Olasılığı
Yukarıdaki web uygulaması, olası bir yarış durumu güvenlik açığı içerir.
Başlamak için 5.000 dolarınız olduğunu ve 1 dolar gönderecek iki farklı HTTP isteği oluşturduğunuzu hayal edin. Bunun için 127.0.0.1:5000/send/1 linkine iki farklı HTTP isteği gönderebilirsiniz . Web sunucusu ilk isteği işler işlemez, CPU’nun bu süreci durdurduğunu ve ikinci isteği işlediğini varsayalım . Örneğin, aşağıdaki kod satırını çalıştırdıktan sonra ilk işlem durabilir:
account.amount = int(account.amount) – amount
Bu kod yeni bir toplam hesapladı ancak kaydı henüz veritabanına kaydetmedi. İkinci istek başladığında, aynı hesaplamayı yapacak, veritabanındaki değerden 5.000$ çıkaracak ve sonucu kaydedecektir. İlk işlem devam ettiğinde, en son hesap bakiyesini yansıtmayan kendi değerini (4,999$) saklayacaktır.
Bu nedenle, iki istek tamamlandı ve her birinin hesap bakiyesinden 1 ABD Doları çıkarılarak 4,998 ABD Doları tutarında yeni bir bakiyeyle sonuçlanması gerekir. Ancak, web sunucusunun bunları işleme sırasına bağlı olarak, nihai hesap bakiyesi 4.999$ olabilir.
Beş saniyelik bir zaman diliminde hedef sisteme 1$’lık transfer yapmak için 128 istek gönderdiğinizi düşünün. Bu işlemin sonucunda, beklenen hesap özeti 5.000 – 128 $ = 4.875 $ olacaktır. Ancak, yarış durumu nedeniyle, nihai bakiye 4,875 ile 4,999 dolar arasında değişebilir.
Programcılar Güvenliğin En Önemli Bileşenlerinden Biridir
Bir yazılım projesinde, bir programcı olarak oldukça fazla sorumluluğunuz vardır. Yukarıdaki örnek basit bir para transferi uygulaması içindi. Bir banka hesabını veya büyük bir e-ticaret sitesinin arka ucunu yöneten bir yazılım projesi üzerinde çalıştığınızı hayal edin.
Bunları korumak için yazdığınız programın güvenlik açıklarından arındırılmış olması için bu tür güvenlik açıklarına aşina olmalısınız. Bu güçlü bir sorumluluk gerektirir.
Bir yarış durumu güvenlik açığı bunlardan yalnızca biridir. Hangi teknolojiyi kullanırsanız kullanın, yazdığınız koddaki güvenlik açıklarına dikkat etmeniz gerekiyor. Bir programcı olarak edinebileceğiniz en önemli becerilerden biri yazılım güvenliğine aşinalıktır.