SARIF SDK وأخطائه

الصورة 2


اليوم ، نحن نختبر مشروعًا آخر عالي الجودة من Microsoft ، ما زلنا نحاول فيه البحث عن الأخطاء باستخدام PVS-Studio. يرمز SARIF إلى تنسيق تبادل نتائج التحليل الثابت ، وهو معيار (تنسيق ملف) مصمم للتفاعل وتبادل نتائج أجهزة التحليل الثابتة مع أدوات أخرى: IDEs ، وأدوات شاملة للتحقق من الشفرة وتحليلها (على سبيل المثال ، SonarQube) ، وأنظمة التكامل المستمر إلخ يحتوي SARIF 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 كمحدد. لا يوجد التحقق من مراجع المساواة فارغة . ربما في مكان ما عند ملء مجموعة الإطارات ، يتم التحكم في هذا (تسجيل المراجع الصفرية) ، لكنني لم أحفر هذا الحد. الاستنتاج واضح بالفعل - تتطلب الشفرة انتباه المؤلفين.

استنتاج


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

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

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




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

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


All Articles