ايجابيات كاذبة في 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-preprocessed على جهاز الكمبيوتر الخاص بي والملف الذي أرسله المستخدم.

حسنًا ... حسنًا ، لنلقِ نظرة على إعلان هذه الوظيفة. إليك ما لدي في ملفي:

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

لاحظ أن المعلمة الرسمية pp Dacl تحمل علامة 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 على Platform Toolset بشكل افتراضي لبرنامج Visual Studio 2017 - "Visual Studio 2017 (v141)" ، في حين تم تعيين تكوين Release على "Visual Studio 2015 - Windows XP (v140_xp)." كنت مجرد التبديل بين التكوينات بالأمس ، وسيظهر التحذير ويختفي وفقًا لذلك.

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

الخاتمة

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

كن عملائنا ، ويضمن لك الحصول على دعم احترافي سريع مني ومن زملائي في الفريق.

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


All Articles