Positivos falsos no PVS-Studio: quão profunda é a toca do coelho

Unicorn PVS-Studio e GetNamedSecurityInfo

Nossa equipe fornece suporte rápido e eficaz ao cliente. As solicitações dos usuários são tratadas apenas pelos programadores, já que nossos clientes são eles mesmos e geralmente fazem perguntas complicadas. Hoje vou falar sobre um pedido recente referente a um falso positivo que até me forçou a realizar uma pequena investigação para resolver o problema.

Trabalhamos duro para reduzir ao mínimo o número de falsos positivos gerados pelo PVS-Studio. Infelizmente, os analisadores estáticos geralmente não conseguem distinguir o código correto de um bug, porque eles simplesmente não têm informações suficientes. Os falsos positivos são, portanto, inevitáveis. No entanto, não é um problema, pois você pode personalizar facilmente o analisador, de modo que 9 em cada 10 avisos apontem para erros genuínos.

Embora os falsos positivos possam não parecer muito, nunca paramos de combatê-los melhorando nossos diagnósticos. Alguns falsos positivos flagrantes são capturados por nossa equipe; outros são relatados por nossos clientes e usuários de versão gratuita.

Um de nossos clientes recentemente nos enviou um e-mail com a seguinte redação:

Por alguma razão, o analisador diz que um determinado ponteiro é sempre nulo, enquanto não é. Além disso, seu comportamento em um projeto de teste é estranho e instável: às vezes emite um aviso e às vezes não. Aqui está um exemplo sintético que reproduz esse falso positivo:

#include <windows.h> #include <aclapi.h> #include <tchar.h> int main() { PACL pDACL = NULL; PSECURITY_DESCRIPTOR pSD = NULL; ::GetNamedSecurityInfo(_T("ObjectName"), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, &pDACL, NULL, &pSD); auto test = pDACL == NULL; // V547 Expression 'pDACL == 0' is always true. return 0; } 

Não é difícil adivinhar como nossos usuários veem falsos positivos assim. A função GetNamedSecurityInfo obviamente modifica o valor da variável pDACL . O que impediu os desenvolvedores de criar um manipulador para casos simples como esse? E por que o aviso não é emitido em todas as sessões? Talvez seja um erro no próprio analisador, digamos, uma variável não inicializada?

Infelizmente ... Oferecer suporte a usuários de um analisador de código estático não é uma tarefa fácil, mas foi minha própria escolha. Então, arregacei as mangas e comecei a investigar o problema.

Comecei verificando a descrição da função GetNamedSecurityInfo e certificando-me de que sua chamada realmente implicava modificar o valor da variável pDACL . Aqui está a descrição do sexto argumento:
ppdacl

Um ponteiro para uma variável que recebe um ponteiro para a DACL no descritor de segurança retornado ou NULL se o descritor de segurança não tiver DACL. O ponteiro retornado é válido apenas se você definir o sinalizador DACL_SECURITY_INFORMATION. Além disso, esse parâmetro pode ser NULL se você não precisar da DACL.

Eu sei que o PVS-Studio obviamente deve ser capaz de lidar com um código tão simples sem gerar um aviso falso. Nesse ponto, minha intuição já estava me dizendo que o caso não era trivial e que levaria um bom tempo para resolver.

Minhas suspeitas foram confirmadas quando não consegui reproduzir o falso positivo com a versão alfa atual do analisador ou a versão instalada no computador do usuário. Não importa o que eu fiz, o analisador ficou em silêncio.

Pedi ao cliente que me enviasse o arquivo i pré-processado gerado para o programa de exemplo. Ele fez isso, e eu continuei minha investigação.

O analisador produziu o falso positivo nesse arquivo imediatamente. Por um lado, foi bom finalmente ter conseguido reproduzi-lo. Por outro lado, tive um sentimento que poderia ser melhor ilustrado por esta figura:

wat


Por que esse sentimento? Veja bem, eu sei perfeitamente bem como o analisador e o diagnóstico V547 funcionam. Simplesmente não há como eles gerarem um falso positivo assim!

OK, vamos fazer um chá e continuar.

A chamada para a função GetNamedSecurityInfo se expande no seguinte código:

 ::GetNamedSecurityInfoW(L"ObjectName", SE_FILE_OBJECT, (0x00000004L), 0, 0, &pDACL, 0, &pSD); 

Esse código parece o mesmo no arquivo i pré-processado no meu computador e no arquivo enviado pelo usuário.

Hmm ... OK, vejamos a declaração desta função. Aqui está o que eu tenho no meu arquivo:

 __declspec(dllimport) DWORD __stdcall GetNamedSecurityInfoW( LPCWSTR pObjectName, SE_OBJECT_TYPE ObjectType, SECURITY_INFORMATION SecurityInfo, PSID * ppsidOwner, PSID * ppsidGroup, PACL * ppDacl, PACL * ppSacl, PSECURITY_DESCRIPTOR * ppSecurityDescriptor ); 

Tudo é lógico e claro. Nada incomum.

Então eu espio o arquivo do usuário e ...

wat wat wat


O que vejo lá não pertence à nossa realidade:

 __declspec(dllimport) DWORD __stdcall GetNamedSecurityInfoW( LPCWSTR pObjectName, SE_OBJECT_TYPE ObjectType, SECURITY_INFORMATION SecurityInfo, const PSID * ppsidOwner, const PSID * ppsidGroup, const PACL * ppDacl, const PACL * ppSacl, PSECURITY_DESCRIPTOR * ppSecurityDescriptor ); 

Observe que o parâmetro formal pp Dacl está marcado como const .

Wat? WTF? Wat? WTF?

O que é essa const !? O que está fazendo aqui!?

Bem, pelo menos tenho certeza de que o analisador é inocente e posso defender sua honra.

O argumento é um ponteiro para um objeto constante. Acontece que, do ponto de vista do analisador, a função GetNamedSecurityInfoW não pode modificar o objeto referido pelo ponteiro. Portanto, no seguinte código:

 PACL pDACL = NULL; PSECURITY_DESCRIPTOR pSD = NULL; ::GetNamedSecurityInfo(_T("ObjectName"), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, &pDACL, NULL, &pSD); auto test = pDACL == NULL; // V547 Expression 'pDACL == 0' is always true. 

a variável pDACL não pode mudar, sobre a qual o analisador nos adverte corretamente (a expressão 'pDACL == 0' sempre é verdadeira.).

OK, agora sabemos o que aciona o aviso. O que ainda não sabemos é de onde veio a palavra-chave const . Simplesmente não pode estar lá!

Bem, tenho um palpite, e é confirmado pelo que encontro na Internet. Acontece que há uma versão antiga do arquivo aclapi.h com uma descrição incorreta da função. Também encontrei alguns links interessantes:


Portanto, era uma vez uma descrição de função no arquivo aclapi.h (6.0.6002.18005-Windows 6.0):

 WINADVAPI DWORD WINAPI GetNamedSecurityInfoW( __in LPWSTR pObjectName, __in SE_OBJECT_TYPE ObjectType, __in SECURITY_INFORMATION SecurityInfo, __out_opt PSID * ppsidOwner, __out_opt PSID * ppsidGroup, __out_opt PACL * ppDacl, __out_opt PACL * ppSacl, __out_opt PSECURITY_DESCRIPTOR * ppSecurityDescriptor ); 

Alguém alterou o tipo do parâmetro pObjectName, mas alterou os tipos de ponteiros ao longo do caminho, adicionando a palavra-chave const . E o arquivo aclapi.h (6.1.7601.23418-Windows 7.0) terminou da seguinte maneira:

 WINADVAPI DWORD WINAPI GetNamedSecurityInfoW( __in LPCWSTR pObjectName, __in SE_OBJECT_TYPE ObjectType, __in SECURITY_INFORMATION SecurityInfo, __out_opt const PSID * ppsidOwner, __out_opt const PSID * ppsidGroup, __out_opt const PACL * ppDacl, __out_opt const PACL * ppSacl, __out PSECURITY_DESCRIPTOR * ppSecurityDescriptor ); 

diff 1


Agora ficou claro que nosso usuário estava trabalhando com essa versão muito incorreta do aclapi.h, que ele confirmou em seu email. Não pude reproduzir o bug porque estava usando uma versão mais recente.

É assim que a descrição da função fixa se parece no arquivo aclapi.h mais recente (6.3.9600.17415-Windows_8.1).

 WINADVAPI DWORD WINAPI GetNamedSecurityInfoW( _In_ LPCWSTR pObjectName, _In_ SE_OBJECT_TYPE ObjectType, _In_ SECURITY_INFORMATION SecurityInfo, _Out_opt_ PSID * ppsidOwner, _Out_opt_ PSID * ppsidGroup, _Out_opt_ PACL * ppDacl, _Out_opt_ PACL * ppSacl, _Out_ PSECURITY_DESCRIPTOR * ppSecurityDescriptor ); 

diff 2


O tipo do argumento pObjectName ainda é o mesmo, mas as constantes extras se foram. Tudo está bom novamente, mas ainda existem cabeçalhos quebrados em uso em algum lugar por aí.

Eu explico tudo isso ao cliente, e ele fica feliz em ver o problema resolvido. Além disso, ele descobriu por que o falso positivo não ocorreu regularmente:

Agora me lembro de experimentar conjuntos de ferramentas neste projeto de teste há algum tempo. A configuração de depuração foi definida como Platform Toolset por padrão para o Visual Studio 2017 - "Visual Studio 2017 (v141)", enquanto a configuração do Release foi definida como "Visual Studio 2015 - Windows XP (v140_xp)". Eu estava simplesmente alternando entre as configurações ontem e o aviso apareceria e desapareceria de acordo.

Só isso. A investigação terminou. Discutimos o problema com o cliente e decidimos não adicionar nenhum kludge ao analisador para poder lidar com esse bug no arquivo de cabeçalho. O mais importante é que descobrimos o problema. "Caso encerrado", como se costuma dizer.

Conclusão

O PVS-Studio é um produto de software complexo, que reúne grandes quantidades de informações do código dos programas e as utiliza em várias técnicas de análise . Nesse caso em particular, ele se mostrou muito inteligente, terminando com um falso positivo devido a uma descrição incorreta da função.

Torne-se nossos clientes e você receberá um suporte profissional imediato de mim e de meus colegas de equipe.

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


All Articles