O Therac-25 foi um acelerador linear de radioterapia desenvolvido pela Atomic Energy of Canada Limited (AECL) e lançado em 1982. Entre 1985 e 1987, pelo menos seis pacientes receberam overdoses massivas de radiação, resultando em três mortes confirmadas e seqüelas permanentes nos sobreviventes.
Ao contrário de seus predecessores (Therac-6 e Therac-20), que possuíam interlocks de hardware independentes do software, o Therac-25 delegou toda a segurança ao software — escrito em assembly para o PDP-11 e reaproveitado das versões anteriores sem auditoria completa de segurança.
O caso é considerado o estudo de caso mais citado da história da segurança de software e da Interação Humano-Computador. As falhas não eram apenas técnicas: envolviam design de interface deficiente, cultura organizacional de minimização de riscos e normalização do risco pelos operadores ao longo de meses de uso.
A investigação de Leveson & Turner (1993) identificou que os bugs existiam nos sistemas anteriores mas eram inofensivos porque o hardware os mascarava. A decisão de remover os interlocks físicos para reduzir custo foi o ponto de inflexão.
O Therac-25 usava um disco óptico rotativo
para posicionar fisicamente o colimador entre o modo Elétron e o modo Raio-X.
A rotação levava aproximadamente 8 segundos.
Durante esse intervalo, o terminal VT100 continuava aceitando entradas normalmente —
inclusive o pressionamento de ↑ para editar campos já confirmados.
Quando o operador usava ↑ para corrigir o modo após o armamento,
o display atualizava imediatamente,
mas o disco continuava rotacionando para a posição original (já travada em hardware).
Resultado: display mostrava modo E (Elétron) enquanto o hardware disparava
feixe X (Raio-X) a 25 MeV — sem o filtro de tungstênio que converte
o feixe de alta energia em raios-X terapêuticos.
A variável de controle do modo era compartilhada entre a rotina de display e a rotina de controle do hardware, sem sincronização. A atualização via cursor-up chegava ao display antes de ser processada pelo subsistema de hardware.
MALFUNCTION 54.
Uma variável de 1 byte (Class3) era usada como
flag booleana para indicar se a verificação de posição do turntable
havia sido realizada. O código usava x = x + 1
para sinalizar "verificado" em vez de x = 1.
A cada 256ª execução, o incremento causava overflow de um byte (255 + 1 = 0). O sistema interpretava o valor 0 como "não verificado" e pulava silenciosamente a verificação de posição do colimador. O feixe de 25 MeV disparava com o turntable em posição incorreta (modo campo-luz).
O pior aspecto: após o disparo irregular, o monitor de unidades exibia 0 UM — zero monitor units. O operador interpretava isso como "nada aconteceu", repetia o tratamento, e o paciente recebia nova dose letal.
Na simulação, o intervalo é 1/8 (em vez de 1/256) para que o fenômeno seja observável em uma sessão pedagógica.
No Therac-25 real, uma única variável compartilhada era usada tanto para analisar valores de entrada do teclado quanto para rastrear a posição do turntable. Quando o operador navegava rapidamente entre campos, a rotina de tratamento de teclado e a rotina de controle de hardware podiam ler e escrever nessa variável simultaneamente — sem nenhum mecanismo de exclusão mútua.
O resultado era um estado inconsistente: os parâmetros exibidos no display não correspondiam ao que havia sido enviado ao hardware de controle. O sistema aceitava a configuração como válida e armava a máquina com parâmetros desconhecidos.
O comando EXECUTAR (tecla B) era registrado pelo sistema
independentemente do estado atual da máquina. Com latência de processamento
ativa, o operador podia pressionar B enquanto o sistema ainda
estava em VALIDANDO — antes que a transição para ARMADO fosse concluída.
O sistema recebia um comando válido (B) em um estado inválido
para esse comando. Em vez de ignorar silenciosamente ou aguardar,
o sistema entrava em FAULT.
B durante estados que não sejam ARMADO. O Therac-25 não fazia isso —
o terminal VT100 aceitava qualquer tecla em qualquer momento,
delegando toda a lógica de controle ao software do PDP-11.
Esta falha é acionada quando o sistema tenta fazer uma transição de estado não prevista na máquina de estados finitos (ex.: de IDLE diretamente para ARMED). No Therac-25 real, inconsistências de estado resultavam de corrupção de memória, race conditions e reentrada em rotinas não reentrantes.
Os bugs do Therac-25 não existiam no vácuo: eram consequências diretas de decisões de arquitetura de hardware e software típicas dos anos 1970–1980, muitas das quais eram consideradas práticas normais à época.
O software foi escrito em assembly PDP-11, operando diretamente sobre
registradores e memória. Sem tipos de dados, sem verificação de overflow
em tempo de compilação, sem abstrações de segurança. O bug
x = x + 1 era
sintaticamente válido e invisível ao montador — responsabilidade total do programador.
O PDP-11 rodava um único loop principal de polling. Não havia preempção de processos, nem primitivas de sincronização (mutex, semáforo, monitor). Variáveis compartilhadas entre a rotina de interrupção de teclado e o loop de controle de hardware eram a norma da época — e a fonte direta das race conditions.
O VT100 era um terminal passivo: exibia o que o host enviava, aceitava qualquer tecla e a transmitia ao PDP-11 sem validação local. Não havia conceito de "campo bloqueado", "estado de espera" ou feedback de processamento. Se o software não bloqueava, o terminal aceitava — e o software raramente bloqueava.
O Therac-20 possuía circuitos de hardware independentes que verificavam a posição do colimador antes de liberar o feixe — independentemente do software. O Therac-25 removeu esses circuitos para reduzir custo e complexidade, delegando 100% da responsabilidade de segurança ao software. Essa única decisão arquitetural transformou bugs latentes em falhas letais.
O código foi reaproveitado do Therac-20. Bugs que eram inofensivos no Therac-20 (mascarados pelos interlocks de hardware) tornaram-se letais no Therac-25. A premissa de que "o código já foi testado no produto anterior" impediu a reavaliação de segurança no novo contexto de operação.
Relatórios de acidente anteriores ao caso Tyler foram descartados pela AECL como "erro do operador". Não havia processo formal de análise de falhas de campo nem canal de comunicação entre unidades hospitalares afetadas. Quando o software foi apontado como causa, a AECL resistiu — a reputação do sistema era parte do argumento de defesa perante reguladores.
Analisados à luz das heurísticas de Nielsen (1994) e de princípios modernos de design de sistemas críticos:
O operador nunca sabia o estado real do hardware — apenas o que o display exibia. Com race conditions e latência, o display era sistematicamente atrasado ou incorreto. O estado físico do disco óptico (rotacionando vs. posicionado) era completamente invisível na interface. (Heurística de Nielsen #1)
O mismatch entre parâmetros do display (PRESCRITO) e o estado físico (CONFIRMADO) não era visível no terminal original. O operador via um único valor — que podia não corresponder ao estado real do hardware. A simulação TELERX-25 torna esse mismatch explícito com a coluna CONFIRMADO em âmbar. (Heurística de Nielsen #2)
A tecla [P] PROSSEGUIR era apresentada como a principal saída de qualquer MALFUNCTION, mesmo quando prosseguir era a ação mais perigosa possível. A interface não oferecia "saída segura" clara — o [R] RESET era apresentado como opção secundária, menos prominente. A simulação reproduz isso: [P] aparece em âmbar (primário), [R] em verde-escuro (secundário). (Heurística de Nielsen #3)
Nenhum mecanismo impedia o operador de: pressionar [B] fora do estado ARMADO; editar campos após armamento; prosseguir após múltiplas MALFUNCTIONs consecutivas do mesmo tipo. O design assumia que o operador sempre tomaria a decisão correta, sem oferece qualquer barreira ou confirmação adicional. (Heurística de Nielsen #5)
Operadores precisavam memorizar o significado de cada código de MALFUNCTION. "MALFUNCTION 54" não diz o que aconteceu, o que significa nem o que fazer. A documentação de referência não estava integrada ao terminal — estava em manuais físicos separados, inacessíveis durante a operação. (Heurística de Nielsen #6)
As mensagens de erro violavam sistematicamente os três requisitos: não descreviam o problema em linguagem clara, não indicavam a causa e não sugeriam uma solução construtiva. "MALFUNCTION XX" era o único feedback disponível para erros potencialmente letais. (Heurística de Nielsen #9)
Além das heurísticas de usabilidade, o caso ilustra o conceito sociológico de normalização do risco: quando desvios de comportamento esperado ocorrem repetidamente sem consequências aparentes, os operadores passam a tratá-los como normais. MALFUNCTIONs eram tão frequentes que os operadores desenvolveram reflexo de pressionar [P] — e a interface reforçava esse reflexo ao apresentar [P] como resposta padrão.
Por razões pedagógicas ou de escopo, alguns comportamentos do Therac-25 real não estão presentes na simulação:
Na 8ª execução acumulada, o sistema não vai para FAULT: exibe "SESSÃO FINALIZADA" normalmente, mas a coluna CONFIRMADO das UNIDADES MON. passa a mostrar 0 enquanto PRESCRITO mantém o valor prescrito (ex.: 200). O mismatch em âmbar indica que "0 UM foram confirmados ao hardware". O operador interpreta isso como nenhuma dose administrada e tende a repetir — exatamente o comportamento observado no acidente de Yakima. O Painel Instrutor exibe a nota pedagógica com o MALFUNCTION 5 real, invisível ao operador.
Um quarto bug descrito em algumas análises: divisão pela variável de potência do feixe poderia causar divisão por zero, levando o sistema a configurar a potência máxima silenciosamente. Este bug não está implementado na simulação atual.
O overflow real ocorria a cada 256 execuções — semanas de operação normal, tornando a falha estatisticamente rara e difícil de associar causalmente ao software durante a investigação inicial. Na simulação, o intervalo é 1/8 para que o fenômeno seja observável em uma sessão pedagógica de 30 minutos.
No terminal VT100 original não havia tela de confirmação com resumo dos parâmetros antes de executar. O operador confiava em memória de curto prazo. A simulação oferece as colunas PRESCRITO/CONFIRMADO como melhoria didática — o terminal real não as tinha.