SARIF SDK وأخطاءه

الصورة 2

اليوم ، لدينا مشروع Microsoft آخر عالي الجودة يتم فحصه ، والذي سنخوضه بطوليًا في محاولة للعثور على أخطاء باستخدام PVS-Studio. SARIF ، وهو اختصار لتنسيق تبادل التحليل الثابت ، وهو معيار (تنسيق ملف) ، مصمم للتفاعل ومشاركة نتائج أجهزة التحليل الثابتة مع أدوات أخرى: IDEs وأدوات التحقق والتحقق من الشفرة المعقدة (مثل SonarQube) وأنظمة التكامل المستمر ، إلخ يحتوي SDIF SDK ، على التوالي ، على أدوات مطور .NET لدعم SARIF وملفات إضافية.

تم إنشاء SARIF في Microsoft وهو الآن معيار طورته OASIS (اتحاد غير ربحي يتعامل مع المعايير المفتوحة). يُقصد بـ SARIF تمرير ليس فقط نتائج المحلل ، ولكن أيضًا البيانات الوصفية حول الأداة ، بالإضافة إلى البيانات الخاصة بكيفية إطلاقها وعلامات الوقت وما إلى ذلك. لمزيد من المعلومات ، يرجى زيارة موقع OASIS . يمكن تنزيل التعليمات البرمجية المصدر لـ SARIF SDK من المستودع على GiHub . الصفحة الرئيسية للمشروع متاحة عن طريق الرابط .

عن المشروع


تحول مشروع SARIF SDK إلى حجم صغير: 799 ملف .cs (حوالي 98000 سطر غير فارغ من التعليمات البرمجية). يحتوي المشروع على اختبارات أستبعدها دائمًا من الفحص. وبالتالي ، كان جزء الكود الذي كنا مهتمين به هو 642 ملف .cs (حوالي 79000 سطر غير فارغ من الشفرة). بالتأكيد ليست كافية. على الجانب الإيجابي ، كان الفحص والتحليل سهلاً وسريعًا ، بين هذا وذاك ، والذي حاولت التفكير فيه في الصورة في البداية. ومع ذلك ، تمكنت من تعقب بعض الحالات الغريبة. دعونا نلقي نظرة عليهم.

أخطاء


V3070 [CWE-457] يستخدم متغير غير مهيأ "ثنائي" عند تهيئة المتغير "الافتراضي". MimeType.cs 90

public static class MimeType { .... /// <summary>The MIME type to use when no better MIME type is known.</summary> public static readonly string Default = Binary; .... /// <summary>The MIME type for binaries.</summary> public static readonly string Binary = "application/octet-stream"; .... } 

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

يتم دائمًا إعادة كتابة المعلمة V3061 "logicalLocationToIndexMap" في نص الطريقة قبل استخدامها. PrereleaseCompatibilityTransformer.cs 1963

 private static JArray ConvertLogicalLocationsDictionaryToArray( .... Dictionary<LogicalLocation, int> logicalLocationToIndexMap, ....) { .... logicalLocationToIndexMap = new Dictionary<LogicalLocation, int>(LogicalLocation.ValueComparer); .... } 

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

 private static bool ApplyChangesFromTC25ThroughTC30(....) { .... Dictionary<LogicalLocation, int> logicalLocationToIndexMap = null; .... logicalLocationToIndexMap = new Dictionary<LogicalLocation, int>(LogicalLocation.ValueComparer); run["logicalLocations"] = ConvertLogicalLocationsDictionaryToArray( ...., logicalLocationToIndexMap, ....); } 

رمز غريب ومشبوه.

V3008 [CWE-563] تم تعيين متغير "run.Tool" لقيم مرتين متتاليتين. ربما هذا خطأ. خطوط التحقق: 116 ، 114. ExportRulesMetadataCommandBase.cs 116

 public partial class Run { .... public Tool Tool { get; set; } .... } public partial class Tool : .... { .... public Tool() { } .... } private void OutputSarifRulesMetada(....) { .... var run = new Run(); run.Tool = new Tool(); run.Tool = Tool.CreateFromAssemblyData(....); // <= .... } 

يتم تعيين الخاصية run.Tool قيمة مرتين. عند إنشاء كائن Tool وعند كتابة قيمة في خاصية Tool ، لا يلزم القيام بعمل إضافي. لذلك ، إعادة تعيين رائحة مريب.

V3042 [CWE-476] ممكن NullReferenceException. "؟" و "." يتم استخدام عوامل التشغيل للوصول إلى أعضاء الكائن "loc" WhereComparer.cs 152

 private static Uri ArtifactUri(ArtifactLocation loc, Run run) { return loc?.Uri ?? loc.Resolve(run)?.Uri; } 

إذا كانت قيمة المتغير loc فارغة ، فسيتم إجراء محاولة لإرجاع القيمة من الجزء الأيمن من ؟؟ المشغل ، مما أدى إلى الوصول بواسطة مرجع فارغة.

V3042 [CWE-476] ممكن NullReferenceException. "؟" و "." تستخدم عوامل التشغيل للوصول إلى أعضاء كائن "formatString" InsertOptionalDataVisitor.cs 194

 public override Message VisitMessage(Message node) { .... node.Text = node.Arguments?.Count > 0 ? string.Format(...., formatString.Text, ....) : formatString?.Text; .... } 

يستخدم المطورون خيارات وصول غير آمنة ومأمونة من خلال مرجع stringString محتمل الخالي في فرعين متوازيين من الشرطية: المشغل.

V3042 [CWE-476] ممكن NullReferenceException. "؟" و "." تستخدم عوامل التشغيل للوصول إلى أعضاء كائن "messageText" FortifyFprConverter.cs 1210

V3042 [CWE-476] ممكن NullReferenceException. "؟" و "." تستخدم عوامل التشغيل للوصول إلى أعضاء كائن "messageText" FortifyFprConverter.cs 1216

 private void AddMessagesToResult(Result result) { .... string messageText = (rule.ShortDescription ?? rule.FullDescription)?.Text; .... if (....) { // Replace the token with an embedded hyperlink. messageText = messageText.Replace(....); } else { // Replace the token with plain text. messageText = messageText.Replace(....); } .... } 

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

V3080 [CWE-476] احتمال إلغاء فارغة. النظر في فحص 'fileDataVersionOne.Uri'. SarifCurrentToVersionOneVisitor.cs 1030

 private IDictionary<string, FileDataVersionOne> CreateFileDataVersionOneDictionary() { .... FileDataVersionOne fileDataVersionOne = CreateFileDataVersionOne(v2File); if (fileDataVersionOne.Uri.OriginalString.Equals(key)) { .... } .... } 

يشتبه المحلل في أن NullReferenceException ممكن عند العمل مع المرجع fileDataVersionOne.Uri . دعونا نرى من أين يأتي هذا المتغير ومعرفة ما إذا كان المحلل هو الصحيح. للقيام بذلك ، دعنا نلقي نظرة فاحصة على نص أسلوب CreateFileDataVersionOne :

 private FileDataVersionOne CreateFileDataVersionOne(Artifact v2FileData) { FileDataVersionOne fileData = null; if (v2FileData != null) { .... fileData = new FileDataVersionOne { .... Uri = v2FileData.Location?.Uri, .... }; .... } return fileData; } public partial class FileDataVersionOne { .... public Uri Uri { get; set; } .... } 

بالفعل ، عند إنشاء كائن فئة FileDataVersionOne ، قد تتلقى خاصية Uri القيمة الخالية . هذا هو مثال عظيم تحليل تدفق البيانات وآليات التحليل interprocedural العمل معا.

V3080 [CWE-476] احتمال إلغاء فارغة. النظر في تفتيش '_jsonTextWriter'. SarifLogger.cs 242

 public virtual void Dispose() { .... if (_closeWriterOnDispose) { if (_textWriter != null) { _textWriter.Dispose(); } if (_jsonTextWriter == null) { _jsonTextWriter.Close(); } // <= } .... } 

هناك خطأ مطبعي في هذه الشظية. من الواضح أن _jsonTextWriter! = يجب أن تكون خالية في حالة الكتلة الثانية. تعرض هذه الشفرة للخطر ، على الأرجح ، لا تتعطل ، نظرًا لأن _jsonTextWriter غير صحيح . الى جانب ذلك ، لا يزال الدفق مفتوحًا.

V3083 [CWE-367] الاحتجاج غير الآمن للحدث "RuleRead" ، NullReferenceException ممكن. النظر في تعيين الحدث إلى متغير محلي قبل استدعاء ذلك. FxCopConverter.cs 897

 private void ReadRule(....) { .... if (RuleRead != null) { RuleRead(....); } .... } 

يتم التعامل مع الأحداث بشكل غير آمن. إنه خطأ لا يمكن إصلاحه بسهولة ، على سبيل المثال ، باتباع نصيحة Visual Studio. وإليك البديل الذي اقترحه IDE:

 private void ReadRule(....) { .... RuleRead?.Invoke(....); .... } 

لا يستغرق الأمر سوى بضع ثوانٍ لإصلاحه ، لكن المحلل لن يشتكي منه بعد الآن ولن يبرز IDE الكود. خطأ مماثل آخر.

  • V3083 [CWE-367] الاحتجاج غير الآمن للحدث "ResultRead" ، NullReferenceException ممكن. النظر في تعيين الحدث إلى متغير محلي قبل استدعاء ذلك. FxCopConverter.cs 813

V3095 [CWE-476] تم استخدام كائن 'v1Location' قبل التحقق من صحته. خطوط التحقق: 333 ، 335. SarifVersionOneToCurrentVisitor.cs 333

 internal Location CreateLocation(LocationVersionOne v1Location) { .... string key = v1Location.LogicalLocationKey ?? v1Location.FullyQualifiedLogicalName; if (v1Location != null) { .... } .... } 

اعتقد المؤلف أن مرجع v1Location قد يكون باطلاً وأضاف شيكًا مناسبًا. بينما في الأعلى يمكننا أن نرى أن هذا المرجع يتم التعامل معه دون أي شيكات. غفلة إعادة بيع المنازل؟ حسنا ، أنت لا تعرف أبدا.

V3125 [CWE-476] تم استخدام كائن 'v1StackFrame' بعد أن تم التحقق منه ضد قيمة خالية. خطوط التحقق: 1182 ، 1171. SarifVersionOneToCurrentVisitor.cs 1182

 internal StackFrame CreateStackFrame(StackFrameVersionOne v1StackFrame) { StackFrame stackFrame = null; if (v1StackFrame != null) { stackFrame = new StackFrame { .... }; } stackFrame.Location = CreateLocation(v1StackFrame.FullyQualifiedLogicalName, v1StackFrame.LogicalLocationKey, ....); return stackFrame; } 

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

 Frames = v1Stack.Frames?.Select(CreateStackFrame).ToList() 

يستخدم CreateStackFrame كمحدد. لا يتم التحقق من المراجع التي تم اجتيازها فارغة . ربما ، عند ملء مجموعة الإطارات ، يتم التحكم في ذلك (كتابة المراجع الفارغة) ، لكنني لم أذهب للحفر بعمق كبير. الاستنتاج واضح بالفعل - تتطلب الشفرة اهتمام المؤلفين.

استنتاج


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

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

لمعرفة المزيد حول منشورات المدونة الجديدة ، نرحب بك للاشتراك في القنوات التالية:

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


All Articles