ايجابيات كاذبة في PVS-Studio: كيف عمق حفرة الأرنب

يونيكورن PVS-Studio و GetNamedSecurityInfo

يوفر فريقنا دعم العملاء بسرعة وكفاءة. المبرمجون هم وحدهم الذين يشاركون في الدعم ، لأن المبرمجين يقومون أيضًا بطرح الأسئلة وعلينا التفكير في الكثير منها. أود أن أصف أحد دعوات الدعم الأخيرة بشأن موضوع الإيجابيات الخاطئة ، والتي أدت إلى دراسة صغيرة كاملة للمشكلة الموضحة في الرسالة.


نحن نعمل بجد لتقليل عدد الإيجابيات الخاطئة الناتجة عن محلل PVS-Studio. لسوء الحظ ، لا يمكن للمحللين الاستاتيكيين في الغالب التمييز بين الشفرة الصحيحة والخطأ ، لأنهم ببساطة لا يملكون معلومات كافية. نتيجة لذلك ، هناك ايجابيات كاذبة على أي حال. ومع ذلك ، هذه ليست مشكلة ، لأن ضبط المحلل من السهل تحقيق موقف حيث تشير 9 من أصل 10 تحذيرات إلى أخطاء حقيقية.

على الرغم من أن الإنذارات الكاذبة ليست مشكلة كبيرة كما قد تبدو للوهلة الأولى ، إلا أننا نكافح باستمرار من أجل تحسين التشخيص. نلاحظ بعض الإيجابيات الكاذبة الصارخة بأنفسنا ، بعضها مكتوب من قبل العملاء والمستخدمين المجانيين.

في الآونة الأخيرة ، كتب أحد عملائنا خطابًا بالمحتوى التالي تقريبًا:

لسبب ما ، يقول المحلل إن المؤشر دائمًا لاغٍ ، على الرغم من أن هذا ليس واضحًا. علاوة على ذلك ، يتصرف المحلل بطريقة غريبة وغير مستقرة في مشروع اختبار: إما أنه يصدر تحذيرًا أو لا. مثال اصطناعي يستنسخ كاذبة إيجابية:

#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 . يمكن للمطورين حقا لا تكون قادرة على كتابة معالج لمثل هذه الحالات البسيطة؟ علاوة على ذلك ، ليس من الواضح سبب قيام المحلل بإصدار رسالة أم لا. ربما لديهم نوعًا من الأخطاء في الأداة ، مثل متغير غير مهيأ؟

ليس من السهل الحفاظ على محلل الكود الثابت. ولكن ماذا أفعل ، لقد اخترت نفسي مثل هذا المصير. نشمر عن سواعدي ، شرعت في التحقيق في سبب إيجابية كاذبة.

بادئ ذي بدء ، لقد درست وصف الدالة GetNamedSecurityInfo وتأكدت من أن الدعوة يجب أن تؤدي بالفعل إلى تغيير في قيمة متغير pDACL . فيما يلي وصف للوسيطة الدالة السادسة:
ppdacl

مؤشر إلى متغير يتلقى مؤشر إلى DACL في واصف الأمان الذي تم إرجاعه أو NULL إذا كان واصف الأمان لا يحتوي على DACL. يكون المؤشر المرتجع صالحًا فقط إذا قمت بتعيين علامة DACL_SECURITY_INFORMATION. أيضا ، يمكن أن تكون هذه المعلمة فارغة إذا كنت لا تحتاج إلى DACL.

أعلم أن محلل PVS-Studio يجب أن يعالج هذه الشفرة البسيطة بشكل صحيح وألا يعطي تحذيرًا لا معنى له. بالفعل في هذه اللحظة ، أخبرني الحدس أن هذه ستكون حالة غير عادية ، والتي يجب أن تقضي الوقت فيها.

أكدت مخاوفي عندما لم أتمكن من إعادة إنتاج إيجابية خاطئة سواء على الإصدار ألفا الحالي للمحلل ، أو بالضبط على الإصدار الذي تم تثبيته على العميل. كذا وكذا ، لكن المحلل صامت.

لقد طلبت من العميل أن يرسل لي ملف i مُجهز مسبقًا تم إنشاؤه لبرنامج يحتوي على مثال اصطناعي. قام بإنشاء الملف وإرساله ، وبدأت في فحصه بالتفصيل.

على الملف المرسلة ، أصدر المحلل على الفور إيجابية إيجابية كاذبة. من ناحية ، هذا أمر جيد ، حيث أن الخطأ مستنسخ. من ناحية أخرى ، واجهت المشاعر التي تصفها هذه الصورة بشكل أفضل.

المقر


لماذا هذه بالضبط؟ أعرف جيدًا كيف يعمل محلل وتشخيصات 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 ); 

كل شيء منطقي ، كل شيء واضح. لا شيء غير متوقع.

بعد ذلك ، أنظر إلى ملف العميل و ...

مقر ؟؟


هناك أرى شيئًا من الواقع الموازي:

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

لاحظ أن الوسيطة الرسمية لـ ppDacl تم تعليمها على أنها const .

وات؟ وتف؟ وات؟ وتف؟

ما هي const ! من أين هو؟

على الأقل من الواضح على الفور أن المحلل لا يتحمل اللوم هنا ويمكنني الدفاع عن شرفه.

الوسيطة مؤشر إلى كائن ثابت. اتضح أنه ، من وجهة نظر المحلل ، لا يمكن لـ 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 الإضافية. لقد عاد كل شيء إلى أماكنه ، لكن ملفات الرأس ذات الإعلانات الدالة غير الصحيحة تستمر في العيش في العالم.

كل هذا اقول للعميل. إنه سعيد ويسر أن الوضع قد تلاشى. علاوة على ذلك ، يجد السبب الذي يجعله يرى إيجابية كاذبة أم لا:

لقد نسيت أنني جربت مرة واحدة الأدوات في مشروع الاختبار هذا. في مشروع اختبار Debug ، يتم تكوين التكوين على "أدوات النظام الأساسي" افتراضيًا لـ Visual Studio 2017 - "Visual Studio 2017 (v141)" ، ولكن تم تكوين التكوين Release على "Visual Studio 2015 - Windows XP (v140_xp)". بالأمس ، قمت فقط في مرحلة ما بتغيير التكوين ، وظهر التحذير واختفى.

هذا كل شيء. يمكنك وضع حد للتحقيق. نقرر مع العميل أننا لن نقوم بعمل أي نسخ احتياطي خاص في المحلل بحيث يأخذ هذا الخطأ في الاعتبار في ملف الرأس. الشيء الرئيسي هو أن الوضع واضح الآن. كما يقول المثل ، "القضية مغلقة".

الخاتمة

PVS-Studio analyser هو منتج برمجي معقد يجمع الكثير من المعلومات من كود البرنامج ويستخدمه لتقنيات التحليل المختلفة. على وجه التحديد ، في هذه الحالة ، أدت الذكاء المفرط للمحلل إلى حقيقة أنه ، بسبب الوصف غير الصحيح للوظيفة ، بدأ ينتج عنه إيجابية خاطئة.

كن عميلاً لك وستحصل على دعم سريع وجيد مني ومن زملائي.



إذا كنت ترغب في مشاركة هذا المقال مع جمهور يتحدث الإنجليزية ، فالرجاء استخدام الرابط الخاص بالترجمة: Andrey Karpov. ايجابيات كاذبة في PVS-Studio: كيف عمق حفرة الأرنب يذهب .

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


All Articles