Τι είναι μια ευπάθεια αγωνιστικής συνθήκης;

Τι είναι μια ευπάθεια αγωνιστικής συνθήκης;
Αναγνώστες σαν εσάς βοηθούν στην υποστήριξη του MUO. Όταν κάνετε μια αγορά χρησιμοποιώντας συνδέσμους στον ιστότοπό μας, ενδέχεται να κερδίσουμε μια προμήθεια θυγατρικών.

Μια συνθήκη αγώνα εμφανίζεται όταν δύο λειτουργίες πρέπει να πραγματοποιηθούν με μια συγκεκριμένη σειρά, αλλά μπορεί να εκτελεστούν με την αντίθετη σειρά.





Για παράδειγμα, σε μια εφαρμογή πολλαπλών νημάτων, δύο ξεχωριστά νήματα ενδέχεται να έχουν πρόσβαση σε μια κοινή μεταβλητή. Ως αποτέλεσμα, εάν ένα νήμα αλλάξει την τιμή της μεταβλητής, το άλλο μπορεί να συνεχίσει να χρησιμοποιεί την παλαιότερη έκδοση, αγνοώντας τη νεότερη τιμή. Αυτό θα προκαλέσει ανεπιθύμητα αποτελέσματα.





ΚΑΤΑΣΚΕΥΗ ΒΙΝΤΕΟ ΤΗΣ ΗΜΕΡΑΣ

Για να κατανοήσουμε καλύτερα αυτό το μοντέλο, καλό θα ήταν να εξετάσουμε προσεκτικά τη διαδικασία εναλλαγής διεργασιών του επεξεργαστή.





Πώς ένας επεξεργαστής αλλάζει διεργασίες

Σύγχρονα λειτουργικά συστήματα μπορεί να εκτελέσει περισσότερες από μία διεργασίες ταυτόχρονα, που ονομάζεται multitasking. Όταν εξετάζετε αυτή τη διαδικασία από την άποψη του κύκλος εκτέλεσης της CPU , μπορεί να διαπιστώσετε ότι το multitasking δεν υπάρχει στην πραγματικότητα.

Αντίθετα, οι επεξεργαστές αλλάζουν συνεχώς μεταξύ διεργασιών για να τις εκτελούν ταυτόχρονα ή τουλάχιστον να ενεργούν σαν να το κάνουν. Η CPU μπορεί να διακόψει μια διαδικασία πριν ολοκληρωθεί και να συνεχίσει μια διαφορετική διαδικασία. Το λειτουργικό σύστημα ελέγχει τη διαχείριση αυτών των διεργασιών.



Για παράδειγμα, ο αλγόριθμος Round Robin, ένας από τους απλούστερους αλγόριθμους μεταγωγής, λειτουργεί ως εξής:

πώς να φτιάξετε ένα διάνυσμα σε εικονογράφο
  Ένα διάγραμμα που δείχνει 3 επεξεργασίες σε ουρά, σε αναστολή λειτουργίας μια 1 ενεργή διεργασία που εκτελεί αυτήν τη στιγμή η CPU

Γενικά, αυτός ο αλγόριθμος επιτρέπει σε κάθε διεργασία να εκτελείται για πολύ μικρά κομμάτια χρόνου, όπως καθορίζει το λειτουργικό σύστημα. Για παράδειγμα, αυτό θα μπορούσε να είναι μια περίοδος δύο μικροδευτερόλεπτων.





Η CPU παίρνει κάθε διεργασία με τη σειρά και εκτελεί εντολές που θα εκτελούνται για δύο μικροδευτερόλεπτα. Στη συνέχεια, συνεχίζει στην επόμενη διαδικασία, ανεξάρτητα από το αν η τρέχουσα έχει τελειώσει ή όχι. Έτσι, από τη σκοπιά ενός τελικού χρήστη, περισσότερες από μία διεργασίες φαίνεται να εκτελούνται ταυτόχρονα. Ωστόσο, όταν κοιτάτε πίσω από τις σκηνές, η CPU εξακολουθεί να κάνει τα πράγματα με τη σειρά.

Παρεμπιπτόντως, όπως δείχνει το παραπάνω διάγραμμα, ο αλγόριθμος Round Robin στερείται οποιασδήποτε έννοιας προτεραιότητας βελτιστοποίησης ή επεξεργασίας. Ως αποτέλεσμα, είναι μια μάλλον στοιχειώδης μέθοδος που χρησιμοποιείται σπάνια σε πραγματικά συστήματα.





Τώρα, για να τα καταλάβετε όλα αυτά καλύτερα, φανταστείτε ότι τρέχουν δύο νήματα. Εάν τα νήματα έχουν πρόσβαση σε μια κοινή μεταβλητή, μπορεί να προκύψει μια συνθήκη κούρσας.

Ένα παράδειγμα εφαρμογής Ιστού και συνθήκης αγώνα

Ρίξτε μια ματιά στην απλή εφαρμογή Flask παρακάτω για να σκεφτείτε ένα συγκεκριμένο παράδειγμα όλων όσων έχετε διαβάσει μέχρι τώρα. Σκοπός αυτής της εφαρμογής είναι η διαχείριση χρηματικών συναλλαγών που θα πραγματοποιούνται στο διαδίκτυο. Αποθηκεύστε τα παρακάτω σε ένα αρχείο με όνομα χρήματα.py :

εφαρμογή walkie talkie που συνδέεται με πραγματικό walkie talkie
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()

Για να εκτελέσετε αυτόν τον κωδικό, θα πρέπει να δημιουργήσετε μια εγγραφή στον πίνακα λογαριασμού και να συνεχίσετε τις συναλλαγές σε αυτήν την εγγραφή. Όπως μπορείτε να δείτε στον κώδικα, αυτό είναι ένα δοκιμαστικό περιβάλλον, επομένως πραγματοποιεί συναλλαγές έναντι της πρώτης εγγραφής στον πίνακα.

from money import db 
db.create_all()
from money import Account
account = Account(5000)
db.session.add(account)
db.session.commit()

Έχετε πλέον δημιουργήσει έναν λογαριασμό με υπόλοιπο 5.000 $. Τέλος, εκτελέστε τον παραπάνω πηγαίο κώδικα χρησιμοποιώντας την ακόλουθη εντολή, με την προϋπόθεση ότι έχετε εγκαταστήσει τα πακέτα Flask και Flask-SQLAlchemy:

python money.py 

Έτσι έχετε την εφαρμογή web Flask που κάνει μια απλή διαδικασία εξαγωγής. Αυτή η εφαρμογή μπορεί να εκτελέσει τις ακόλουθες λειτουργίες με συνδέσμους αιτήματος GET. Εφόσον το Flask εκτελείται στη θύρα 5000 από προεπιλογή, η διεύθυνση στην οποία έχετε πρόσβαση είναι 127.0.0.1:5000/ . Η εφαρμογή παρέχει τα ακόλουθα τελικά σημεία:

  • 127.0.0.1:5000/ εμφανίζει το τρέχον υπόλοιπο.
  • 127.0.0.1:5000/send/{amount} αφαιρεί ποσό από τον λογαριασμό.
  • 127.0.0.1:5000/επαναφορά επαναφέρει τον λογαριασμό στα 5.000 $.

Τώρα, σε αυτό το στάδιο, μπορείτε να εξετάσετε πώς εμφανίζεται η ευπάθεια της συνθήκης αγώνα.

Πιθανότητα ευπάθειας αγωνιστικής συνθήκης

Η παραπάνω εφαρμογή web περιέχει μια πιθανή ευπάθεια συνθήκης φυλής.

Φανταστείτε ότι έχετε 5.000 $ για να ξεκινήσετε και δημιουργήστε δύο διαφορετικά αιτήματα HTTP που θα στείλουν 1 $. Για αυτό, μπορείτε να στείλετε δύο διαφορετικά αιτήματα HTTP στον σύνδεσμο 127.0.0.1:5000/αποστολή/1 . Ας υποθέσουμε ότι, μόλις ο διακομιστής Ιστού επεξεργάζεται το πρώτο αίτημα, η CPU διακόπτει αυτή τη διαδικασία και επεξεργάζεται το δεύτερο αίτημα. Για παράδειγμα, η πρώτη διαδικασία μπορεί να σταματήσει μετά την εκτέλεση της ακόλουθης γραμμής κώδικα:

πώς να αποσυνδέσετε το instagram από το facebook στο android
account.amount = int(account.amount) - amount 

Αυτός ο κωδικός έχει υπολογίσει ένα νέο σύνολο, αλλά δεν έχει αποθηκεύσει ακόμη την εγγραφή στη βάση δεδομένων. Όταν ξεκινήσει το δεύτερο αίτημα, θα εκτελέσει τον ίδιο υπολογισμό, αφαιρώντας από την τιμή στη βάση δεδομένων—5.000$—και αποθηκεύοντας το αποτέλεσμα. Όταν συνεχιστεί η πρώτη διαδικασία, θα αποθηκεύσει τη δική της αξία—4.999$—η οποία δεν θα αντικατοπτρίζει το πιο πρόσφατο υπόλοιπο του λογαριασμού.

Έτσι, δύο αιτήματα έχουν ολοκληρωθεί και το καθένα θα έπρεπε να έχει αφαιρέσει 1 $ από το υπόλοιπο του λογαριασμού, με αποτέλεσμα ένα νέο υπόλοιπο 4.998 $. Ωστόσο, ανάλογα με τη σειρά με την οποία ο διακομιστής ιστού τα επεξεργάζεται, το τελικό υπόλοιπο του λογαριασμού μπορεί να είναι 4.999 $.

Φανταστείτε ότι στέλνετε 128 αιτήματα για να κάνετε μια μεταφορά στο σύστημα προορισμού σε χρονικό διάστημα πέντε δευτερολέπτων. Ως αποτέλεσμα αυτής της συναλλαγής, η αναμενόμενη κατάσταση λογαριασμού θα είναι 5.000 $ - 128 $ = 4.875 $. Ωστόσο, λόγω της συνθήκης του αγώνα, το τελικό υπόλοιπο μπορεί να ποικίλλει μεταξύ .875 και .999.

Οι προγραμματιστές είναι ένα από τα πιο σημαντικά στοιχεία της ασφάλειας

Σε ένα έργο λογισμικού, ως προγραμματιστής, έχετε αρκετές ευθύνες. Το παραπάνω παράδειγμα ήταν για μια απλή εφαρμογή μεταφοράς χρημάτων. Φανταστείτε να εργάζεστε σε ένα έργο λογισμικού που διαχειρίζεται έναν τραπεζικό λογαριασμό ή το backend ενός μεγάλου ιστότοπου ηλεκτρονικού εμπορίου.

Πρέπει να είστε εξοικειωμένοι με τέτοια τρωτά σημεία, ώστε το πρόγραμμα που έχετε γράψει για την προστασία τους να είναι απαλλαγμένο από τρωτά σημεία. Αυτό απαιτεί ισχυρή ευθύνη.

Η ευπάθεια μιας συνθήκης φυλής είναι μόνο μία από αυτές. Ανεξάρτητα από την τεχνολογία που χρησιμοποιείτε, πρέπει να προσέχετε για τρωτά σημεία στον κώδικα που γράφετε. Μία από τις πιο σημαντικές δεξιότητες που μπορείτε να αποκτήσετε ως προγραμματιστής είναι η εξοικείωση με την ασφάλεια λογισμικού.