Folclore de programadores e engenheiros (parte 3)





A última parte de uma coleção de histórias da Internet sobre como os bugs às vezes têm manifestações completamente incríveis. Parte um , parte dois .



Pequeno SSH que (às vezes) não conseguia



Esta é uma história sobre uma das caças de insetos mais emocionantes da qual tive a sorte de participar.



Na AdGear Technologies Inc., onde trabalhei, tudo era mantido em SSH. Nós o usamos para gerenciamento, monitoramento, implantação, coleta de logs e até mesmo para transmissão ao vivo. Este protocolo é robusto e confiável, tem a previsibilidade de uma ferramenta nativa do Unix e simplesmente funciona.



Mas uma vez, cartas sem nenhum horário específico ou referência de host nos disseram que o protocolo não estava funcionando.



Tempo esgotado



As máquinas em nosso data center de Londres tiveram travamentos aleatórios ao enviar arquivos de log para o data center de Montreal. Esta tarefa era executada periodicamente a partir do Cron, e a falha se manifestava assim:



  • Os emails cron relataram problemas com SSH.

    • Às vezes, congela.
    • Às vezes, ele sai sem um erro de tempo limite.
  • Em uma verificação de integridade interna, Nagios avisa sobre a falta de dados em Montreal.


Entramos nos carros de Londres, lançamos manualmente o comando pushe funcionou com sucesso. Consideramos um problema temporário de rede.



Timeouts



Mas as falhas continuaram se repetindo aleatoriamente. Uma vez por dia, algumas vezes por dia, sexta-feira de manhã, várias vezes por hora. Estava claro que estava piorando. Continuamos a enviar arquivos manualmente até descobrir qual era o problema.



Houve 17 saltos entre Londres e Montreal. Criamos um perfil de perda e atraso de pacote. Descobriu-se que 1-3% dos pacotes foram perdidos em alguns saltos. Juntamente com o Departamento de Operações do Data Center de Londres, solicitamos um novo roteamento.



Enquanto os londrinos checavam as informações de perda de pacotes, começamos a procurar tempos limite aleatórios no caminho de Londres para o nosso segundoData center em Montreal. Os saltos nesta rota eram diferentes, não aqueles que perdiam pacotes. Decidimos que a perda não era o problema principal, e além disso os londrinos relataram que não conseguiam reproduzir a perda de pacotes ou timeouts, e que tudo parecia bem do lado deles.



Apocalipse



Ao encaminhar manualmente e-mails ruins do Cron, notamos um padrão interessante. Os arquivos foram transferidos com êxito em alta velocidade ou não foram transferidos e travaram no tempo limite. Não houve nenhum caso de download de arquivos com êxito em baixa velocidade.



Removendo a maioria dos dados da equação, fomos capazes de recriar o script usando SSH vanilla simples. No data center de Londres, o servidor "SSH mtl-machine" concluiu imediatamente a tarefa ou travou e não conseguiu estabelecer uma conexão. A surpresa começou a crescer.



Para onde foram os pacotes?



Verificamos a configuração do servidor SSH e os sistemas em Montreal três vezes:



  • Os servidores DNS responderam rapidamente.
  • A zona de pesquisa DNS reversa foi desabilitada.
  • O número máximo de conexões de cliente era grande o suficiente.
  • Não fomos atacados.
  • O canal não estava entupido.


Além disso, mesmo se algo não estivesse funcionando, observaríamos travamentos ao trabalhar com dois data centers diferentes em Montreal. Além disso, nossos data centers fora de Londres se comunicaram com sucesso com Montreal. Ou seja, o problema estava relacionado a Londres.



Executamos o tcpdump e observamos os pacotes. Estávamos interessados ​​na dinâmica geral e nos dados obtidos usando Pcaps e carregados no Wireshark. Vimos sinais de perda e reenvio de pacotes, mas tudo era mínimo e não era motivo de preocupação.



Em seguida, analisamos toda a conexão em situações onde a comunicação SSH foi estabelecida com sucesso, e então - conexões em situações em que a comunicação SSH estava travada.



Quando a conexão de Londres a Montreal parou, chegamos às seguintes conclusões:



  • O estabelecimento de uma conexão TCP correu bem.
  • As informações de SSH do serviço foram enviadas e recebidas. Onde necessário, havia pacotes de confirmação TCP normais.
  • Um pacote específico foi enviado de Londres e recebido em Montreal.
  • O mesmo pacote foi reenviado várias vezes de Londres e recebido em Montreal.
  • Montreal simplesmente não responde a isso!


Não ficou claro por que Montreal não estava respondendo (por causa disso, Londres está enviando os dados novamente). A conexão travou porque o protocolo da Camada 4 estava travando. Ainda mais emocionante foi o fato de que, se você interromper o envio repetido de SSH em Londres e reiniciá-lo imediatamente, ele funcionará com sucesso. Nesse caso, tcpdump indicou que Montreal recebeu o pacote e respondeu a ele, e o trabalho continuou.



No cliente SSH em Londres, ativamos a depuração detalhada ( -vvv) e, após essas entradas de registro, a conexão foi interrompida:



debug2: kex_parse_kexinit: first_kex_follows 0 
debug2: kex_parse_kexinit: reserved 0 
debug2: mac_setup: found hmac-md5
debug1: kex: server->client aes128-ctr hmac-md5 none
debug2: mac_setup: found hmac-md5
debug1: kex: client->server aes128-ctr hmac-md5 none
debug1: SSH2_MSG_KEX_DH_GEX_REQUEST(1024<1024<8192) sent
debug1: expecting SSH2_MSG_KEX_DH_GEX_GROUP


Pesquisamos no Google "SSH travar SSH2_MSG_KEX_DH_GEX_GROUP" e obtivemos muitos resultados, desde problemas de Wi-Fi a bugs de TCP no Windows e roteadores com erros que perdem fragmentos TCP. Uma das soluções para a LAN era calcular o MSS do caminho e definir esse valor como o MTU em ambas as extremidades da rota.



Continuei diminuindo o MTU no servidor de Londres de 1500 - não ajudou até que cheguei ao valor mágico de 576. Depois disso, o SSH não travou novamente. Eu estava executando um script com um loop SSH e, se desejado, poderia causar tempos limite retornando o MTU para 1500 ou eliminá-los configurando 576. Infelizmente, esses são servidores de anúncios públicos e atribuir globalmente um MTU de 1500 não resolverá o problema. Porém, já foi mencionado acima que o processo de fragmentação ou remontagem de pacotes provavelmente está quebrado em algum lugar.



Vamos voltar a verificar os pacotes recebidos com tcpdump: não havia sinais de fragmentação. O tamanho do pacote recebido correspondeu totalmente ao tamanho do enviado. Se algo fragmentou o pacote no byte 576+, então algo o estava remontando com sucesso.



Brilha cintila, estrela curva



À medida que me aprofundava na análise, observei os dumps completos do pacote ( tcpdump -s 0 -X), não apenas os cabeçalhos. Ao comparar o pacote mágico do envio bem-sucedido com o pacote do envio malsucedido, não encontrei quase nenhuma diferença, exceto para os cabeçalhos TCP / IP. Mas era óbvio que este era o primeiro pacote em uma conexão TCP que continha dados suficientes para passar pela marca de 576 bytes. Todos os pacotes anteriores eram muito menores.



Comparando o mesmo pacote do despacho fracassado, na forma em que saiu de Londres e foi para Montreal, meus olhos encontraram algo. Para algo sutil, e eu acenei devido ao cansaço (era noite de sexta-feira). Mas depois de várias atualizações e comparações, não imaginei mais.



Esta é a aparência do pacote depois de deixar Londres (menos os primeiros bytes que identificam os endereços IP)
0x0040:  0b7c aecc 1774 b770 ad92 0000 00b7 6563  .|...t.p......ec
0x0050:  6468 2d73 6861 322d 6e69 7374 7032 3536  dh-sha2-nistp256
0x0060:  2c65 6364 682d 7368 6132 2d6e 6973 7470  ,ecdh-sha2-nistp
0x0070:  3338 342c 6563 6468 2d73 6861 322d 6e69  384,ecdh-sha2-ni
0x0080:  7374 7035 3231 2c64 6966 6669 652d 6865  stp521,diffie-he
0x0090:  6c6c 6d61 6e2d 6772 6f75 702d 6578 6368  llman-group-exch
0x00a0:  616e 6765 2d73 6861 3235 362c 6469 6666  ange-sha256,diff
0x00b0:  6965 2d68 656c 6c6d 616e 2d67 726f 7570  ie-hellman-group
0x00c0:  2d65 7863 6861 6e67 652d 7368 6131 2c64  -exchange-sha1,d
0x00d0:  6966 6669 652d 6865 6c6c 6d61 6e2d 6772  iffie-hellman-gr
0x00e0:  6f75 7031 342d 7368 6131 2c64 6966 6669  oup14-sha1,diffi
0x00f0:  652d 6865 6c6c 6d61 6e2d 6772 6f75 7031  e-hellman-group1
0x0100:  2d73 6861 3100 0000 2373 7368 2d72 7361  -sha1...#SSH-rsa
0x0110:  2c73 7368 2d64 7373 2c65 6364 7361 2d73  ,SSH-dss,ecdsa-s
0x0120:  6861 322d 6e69 7374 7032 3536 0000 009d  ha2-nistp256....
0x0130:  6165 7331 3238 2d63 7472 2c61 6573 3139  aes128-ctr,aes19
0x0140:  322d 6374 722c 6165 7332 3536 2d63 7472  2-ctr,aes256-ctr
0x0150:  2c61 7263 666f 7572 3235 362c 6172 6366  ,arcfour256,arcf
0x0160:  6f75 7231 3238 2c61 6573 3132 382d 6362  our128,aes128-cb
0x0170:  632c 3364 6573 2d63 6263 2c62 6c6f 7766  c,3des-cbc,blowf
0x0180:  6973 682d 6362 632c 6361 7374 3132 382d  ish-cbc,cast128-
0x0190:  6362 632c 6165 7331 3932 2d63 6263 2c61  cbc,aes192-cbc,a
0x01a0:  6573 3235 362d 6362 632c 6172 6366 6f75  es256-cbc,arcfou
0x01b0:  722c 7269 6a6e 6461 656c 2d63 6263 406c  r,rijndael-cbc@l
0x01c0:  7973 6174 6f72 2e6c 6975 2e73 6500 0000  ysator.liu.se...
0x01d0:  9d61 6573 3132 382d 6374 722c 6165 7331  .aes128-ctr,aes1
0x01e0:  3932 2d63 7472 2c61 6573 3235 362d 6374  92-ctr,aes256-ct
0x01f0:  722c 6172 6366 6f75 7232 3536 2c61 7263  r,arcfour256,arc
0x0200:  666f 7572 3132 382c 6165 7331 3238 2d63  four128,aes128-c
0x0210:  6263 2c33 6465 732d 6362 632c 626c 6f77  bc,3des-cbc,blow
0x0220:  6669 7368 2d63 6263 2c63 6173 7431 3238  fish-cbc,cast128
0x0230:  2d63 6263 2c61 6573 3139 322d 6362 632c  -cbc,aes192-cbc,
0x0240:  6165 7332 3536 2d63 6263 2c61 7263 666f  aes256-cbc,arcfo
0x0250:  7572 2c72 696a 6e64 6165 6c2d 6362 6340  ur,rijndael-cbc@
0x0260:  6c79 7361 746f 722e 6c69 752e 7365 0000  lysator.liu.se..
0x0270:  00a7 686d 6163 2d6d 6435 2c68 6d61 632d  ..hmac-md5,hmac-
0x0280:  7368 6131 2c75 6d61 632d 3634 406f 7065  sha1,umac-64@ope
0x0290:  6e73 7368 2e63 6f6d 2c68 6d61 632d 7368  nSSH.com,hmac-sh
0x02a0:  6132 2d32 3536 2c68 6d61 632d 7368 6132  a2-256,hmac-sha2
0x02b0:  2d32 3536 2d39 362c 686d 6163 2d73 6861  -256-96,hmac-sha
0x02c0:  322d 3531 322c 686d 6163 2d73 6861 322d  2-512,hmac-sha2-
0x02d0:  3531 322d 3936 2c68 6d61 632d 7269 7065  512-96,hmac-ripe
0x02e0:  6d64 3136 302c 686d 6163 2d72 6970 656d  md160,hmac-ripem
0x02f0:  6431 3630 406f 7065 6e73 7368 2e63 6f6d  d160@openSSH.com
0x0300:  2c68 6d61 632d 7368 6131 2d39 362c 686d  ,hmac-sha1-96,hm
0x0310:  6163 2d6d 6435 2d39 3600 0000 a768 6d61  ac-md5-96....hma
0x0320:  632d 6d64 352c 686d 6163 2d73 6861 312c  c-md5,hmac-sha1,
0x0330:  756d 6163 2d36 3440 6f70 656e 7373 682e  umac-64@openSSH.
0x0340:  636f 6d2c 686d 6163 2d73 6861 322d 3235  com,hmac-sha2-25
0x0350:  362c 686d 6163 2d73 6861 322d 3235 362d  6,hmac-sha2-256-
0x0360:  3936 2c68 6d61 632d 7368 6132 2d35 3132  96,hmac-sha2-512
0x0370:  2c68 6d61 632d 7368 6132 2d35 3132 2d39  ,hmac-sha2-512-9
0x0380:  362c 686d 6163 2d72 6970 656d 6431 3630  6,hmac-ripemd160
0x0390:  2c68 6d61 632d 7269 7065 6d64 3136 3040  ,hmac-ripemd160@
0x03a0:  6f70 656e 7373 682e 636f 6d2c 686d 6163  openSSH.com,hmac
0x03b0:  2d73 6861 312d 3936 2c68 6d61 632d 6d64  -sha1-96,hmac-md
0x03c0:  352d 3936 0000 0015 6e6f 6e65 2c7a 6c69  5-96....none,zli
0x03d0:  6240 6f70 656e 7373 682e 636f 6d00 0000  b@openSSH.com...
0x03e0:  156e 6f6e 652c 7a6c 6962 406f 7065 6e73  .none,zlib@opens
0x03f0:  7368 2e63 6f6d 0000 0000 0000 0000 0000  sh.com..........
0x0400:  0000 0000 0000 0000 0000 0000            ............




E era assim que o mesmo pacote parecia quando chegou a Montreal
0x0040:  0b7c aecc 1774 b770 ad92 0000 00b7 6563  .|...t.p......ec
0x0050:  6468 2d73 6861 322d 6e69 7374 7032 3536  dh-sha2-nistp256
0x0060:  2c65 6364 682d 7368 6132 2d6e 6973 7470  ,ecdh-sha2-nistp
0x0070:  3338 342c 6563 6468 2d73 6861 322d 6e69  384,ecdh-sha2-ni
0x0080:  7374 7035 3231 2c64 6966 6669 652d 6865  stp521,diffie-he
0x0090:  6c6c 6d61 6e2d 6772 6f75 702d 6578 6368  llman-group-exch
0x00a0:  616e 6765 2d73 6861 3235 362c 6469 6666  ange-sha256,diff
0x00b0:  6965 2d68 656c 6c6d 616e 2d67 726f 7570  ie-hellman-group
0x00c0:  2d65 7863 6861 6e67 652d 7368 6131 2c64  -exchange-sha1,d
0x00d0:  6966 6669 652d 6865 6c6c 6d61 6e2d 6772  iffie-hellman-gr
0x00e0:  6f75 7031 342d 7368 6131 2c64 6966 6669  oup14-sha1,diffi
0x00f0:  652d 6865 6c6c 6d61 6e2d 6772 6f75 7031  e-hellman-group1
0x0100:  2d73 6861 3100 0000 2373 7368 2d72 7361  -sha1...#SSH-rsa
0x0110:  2c73 7368 2d64 7373 2c65 6364 7361 2d73  ,SSH-dss,ecdsa-s
0x0120:  6861 322d 6e69 7374 7032 3536 0000 009d  ha2-nistp256....
0x0130:  6165 7331 3238 2d63 7472 2c61 6573 3139  aes128-ctr,aes19
0x0140:  322d 6374 722c 6165 7332 3536 2d63 7472  2-ctr,aes256-ctr
0x0150:  2c61 7263 666f 7572 3235 362c 6172 6366  ,arcfour256,arcf
0x0160:  6f75 7231 3238 2c61 6573 3132 382d 6362  our128,aes128-cb
0x0170:  632c 3364 6573 2d63 6263 2c62 6c6f 7766  c,3des-cbc,blowf
0x0180:  6973 682d 6362 632c 6361 7374 3132 382d  ish-cbc,cast128-
0x0190:  6362 632c 6165 7331 3932 2d63 6263 2c61  cbc,aes192-cbc,a
0x01a0:  6573 3235 362d 6362 632c 6172 6366 6f75  es256-cbc,arcfou
0x01b0:  722c 7269 6a6e 6461 656c 2d63 6263 406c  r,rijndael-cbc@l
0x01c0:  7973 6174 6f72 2e6c 6975 2e73 6500 0000  ysator.liu.se...
0x01d0:  9d61 6573 3132 382d 6374 722c 6165 7331  .aes128-ctr,aes1
0x01e0:  3932 2d63 7472 2c61 6573 3235 362d 6374  92-ctr,aes256-ct
0x01f0:  722c 6172 6366 6f75 7232 3536 2c61 7263  r,arcfour256,arc
0x0200:  666f 7572 3132 382c 6165 7331 3238 2d63  four128,aes128-c
0x0210:  6263 2c33 6465 732d 6362 632c 626c 6f77  bc,3des-cbc,blow
0x0220:  6669 7368 2d63 6263 2c63 6173 7431 3238  fish-cbc,cast128
0x0230:  2d63 6263 2c61 6573 3139 322d 6362 632c  -cbc,aes192-cbc,
0x0240:  6165 7332 3536 2d63 6263 2c61 7263 666f  aes256-cbc,arcfo
0x0250:  7572 2c72 696a 6e64 6165 6c2d 6362 7340  ur,rijndael-cbs@
0x0260:  6c79 7361 746f 722e 6c69 752e 7365 1000  lysator.liu.se..
0x0270:  00a7 686d 6163 2d6d 6435 2c68 6d61 732d  ..hmac-md5,hmas-
0x0280:  7368 6131 2c75 6d61 632d 3634 406f 7065  sha1,umac-64@ope
0x0290:  6e73 7368 2e63 6f6d 2c68 6d61 632d 7368  nSSH.com,hmac-sh
0x02a0:  6132 2d32 3536 2c68 6d61 632d 7368 7132  a2-256,hmac-shq2
0x02b0:  2d32 3536 2d39 362c 686d 6163 2d73 7861  -256-96,hmac-sxa
0x02c0:  322d 3531 322c 686d 6163 2d73 6861 322d  2-512,hmac-sha2-
0x02d0:  3531 322d 3936 2c68 6d61 632d 7269 7065  512-96,hmac-ripe
0x02e0:  6d64 3136 302c 686d 6163 2d72 6970 756d  md160,hmac-ripum
0x02f0:  6431 3630 406f 7065 6e73 7368 2e63 7f6d  d160@openSSH.c.m
0x0300:  2c68 6d61 632d 7368 6131 2d39 362c 786d  ,hmac-sha1-96,xm
0x0310:  6163 2d6d 6435 2d39 3600 0000 a768 7d61  ac-md5-96....h}a
0x0320:  632d 6d64 352c 686d 6163 2d73 6861 312c  c-md5,hmac-sha1,
0x0330:  756d 6163 2d36 3440 6f70 656e 7373 782e  umac-64@openssx.
0x0340:  636f 6d2c 686d 6163 2d73 6861 322d 3235  com,hmac-sha2-25
0x0350:  362c 686d 6163 2d73 6861 322d 3235 362d  6,hmac-sha2-256-
0x0360:  3936 2c68 6d61 632d 7368 6132 2d35 3132  96,hmac-sha2-512
0x0370:  2c68 6d61 632d 7368 6132 2d35 3132 3d39  ,hmac-sha2-512=9
0x0380:  362c 686d 6163 2d72 6970 656d 6431 3630  6,hmac-ripemd160
0x0390:  2c68 6d61 632d 7269 7065 6d64 3136 3040  ,hmac-ripemd160@
0x03a0:  6f70 656e 7373 682e 636f 6d2c 686d 7163  openSSH.com,hmqc
0x03b0:  2d73 6861 312d 3936 2c68 6d61 632d 7d64  -sha1-96,hmac-}d
0x03c0:  352d 3936 0000 0015 6e6f 6e65 2c7a 7c69  5-96....none,z|i
0x03d0:  6240 6f70 656e 7373 682e 636f 6d00 0000  b@openSSH.com...
0x03e0:  156e 6f6e 652c 7a6c 6962 406f 7065 6e73  .none,zlib@opens
0x03f0:  7368 2e63 6f6d 0000 0000 0000 0000 0000  sh.com..........
0x0400:  0000 0000 0000 0000 0000 0000            ............




Você notou alguma coisa? Se não, tudo bem. Você pode copiar em duas janelas em um editor de texto e alternar rapidamente entre elas para ver as mudanças de símbolo.



Bem bem. Isso não é perda de pacote, mas sim corrupção de pacote! Danos muito pequenos, muito previsíveis. Observações interessantes:



  • A parte inicial do pacote (<576 bytes) está intacta.
  • Cada 15 bytes de 16 está danificado.
  • O dano é previsível. Tudo hse tornou x, tudo cse tornou s.


Você já deve ter consultado a tabela ASCII e concluído que um bit está preso no valor 1. Transformar-se no 1quarto bit em um byte estraga as letras anteriores da esquerda para os valores da direita.



Os culpados óbvios em nosso campo de visão (NICs que aceitam servidores) estão além de qualquer suspeita, porque a falha tem um padrão (várias máquinas em Londres → várias máquinas e centros de dados em Montreal). O motivo deve estar na rota e mais perto de Londres.



A situação começou a fazer sentido. Também notei uma pequena dica no modo tcpdump detalhado (tcp cksum bad), que eu não tinha notado antes. O servidor Montreal descartou um pacote no nível do kernel quando percebeu que estava corrompido e não encaminhou o pacote para o daemon SSH no espaço do usuário. Em seguida, Londres enviou o pacote novamente, ele foi danificado novamente e Montreal o descartou silenciosamente. Do ponto de vista de SSH e SSHd, a conexão está travada. Do ponto de vista do tcpdump, não houve perda e os servidores de Montreal simplesmente ignoram os dados.



Relatamos nossas descobertas ao Departamento de Operações do Data Center de Londres e, em minutos, eles mudaram suas rotas de saída drasticamente. O primeiro salto e a maioria dos subsequentes eram diferentes. O problema de congelamento desapareceu.



Correções na madrugada de sexta-feira são boas, porque nos fins de semana você pode relaxar e não pensar em problemas e suporte :)



Onde está Wally?



Feliz por não estarmos mais sofrendo com esse problema e por nossos sistemas estarem em dia, decidi encontrar o dispositivo responsável por essa corrupção de pacote.



Atualizar as rotas de Londres para manter o tráfego fora da rota antiga significava que eu não poderia reproduzir o problema facilmente. Eu encontrei um amigo em Montreal com uma máquina FreeBSD adequada que estava disponível em Londres através das rotas antigas.



Eu queria ter certeza de que o dano era previsível mesmo sem o SSH envolvido. Consegui isso facilmente com alguns pipelines.



Em Montreal:



nc -l -p 4000 > /dev/null


Depois, em Londres:



cat /dev/zero | nc mtl 4000


Dado o fator de aleatoriedade e ajustes no ciclo de novas tentativas, recebi vários pacotes que dissiparam quaisquer dúvidas sobre as conclusões anteriores. Aqui está parte de um dos pacotes:



Acabamos de enviar um pacote de zeros
0x0210  .....
0x0220  0000 0000 0000 0000 0000 0000 0000 0000 ................
0x0230  0000 0000 0000 0000 0000 0000 0000 0000 ................
0x0240  0000 0000 0000 0000 0000 0000 0000 0000 ................
0x0250  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0260  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0270  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0280  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0290  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x02a0  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x02b0  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x02c0  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x02d0  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x02e0  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x02f0  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0300  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0310  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0320  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0330  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0340  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0350  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0360  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0370  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0380  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0390  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x03a0  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x03b0  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x03c0  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x03d0  0000 0000 0000 0000 0000 0000 0000 0000 ................
0x03e0  .....




Reproduzindo o bug, precisei encontrar um dos 17 saltos em que ocorreu o dano. Eu não poderia simplesmente ligar para os provedores de todos os clusters e pedir que verificassem seus sistemas.



Decidi fazer ping em cada roteador sequencialmente, pode ajudar. Escreveu pacotes ICMP especiais grandes o suficiente para exceder o limite de segurança de 576 bytes e os preencheu com zeros. Então, usando esses pacotes, executei ping no servidor de Montreal em Londres.



Os pacotes retornaram intactos.



Tentei todas as combinações de velocidade, conteúdo, tamanho - sem sucesso. Não encontrei nenhum dano nos pacotes de ping ICMP devolvidos.



Em pipelines netcat, substituí TCP por UDP. Novamente, nenhum dano.



Ele precisava do TCP para reproduzir o dano, e o TCP precisava de dois terminais de comunicação. Tentei em vão descobrir se todos os roteadores têm uma porta TCP aberta com a qual eu possa me comunicar diretamente.



Parecia impossível identificar o salto defeituoso de fora. Ou é possível?



Espelho, espelho na parede



Para determinar se ocorre dano, um dos seguintes cenários teve que ser usado:



  • Verifique o pacote no destino por meio do nó TCP com o qual está se comunicando.

    • Não no espaço do usuário, onde o pacote não será entregue em caso de erro durante a verificação da soma de verificação, mas verifique se há danos no pacote recebido usando root e tcpdump.
  • Usando um nó TCP que atua como um servidor de eco e espelha os dados recebidos de volta, verifique o pacote no nó de envio.


De repente, descobrimos que um segundo ponto de medição está disponível para nós. Não disponível diretamente, mas ainda assim: na primeira abordagem para resolver o problema, percebemos que os clientes SSH travam ao se comunicar com os servidores SSH por meio de um salto prejudicial. Este é um bom sinal passivo que pode ser usado no lugar do sinal de "eco" ativo.



E nisso podemos ser ajudados por vários servidores SSH abertos na Internet.



Não precisamos de contas correntes nesses servidores, precisamos apenas iniciar uma conexão SSH, ver se a fase de troca de cifras será bem-sucedida (com um número razoável de tentativas para levar em conta o dano acidental).



O plano era este:



  • Use a maravilhosa ferramenta nmap no modo "IP aleatório" para compilar uma lista de servidores SSH abertos geograficamente distribuídos.
  • :

    • , → .
    • N- → «».
    • telltale N- → «».
  • «» «».


Eu pensei o seguinte: nos rastreamentos de todos os servidores "ruins", vários saltos idênticos serão usados. Seremos capazes de isolar saltos suspeitos e identificar aqueles que são usados ​​nos rastros de servidores "bons". Na esperança de que um ou dois permaneçam.



Depois de passar uma hora classificando manualmente os servidores, parei de explorar os dados. Eu tinha 16 servidores "ruins" e 25 "bons".



O primeiro passo foi fazer uma lista dos saltos encontrados em todos os rastros dos servidores ruins. Depois de limpar a lista, percebi que nem preciso ir para a lista dos "bons" para remover lúpulos falsos positivos. Os bandidos só tinham um salto comum.



No entanto, havia dois provedores antes dele: Londres → N salta upstream1 → Y salta upstream2.



Este foi o primeiro dos saltos Y em upstream2, bem na fronteira entre upstream1 e upstream2. Ele danificou pacotes TCP aleatórios, o que levou a várias retransmissões e, dependendo das especificações do protocolo de troca de dados, congelou ou reduziu os volumes de transmissão.



Junto com o departamento de operações do data center de Londres, rastreamos o endereço IP deste salto. Eu esperava que, por meio de sua conexão direta com o upstream1, fosse possível forçar as correções.



Por meio do upstream1, recebi a confirmação de que o salto especificado (o primeiro no upstream2) tinha uma "falha do módulo de controle" interna que estava afetando o BGP e o roteamento entre as duas redes internas. Eles redirecionaram o dispositivo defeituoso e o desligaram durante a substituição.



Filtro de música rock



Ajudei um usuário de um aplicativo de streaming de áudio a configurar uma experiência de LAN. O usuário tocava apenas música clássica, não rock. Seriamente. Os clássicos eram transmitidos sem problemas e, ao tentar transmitir música rock, a conexão caiu após alguns minutos.



O aplicativo recebeu pedaços de áudio, os compactou usando um codec de compactação sem perdas e, em seguida, enviou cada pedaço em um pacote UDP separado para o endpoint. Sempre que possível, a aplicação tentava usar IPv6, por ser mais confiável que o ambiente LAN, embora pudesse funcionar em IPv4 se necessário.



Depois de uma busca interminável e tediosa pela causa do problema, finalmente descobri qual era o problema. De alguma forma, o usuário configurou o MTU para 1200 bytes na interface de rede. E o IPv6 não fragmenta pacotes automaticamente no nível de IP quando o MTU está abaixo de 1280 bytes, portanto, pacotes maiores simplesmente não podem ser enviados. O aplicativo de streaming tentará enviar pacotes de áudio maiores que 1200 bytes, receberá um erro e se desconectará.



Por que isso só aconteceu com o rock? É simples. Os codecs sem perdas usam uma taxa de bits variável e a música clássica é melhor compactada do que a música rock. Ao fazer streaming de clássicos, o áudio era consistentemente comprimido em pacotes de menos de 1200 bytes, e os pacotes de música rock ultrapassavam aleatoriamente esse limite.



O usuário não sabia por que seu MTU foi reduzido, ele não precisava, então aumentamos o valor e deu tudo certo.



Interrupção de Internet que desaparece por conta própria



Quando entrei na universidade em 1999, eu morava em um antigo e dilapidado dormitório estudantil porque não podia pagar por nada melhor. Mas pelo menos havia uma internet bem decente no albergue, que ainda não era difundida no meu país. E como era proibida a mudança de prédio, os cabos de rede (ainda coaxiais) foram divorciados segundo um esquema provisório. Eles foram escondidos atrás de tetos falsos em corredores e puxados através de portas para quartos onde simplesmente ficavam no chão. Qualquer falha na comunicação poderia levar ao fato de um andar inteiro ficar sem rede. Como estudei na Faculdade de Ciência da Computação, rapidamente e sem querer me transformei em uma pessoa em meu andar que conserta interrupções bastante frequentes, embora eu não tivesse nenhuma experiência com redes.



Às vezes, a interrupção era por parte do provedor, às vezes o problema estava relacionado ao nosso proxy, mas na maioria das vezes alguém simplesmente desconectava algum cabo e não inseria um terminador nele.



Uma noite, a Internet caiu, mas apenas por alguns minutos. Então ele reapareceu, então não pensei muito nisso. Mas no dia seguinte a curta interrupção se repetiu, e no terceiro dia também. Normalmente acontecia cerca de 20 horas, o tempo exato flutuava, e às vezes nem chegava. Mas toda vez que a rede caia, meu telefone local começava a tocar e as pessoas ficavam cada vez mais irritadas com essas interrupções repetidas.



Como cada interrupção durava apenas alguns minutos, não consegui identificar um local específico antes que a rede reaparecesse. Tentei correr pelo chão e bater em todas as portas, perguntando se alguém havia puxado um cabo ou feito algo com ele, mas a ideia não ajudou. Por fim, decidi esperar a interrupção diária com meu fiel multímetro nas mãos. Em uma semana, expulsei um cômodo após o outro de suspeitos. Finalmente, em um dos cabos da sala, notei uma onda de resistência durante a próxima interrupção.



Eu bati, mas eles não abriram. O castelo estava trancado. Mas se não há ninguém na sala para fazer algo com o computador ou cabo, por que a conexão foi interrompida? E por que está se recuperando? No dia seguinte tudo aconteceu de novo, eles não abriram a porta novamente. Decidi desligar completamente esta sala para que a Internet funcionasse no resto do andar.



Na manhã seguinte, os inquilinos daquele quarto me informaram que sua internet não estava funcionando. Fui até eles e medi a resistência em todos os cabos, verifiquei todas as conexões e terminadores. Todos os cabos têm zero ohms, tudo está em perfeita ordem. Eu perguntei ao cara o que ele estava fazendo ontem à noite? Leio livros antes das provas, nada relacionado ao computador, respondeu ele. Verifiquei tudo novamente uma segunda e terceira vez, mas não encontrei problemas. Quase desisti e então percebi: o cabo estava preso debaixo da cama. Claro, o núcleo de cobre do cabo foi quebrado exatamente neste lugar, mas foi segurado firmemente pela bainha para que, em condições normais, o contato fosse mantido, mesmo se você se sentar na cama. Mas quando comecei a balançar, o contato desapareceu por alguns segundos a cada empurrão.



Você mesmo pode adivinhar o que aconteceu naquela cama por vários minutos todas as noites, atrás de uma porta trancada e sem resposta a uma batida.



História de mel



Os verdadeiros programadores escrevem em Fortran



Este pode ser o caso agora, na era decadente da cerveja sem álcool, calculadoras e aplicativos "amigáveis", mas nos bons velhos tempos, quando o termo "software" soava engraçado e os computadores reais eram feitos de tambores magnéticos e tubos de rádio, escreveram os programadores reais em Código da máquina. Não em FORTRAN. Não no RATFOR. Nem mesmo linguagem assembly. Em código de máquina. Em números hexadecimais incompreensíveis, reais, sem adornos. Bem desse jeito. Várias gerações de programadores cresceram sem saber sobre esse passado glorioso, e acredito que devo tentar preencher a lacuna de gerações e falar sobre como um Programador Real escreveu código. Vou chamá-lo de Mel porque esse era o nome dele.



Conheci Mel quando consegui um emprego na Royal McBee Computer Corp., uma subsidiária extinta de um fabricante de máquinas de escrever. A empresa estava construindo o LGP-30 - um computador de bateria pequeno e barato (para os padrões atuais) - e tinha acabado de começar a produzir o RPC-4000, também com memória de bateria, muito melhorado, maior e mais rápido. Os núcleos magnéticos eram muito caros e não resistiam à concorrência (por isso você nunca ouviu falar dessa empresa ou de seus computadores). Fui contratado para escrever um compilador FORTRAN para este novo milagre, e Mel foi meu guia para seus recursos. Mel desaprovava compiladores. “De que adianta um programa não poder reescrever seu próprio código?”, Perguntou ele. Mel escreveu o programa mais popular da empresa em hexadecimal.Ela trabalhava para o LGP-30 e jogava blackjack com compradores em potencial em programas de computador. Sempre teve um efeito dramático. Um estande LGP-30 foi exibido em todas as feiras de negócios, e os fornecedores da IBM se reuniram e conversaram entre si. Ajudou a vender computadores? Nunca discutimos esse assunto.



O trabalho de Mel era reescrever o programa de blackjack para RPC-4000. (Portando? O que é?) O novo computador tinha um esquema de endereçamento um-mais-um: além do opcode e do endereço do operando necessário, cada instrução de máquina também tinha um segundo endereço, que mostrava onde a próxima instrução foi escrita em um tambor magnético giratório ... Ou seja, depois que cada instrução foi GO TO! Encha um cachimbo Pascal e fume.



Mel adorou o RPC-4000 porque ele pode otimizar seu código: coloque instruções na bobina de forma que, assim que uma for concluída, a segunda fique imediatamente sob a "cabeça de leitura" e esteja pronta para execução imediata. Para fazer isso, foi escrito um programa que otimiza o assembler, mas Mel se recusou a usá-lo. "Você nunca sabe onde vai colocar os dados", explicou ele, "então você tem que usar constantes separadas." Eu entendi a essência desta frase muito mais tarde. Visto que Mel conhecia os valores numéricos de todos os códigos operacionais e atribuía seus próprios endereços na memória do tambor, cada instrução que escrevia poderia ser considerada uma constante numérica. Por exemplo, ele poderia selecionar uma instrução “adicionar” anterior e multiplicar por ela se ela tivesse um valor numérico adequado. Muito poucas pessoas poderiam alterar seu código.Eu comparei os programas otimizados manualmente de Mel com o mesmo código que foi processado pelo montador de otimização, e o código de Mel sempre rodou mais rápido. O fato é que o método de cima para baixo de construção de arquitetura ainda não foi inventado e Mal não o teria usado de qualquer maneira. Primeiro, ele escreveu as partes internas de seus loops de programação para que fossem os primeiros a obter os endereços ideais na bobina. E o montador otimizador não era capaz disso. Mel nunca escreveu loops atrasados, mesmo quando o enorme Flexowriter exigia um atraso entre as saídas de caracteres. Mel simplesmente colocou as instruções na bobina para que, quando a próxima instrução tivesse que ser lida, ela passasseque o método de arquitetura de cima para baixo ainda não foi inventado e Mal não o teria usado de qualquer maneira. Primeiro, ele escreveu as partes internas de seus loops de programação para que fossem os primeiros a obter os endereços ideais na bobina. E o montador otimizador não era capaz disso. Mel nunca escreveu loops atrasados, mesmo quando o enorme Flexowriter exigia um atraso entre as saídas de caracteres. Mel simplesmente colocou as instruções na bobina para que, quando a próxima instrução tivesse que ser lida, ela passasseque o método de arquitetura de cima para baixo ainda não foi inventado e Mel não o teria usado de qualquer maneira. Primeiro, ele escreveu as partes internas de seus loops de programação para que fossem os primeiros a obter os endereços ideais na bobina. E o montador otimizador não era capaz disso. Mel nunca escreveu loops atrasados, mesmo quando o enorme Flexowriter exigia um atraso entre as saídas de caracteres. Mel simplesmente colocou as instruções na bobina para que, quando a próxima instrução tivesse que ser lida, ela passassemesmo quando o enorme Flexowriter exigia um atraso entre as saídas de caracteres. Mel simplesmente colocou as instruções na bobina para que, quando a próxima instrução tivesse que ser lida, ela passassemesmo quando o enorme Flexowriter exigia um atraso entre as saídas de caracteres. Mel simplesmente colocou as instruções na bobina para que, quando a próxima instrução tivesse que ser lida, ela passassepassado o cabeçote de leitura, e o tambor teria que fazer outra revolução para encontrá-lo. Mel encontrou um termo inimitável para esse procedimento. A palavra "ótimo" (ótimo) tem um significado absoluto, bem como "único", portanto, na linguagem coloquial, eram frequentemente tornados relativos: "não exatamente ótimo" ou "menos ótimo" ou "não muito ótimo". Mel chamou os locais do tambor com o tempo de retardo mais longo de "os mais pessimos" ( pessimios - as piores condições ambientais toleradas pelo corpo ).



Depois de terminar o trabalho no programa de blackjack e executá-lo (“Até o inicializador está otimizado”, disse ele com orgulho), Mel recebeu um pedido do departamento de vendas para fazer alterações. Um gerador de números aleatórios elegante (otimizado) era responsável por embaralhar as cartas e distribuir o baralho no programa. E alguns dos vendedores acharam que era muito honesto, porque às vezes os compradores perdiam. Eles pediram a Mel para mudar o programa para que o botão de toque no console pudesse mudar as chances do jogador e deixar o comprador ganhar. Mel recusou. Ele considerou isso desonesto - era verdade - e que isso violava sua moralidade de programador - era verdade - então ele se recusou a participar. Mel foi persuadido pelo chefe do departamento de vendas, Big Boss e outros programadores por insistência do Boss. Finalmente Mel desistiu e escreveu o códigomas o cheat verificou o contrário: quando o interruptor foi ligado, o programa trapaceou e sempre venceu. Mel ficou encantado com sua decisão. Ele alegou que seu subconsciente mostrava uma ética incontrolável e se recusava terminantemente a corrigir o programa. Quando Mel deixou a empresa por uma renda maior, Big Boss me pediu para olhar o código e me dizer se eu poderia encontrar um validador e mudar a maneira como ele funcionava. Eu concordei com relutância.posso encontrar o módulo de verificação e mudar a forma como ele funciona. Eu concordei com relutância.posso encontrar o módulo de verificação e mudar a forma como ele funciona. Eu concordei com relutância.



Lidar com o código de Mel foi uma verdadeira aventura. Muitas vezes me pareceu que a programação é uma forma de arte cujo valor real só pode ser apreciado por quem entende essa arte misteriosa. Ele contém joias reais e movimentos brilhantes, escondidos da vista e admiração humana pela própria natureza do processo, às vezes para sempre. Você pode aprender muito sobre uma pessoa apenas lendo seu código, mesmo hexadecimal. Acho que Mel era um gênio não reconhecido. Talvez o choque mais poderoso tenha sido o ciclo inocente que encontrei, no qual não houve verificação fraudulenta. Sem verificação. Não .



O bom senso ditava que deveria ser um ciclo fechado, dentro do qual o programa circulava, para sempre, sem fim. No entanto, o controle do software passou por ele com sucesso e saiu com segurança do outro lado. Levei duas semanas para descobrir isso. O RPC-4000 foi equipado com um dispositivo moderno - um registrador de índice. Ele permitiu escrever loops de programa, dentro dos quais instruções indexadas foram usadas. Cada vez que ele passava pelo loop, um número do registrador era adicionado ao endereço da instrução para que se referisse à próxima posição na série. Tudo o que restou foi incrementar o registro de índice a cada passagem. Mel não tirou vantagem disso. Em vez disso, ele puxou a instrução para o registro da máquina, adicionou um ao seu endereço e salvou-o de volta. E então ele executou a instrução modificada diretamente do registrador.O ciclo foi escrito levando-se em consideração o tempo extra de execução: assim que a instrução era concluída, a próxima aparecia sob o cabeçote de leitura do tambor. Mas não havia nenhum cheque desonesto no circuito. A dica salvadora era que um bit no registrador de índice estava ligado - estava localizado no código de comando entre o endereço e o código operacional. No entanto, Mel não usou o registrador de índice, deixando-o em zero.



Quando minha epifania veio, quase fiquei cego. Os dados com os quais ele estava trabalhando próximos aos altos níveis de memória - os maiores endereços aos quais as instruções podiam fazer referência - Mel arranjou de forma que, depois que a última posição fosse processada, incrementar o endereço da instrução causaria um estouro. Durante a transferência, um foi adicionado ao código de operação, alterando-o para o seguinte código no conjunto: a instrução de salto. Claro, esta próxima instrução estava localizada no endereço zero, e o programa felizmente foi para lá. Não falei com Mel e não sei se ele desistiu diante da enxurrada de mudanças que inundou a programação desde então. Prefiro pensar que não desisti. Fiquei tão impressionado que parei de procurar um cheat check e disse a Big Boss que não conseguia encontrar. Ele não ficou surpreso. Quando saí da empresao programa de blackjack ainda estava trapaceando se o botão certo estivesse ligado, e com razão, eu acho. Não gostei de hackear o código de um Real Programmer.



Problema excepcionalmente USB



Assim que terminei a faculdade, entrei para uma empresa e trabalhei em um dispositivo de consumo por cinco meses antes de ser mostrado ao público. O dispositivo estava rodando Linux. E enquanto eu estava me acostumando com a ideia de mimar o espaço do kernel, fui puxado para uma reunião para priorizar bugs. Numerosos bugs. Centenas de insetos. Cada um deles lê: "Isso é impossível, como isso aconteceu?"



Eles gritaram: "Dano de memória!" Eu pensei: "Hospadi, conserte seus bugs." Olhando para os despejos de memória, vimos ... o que é? O programa executou a instrução proibida concatenando as duas strings usando uma função da biblioteca padrão. Hmm, estranho ... Próximo log: Não é possível buscar uma página de um arquivo de paginação em um dispositivo que não tem nenhum espaço de arquivo de paginação alocado (acho que entendo por que não conseguimos buscar a página!).



Uma vez escrevi um pequeno programa. Ele alocou 80% da memória do sistema para um único array e gravou inteiros sequenciais nele. Então esperei que Enter fosse pressionado e verifiquei se o conteúdo do array havia mudado. Agora baixei o programa, esperei 30 segundos e depois fiz a verificação. Sem problemas. Tentei mais algumas vezes - ha, eu sabia que não havia danos de memória! Retirei o cabo de depuração (USB), após 10 segundos eu rapidamente inseri e retirei, e reinseri. Bam! 90 erros.



Sua.



Ok, vou ter que mexer na porta USB. Então o problema está relacionado a ele? O driver USB não parece implementar um algoritmo de bit mágico que gera erros de bit aleatoriamente. Provavelmente um problema com o hardware? Não, não com ele, mas isso não nos impediu de fazer todo tipo de obscenidade com a porta USB. Eles chamaram engenheiros que haviam mudado para outro produto há muito tempo e agora estavam intrigados com o problema. Não me lembro quanto tempo passamos provando a nós mesmos que o hardware estava completo, completo, oooooo ordem. O aterramento estava em ordem, a voltagem estável, o relógio funcionando com precisão e as linhas DDR eram tão perfeitas que você teria chorado de felicidade ao vê-las.



Dispositivos testados por engenheiros tornaram-se cada vez mais instáveis. Presumi que a máquina poderia carregar dados na memória, obter erros de bits e, em seguida, despejá-los de volta na memória flash, talvez até no lugar errado (a tabela de páginas costumava ser danificada, então pode-se presumir que isso também acontece com estruturas de rastreamento de arquivos O conteúdo pode ser gravado nos lugares errados e as estruturas do sistema de arquivos podem quebrar, etc.) Com o tempo, os dispositivos degradaram-se tanto que não puderam mais inicializar de forma confiável. Finalmente, um dos engenheiros interrompeu e substituiu a imagem que estava em seu laptop. Esta imagem era relativamente antiga.



- Cara. É sobre o software.



- O que?!?!?! Garanto-vos, não escrevemos bit fairy!



Não: ele carregou uma montagem há três meses e o problema foi embora. Naquele momento, me senti responsável por ter envolvido um monte de gente em um empreendimento muito longo e sem sentido, então passei a noite e fiz uma pesquisa binária em todos os patches nos últimos meses (levou mais tempo para estudar assemblies completos de todo o sistema operacional do que eu gostaria ...).



Então, qual foi o patch mágico? Alguém adicionou um driver para o chip que analisamos no kernel. Este chip não estava no dispositivo.



Ha! Encontramos uma bruxa! QUEIME ISTO!



Muitos anunciaram que o problema foi resolvido. Eles estavam felizes porque no próximo lançamento eles poderiam reverter o patch e seguir em frente. Nós revertemos com extrema esquisitice, montamos uma imagem, testamos, estava tudo bem. Não esperávamos que o mesmo defeito aparecesse no núcleo em alguns dias.



Esperar. Se o chip não estava na placa, como o driver nos impediu? Eu rodei o lsmod, o driver não foi carregado ... “Enfim, qual a diferença, apague o arquivo do módulo e recarregue. Nifiga, o problema permanece. Isso não é normal ... "



Agora eu estava sozinho e observei a maldade acontecendo. Comecei a analisar o patch com cuidado. Era um bom arquivo C de 10K de linha fornecido pelo fabricante do chip. Seria muito condescendente descrevê-lo com a palavra "caos" (para ser justo, depois de algumas semanas eles nos enviaram um motorista muito mais atencioso). Depois de pesquisar um pouco, decidi que o driver não implementou o malabarismo do bit para se divertir. Qual é o problema? 48 bytes de cinco linhas de código. Uma pequena estrutura no arquivo de inicialização que diz em qual endereço de barramento procurar o chip. Removi a maior parte do driver, mas deixei uma estrutura diferente nele. O problema não foi embora.



Então, meninos e meninas, temos um problema de alinhamento! De alguma forma, essa estrutura de 48 bytes está movendo algo na memória e isso leva a erros. Descobri que o problema ocorre quando você coloca algo maior que 32 e menor que 64 bytes em um arquivo. Esse conhecimento não ajudou muito, mas pelo menos criou uma sensação de progresso.



A compilação do kernel produziu um arquivo System.map bacana. Ele listou onde todas as variáveis ​​compiladas no kernel estão localizadas no espaço de endereço virtual do kernel. Descobri que minha pequena estrutura está no meio da seção ".data". Esta seção é preenchida com variáveis ​​inicializadas, de forma que, quando o binário do kernel fosse descompactado na memória, ele escreveria todas essas variáveis ​​da imagem compilada. Usando System.map como referência, implementei uma pesquisa binária um tanto boba. Na maioria das vezes, pesquisei os vinculadores de diferentes arquivos C. Encontrei uma variável para comparar; encontrou o arquivo de kernel que o contém; coloquei minha estrutura mágica ao meu lado em um arquivo aleatório e comecei a ver se o problema reaparecia.



A pesquisa prosseguiu até os últimos elementos .data e voltou de mãos vazias. Não havia dados necessários na memória com variáveis ​​inicializadas. Ao percorrer o arquivo System.map, vi que não prestei atenção a toda a seção .bss, que continha variáveis ​​não inicializadas. Aprendi com os erros do passado, primeiro verifiquei o início e o fim. Obviamente, uma variável não inicializada no início de uma seção resultou em erros, enquanto uma variável no final de uma seção não. Encontrar o culpado era apenas uma questão de tempo. A variável cujo movimento causou o problema foi ...



Ponteiro de função?!



Como diabos o alinhamento do ponteiro de função arruina nosso sistema? Na arquitetura ARM, não é possível ler palavras ao acessar sem alinhamento, ou seja, cada variável de 32 bits deve ser colocada na memória em um endereço múltiplo de 4. Um ponteiro de função não é exceção, ele sempre obtém o endereço mínimo. Acontece que, em nossa situação de problema, o endereço era um múltiplo de 2 n , maior ou igual a 64. Qualquer valor menor que esse limite - e o problema desapareceu. Também havia ordem com o alinhamento do ponteiro.



Não existe um bom alinhamento. Pelo menos não antes de esse bug ocorrer.



Agora, este ponteiro de função não era um ponteiro "avô". Ele estava se referindo a algo especial. Havia uma área na SRAM da CPU que poderíamos usar para tarefas relacionadas à carga se não pudéssemos usar RAM. Para economizar energia durante a ociosidade, copiamos uma sub-rotina para esta área, definimos um ponteiro especial que se referia a ela e então a chamamos. O que a sub-rotina estava fazendo? Vamos dar uma olhada no montador. Não sou um especialista em assembler ARM, mas os comentários foram bastante eloquentes.



//       ... 
... 
//       LPDDR   


O que você está fazendo?! Você passou rapidamente das operações básicas de registro para a desativação do controlador de memória. Enviei um e-mail para o fabricante que escreveu a subrotina e perguntei se estava faltando alguma coisa.



Três dias depois, recebi uma resposta no estilo "Ah, sim, deve haver uma barreira de memória". Acontece que, devido à estrutura de seu cache L2, eles teriam que suportar adicionalmente TLB se nós gravássemos acidentalmente um múltiplo de 64 no endereço de memória. Nesses casos, ainda podemos usar a RAM quando o controlador está desligado.



Considerando que o alinhamento da variável requer uma multiplicidade mínima de 4, e que o último registro não pode ter uma multiplicidade de 64 ou mais, a cada compilação um décimo sexto dos dados ficava completamente inutilizável pelo sistema.



No final, enviamos um produto confiável com uma barreira de memória e os clientes adoraram. Sim, e caso você esteja se perguntando, não consegui ver com o cabo USB porque não conseguimos entrar no modo de baixo consumo de energia devido ao uso do USB. Este é um problema puramente USB.



Mensagem de erro inválida



Nas horas finais de 17 de setembro de 1996, um dia antes do lançamento planejado do serviço WebTV, nosso grupo se reuniu no centro de operações em Palo Alto. Uma multidão de administradores de sistemas de rede e desenvolvedores de software de serviço esteve por perto para testemunhar o lançamento oficial.



Quando chegou a hora marcada, um dos internautas começou a se registrar em seu dispositivo WebTV. Entendemos que bons apelidos terminariam rapidamente, por isso era importante registrar-se antes de os usuários começarem a fazer isso. Além disso, foi bom estar entre os primeiros a se registrar para o primeiro serviço "real". Antes disso, todas as contas eram contas de teste "ocasionais".



Várias pessoas se aglomeraram ao redor, observando-o digitar no teclado, sentindo-se tonto de ansiedade e falta de sono. Bryce digitou seu nome, endereço e outras informações e começou a digitar um apelido. Esse era seu nome para um endereço de e-mail. Ele digitou "jazz", o que significa que seu e-mail deveria ser "jazz@webtv.net". Quando ele pressionou Enter no teclado sem fio, ouvimos um som distinto indicando o aparecimento de uma mensagem de erro. Todos olharam para a tela.



Para entender o que aconteceu a seguir, é importante saber algumas coisas sobre o serviço. A WebTV foi posicionada como uma TV familiar, portanto, era necessário verificar se havia linguagem obscena e filtrar nomes de usuário e outras informações visíveis para os usuários. É impossível pegar tudo, mas não é difícil filtrar as coisas óbvias.



Os nomes personalizados foram comparados a uma lista de expressões regulares, o que permitiu que eles correspondessem a um padrão. Por exemplo, "fu. * Bar" será comparado com todos os nomes que começam com "fu" e terminam com "bar". Se você escolher seus padrões com cuidado, poderá capturar e rejeitar variações flagrantes como "shitake" e "matsushita", que possuem maldições embutidas.



O mesmo mecanismo foi usado para prevenir os usuários de escolherem nomes "proibidos" como "postmaster", "root", "admin" e "help". Tínhamos um arquivo de texto como este:



admin.*
      "admin".
postmaster
  postmaster.
poop
  .
weenie
  .


Cada entrada consistia em duas linhas. A primeira era a expressão regular a ser comparada e a segunda linha era a mensagem de erro mostrada ao usuário. O sistema leu o arquivo duas linhas por vez e, quando o usuário inseriu o nome, ele foi comparado com todas as expressões regulares. Uma mensagem de erro foi exibida para a primeira correspondência encontrada. Se não houve correspondência, o nome personalizado foi aceito.



O código que leu o arquivo sabia como pular comentários. Mas ele não sabia como lidar com linhas vazias.



Alguém fez alterações no arquivo de palavrões, adicionando ao longo do caminho uma linha em branco após os nomes "reservados" e antes dos palavrões. Quando o código lê a lista, ele considera a string vazia como uma expressão regular e a palavra a seguir como uma mensagem de erro. Uma expressão de string vazia corresponde a qualquer coisa.



Meia-noite. Estamos todos um pouco nervosos. Bryce escreve o nome e o sistema responde com uma mensagem simples:





Começamos a rir histericamente. Outros vieram até nós para descobrir o que estava acontecendo. Mostramos na tela. Eles começaram a rir histericamente.



Naquela época, em outro prédio, Mark Armstrong (encarregado do QA), junto com Bruce Leek (um dos fundadores da empresa), estavam sentados em frente a um balcão de dezesseis decodificadores WebTV. Este rack, apelidado de "racksville", foi conectado por meio de um multiplexador de vídeo a uma grande TV exibindo imagens de todas as 16 caixas simultaneamente. Mark e Bruce começaram a registrar os decodificadores usando um teclado com transmissor infravermelho. Chamamos pelo interfone:



- Como vai?



- Tudo perfeitamente.



- Oh bom. Você deve ter notado algumas coisas ao se registrar.



- Sim? Não notamos nada de estranho.



- Aviso prévio.



- OK. Inserindo o código postal ... até agora está tudo bem. OGO !!!



Uma mensagem amigável apareceu em imagens de todos os 16 consoles. Os chefes sugeriram que talvez precisássemos consertar essa falha o mais rápido possível. Pareceu-nos uma ótima ideia.



Corrigimos o arquivo e ensinamos o código a reconhecer e ignorar linhas vazias. Pelo que eu sei, a WebTV não disse "f - k" a nenhum cliente.



Problema de travamento do Xbox



Na época, a equipe estava trabalhando em um dos primeiros jogos para um novo console chamado Xbox. Quando o teste final foi acelerado, o QA lançou três decodificadores do lote de instalação para executar testes automatizados à noite. Se a compilação do jogo de ontem ainda estava sendo testada pela manhã, indicava sua estabilidade.



Infelizmente, um dos consoles travou pela manhã. Travamentos são sempre ruins, mas era um caso extremamente grave: algo executado pela placa de vídeo travava todo o sistema. Diagnosticar problemas na placa de vídeo é difícil: sem depuradores, sem rastreamentos de pilha, sem depuração com printf. Você só pode ler o código e experimentar.



Assim começou a Caça aos Insetos. Todos os dias, os engenheiros líderes revisavam as evidências disponíveis, levantavam hipóteses e descartavam possibilidades. Todas as noites, o controle de qualidade recebia uma queda "aleatória" sem motivo. "Isso é impossível", "Como isso acontece?", "Talvez seja um bug no compilador?" - todos os sucessos mais populares.



No carro dos engenheiros, o jogo funcionou perfeitamente por muitos dias. Mas isso não serviu de consolo, pois o prazo para envio do jogo para impressão e envio para as lojas estava se aproximando.



Felizmente, logo encontramos um padrão, embora um tanto estranho. O jogo travou apenas à noite e apenas em um dos três consoles. Começamos a procurar diferenças entre eles. Não era sobre o cabo de alimentação. Não em controladores. DVD queimado fora de serviço. Transferindo o console para sua mesa - ele não cai. Coloque de volta - ele cai. Tratava-se de um estande específico usado pelo QA.



Agora, o processo de exclusão de fatores exigia a exclusão de todas as variáveis. No final, desesperado, o engenheiro tentou trocar os acessórios da mesa.



Descobriu-se que não era um prefixo específico que estava com defeito. Qualquer prefixo nesta tabela caiu. No meio da noite. Às vezes, pelo bem da ciência, você tem que agir de forma estranha, e esse foi um desses casos. O engenheiro sentou-se estoicamente em uma cadeira, coberta com latas de Red Bull, e Bug Hunt se transformou em Bug Watching. O engenheiro jurou que iria assistir a testes automatizados rodando nos consoles dessa maldita mesa até ver a falha com seus próprios olhos.



A noite passou lentamente, depois rapidamente e, finalmente, o amanhecer chegou. O jogo continuou a correr. Foi inspirador. O sol começou a nascer.



E então algo interessante finalmente aconteceu: um raio do sol nascente caiu sobre a mesa. Minuto após minuto, a viga rastejou pela mesa até os acessórios, seu brilho quente envolveu silenciosamente a cúpula preta do acessório.



Que caiu rapidamente.



O primeiro Xbox tinha um problema: a placa de vídeo poderia funcionar mal se a temperatura do console atingisse um determinado valor. O software não teve nada a ver com isso. Um problema de hardware foi relatado, o jogo foi lançado e o Red Bull foi substituído por cerveja. Ok, vamos ser honestos, para o uísque. Um: zero para a ciência.



All Articles