I lettori come te aiutano a sostenere MUO. Quando effettui un acquisto utilizzando i link sul nostro sito, potremmo guadagnare una commissione di affiliazione.
Una condizione di competizione si verifica quando due operazioni devono essere eseguite in un ordine specifico, ma possono essere eseguite nell'ordine opposto.
Ad esempio, in un'applicazione multithread, due thread separati potrebbero accedere a una variabile comune. Di conseguenza, se un thread modifica il valore della variabile, l'altro può ancora utilizzare la versione precedente, ignorando il valore più recente. Ciò causerà risultati indesiderati.
Per comprendere meglio questo modello, sarebbe bene esaminare da vicino il processo di commutazione del processore.
Come un processore cambia processo
Sistemi operativi moderni può eseguire più di un processo contemporaneamente, chiamato multitasking. Quando si guarda a questo processo in termini di Ciclo di esecuzione della CPU, potresti scoprire che il multitasking in realtà non esiste.
Invece, i processori passano costantemente da un processo all'altro per eseguirli contemporaneamente o almeno agiscono come se lo stessero facendo. La CPU può interrompere un processo prima che sia completato e riprendere un processo diverso. Il sistema operativo controlla la gestione di questi processi.
Ad esempio, l'algoritmo Round Robin, uno dei più semplici algoritmi di commutazione, funziona come segue:
In genere, questo algoritmo consente a ciascun processo di essere eseguito per periodi di tempo molto piccoli, come determinato dal sistema operativo. Ad esempio, questo potrebbe essere un periodo di due microsecondi.
La CPU prende ogni processo a turno ed esegue comandi che verranno eseguiti per due microsecondi. Quindi passa al processo successivo, indipendentemente dal fatto che quello attuale sia terminato o meno. Pertanto, dal punto di vista di un utente finale, più di un processo sembra essere in esecuzione contemporaneamente. Tuttavia, quando guardi dietro le quinte, la CPU sta ancora facendo le cose in ordine.
A proposito, come mostra il diagramma sopra, l'algoritmo Round Robin manca di nozioni di priorità di ottimizzazione o elaborazione. Di conseguenza, è un metodo piuttosto rudimentale che viene usato raramente nei sistemi reali.
Ora, per capire meglio tutto questo, immagina che siano in esecuzione due thread. Se i thread accedono a una variabile comune, potrebbe verificarsi una race condition.
Un'applicazione Web di esempio e una condizione di competizione
Dai un'occhiata alla semplice app Flask qui sotto per riflettere su un esempio concreto di tutto ciò che hai letto finora. Lo scopo di questa applicazione è gestire le transazioni di denaro che avverranno sul web. Salva quanto segue in un file denominato denaro.py:
da borraccia importare Borraccia
da flask.ext.sqlalchemy importare SQLAlchimiaapp = Boccetta (__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy (applicazione)classeAccount(db. Modello):
id = db. Colonna (db. Numero intero, primary_key = VERO)
importo = db. Colonna (db. Corda(80), unico = VERO)def__dentro__(se stesso, contare):
self.importo = importodef__repr__(se stesso):
ritorno '' % auto.importo@app.percorso("/")
defCIAO():
conto = Conto.query.get(1) # C'è solo un portafoglio.
ritorno "Denaro totale = {}".format (account.amount)@app.route("/send/")
defInviare(quantità):
conto = Conto.query.get(1)Se int (account.importo) < importo:
ritorno "Equilibrio insufficiente. Ripristina denaro con /reset!)"account.amount = int (account.amount) - importo
db.sessione.commit()
ritorno "Importo inviato = {}".format (importo)@app.route("/reset")
defRipristina():
conto = Conto.query.get(1)
conto.importo = 5000
db.sessione.commit()
ritorno "Ripristino del denaro".
Se __name__ == "__main__":
app.secret_key = 'heLLoTHisIsSeCReTKey!'
app.run()
Per eseguire questo codice, dovrai creare un record nella tabella degli account e continuare le transazioni su questo record. Come puoi vedere nel codice, questo è un ambiente di test, quindi effettua transazioni rispetto al primo record nella tabella.
da soldi importare db
db.create_all()
da soldi importare Account
conto = Conto (5000)
db.sessione.aggiungere(account)
db.sessione.commettere()
Ora hai creato un account con un saldo di $ 5.000. Infine, esegui il codice sorgente sopra usando il seguente comando, a condizione che tu abbia installato i pacchetti Flask e Flask-SQLAlchemy:
pitonesoldi.py
Quindi hai l'applicazione web Flask che esegue un semplice processo di estrazione. Questa applicazione può eseguire le seguenti operazioni con i collegamenti di richiesta GET. Poiché Flask viene eseguito sulla porta 5000 per impostazione predefinita, l'indirizzo a cui si accede è 127.0.0.1:5000/. L'app fornisce i seguenti endpoint:
- 127.0.0.1:5000/ visualizza il saldo corrente.
- 127.0.0.1:5000/invio/{importo} sottrae importo dal conto.
- 127.0.0.1:5000/reset reimposta l'account a $ 5.000.
Ora, in questa fase, puoi esaminare come si verifica la vulnerabilità della race condition.
Probabilità di una vulnerabilità race condition
L'applicazione Web di cui sopra contiene una possibile vulnerabilità race condition.
Immagina di avere $ 5.000 per iniziare e di creare due diverse richieste HTTP che invieranno $ 1. Per questo, puoi inviare due diverse richieste HTTP al link 127.0.0.1:5000/invio/1. Supponi che, non appena il server web elabora la prima richiesta, la CPU interrompe questo processo ed elabora la seconda richiesta. Ad esempio, il primo processo potrebbe interrompersi dopo aver eseguito la seguente riga di codice:
conto.importo = int(account.amount) - importo
Questo codice ha calcolato un nuovo totale ma non ha ancora salvato il record nel database. Quando inizia la seconda richiesta, eseguirà lo stesso calcolo, sottraendo $ 1 dal valore nel database, $ 5.000, e memorizzando il risultato. Quando il primo processo riprende, memorizzerà il proprio valore, $ 4.999, che non rifletterà il saldo del conto più recente.
Quindi, due richieste sono state completate e ciascuna dovrebbe aver sottratto $ 1 dal saldo del conto, ottenendo un nuovo saldo di $ 4.998. Tuttavia, a seconda dell'ordine in cui il server Web li elabora, il saldo finale del conto può essere di $ 4.999.
Immagina di inviare 128 richieste per effettuare un trasferimento di $ 1 al sistema di destinazione in un lasso di tempo di cinque secondi. Come risultato di questa transazione, l'estratto conto previsto sarà di $ 5.000 - $ 128 = $ 4.875. Tuttavia, a causa delle condizioni di gara, il saldo finale potrebbe variare tra $ 4.875 e $ 4.999.
I programmatori sono una delle componenti più importanti della sicurezza
In un progetto software, come programmatore, hai parecchie responsabilità. L'esempio sopra riguarda una semplice applicazione di trasferimento di denaro. Immagina di lavorare su un progetto software che gestisce un conto bancario o il backend di un grande sito di e-commerce.
Devi avere familiarità con tali vulnerabilità in modo che il programma che hai scritto per proteggerle sia esente da vulnerabilità. Ciò richiede una forte responsabilità.
Una vulnerabilità race condition è solo una di queste. Indipendentemente dalla tecnologia che utilizzi, devi fare attenzione alle vulnerabilità nel codice che scrivi. Una delle abilità più importanti che puoi acquisire come programmatore è la familiarità con la sicurezza del software.