Verificando o FreeRDP com o analisador PVS-Studio

Quadro 2

O FreeRDP é uma implementação de código aberto do Remote Desktop Protocol (RDP), um protocolo que implementa o controle remoto do computador desenvolvido pela Microsoft. O projeto suporta muitas plataformas, incluindo Windows, Linux, macOS e até iOS com Android. Este projeto foi selecionado como o primeiro de uma série de artigos dedicados à verificação de clientes RDP usando o analisador estático PVS-Studio.

Um pouco de história


O projeto FreeRDP veio depois que a Microsoft revelou as especificações de seu protocolo RDP proprietário. Naquela época, havia um cliente rdesktop, cuja implementação é baseada nos resultados da Engenharia Reversa.

No processo de implementação do protocolo, ficou mais difícil adicionar novas funcionalidades devido à arquitetura do projeto então existente. As mudanças nele criaram um conflito entre os desenvolvedores, o que levou à criação do fork do rdesktop - FreeRDP. A distribuição posterior do produto foi limitada pela licença GPLv2, como resultado da decisão de licenciar novamente para a Apache License v2. No entanto, nem todos concordaram em alterar a licença de seu código, então os desenvolvedores decidiram reescrever o projeto, como resultado, temos uma aparência moderna da base de código.

Mais detalhes sobre a história do projeto podem ser encontrados na nota oficial do blog: “A história do projeto FreeRDP”.

O PVS-Studio foi usado como uma ferramenta para detectar erros e possíveis vulnerabilidades no código. É um analisador de código estático para C, C ++, C # e Java, disponível no Windows, Linux e macOS.

O artigo apresenta apenas os erros que me pareceram mais interessantes.

Vazamento de memória


V773 A função foi encerrada sem liberar o ponteiro 'cwd'. É possível um vazamento de memória. environment.c 84

DWORD GetCurrentDirectoryA(DWORD nBufferLength, LPSTR lpBuffer) { char* cwd; .... cwd = getcwd(NULL, 0); .... if (lpBuffer == NULL) { free(cwd); return 0; } if ((length + 1) > nBufferLength) { free(cwd); return (DWORD) (length + 1); } memcpy(lpBuffer, cwd, length + 1); return length; .... } 

Este fragmento foi retirado do subsistema winpr que implementa o wrapper WINAPI para sistemas não Windows, ou seja, É uma contrapartida leve para o vinho. Há um vazamento aqui: a memória alocada pela função getcwd é liberada apenas em casos especiais. Para corrigir o erro, adicione uma chamada para liberar após o memcpy .

Indo além dos limites de uma matriz


A saturação da matriz V557 é possível. O valor do índice 'event-> EventHandlerCount' pode chegar a 32. PubSub.c 117

 #define MAX_EVENT_HANDLERS 32 struct _wEventType { .... int EventHandlerCount; pEventHandler EventHandlers[MAX_EVENT_HANDLERS]; }; int PubSub_Subscribe(wPubSub* pubSub, const char* EventName, pEventHandler EventHandler) { .... if (event->EventHandlerCount <= MAX_EVENT_HANDLERS) { event->EventHandlers[event->EventHandlerCount] = EventHandler; event->EventHandlerCount++; } .... } 

Neste exemplo, um novo item é adicionado à lista, mesmo que o número de itens tenha atingido o máximo. Basta substituir o operador <= por < para não ultrapassar os limites da matriz.

Outro erro deste tipo foi encontrado:

  • A saturação da matriz V557 é possível. O valor do índice 'iBitmapFormat' pode chegar a 8. orders.c 2623

Erros de digitação


Fragmento 1


A expressão V547 '! Pipe-> In' é sempre falsa. MessagePipe.c 63

 wMessagePipe* MessagePipe_New() { .... pipe->In = MessageQueue_New(NULL); if (!pipe->In) goto error_in; pipe->Out = MessageQueue_New(NULL); if (!pipe->In) // <= goto error_out; .... } 

Aqui vemos o erro de digitação usual: na segunda condição, a mesma variável é verificada como na primeira. Provavelmente, o erro apareceu como resultado da cópia malsucedida do código.

Fragmento 2


V760 Foram encontrados dois blocos de texto idênticos. O segundo bloco começa na linha 771. tsg.c 770

 typedef struct _TSG_PACKET_VERSIONCAPS { .... UINT16 majorVersion; UINT16 minorVersion; .... } TSG_PACKET_VERSIONCAPS, *PTSG_PACKET_VERSIONCAPS; static BOOL TsProxyCreateTunnelReadResponse(....) { .... PTSG_PACKET_VERSIONCAPS versionCaps = NULL; .... /* MajorVersion (2 bytes) */ Stream_Read_UINT16(pdu->s, versionCaps->majorVersion); /* MinorVersion (2 bytes) */ Stream_Read_UINT16(pdu->s, versionCaps->majorVersion); .... } 

Outro erro de digitação: um comentário no código implica que minorVersion deve vir do fluxo, no entanto, a leitura ocorre em uma variável denominada majorVersion . No entanto, eu não estou familiarizado com o protocolo, então isso é apenas uma suposição.

Fragmento 3


V524 É estranho que o corpo da função 'trio_index_last' seja totalmente equivalente ao corpo da função 'trio_index'. triostr.c 933

 /** Find first occurrence of a character in a string. .... */ TRIO_PUBLIC_STRING char * trio_index TRIO_ARGS2((string, character), TRIO_CONST char *string, int character) { assert(string); return strchr(string, character); } /** Find last occurrence of a character in a string. .... */ TRIO_PUBLIC_STRING char * trio_index_last TRIO_ARGS2((string, character), TRIO_CONST char *string, int character) { assert(string); return strchr(string, character); } 

A julgar pelo comentário, a função trio_index encontra a primeira correspondência de um caractere em uma string quando trio_index_last é o último. Mas os corpos dessas funções são idênticos! Provavelmente, esse é um erro de digitação e, na função trio_index_last , você precisa usar strrchr em vez de strchr . Então o comportamento será esperado.

Fragmento 4


V769 O ponteiro 'data' na expressão é igual a nullptr. O valor resultante das operações aritméticas nesse ponteiro não faz sentido e não deve ser usado. nsc_encode.c 124

 static BOOL nsc_encode_argb_to_aycocg(NSC_CONTEXT* context, const BYTE* data, UINT32 scanline) { .... if (!context || data || (scanline == 0)) return FALSE; .... src = data + (context->height - 1 - y) * scanline; .... } 

Parece que eles perderam acidentalmente o operador de negação ! próximo aos dados . É estranho que isso tenha passado despercebido.

Fragmento 5


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: 213, 222. rdpei_common.c 213

 BOOL rdpei_write_4byte_unsigned(wStream* s, UINT32 value) { BYTE byte; if (value <= 0x3F) { .... } else if (value <= 0x3FFF) { .... } else if (value <= 0x3FFFFF) { byte = (value >> 16) & 0x3F; Stream_Write_UINT8(s, byte | 0x80); byte = (value >> 8) & 0xFF; Stream_Write_UINT8(s, byte); byte = (value & 0xFF); Stream_Write_UINT8(s, byte); } else if (value <= 0x3FFFFF) { byte = (value >> 24) & 0x3F; Stream_Write_UINT8(s, byte | 0xC0); byte = (value >> 16) & 0xFF; Stream_Write_UINT8(s, byte); byte = (value >> 8) & 0xFF; Stream_Write_UINT8(s, byte); byte = (value & 0xFF); Stream_Write_UINT8(s, byte); } .... } 

As duas últimas condições são as mesmas: aparentemente, alguém esqueceu de verificá-las após a cópia. De acordo com o código, é perceptível que a última parte funciona com valores de quatro bytes, portanto, podemos assumir que a última condição deve ser o valor <= 0x3FFFFFFF .

Outro erro deste tipo foi encontrado:

  • 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: 169, 173. file.c 169

Validação de entrada


Fragmento 1


A expressão V547 'strcat (destino, origem)! = NULL' sempre é verdadeira. triostr.c 425

 TRIO_PUBLIC_STRING int trio_append TRIO_ARGS2((target, source), char *target, TRIO_CONST char *source) { assert(target); assert(source); return (strcat(target, source) != NULL); } 

A verificação do resultado da função neste exemplo está incorreta. A função strcat retorna um ponteiro para a versão final da string, ou seja, primeiro parâmetro passado. Neste caso, é alvo . No entanto, se for NULL , será tarde demais para verificar, pois na função strcat será desreferenciada.

Fragmento 2


A expressão V547 'cache' é sempre verdadeira. glyph.c 730

 typedef struct rdp_glyph_cache rdpGlyphCache; struct rdp_glyph_cache { .... GLYPH_CACHE glyphCache[10]; .... }; void glyph_cache_free(rdpGlyphCache* glyphCache) { .... GLYPH_CACHE* cache = glyphCache->glyphCache; if (cache) { .... } .... } 

Nesse caso, a variável de cache recebe o endereço da matriz estática glyphCache-> glyphCache . Portanto, a verificação if (cache) pode ser omitida.

Erro de gerenciamento de recursos


V1005 O recurso foi adquirido usando a função 'CreateFileA', mas foi liberado usando a função 'fclose' incompatível. certificate.c 447

 BOOL certificate_data_replace(rdpCertificateStore* certificate_store, rdpCertificateData* certificate_data) { HANDLE fp; .... fp = CreateFileA(certificate_store->file, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); .... if (size < 1) { CloseHandle(fp); return FALSE; } .... if (!data) { fclose(fp); return FALSE; } .... } 

O descritor de arquivo fp criado chamando a função CreateFile é fechado por engano pela função fclose da biblioteca padrão, em vez de CloseHandle .

Mesmas condições


V581 As expressões condicionais das instruções 'if' localizadas uma ao lado da outra são idênticas. Verifique as linhas: 269, 283. ndr_structure.c 283

 void NdrComplexStructBufferSize(PMIDL_STUB_MESSAGE pStubMsg, unsigned char* pMemory, PFORMAT_STRING pFormat) { .... if (conformant_array_description) { ULONG size; unsigned char array_type; array_type = conformant_array_description[0]; size = NdrComplexStructMemberSize(pStubMsg, pFormat); WLog_ERR(TAG, "warning: NdrComplexStructBufferSize array_type: " "0x%02X unimplemented", array_type); NdrpComputeConformance(pStubMsg, pMemory + size, conformant_array_description); NdrpComputeVariance(pStubMsg, pMemory + size, conformant_array_description); MaxCount = pStubMsg->MaxCount; ActualCount = pStubMsg->ActualCount; Offset = pStubMsg->Offset; } if (conformant_array_description) { unsigned char array_type; array_type = conformant_array_description[0]; pStubMsg->MaxCount = MaxCount; pStubMsg->ActualCount = ActualCount; pStubMsg->Offset = Offset; WLog_ERR(TAG, "warning: NdrComplexStructBufferSize array_type: " "0x%02X unimplemented", array_type); } .... } 

Talvez este exemplo não seja um erro. No entanto, ambas as condições contêm a mesma mensagem, uma das quais, provavelmente, pode ser removida.

Apagar ponteiros nulos


V575 O ponteiro nulo é passado para a função 'livre'. Inspecione o primeiro argumento. smartcard_pcsc.c 875

 WINSCARDAPI LONG WINAPI PCSC_SCardListReadersW( SCARDCONTEXT hContext, LPCWSTR mszGroups, LPWSTR mszReaders, LPDWORD pcchReaders) { LPSTR mszGroupsA = NULL; .... mszGroups = NULL; /* mszGroups is not supported by pcsc-lite */ if (mszGroups) ConvertFromUnicode(CP_UTF8,0, mszGroups, -1, (char**) &mszGroupsA, 0, NULL, NULL); status = PCSC_SCardListReaders_Internal(hContext, mszGroupsA, (LPSTR) &mszReadersA, pcchReaders); if (status == SCARD_S_SUCCESS) { .... } free(mszGroupsA); .... } 

Você pode passar um ponteiro nulo para a função livre , e o analisador sabe disso. Mas se for revelada uma situação em que o ponteiro sempre passa zero, como neste fragmento, um aviso será emitido.

O ponteiro mszGroupsA é inicialmente NULL e não é inicializado em nenhum outro lugar. O único ramo do código em que o ponteiro pode ser inicializado é inacessível.

Havia outros posts como este:

  • V575 O ponteiro nulo é passado para a função 'livre'. Inspecione o primeiro argumento. license.c 790
  • V575 O ponteiro nulo é passado para a função 'livre'. Inspecione o primeiro argumento. rdpsnd_alsa.c 575

Muito provavelmente, essas variáveis ​​esquecidas surgem durante a refatoração e podem ser simplesmente excluídas.

Possível estouro


V1028 Possível estouro. Considere lançar operandos, não o resultado. makecert.c 1087

 // openssl/x509.h ASN1_TIME *X509_gmtime_adj(ASN1_TIME *s, long adj); struct _MAKECERT_CONTEXT { .... int duration_years; int duration_months; }; typedef struct _MAKECERT_CONTEXT MAKECERT_CONTEXT; int makecert_context_process(MAKECERT_CONTEXT* context, ....) { .... if (context->duration_months) X509_gmtime_adj(after, (long)(60 * 60 * 24 * 31 * context->duration_months)); else if (context->duration_years) X509_gmtime_adj(after, (long)(60 * 60 * 24 * 365 * context->duration_years)); .... } 

Trazer o resultado a longo prazo não é proteção contra o estouro, uma vez que o próprio cálculo é realizado usando o tipo int .

Desreferenciamento de ponteiro na inicialização


V595 O ponteiro 'context' foi utilizado antes de ser verificado no nullptr. Verifique as linhas: 746, 748. gfx.c 746

 static UINT gdi_SurfaceCommand(RdpgfxClientContext* context, const RDPGFX_SURFACE_COMMAND* cmd) { .... rdpGdi* gdi = (rdpGdi*) context->custom; if (!context || !cmd) return ERROR_INVALID_PARAMETER; .... } 

Aqui, o ponteiro de contexto é desreferenciado na inicialização - antes de ser verificado.

Outros erros deste tipo foram encontrados:

  • V595 O ponteiro 'ntlm' foi utilizado antes de ser verificado no nullptr. Verifique as linhas: 236, 255. ntlm.c 236
  • V595 O ponteiro 'context' foi utilizado antes de ser verificado no nullptr. Verifique as linhas: 1003, 1007. rfx.c 1003
  • V595 O ponteiro 'rdpei' foi utilizado antes de ser verificado no nullptr. Verifique as linhas: 176, 180. rdpei_main.c 176
  • V595 O ponteiro 'gdi' foi utilizado antes de ser verificado no nullptr. Verifique as linhas: 121, 123. xf_gfx.c 121

Condição sem sentido


A expressão V547 'rdp-> state> = CONNECTION_STATE_ACTIVE' sempre é verdadeira. connection.c 1489

 int rdp_server_transition_to_state(rdpRdp* rdp, int state) { .... switch (state) { .... case CONNECTION_STATE_ACTIVE: rdp->state = CONNECTION_STATE_ACTIVE; // <= .... if (rdp->state >= CONNECTION_STATE_ACTIVE) // <= { IFCALLRET(client->Activate, client->activated, client); if (!client->activated) return -1; } .... } .... } 

É fácil ver que a primeira condição não faz sentido devido à atribuição do valor correspondente anteriormente.

Análise de linha incorreta


V576 Formato incorreto. Considere verificar o terceiro argumento real da função 'sscanf'. Um ponteiro para o tipo int não assinado é esperado. proxy.c 220

V560 Uma parte da expressão condicional é sempre verdadeira: (rc> = 0). proxy.c 222
 static BOOL check_no_proxy(....) { .... int sub; int rc = sscanf(range, "%u", &sub); if ((rc == 1) && (rc >= 0)) { .... } .... } 

O analisador para este fragmento emite imediatamente 2 avisos. O especificador % u espera uma variável do tipo int sem sinal , mas a variável sub é do tipo int . A seguir, vemos uma verificação suspeita: a condição à direita não faz sentido, pois no início há uma comparação com a unidade. Não sei o que o autor deste código tinha em mente, mas há claramente algo errado aqui.

Verificações não ordenadas


A expressão V547 'status == 0x00090314' é sempre falsa. ntlm.c 299

 BOOL ntlm_authenticate(rdpNtlm* ntlm, BOOL* pbContinueNeeded) { .... if (status != SEC_E_OK) { .... return FALSE; } if (status == SEC_I_COMPLETE_NEEDED) // <= status = SEC_E_OK; else if (status == SEC_I_COMPLETE_AND_CONTINUE) // <= status = SEC_I_CONTINUE_NEEDED; .... } 

As condições marcadas sempre serão falsas, pois a execução alcançará a segunda condição somente se status == SEC_E_OK . O código correto pode ser assim:

 if (status == SEC_I_COMPLETE_NEEDED) status = SEC_E_OK; else if (status == SEC_I_COMPLETE_AND_CONTINUE) status = SEC_I_CONTINUE_NEEDED; else if (status != SEC_E_OK) { .... return FALSE; } 

Conclusão


Assim, a verificação do projeto revelou muitos problemas, mas apenas a parte mais interessante foi descrita no artigo. Os desenvolvedores do projeto podem verificar o projeto solicitando uma chave de licença temporária no site do PVS-Studio . Havia também falsos positivos, trabalhos em que ajudarão a melhorar o analisador. No entanto, a análise estática é importante se você deseja não apenas melhorar a qualidade do código, mas também reduzir o tempo para procurar erros, e o PVS-Studio pode ajudar com isso.



Se você deseja compartilhar este artigo com um público que fala inglês, use o link para a tradução: Sergey Larin. Verificando o FreeRDP com PVS-Studio

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


All Articles