Leitores como você ajudam a apoiar o MUO. Quando você faz uma compra usando links em nosso site, podemos ganhar uma comissão de afiliado.
Uma condição de corrida ocorre quando duas operações devem ocorrer em uma ordem específica, mas podem ser executadas na ordem oposta.
Por exemplo, em um aplicativo multithread, dois threads separados podem acessar uma variável comum. Como resultado, se um thread alterar o valor da variável, o outro ainda poderá usar a versão mais antiga, ignorando o valor mais recente. Isso causará resultados indesejáveis.
Para entender melhor esse modelo, seria bom examinar de perto o processo de comutação do processador.
Como um processador alterna processos
Sistemas operacionais modernos pode executar mais de um processo simultaneamente, chamado multitarefa. Quando você olha para este processo em termos de Ciclo de execução da CPU, você pode descobrir que a multitarefa realmente não existe.
Em vez disso, os processadores estão constantemente alternando entre os processos para executá-los simultaneamente ou, pelo menos, agir como se estivessem fazendo isso. A CPU pode interromper um processo antes que ele seja concluído e retomar um processo diferente. O sistema operacional controla o gerenciamento desses processos.
Por exemplo, o algoritmo Round Robin, um dos algoritmos de comutação mais simples, funciona da seguinte maneira:
Geralmente, esse algoritmo permite que cada processo seja executado por períodos muito pequenos de tempo, conforme determinado pelo sistema operacional. Por exemplo, pode ser um período de dois microssegundos.
A CPU pega cada processo por vez e executa comandos que serão executados por dois microssegundos. Em seguida, continua para o próximo processo, independentemente de o atual ter terminado ou não. Assim, do ponto de vista do usuário final, mais de um processo parece estar sendo executado simultaneamente. No entanto, quando você olha nos bastidores, a CPU ainda está fazendo as coisas em ordem.
A propósito, como mostra o diagrama acima, o algoritmo Round Robin carece de qualquer noção de otimização ou prioridade de processamento. Como resultado, é um método bastante rudimentar que raramente é usado em sistemas reais.
Agora, para entender melhor tudo isso, imagine que duas threads estão rodando. Se os threads acessarem uma variável comum, pode surgir uma condição de corrida.
Um exemplo de aplicativo da Web e condição de corrida
Confira o aplicativo Flask simples abaixo para refletir sobre um exemplo concreto de tudo o que você leu até agora. O objetivo deste aplicativo é gerenciar transações de dinheiro que ocorrerão na web. Salve o seguinte em um arquivo chamado dinheiro.py:
de frasco importar Frasco
de flask.ext.sqlalchemy importar SQLAlquimiaapp = Frasco (__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy (aplicativo)aulaConta(db. Modelo):
id = db. Coluna (db. Número inteiro, chave_primária = Verdadeiro)
quantidade = db. Coluna (db. Corda(80), único = Verdadeiro)def__iniciar__(auto, contagem):
auto.quantia = quantidadedef__repr__(auto):
retornar '' % self.mount@app.route("/")
defoi():
conta = Account.query.get(1) # Existe apenas uma carteira.
retornar "Total Money = {}".format (account.amount)@app.route("/enviar/")
defenviar(quantia):
conta = Account.query.get(1)se int (quantia.conta) < quantia:
retornar "Saldo insuficiente. Redefinir dinheiro com /reset!)"valor da conta = int (valor da conta) - valor
db.session.commit()
retornar "Valor enviado = {}".formato (valor)@app.route("/reset")
defreiniciar():
conta = Account.query.get(1)
conta.valor = 5000
db.session.commit()
retornar "Redefinição de dinheiro."
se __name__ == "__main__":
app.secret_key = 'heLLoTHisIsSeCReTKey!'
app.run()
Para executar este código, você precisará criar um registro na tabela de contas e continuar as transações neste registro. Como você pode ver no código, este é um ambiente de teste, então ele faz transações no primeiro registro da tabela.
de dinheiro importar banco de dados
banco de dados.create_all()
de dinheiro importar Conta
conta = Conta (5000)
banco de dados.sessão.adicionar(conta)
banco de dados.sessão.comprometer-se()
Agora você criou uma conta com um saldo de $ 5.000. Por fim, execute o código-fonte acima usando o seguinte comando, desde que você tenha os pacotes Flask e Flask-SQLAlchemy instalados:
Pitãodinheiro.py
Então você tem o aplicativo web Flask que faz um processo de extração simples. Este aplicativo pode executar as seguintes operações com links de solicitação GET. Como o Flask é executado na porta 5000 por padrão, o endereço no qual você o acessa é 127.0.0.1:5000/. O aplicativo fornece os seguintes pontos de extremidade:
- 127.0.0.1:5000/ exibe o saldo atual.
- 127.0.0.1:5000/enviar/{valor} subtrai o valor da conta.
- 127.0.0.1:5000/reset redefine a conta para $ 5.000.
Agora, neste estágio, você pode examinar como ocorre a vulnerabilidade da condição de corrida.
Probabilidade de uma vulnerabilidade de condição de corrida
O aplicativo da Web acima contém uma possível vulnerabilidade de condição de corrida.
Imagine que você tenha $ 5.000 para começar e crie duas solicitações HTTP diferentes que enviarão $ 1. Para isso, você pode enviar duas requisições HTTP diferentes para o link 127.0.0.1:5000/enviar/1. Suponha que, assim que o servidor web processa a primeira solicitação, a CPU interrompe esse processo e processa a segunda solicitação. Por exemplo, o primeiro processo pode parar depois de executar a seguinte linha de código:
conta.valor = int(valor da conta) - valor
Este código calculou um novo total, mas ainda não salvou o registro no banco de dados. Quando a segunda solicitação começar, ela fará o mesmo cálculo, subtraindo $ 1 do valor no banco de dados - $ 5.000 - e armazenando o resultado. Quando o primeiro processo for retomado, ele armazenará seu próprio valor - $ 4.999 - que não refletirá o saldo da conta mais recente.
Portanto, duas solicitações foram concluídas e cada uma deveria ter subtraído US$ 1 do saldo da conta, resultando em um novo saldo de US$ 4.998. Mas, dependendo da ordem em que o servidor web os processa, o saldo final da conta pode ser de US$ 4.999.
Imagine que você envie 128 solicitações para fazer uma transferência de US$ 1 para o sistema de destino em um período de cinco segundos. Como resultado dessa transação, o extrato de conta esperado será de $ 5.000 - $ 128 = $ 4.875. No entanto, devido às condições da corrida, o saldo final pode variar entre US$ 4.875 e US$ 4.999.
Os programadores são um dos componentes mais importantes da segurança
Em um projeto de software, como programador, você tem algumas responsabilidades. O exemplo acima foi para um aplicativo simples de transferência de dinheiro. Imagine trabalhar em um projeto de software que gerencia uma conta bancária ou o back-end de um grande site de comércio eletrônico.
Você deve estar familiarizado com essas vulnerabilidades para que o programa que você escreveu para protegê-las esteja livre de vulnerabilidades. Isso requer uma forte responsabilidade.
Uma vulnerabilidade de condição de corrida é apenas uma delas. Não importa a tecnologia que você usa, você precisa ficar atento às vulnerabilidades no código que escreve. Uma das habilidades mais importantes que você pode adquirir como programador é a familiaridade com a segurança do software.