- Castro/
- blog/
- Quando arquivos de override redefinem funções silenciosamente: bug difícil de encontrar em toolkits Shell/
Quando arquivos de override redefinem funções silenciosamente: bug difícil de encontrar em toolkits Shell
Como a ordem de source pode redefinir silenciosamente funções Bash, fazer menus perderem itens e criar bugs que só aparecem no plantão.
Em Bash, redefinir uma função não gera warning.
Se dois arquivos carregados pelo shell possuem:
| |
a última definição carregada vence.
Isso parece óbvio quando o exemplo tem três linhas. Em um toolkit operacional dividido em vários módulos e overrides, pode virar um bug difícil de perceber.
Em uma auditoria real do meu toolkit de operação, encontrei funções críticas definidas várias vezes em arquivos diferentes. O caso mais visível era um menu de plantão com versões contendo quantidades diferentes de itens.
Se a ordem de carregamento mudasse, o shell continuaria abrindo normalmente — só que o operador receberia uma versão incompleta do menu.
Nenhum crash. Nenhum erro de sintaxe. Apenas comportamento diferente.
O Bash aceita a redefinição sem reclamar #
Considere:
| |
Ao executar:
| |
o resultado é:
| |
Para verificar a definição efetiva:
| |
O problema aparece quando as duas definições estão em arquivos separados:
| |
Agora a função efetiva depende da ordem de source.
A auditoria encontrou duplicação real #
No toolkit analisado, várias funções tinham mais de uma definição.
Entre elas estavam funções equivalentes a:
| |
Algumas apareciam duas vezes. Outras, três ou mais.
O caso mais crítico era o menu principal de plantão: foram encontradas versões com 5, 6 e 7 itens.
Isso significava que uma mudança aparentemente inocente na ordem de carregamento poderia retirar funcionalidades do menu sem produzir qualquer erro técnico.
O último carregado vira a verdade #
Uma forma simples de reproduzir:
10-base.sh #
| |
90-override.sh #
| |
Se .bashrc fizer:
| |
o menu final terá dois itens.
Inverter a ordem muda o resultado.
Isso é exatamente o tipo de bug que pode passar por revisão porque cada arquivo isoladamente parece válido.
Descobrir a definição ativa #
Para função já carregada:
| |
ou:
| |
Também é útil listar todas as ocorrências no código:
| |
Para procurar várias funções:
| |
O objetivo não é apenas saber qual código está rodando, mas também quantas definições concorrentes existem.
Ordem de carregamento precisa ser explícita #
Depois da auditoria, o toolkit foi organizado com prefixos numéricos.
Conceitualmente:
| |
Isso transforma uma ordem implícita em contrato visível.
O zz- final continua sendo override, mas agora sua função é deliberada.
Se uma função existir ali, fica claro que ela pretende ter a última palavra.
Melhor ainda: reduzir overrides #
Ordenar os arquivos resolve previsibilidade, mas não elimina dívida técnica.
Se uma função aparece em quatro lugares, qualquer manutenção continua arriscada.
O melhor desenho tende a ser:
| |
em vez de:
| |
Overrides devem ser exceção, não mecanismo principal de versionamento.
Outro bug parecido: guards com nomes diferentes #
A mesma auditoria encontrou duas versões de um banner usando variáveis de guarda distintas.
Conceitualmente:
| |
em um arquivo, e:
| |
em outro.
As duas funções tentavam impedir execução duplicada, mas os guards não compartilhavam estado.
Resultado possível: o banner aparecer duas vezes dependendo da combinação de funções carregadas.
A correção é definir uma única variável de contrato.
source também pode ter efeitos colaterais #
Outro achado da auditoria era código que criava/sobrescrevia arquivo no nível principal de um script carregado pelo shell.
Algo assim:
| |
Se o arquivo é source-ado toda vez que abre o terminal, essa escrita acontece novamente a cada shell.
Isso é perigoso porque:
- sobrescreve edição manual;
- adiciona efeito colateral ao login;
- torna debugging mais difícil;
- mistura carregamento de funções com instalação.
A solução é mover para uma função explícita:
| |
E chamá-la somente no fluxo de instalação/upgrade.
Como auditar um toolkit modular #
Eu faria pelo menos quatro verificações.
1. Funções duplicadas #
Uma aproximação:
| |
Depois investigar as ocorrências reais com grep -Rn.
2. Ordem de source #
| |
3. Definição efetiva #
| |
4. Efeitos colaterais no top-level #
Procurar comandos de escrita, instalação ou chamadas executadas fora de funções.
Um teste simples para impedir regressão #
Depois de modularizar, vale criar um validador que carregue o toolkit e confirme a presença das funções esperadas.
Exemplo:
| |
Um passo além é calcular hash ou comparar a origem esperada das funções críticas.
O que a fonte deste artigo comprova #
A auditoria técnica recuperada registra:
- múltiplas definições reais de funções críticas;
- menu de plantão com versões contendo 5, 6 e 7 opções;
- última definição carregada como comportamento efetivo;
- guards inconsistentes para banner;
- side-effect de geração de arquivo durante
source; - refatoração posterior para uma ordem de módulos numerados e override final explícito.
Os nomes de clientes, hosts, FQDNs, IPs e demais detalhes de infraestrutura encontrados na auditoria não são necessários para explicar o problema e ficaram fora deste artigo.
Checklist #
| |
O principal aprendizado #
Bash permite que a versão errada de uma função rode perfeitamente.
Por isso esse tipo de defeito é tão traiçoeiro: o shell não quebra; a sua operação muda silenciosamente.
Em toolkits usados durante incidente, ordem de carregamento e unicidade das funções precisam ser tratadas como parte da arquitetura, não como detalhe do .bashrc.
Me chama.
Manda o contexto, os sintomas e o que já foi testado. Bora organizar as evidências antes de sair mexendo.