PVS-Studio中的误报:兔子洞有多深

独角兽PVS-Studio和GetNamedSecurityInfo

我们的团队提供快速有效的客户支持。 用户请求仅由程序员处理,因为我们的客户本身就是程序员,他们经常问一些棘手的问题。 今天,我将向您介绍一个关于误报的最新要求,该要求甚至迫使我进行一次小规模调查以解决问题。

我们努力将PVS-Studio产生的误报数量降至最低。 不幸的是,静态分析器通常无法分辨出错误的正确代码,因为它们只是没有足够的信息。 因此,误报是不可避免的。 但是,这不是问题,因为您可以轻松自定义分析仪,从而使十分之九的警告指向真正的错误。

尽管误报似乎没什么大不了,但我们从未通过改进诊断程序来与之抗衡。 我们的团队发现了一些公然的误报; 其他则由我们的客户和免费版本用户报告。

我们的一位客户最近向我们发送了一封电子邮件,内容如下:

由于某种原因,分析器会说某个指针始终为空,而并非如此。 而且,它在测试项目上的行为是奇怪且不稳定的:有时会发出警告,有时则不会。 这是一个合成的示例,可重现该误报:

#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; } 

不难猜测,我们的用户如何看到这样的误报。 GetNamedSecurityInfo函数显然会修改变量pDACL的值。 是什么导致开发人员无法为此类简单情况创建处理程序? 为什么不在每个会话中发出警告? 也许这是分析仪本身的错误,例如未初始化的变量?

las ...支持静态代码分析器的用户并非易事,但这是我自己的选择。 因此,我卷起袖子,开始研究问题。

我首先检查了GetNamedSecurityInfo函数的描述,并确保它的调用确实暗示着修改pDACL变量的值。 这是第六个参数的说明:
聚氯乙烯

指向变量的指针,该变量在返回的安全描述符中接收到指向DACL的指针;如果安全描述符没有DACL,则返回NULL。 仅当您设置DACL_SECURITY_INFORMATION标志时,返回的指针才有效。 另外,如果不需要DACL,则此参数可以为NULL。

我知道PVS-Studio显然应该能够处理这样的简单代码而不会产生错误警告。 到那时,我的直觉已经告诉我这个案子不是一件小事,要花很长时间才能解决。

当我无法使用当前的分析仪的Alpha版本或用户计算机上安装的版本来重现误报时,我的疑虑得到了证实。 不管我做什么,分析仪都保持沉默。

我要求客户将为示例程序生成的经过预处理的i文件发送给我。 他这样做了,我继续进行调查。

分析仪确实在该文件上立即产生了误报。 一方面,很好的是我终于设法重制了它。 另一方面,这张图片可以最好地说明我的感觉:

at


为什么会有这种感觉? 您知道,我非常了解分析仪和V547诊断程序如何工作。 他们根本不可能产生这样的误报!

好吧,让我们泡茶并继续。

GetNamedSecurityInfo函数的调用扩展为以下代码:

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

该代码在我的计算机上预处理的i文件和用户发送的文件中看起来都相同。

嗯...好,让我们看一下这个函数的声明。 这是我在文件中得到的:

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

一切都是逻辑清晰的。 没什么不寻常的。

然后我偷看用户的文件并...

扫管w


我在那里看到的不属于我们的现实:

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

注意,形式参数pp Dacl被标记为const

WTF? WTF?

那是什么常量 ! 这是在做什么!

好吧,至少我确信分析仪是无辜的,我可以捍卫它的荣誉。

该参数是指向常量对象的指针。 事实证明,从分析器的角度来看, GetNamedSecurityInfoW函数无法修改指针引用的对象。 因此,在以下代码中:

 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. 

pDACL变量不能更改,分析器正确地警告我们(表达式'pDACL == 0'始终为真。)。

好,现在我们知道是什么触发了警告。 我们仍然不知道const关键字的来源。 就是不能在那里!

好吧,我有一个猜测,我在互联网上发现的事实证实了这一点。 原来,文件aclapi.h的版本较旧,其功能说明不正确。 我还遇到了几个有趣的链接:


因此,曾有一段时间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 ); 

然后有人更改了pObjectName参数的类型,但是通过添加const关键字弄乱了指针的类型。 最终aclapi.h文件(6.1.7601.23418-Windows 7.0)如下所示:

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

差异1


现在很明显,我们的用户使用的是非常错误的aclapi.h版本,然后他在电子邮件中确认了该版本。 由于使用的是更新版本,因此无法重现该错误。

这就是最新的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 ); 

差异2


pObjectName参数的类型仍然相同,但是多余的const消失了。 一切都很好,但是仍然有坏的标题在使用。

我向客户解释了所有这些问题,他很高兴看到问题已解决。 此外,他发现了为什么误报率没有经常发生的原因:

我现在回想起前一段时间在这个测试项目上使用工具集进行实验。 默认情况下,Visual Studio 2017的“调试”配置设置为“平台工具集”-“ Visual Studio 2017(v141)”,而发布配置则设置为“ Visual Studio 2015-Windows XP(v140_xp)”。 昨天我只是在配置之间进行切换,并且警告会相应出现并消失。

仅此而已。 调查结束了。 我们与客户端讨论此问题,并决定不向分析器添加任何垃圾以使其能够处理此头文件错误。 最重要的是我们已经解决了问题。 正如他们所说,“解散案件”。

结论

PVS-Studio是一个复杂的软件产品,它从程序的代码中收集大量信息,并将其用于各种分析技术中 。 在这种特殊情况下,事实证明它太聪明了,由于功能描述不正确,最终导致误报。

成为我们的客户,您一定会得到我和我的队友的迅速专业支持。

Source: https://habr.com/ru/post/zh-CN441124/


All Articles