Não importa se é o melhor brinquedo do parquinho ou a melhor vaga para estacionar no Supermercado. Nossa vida é uma eterna disputa por recursos. E na Ciência da Computação não é muito diferente.
No artigo sobre o Jantar dos Filósofos, eu expliquei como acontece a disputa de processos que desejam usar recursos do Computador que são impossíveis de serem utilizados ao mesmo tempo (a impressora, por exemplo).
Neste artigo de agora, irei demonstrar como acontecem as vulnerabilidades de Condições de Corrida que envolvem recursos que podem ser utilizados por vários processos ao mesmo tempo.
Introdução
Condições de corrida ocorrem quando há dois ou mais processos disputando um recurso compartilhado e a ordem das ações nessa disputa não é controlada corretamente. Gerando diversos problemas de sincronização.
Para ilustrar, irei usar um exemplo de Transferência Bancária puramente didático e abstrair os diversos detalhes técnicos envolvidos em uma operação real.
Imagine que Alice e Bob possuem uma conta conjunta com o saldo de R$ 100,00 e pretendem transferir, cada um, R$ 50,00 para o seu filho João. Em um cenário perfeito e sincronizado, a operação acontece nas seguintes etapas:
Etapa A – Alice aperta o botão transferir e o seu banco recupera o saldo da Conta (R$100,00) para saber se ela está apta.
Etapa B – Logo após, o Banco de Alice envia o crédito de R$ 50,00 para o banco de João.
Etapa C – O Banco do João confirma que recebeu os R$ 50,00.
Etapa D – O Banco da Alice atualiza saldo da Conta Conjunta (R$ 50,00).
O mesmo fluxo acontece com Bob logo depois:
Etapa A – Bob aperta o botão transferir e o seu banco recupera o saldo da Conta Conjunta (R$50,00) para saber se ele está apto a transferir.
Etapa B – Logo depois o banco transfere R$ 50,00 para o Banco de João
Etapa C – O Banco do João confirma que recebeu mais R$50,00.
Etapa D – O Banco do Bob atualiza o Saldo da Conta que ele possui com Alice (R$ 0,00).
O Problema
Todos nós sabemos que a vida real é bem mais caótica e pode acontecer um cenário de Alice e Bob apertarem o botão de transferir ao mesmo tempo.
Dessa forma, as operações de Leitura dos dados (recuperação de saldo) serão realizadas de forma paralela. Ou seja, ao mesmo tempo. E ambas vão recuperar um saldo de R$100,00 na conta compartilhada entre os dois.
Só que a operação de retorno, é de escrita (atualização de saldo após a confirmação) e, como ela não é possível de ser feita de forma simultânea, temos a nossa infeliz condição de corrida.
Neste caso específico, independente de qual for a confirmação vencedora (de Alice ou de Bob) o resultado final será desastroso para o banco, pois a conta conjunta dos dois irá terminar com R$50,00, ao invés de R$0,00.
Mitigação do Problema
O bloqueio de certos recursos, para que eles temporariamente se tornem exclusivos, elimina o risco de condições de corrida comprometerem todo o sistema. Esta trava pode ser feita utilizando-se de dois recursos clássicos do mundo dos Sistemas Distribuídos: Regiões de Exclusão Mútua ou Semáforos.
Irei exemplificar apenas os semáforos, mas os conceitos são bastante parecidos.
Como o próprio nome diz, um semáforo irá sinalizar aos envolvidos que alguém está utilizando exclusivamente, um recurso que antes era disponível a todos. No caso do nosso exemplo, o saldo da conta será lido apenas por Bob ou por Alice. E só estará disponível novamente quando toda a operação de transferência for realizada.
Assim que a transferência de Bob é concluída, ele é bloqueado para acessar o saldo da Conta Conjunta e o Semáforo libera Alice para ela fazer a sua operação.
Após a conclusão da transferência de Alice ser concluída, o saldo está disponível para os dois novamente. E desta vez vai estar correto.
Um triste exemplo de Condição de Corrida no Mundo Real
Apesar de ser um conceito bastante antigo (em 1954 David A. Huffman o já abordava em sua tese de Doutorado sobre Circuitos Lógicos) e de não ser uma exclusividade do mundo da Programação Concorrente (pode ocorrer em Redes de Comunicação e Bancos de Dados) a vulnerabilidade de Condição de Corrida já foi responsável por grandes prejuízos financeiros e até por perdas de vidas humanas ao longo da história.
No início dos anos 80, a Atomic Energy of Canada Limited (AECL) um Laboratório Canadense de Energia Nuclear lançou no mercado uma nova versão de sua máquina de tratamento de radioterapia: o Therac-25. Esse novo dispositivo possuía atualizações significativas em relação aos antigos Therac-6 e Therac-20.
Basicamente, o Therac-25 tinha 3 Modos de operação que podiam ser alterados pelo operador: “Modo Raio-x”, “Modo Elétron” e “Modo Raio de Luz”. No Modo Raio-X, um magneto posicionava um filtro entre o paciente e o feixe de elétrons irradiado pelo aparelho, nos outros modos esse filtro não era necessário.
Na teoria, essa troca de estados (Feixe com Filtro e sem Filtro) funcionava de forma fluida e perfeitamente sincronizada.
Mas na prática, uma Condição de Corrida intermitente, gerava um atraso mortal de 8 segundos na movimentação do magneto. E infelizmente, uma dose de radiação sem filtros era bombardeada ao paciente durante as sessões.
Após diversas denúncias e uma investigação infelizmente tardia, foram registrados 6 graves incidentes provocados pelo Therac-25 sendo 4 mortes diretamente vinculadas a este problema no Software.
É importante ressaltar que essa tragédia aconteceu há 40 anos atrás e seria praticamente impossível de acontecer novamente. Pois os diversos erros de Engenharia de Software na construção desse aparelho tão crítico (apenas um programador estava envolvido na construção do código, troca de travamentos físicos por travamentos de Software, alertas confusos e com diversos falsos-positivos…) jamais seriam aceitos pelas Empresas Homologadoras de Aparelhos de Radioterapia de hoje.
Conclusão
Atualmente, as falhas de condição de corrida estão entre as 25 vulnerabilidades de Software mais perigosas, mas por não serem simples de se reproduzir, são objeto de estudo de pesquisadores de Segurança Cibernética mais experientes e em ambientes controlados.
Nos raros casos em que conseguem ser reproduzidas artificialmente, garantem prestígio e um bom retorno financeiro para os envolvidos. Como exemplo, o Grupo Francês Synacktiv’s, que utilizou uma variante de condição de Corrida chamada TOCTOU (Time- -To-Check to Time-To-Use) para hackear completamente um veículo elétrico da marca Tesla em um Bug Bounty promovido pela Marca.
Caso tenha interesse em saber mais detalhes sobre o assunto, seguem abaixo os links de Biografia complementar.
Um abraço e obrigado pela leitura.
https://en.wikipedia.org/wiki/Race_condition