Devido ao tema sombrio, o Thunderbird teve que executar um analisador de código

Quadro 3
A “aventura” com o cliente de email Mozilla Thunderbird começou com uma atualização automática para a versão 68.0. Os recursos mais notáveis ​​desta versão foram: mais texto é adicionado às notificações pop-up e um tema sombrio por padrão. Houve um erro que eu queria tentar detectar usando a análise estática. Foi uma ocasião para verificar novamente o código fonte do projeto usando o PVS-Studio. Aconteceu que, no momento da análise, o erro já havia sido corrigido. Mas, como chamamos a atenção para este projeto, podemos escrever sobre outros defeitos encontrados nele.

1. Introdução


O tema sombrio da nova versão do Thunderbird parece bastante bonito. Eu amo temas sombrios. Já mudou para eles em mensageiros instantâneos, Windows, macOS. Em breve, o iPhone será atualizado para o iOS 13, onde um tema sombrio apareceu. Para isso, tive que mudar meu iPhone 5S para um modelo mais novo. Na prática, o tema sombrio exige mais esforço dos desenvolvedores para escolher as cores da interface. Nem todo mundo lida com isso pela primeira vez. Então, minhas tags padrão no Thunderbird começaram a se parecer com:

Quadro 1


Uso seis tags (5 padrão + 1 personalizado) para marcar e-mails. Tornou-se impossível assistir metade deles após a atualização, e decidi mudar a cor para mais brilhante nas configurações. Mas aqui encontrei um bug:

Quadro 2


Você não pode mudar a cor da etiqueta !!! Mais precisamente, é possível, mas o editor não permitirá que ele seja salvo, referindo-se a um nome existente (WTF ???).

Outra manifestação do bug será a inação do botão OK, se você tentar alterar o nome, pois não poderá salvar esse nome. Você também não pode renomear.

Finalmente, você pode perceber que o tema sombrio não tocou nas configurações, o que também não é muito bonito.

Após uma longa luta com o sistema de compilação no Windows, ainda era possível montar o Thunderbird a partir da fonte. A versão mais recente do cliente de email acabou sendo muito melhor que a versão mais recente. Nele, um tema sombrio chegou às configurações, e esse bug com o editor de tags também desapareceu. Mas, para que o trabalho de montagem do projeto não fosse desperdiçado, o analisador de código estático PVS-Studio foi lançado.

Nota O código-fonte do Thunderbird se sobrepõe de alguma forma à base de código do Firefox. Portanto, a análise incluiu erros de diferentes componentes, que valem uma olhada de perto nos desenvolvedores de diferentes equipes.

Nota 2 Enquanto o artigo estava sendo escrito, a atualização do Thunderbird 68.1 foi lançada com uma correção para esse bug:

Quadro 5


comm


comm-central é um repositório Mercurial do código de extensão Thunderbird, SeaMonkey e Lightning.

V501 Existem subexpressões idênticas '(! Strcmp (cabeçalho, "Responder para"))' à esquerda e à direita do '||' operador. nsEmitterUtils.cpp 28

extern "C" bool EmitThisHeaderForPrefSetting(int32_t dispType, const char *header) { .... if (nsMimeHeaderDisplayTypes::NormalHeaders == dispType) { if ((!strcmp(header, HEADER_DATE)) || (!strcmp(header, HEADER_TO)) || (!strcmp(header, HEADER_SUBJECT)) || (!strcmp(header, HEADER_SENDER)) || (!strcmp(header, HEADER_RESENT_TO)) || (!strcmp(header, HEADER_RESENT_SENDER)) || (!strcmp(header, HEADER_RESENT_FROM)) || (!strcmp(header, HEADER_RESENT_CC)) || (!strcmp(header, HEADER_REPLY_TO)) || (!strcmp(header, HEADER_REFERENCES)) || (!strcmp(header, HEADER_NEWSGROUPS)) || (!strcmp(header, HEADER_MESSAGE_ID)) || (!strcmp(header, HEADER_FROM)) || (!strcmp(header, HEADER_FOLLOWUP_TO)) || (!strcmp(header, HEADER_CC)) || (!strcmp(header, HEADER_ORGANIZATION)) || (!strcmp(header, HEADER_REPLY_TO)) || (!strcmp(header, HEADER_BCC))) return true; else return false; .... } 

A linha do cabeçalho foi comparada com a constante HEADER_REPLY_TO duas vezes. Talvez em seu lugar devesse ter havido outra constante.

V501 Existem subexpressões idênticas 'obj-> options-> headers! = MimeHeadersCitation' à esquerda e à direita do operador '&&'. mimemsig.cpp 536

 static int MimeMultipartSigned_emit_child(MimeObject *obj) { .... if (obj->options && obj->options->headers != MimeHeadersCitation && obj->options->write_html_p && obj->options->output_fn && obj->options->headers != MimeHeadersCitation && sig->crypto_closure) { .... } .... } 

Outra comparação estranha de uma variável com um nome semelhante são os cabeçalhos . Como sempre, existem duas explicações possíveis: um cheque extra ou um erro de digitação.

V517 O uso do padrão 'if (A) {...} else if (A) {...}' foi detectado. Há uma probabilidade de presença de erro lógico. Verifique as linhas: 1306, 1308. MapiApi.cpp 1306

 void CMapiApi::ReportLongProp(const char *pTag, LPSPropValue pVal) { if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_LONG)) { nsCString num; nsCString num2; num.AppendInt((int32_t)pVal->Value.l); num2.AppendInt((int32_t)pVal->Value.l, 16); MAPI_TRACE3("%s %s, 0x%s\n", pTag, num, num2); } else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_NULL)) { MAPI_TRACE1("%s {NULL}\n", pTag); } else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_ERROR)) { // <= MAPI_TRACE1("%s {Error retrieving property}\n", pTag); } else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_ERROR)) { // <= MAPI_TRACE1("%s {Error retrieving property}\n", pTag); } else { MAPI_TRACE1("%s invalid value, expecting long\n", pTag); } if (pVal) MAPIFreeBuffer(pVal); } 

A cascata de expressões condicionais foi claramente acelerada pressionando Ctrl + C e Ctrl + V. Como resultado, uma das ramificações nunca é executada.

V517 O uso do padrão 'if (A) {...} else if (A) {...}' foi detectado. Há uma probabilidade de presença de erro lógico. Verifique as linhas: 777, 816. nsRDFContentSink.cpp 777

 nsresult RDFContentSinkImpl::GetIdAboutAttribute(const char16_t** aAttributes, nsIRDFResource** aResource, bool* aIsAnonymous) { .... if (localName == nsGkAtoms::about) { .... } else if (localName == nsGkAtoms::ID) { .... } else if (localName == nsGkAtoms::nodeID) { nodeID.Assign(aAttributes[1]); } else if (localName == nsGkAtoms::about) { // XXX we don't deal with aboutEach... //MOZ_LOG(gLog, LogLevel::Warning, // ("rdfxml: ignoring aboutEach at line %d", // aNode.GetSourceLineNumber())); } .... } 

A primeira e a última condição são as mesmas. O código mostra que o código está em processo de gravação. É seguro dizer que, com uma alta probabilidade, o erro se manifestará após a finalização do código. Um programador pode alterar o código comentado, mas ele nunca terá controle. Tenha cuidado e cuidado com este código.

V522 A desreferenciação do ponteiro nulo 'linha' pode ocorrer. morkRowCellCursor.cpp 175

 NS_IMETHODIMP morkRowCellCursor::MakeCell( // get cell at current pos in the row nsIMdbEnv* mev, // context mdb_column* outColumn, // column for this particular cell mdb_pos* outPos, // position of cell in row sequence nsIMdbCell** acqCell) { nsresult outErr = NS_OK; nsIMdbCell* outCell = 0; mdb_pos pos = 0; mdb_column col = 0; morkRow* row = 0; morkEnv* ev = morkEnv::FromMdbEnv(mev); if (ev) { pos = mCursor_Pos; morkCell* cell = row->CellAt(ev, pos); if (cell) { col = cell->GetColumn(); outCell = row->AcquireCellHandle(ev, cell, col, pos); } outErr = ev->AsErr(); } if (acqCell) *acqCell = outCell; if (outPos) *outPos = pos; if (outColumn) *outColumn = col; return outErr; } 

A desreferenciação do ponteiro nulo da linha é possível na seguinte linha:

 morkCell* cell = row->CellAt(ev, pos); 

Provavelmente, a inicialização do ponteiro foi ignorada antes desta linha, por exemplo, usando o método GetRow , etc.

V543 É estranho que o valor '-1' seja designado à variável 'm_lastError' do tipo HRESULT. MapiApi.cpp 1050

 class CMapiApi { .... private: static HRESULT m_lastError; .... }; CMsgStore *CMapiApi::FindMessageStore(ULONG cbEid, LPENTRYID lpEid) { if (!m_lpSession) { MAPI_TRACE0("FindMessageStore called before session is open\n"); m_lastError = -1; return NULL; } .... } 

O tipo HRESULT é um tipo de dados complexo. Diferentes bits de uma variável desse tipo representam diferentes campos de descrição de erro. O código de erro deve ser definido usando constantes especiais dos arquivos de cabeçalho do sistema.

Mais alguns desses lugares:

  • V543 É estranho que o valor '-1' seja designado à variável 'm_lastError' do tipo HRESULT. MapiApi.cpp 817
  • V543 É estranho que o valor '-1' seja designado à variável 'm_lastError' do tipo HRESULT. MapiApi.cpp 1749

V579 A função memset recebe o ponteiro e seu tamanho como argumentos. É possivelmente um erro. Inspecione o terceiro argumento. icalmime.c 195

 icalcomponent* icalmime_parse(....) { struct sspm_part *parts; int i, last_level=0; icalcomponent *root=0, *parent=0, *comp=0, *last = 0; if ( (parts = (struct sspm_part *) malloc(NUM_PARTS*sizeof(struct sspm_part)))==0) { icalerror_set_errno(ICAL_NEWFAILED_ERROR); return 0; } memset(parts,0,sizeof(parts)); sspm_parse_mime(parts, NUM_PARTS, /* Max parts */ icalmime_local_action_map, /* Actions */ get_string, data, /* data for get_string*/ 0 /* First header */); .... } 

A variável parts é um ponteiro para uma matriz de estruturas. Para redefinir os valores das estruturas, eles usaram a função memset , mas transferiram o tamanho do ponteiro para ele como o tamanho de um pedaço de memória.

Outros locais suspeitos:

  • V579 A função memset recebe o ponteiro e seu tamanho como argumentos. É possivelmente um erro. Inspecione o terceiro argumento. icalmime.c 385
  • V579 A função memset recebe o ponteiro e seu tamanho como argumentos. É possivelmente um erro. Inspecione o terceiro argumento. icalparameter.c 114
  • V579 A função snprintf recebe o ponteiro e seu tamanho como argumentos. É possivelmente um erro. Inspecione o segundo argumento. icaltimezone.c 1908
  • V579 A função snprintf recebe o ponteiro e seu tamanho como argumentos. É possivelmente um erro. Inspecione o segundo argumento. icaltimezone.c 1910
  • V579 A função strncmp recebe o ponteiro e seu tamanho como argumentos. É possivelmente um erro. Inspecione o terceiro argumento. sspm.c 707
  • V579 A função strncmp recebe o ponteiro e seu tamanho como argumentos. É possivelmente um erro. Inspecione o terceiro argumento. sspm.c 813

V595 O ponteiro 'aValues' foi utilizado antes de ser verificado no nullptr. Verifique as linhas: 553, 555. nsLDAPMessage.cpp 553

 NS_IMETHODIMP nsLDAPMessage::GetBinaryValues(const char *aAttr, uint32_t *aCount, nsILDAPBERValue ***aValues) { .... *aValues = static_cast<nsILDAPBERValue **>( moz_xmalloc(numVals * sizeof(nsILDAPBERValue))); if (!aValues) { ldap_value_free_len(values); return NS_ERROR_OUT_OF_MEMORY; } .... } 

O diagnóstico V595 geralmente encontra erros típicos de cancelamento de referência de ponteiro nulo. Mas foi encontrado um caso muito interessante, digno de atenção especial.

Tecnicamente, o analisador está certo de que o ponteiro aValues ​​é primeiro desreferenciado e depois verificado, mas o erro é diferente. Como é um ponteiro duplo, o código correto deve ficar assim:

 *aValues = static_cast<nsILDAPBERValue **>( moz_xmalloc(numVals * sizeof(nsILDAPBERValue))); if (!*aValues) { ldap_value_free_len(values); return NS_ERROR_OUT_OF_MEMORY; } 

Outro lugar muito semelhante:

  • V595 O ponteiro '_retval' foi utilizado antes de ser verificado no nullptr. Verifique as linhas: 357, 358. nsLDAPSyncQuery.cpp 357

V1044 As condições de interrupção do loop não dependem do número de iterações. mimemoz2.cpp 1795

 void ResetChannelCharset(MimeObject *obj) { .... if (cSet) { char *ptr2 = cSet; while ((*cSet) && (*cSet != ' ') && (*cSet != ';') && (*cSet != '\r') && (*cSet != '\n') && (*cSet != '"')) ptr2++; if (*cSet) { PR_FREEIF(obj->options->default_charset); obj->options->default_charset = strdup(cSet); obj->options->override_charset = true; } PR_FREEIF(cSet); } .... } 

Este erro foi encontrado usando um novo diagnóstico, que estará disponível na próxima versão do analisador. Todas as variáveis ​​usadas na condição para interromper o loop while não são modificadas, porque as variáveis ptr2 e cSet são misturadas no corpo da função.

netwerk


O netwerk contém interfaces e código C para acesso de baixo nível à rede (usando soquetes e caches de arquivos e memória), bem como acesso de nível superior (usando vários protocolos como http, ftp, gopher, castanet). Esse código também é conhecido pelos nomes "netlib" e "Necko".

V501 Existem subexpressões idênticas 'connectStarted' à esquerda e à direita do operador '&&'. nsSocketTransport2.cpp 1693

 nsresult nsSocketTransport::InitiateSocket() { .... if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase() && connectStarted && connectCalled) { // <= good, line 1630 SendPRBlockingTelemetry( connectStarted, Telemetry::PRCONNECT_BLOCKING_TIME_NORMAL, Telemetry::PRCONNECT_BLOCKING_TIME_SHUTDOWN, Telemetry::PRCONNECT_BLOCKING_TIME_CONNECTIVITY_CHANGE, Telemetry::PRCONNECT_BLOCKING_TIME_LINK_CHANGE, Telemetry::PRCONNECT_BLOCKING_TIME_OFFLINE); } .... if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase() && connectStarted && connectStarted) { // <= fail, line 1694 SendPRBlockingTelemetry( connectStarted, Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_NORMAL, Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_SHUTDOWN, Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_CONNECTIVITY_CHANGE, Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_LINK_CHANGE, Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_OFFLINE); } .... } 

No começo, pensei que duplicar a variável connectStarted é apenas um código supérfluo até examinar toda a função suficientemente longa e encontrar um fragmento semelhante. Provavelmente, em vez de uma única variável connectStarted aqui, também deve ser uma variável connectCalled .

V611 A memória foi alocada usando o operador 'new T []', mas foi liberada usando o operador 'delete'. Considere inspecionar este código. Provavelmente é melhor usar 'delete [] mData;'. Verifique as linhas: 233, 222. DataChannel.cpp 233

 BufferedOutgoingMsg::BufferedOutgoingMsg(OutgoingMsg& msg) { size_t length = msg.GetLeft(); auto* tmp = new uint8_t[length]; // infallible malloc! memcpy(tmp, msg.GetData(), length); mLength = length; mData = tmp; mInfo = new sctp_sendv_spa; *mInfo = msg.GetInfo(); mPos = 0; } BufferedOutgoingMsg::~BufferedOutgoingMsg() { delete mInfo; delete mData; } 

O ponteiro mData aponta para uma matriz, não para um único objeto. Eles cometeram um erro no destruidor de classes, esquecendo de adicionar colchetes ao operador de exclusão .

V1044 As condições de interrupção do loop não dependem do número de iterações. ParseFTPList.cpp 691

 int ParseFTPList(....) { .... pos = toklen[2]; while (pos > (sizeof(result->fe_size) - 1)) pos = (sizeof(result->fe_size) - 1); memcpy(result->fe_size, tokens[2], pos); result->fe_size[pos] = '\0'; .... } 

O valor de pos é substituído no loop pela mesma quantidade. Parece que o novo diagnóstico encontrou outro erro.

gfx


O gfx contém interfaces C e código para desenho e imagem independentes da plataforma. Pode ser usado para desenhar retângulos, linhas, imagens, etc. Essencialmente, é um conjunto de interfaces para um contexto de dispositivo independente de plataforma (desenho). Ele não suporta widgets ou rotinas de desenho específicas; apenas fornece as operações primitivas para o desenho.

V501 Existem subexpressões idênticas à esquerda e à direita do '||' operador: mVRSystem || mVRCompositor || mVRSystem OpenVRSession.cpp 876

 void OpenVRSession::Shutdown() { StopHapticTimer(); StopHapticThread(); if (mVRSystem || mVRCompositor || mVRSystem) { ::vr::VR_Shutdown(); mVRCompositor = nullptr; mVRChaperone = nullptr; mVRSystem = nullptr; } } 

Na condição, a variável mVRSystem está presente duas vezes. Obviamente, um deles deve ser substituído por mVRChaperone .

dom


dom contém interfaces e código C para implementar e rastrear objetos DOM (Document Object Model) em Javascript. Ele forma a subestrutura C que cria, destrói e manipula objetos internos e definidos pelo usuário de acordo com o script Javascript.

V570 A variável 'clonedDoc-> mPreloadReferrerInfo' é atribuída a si mesma. Document.cpp 12049

 already_AddRefed<Document> Document::CreateStaticClone( nsIDocShell* aCloneContainer) { .... clonedDoc->mReferrerInfo = static_cast<dom::ReferrerInfo*>(mReferrerInfo.get())->Clone(); clonedDoc->mPreloadReferrerInfo = clonedDoc->mPreloadReferrerInfo; .... } 

O analisador detectou a atribuição de uma variável a si mesma.

xpcom


O xpcom contém as interfaces C de baixo nível, código C, código C, um pouco de código de montagem e ferramentas de linha de comando para implementar o mecanismo básico dos componentes XPCOM (que significa "Modelo de objeto de componente de plataforma cruzada"). XPCOM é o mecanismo que permite ao Mozilla exportar interfaces e disponibilizá-las automaticamente para scripts JavaScript, Microsoft COM e código Mozilla C regular.

V611 A memória foi alocada usando a função 'malloc / realloc', mas foi liberada usando o operador 'delete'. Considere inspecionar as lógicas de operação por trás da variável 'key'. Verifique as linhas: 143, 140. nsINIParser.h 143

 struct INIValue { INIValue(const char* aKey, const char* aValue) : key(strdup(aKey)), value(strdup(aValue)) {} ~INIValue() { delete key; delete value; } void SetValue(const char* aValue) { delete value; value = strdup(aValue); } const char* key; const char* value; mozilla::UniquePtr<INIValue> next; }; 

Depois de chamar a função strdup , é necessário liberar memória usando a função free , não o operador delete .

V716 Conversão de tipo suspeita na inicialização: 'HRESULT var = BOOL'. SpecialSystemDirectory.cpp 73

 BOOL SHGetSpecialFolderPathW( HWND hwnd, LPWSTR pszPath, int csidl, BOOL fCreate ); static nsresult GetWindowsFolder(int aFolder, nsIFile** aFile) { WCHAR path_orig[MAX_PATH + 3]; WCHAR* path = path_orig + 1; HRESULT result = SHGetSpecialFolderPathW(nullptr, path, aFolder, true); if (!SUCCEEDED(result)) { return NS_ERROR_FAILURE; } .... } 

A função WinGI SHGetSpecialFolderPathW retorna um valor do tipo BOOL , não HRESULT . A verificação do resultado da função deve ser reescrita para a correta.

nsprpub


O nsprpub contém código C para a biblioteca de tempo de execução "C" de plataforma cruzada. A biblioteca de tempo de execução “C” contém funções C não visuais básicas para alocar e desalocar memória, obter hora e data, ler e gravar arquivos, manipular threads e manipular e comparar strings em todas as plataformas

V647 O valor do tipo 'int' é atribuído ao ponteiro do tipo 'curto'. Considere inspecionar a atribuição: 'out_flags = 0x2'. prsocket.c 1220

 #define PR_POLL_WRITE 0x2 static PRInt16 PR_CALLBACK SocketPoll( PRFileDesc *fd, PRInt16 in_flags, PRInt16 *out_flags) { *out_flags = 0; #if defined(_WIN64) if (in_flags & PR_POLL_WRITE) { if (fd->secret->alreadyConnected) { out_flags = PR_POLL_WRITE; return PR_POLL_WRITE; } } #endif return in_flags; } /* SocketPoll */ 

O analisador detectou a atribuição de uma constante numérica ao ponteiro out_flags . Provavelmente, eles simplesmente esqueceram de desreferê-lo:

 if (fd->secret->alreadyConnected) { *out_flags = PR_POLL_WRITE; return PR_POLL_WRITE; } 

Conclusão


Este não é o fim. Ser novas revisões de código. O código Thunderbird e Firefox inclui duas grandes bibliotecas: Network Security Services (NSS) e WebRTC (Web Real Time Communications). Houve alguns erros muito interessantes. Nesta revisão, mostrarei uma de cada vez.

Nss

V597 O compilador pode excluir a chamada de função 'memset', usada para liberar o buffer 'newdeskey'. A função RtlSecureZeroMemory () deve ser usada para apagar os dados particulares. pkcs11c.c 1033

 static CK_RV sftk_CryptInit(....) { .... unsigned char newdeskey[24]; .... context->cipherInfo = DES_CreateContext( useNewKey ? newdeskey : (unsigned char *)att->attrib.pValue, (unsigned char *)pMechanism->pParameter, t, isEncrypt); if (useNewKey) memset(newdeskey, 0, sizeof newdeskey); sftk_FreeAttribute(att); .... } 

O NSS é uma biblioteca para o desenvolvimento de aplicativos seguros para clientes e servidores, e aqui a DES Key não é limpa. O compilador removerá a chamada memset do código, conforme a matriz newdeskey não é mais usada no código além desse local.

WebRTC

V519 A variável 'state [state_length - x_length + i]' recebe valores duas vezes sucessivos. Talvez isso seja um erro. Verifique as linhas: 83, 84. filter_ar.c 84

 size_t WebRtcSpl_FilterAR(....) { .... for (i = 0; i < state_length - x_length; i++) { state[i] = state[i + x_length]; state_low[i] = state_low[i + x_length]; } for (i = 0; i < x_length; i++) { state[state_length - x_length + i] = filtered[i]; state[state_length - x_length + i] = filtered_low[i]; // <= } .... } 

No segundo ciclo, os dados são gravados na matriz errada, porque o autor copiou o código e esqueceu de alterar o nome da matriz de state para state_low .

Provavelmente existem bugs mais interessantes nesses projetos que vale a pena mencionar. E faremos isso em um futuro próximo. Enquanto isso, tente o PVS-Studio em seu projeto.



Se você deseja compartilhar este artigo com um público que fala inglês, use o link para a tradução: Svyatoslav Razmyslov. Tema sombrio do Thunderbird como um motivo para executar um analisador de código .

Source: https://habr.com/ru/post/pt469129/


All Articles