Falsos positivos no PVS-Studio: quão profunda é a toca do coelho

Unicorn PVS-Studio e GetNamedSecurityInfo

Nossa equipe fornece suporte rápido e eficiente ao cliente. Somente programadores participam do suporte, pois os programadores também fazem perguntas e temos que pensar em muitos deles. Gostaria de descrever um dos pedidos de apoio recentes sobre o assunto de falsos positivos, o que levou a um pequeno estudo do problema descrito na carta.


Estamos trabalhando duro para reduzir o número de falsos positivos gerados pelo analisador PVS-Studio. Infelizmente, os analisadores estáticos geralmente não conseguem distinguir o código correto do erro, pois eles simplesmente não têm informações suficientes. Como resultado, existem falsos positivos de qualquer maneira. No entanto, isso não é um problema, pois, após o ajuste do analisador, é fácil obter uma situação em que 9 em cada 10 avisos indicem erros reais.

Embora os alarmes falsos não sejam um problema tão grande quanto possa parecer à primeira vista, estamos constantemente lutando com eles, melhorando o diagnóstico. Percebemos alguns falsos positivos flagrantes, alguns dos quais foram escritos por clientes e usuários gratuitos.

Recentemente, um de nossos clientes escreveu uma carta com aproximadamente o seguinte conteúdo:

Por alguma razão, o analisador diz que o ponteiro é sempre nulo, embora isso claramente não seja verdade. Além disso, o analisador se comporta de maneira estranha e instável em um projeto de teste: ou emite um aviso ou não. Um exemplo sintético que reproduz um 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; } 

Eu posso imaginar como respostas semelhantes parecem de nossos usuários. Fica imediatamente claro que a função GetNamedSecurityInfo altera o valor da variável pDACL . Os desenvolvedores poderiam realmente não conseguir escrever um manipulador para situações tão simples? Além disso, não está claro por que o analisador emite uma mensagem ou não. Talvez eles próprios tenham algum tipo de bug na ferramenta, como uma variável não inicializada?

Eh ... Não é uma tarefa fácil manter um analisador de código estático. Mas o que fazer, eu mesmo escolhi esse destino. Arregaçando as mangas, comecei a investigar a causa do falso positivo.

Para começar, estudei a descrição da função GetNamedSecurityInfo e certifiquei-me de que sua chamada realmente levasse a uma alteração no valor da variável pDACL . Aqui está uma descrição do argumento da 6ª função:
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.

Sei que o analisador PVS-Studio definitivamente deve processar corretamente esse código simples e não emitir um aviso sem sentido. Já neste momento, minha intuição me disse que esse seria um caso incomum, que terá que gastar tempo.

Confirmei meus medos quando não consegui reproduzir um falso positivo na versão alfa atual do analisador ou exatamente na versão que foi instalada no cliente. Assim e assim, mas o analisador está silencioso.

Pedi ao cliente para me enviar um arquivo i pré-processado gerado para um programa com um exemplo sintético. Ele gerou, enviou o arquivo e eu comecei a examiná-lo em detalhes.

No arquivo enviado, o analisador emitiu imediatamente um falso positivo. Por um lado, isso é bom, pois o bug é reproduzido. Por outro lado, experimentei os sentimentos que essa imagem melhor descreve.

sede


Por que exatamente esses? Sei muito bem como o analisador e o diagnóstico do V547 funcionam . Bem, não pode haver tal atuação!

Ok, faça chá e continue.

A chamada para a função GetNamedSecurityInfo se expande para:

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

Esse código parece o mesmo no meu próprio arquivo i pré-processado e no arquivo enviado pelo cliente.

Hmm ... Ok, agora vamos examinar como essa função é declarada. No meu arquivo eu vejo:

 __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, tudo é claro. Nada inesperado.

Em seguida, olho para o arquivo do cliente e ...

sede ???


Aí vejo algo da realidade paralela:

 __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 argumento formal para ppDacl está marcado como const .

Wat? WTF? Wat? WTF?

Que const !? De onde ele é!

Pelo menos, fica imediatamente claro que o analisador não tem culpa aqui 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 alterar o objeto ao qual o ponteiro se refere. Portanto, aqui:

 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 ser alterada e o analisador gera um aviso razoável (a expressão 'pDACL == 0' é sempre verdadeira.).

Por que um aviso é emitido é compreensível. Mas não está claro de onde essa const veio. Simplesmente não pode estar lá!

No entanto, há um palpite, e é confirmado por pesquisas na Internet. Acontece que existe um arquivo aclapi.h antigo e inválido, com uma descrição incorreta da função. Eu também encontrei dois links interessantes na Internet:


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 ); 

Então alguém queria consertar o tipo do argumento formal pObjectName , mas ao longo do caminho atrapalhou os tipos de ponteiros escrevendo const . E aclapi.h (6.1.7601.23418-Windows 7.0) ficou assim:

 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


Torna-se claro que é precisamente o arquivo aclapi.h errado que é usado pelo cliente. Mais tarde, ele confirmou esta hipótese em correspondência. Eu uso uma versão mais recente, para que o erro não seja reproduzido.

Aqui está a aparência da descrição da função já corrigida em aclapi.h (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 de argumento pObjectName permaneceu o mesmo, mas a const extra foi removida. Tudo voltou aos seus lugares, mas os arquivos de cabeçalho com declarações de função incorretas continuam vivendo no mundo.

Tudo isso eu digo ao cliente. Ele está satisfeito e satisfeito por a situação ter esclarecido. Além disso, ele encontra a razão pela qual ele vê um falso positivo ou não:

Esqueci que uma vez experimentei conjuntos de ferramentas neste projeto de teste. No projeto de teste de depuração, a configuração é configurada no conjunto de ferramentas da plataforma por padrão para o Visual Studio 2017 - "Visual Studio 2017 (v141)", mas a configuração da versão está configurada no "Visual Studio 2015 - Windows XP (v140_xp)". Ontem, em algum momento, mudei a configuração e o aviso apareceu e desapareceu.

Só isso. Você pode pôr um fim à investigação. Decidimos com o cliente que não faremos nenhum backup especial no analisador para que esse erro seja levado em consideração no arquivo de cabeçalho. O principal é que a situação agora está clara. Como diz o ditado, "o caso está encerrado".

Conclusão

O analisador PVS-Studio é um produto de software complexo que coleta muitas informações do código do programa e as utiliza para várias tecnologias de análise . Especificamente, nesse caso, a inteligência excessiva do analisador levou ao fato de que, devido a uma descrição incorreta da função, ele começou a produzir um falso positivo.

Torne-se nosso cliente e você receberá um suporte rápido e de qualidade de mim e de meus colegas.



Se você deseja compartilhar este artigo com um público que fala inglês, use o link para a tradução: Andrey Karpov. Positivos falsos no PVS-Studio: quão profunda é a toca do coelho .

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


All Articles