حسب ترتيب المطورين المضمنين: البحث عن الأخطاء في Amazon FreeRTOS

كل من يقوم ببرامج ميكروكنترولر ربما يعرف عن FreeRTOS ، أو على الأقل سمع عن نظام التشغيل هذا. قرر Amazon Amazon توسيع قدرات نظام التشغيل هذا للعمل مع خدمات AWS Internet of Things - هكذا ظهرت Amazon FreeRTOS. طُلب منا ، مطورو محلل كود PVS-Studio ، التحقق من هذه المشاريع في البريد وفي التعليقات الواردة أدناه. حسنًا ، لقد طلبت - فعلنا ذلك. ما جاء منه - تابع القراءة.

الشكل 3

قليلا عن المشاريع


للبدء ، سوف أخبركم قليلاً عن "أبي" المشروع المحدد - FreeRTOS (يمكنك العثور على الكود المصدري هنا ). كما تقول ويكيبيديا ، فإن FreeRTOS هو نظام تشغيل متعدد المهام في الوقت الفعلي للأنظمة المدمجة.

لقد كُتب في لغة C القديمة الجيدة ، وهو أمر غير مفاجئ - يجب أن يعمل نظام التشغيل هذا في ظروف نموذجية للميكروكونترولر: طاقة حوسبة منخفضة ، كمية صغيرة من ذاكرة الوصول العشوائي ، وما شابه. تسمح لك لغة C بالعمل مع الموارد بمستوى منخفض ولديها أداء عالٍ ، لذلك فهي الأنسب لتطوير نظام التشغيل هذا.

عاد الآن إلى منطقة الأمازون ، التي لا تقف مكتوفة الأيدي وتتطور في مختلف المجالات الواعدة. على سبيل المثال ، تعمل أمازون على تطوير محرك AAA للعبة Amazon Lumberyard ، والذي قمنا باختباره أيضًا .

أحد هذه المجالات هو إنترنت الأشياء (إنترنت الأشياء ، إنترنت الأشياء). للتطوير في هذا المجال ، قررت Amazon كتابة نظام التشغيل الخاص بها - وأخذوا نواة FreeRTOS كأساس.

يتم وضع النظام الناتج - Amazon FreeRTOS - على أنه "يوفر القدرة على الاتصال بأمان بخدمات الويب من Amazon ، مثل AWS IoT Core أو AWS IoT Greengrass." يتم تخزين التعليمات البرمجية المصدر لهذا المشروع على جيثب.

في هذه المقالة ، سنبحث فيما إذا كانت هناك أخطاء في FreeRTOS ، وكذلك مدى أمان نظام التشغيل من Amazon من حيث تحليل الكود الثابت.

كيف كان الشيك


تم فحص الكود باستخدام أداة البحث التلقائي عن الأخطاء: محلل الكود الثابت PVS-Studio. إنه قادر على اكتشاف الأخطاء في البرامج المكتوبة بلغات C و C ++ و C # و Java.

قبل البدء في التحليل ، من الضروري تجميع المشروع - لذلك سأكون متأكدًا من أن لدي كل التبعيات اللازمة وكل شيء يتوافق مع المشروع. هناك عدة طرق للتحقق من المشروع - على سبيل المثال ، باستخدام نظام مراقبة الترجمة. لقد أجريت التحليل باستخدام المكون الإضافي لبرنامج Visual Studio - من الجيد أن تحتوي مستودعات كلا المشروعين على مجموعة من ملفات المشاريع التي تسهل الإنشاء في نظام Windows.

كل ما كان مطلوبًا بالنسبة لي هو جمع المشروعات للتأكد من وجود كل ما هو ضروري للتحقق. بعد ذلك ، أطلقت التحليل وفويلا! - أمامي تقرير محلل جاهز.

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

لذلك ، يتم تحليل المشاريع ، تلقي التقارير ، يتم كتابة أخطاء مثيرة للاهتمام. حان الوقت للانتقال إلى تحليلهم!

ما يخفي FreeRTOS


في البداية ، كنت أتوقع أن أكتب مقالتين منفصلتين: واحدة لكل نظام تشغيل. لقد فركت يدي بالفعل ، وأعد لكتابة مقال جيد حول FreeRTOS. توقعًا لاكتشاف بضع أخطاء على الأقل (مثل CWE-457 ) ، نظرت بشغف إلى تحذيرات المحلل القليلة ، و ... ولا شيء. لم أجد أي خطأ مثير للاهتمام.

العديد من التحذيرات التي أصدرها المحلل لم تكن ذات صلة بـ FreeRTOS. على سبيل المثال ، كانت هذه التحذيرات من عيوب 64 بت مثل casting size_t إلى uint32_t . هذا يرجع إلى حقيقة أن FreeRTOS مصمم للعمل على أجهزة بحجم مؤشر لا يزيد عن 32 بت.

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

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

بشكل عام ، أريد الاتصال بمطوري FreeRTOS. يا رفاق رائعة حقًا! لم نلتق تقريبًا بمشاريع نظيفة وعالية الجودة مثل مشاريعك. وسرني جدًا أن أقرأ رمزًا نظيفًا ومرتبًا وموثقًا جيدًا. القبعات قبالة لك.

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

ما يخفي الأمازون FreeRTOS


تحولت نسخة نظام أمازون إلى ... لتكون معتدلة ، أسوأ قليلاً. ظل تراث FreeRTOS نظيفًا تمامًا ، لكن المراجعات الجديدة اتضح أنها مثيرة جدًا للاهتمام.

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

شيء تأخرت مع المقدمة. دعنا نبدأ في تحليل الأخطاء!

انتهاك منطق البرنامج


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

/** * @brief Pool of request and associated response buffers, * handles, and configurations. */ static _requestPool_t _requestPool = { 0 }; .... static int _scheduleAsyncRequest(int reqIndex, uint32_t currentRange) { .... /* Set the user private data to use in the asynchronous callback context. */ _requestPool.pRequestDatas[reqIndex].pConnHandle = &_connHandle; _requestPool.pRequestDatas[reqIndex].pConnConfig = &_connConfig; _requestPool.pRequestDatas[reqIndex].reqNum = reqIndex; _requestPool.pRequestDatas[reqIndex].currRange = currentRange; _requestPool.pRequestDatas[reqIndex].currDownloaded = 0; _requestPool.pRequestDatas[reqIndex].numReqBytes = numReqBytes; .... _requestPool.pRequestDatas->scheduled = true; .... } 

أصدر PVS-Studio تحذيرين لهذا الجزء من الكود:

  • V619 يتم استخدام الصفيف '_requestPool.pRequestDatas' كمؤشر لكائن مفرد. iot_demo_https_s3_download_async.c 973
  • V574 يتم استخدام مؤشر '_requestPool.pRequestDatas' في نفس الوقت كصفيف وكمؤشر لكائن واحد. خطوط التحقق: 931 ، 973. iot_demo_https_s3_download_async.c 973

فقط في حالة اسمح لي أن أذكركم: اسم المصفوفة هو مؤشر على العنصر الأول. هذا إذا كان _requestPool.pRequestDatas عبارة عن صفيف من الهياكل ، فإن _requestPool.pRequestDatas [i] .scheduled هو الوصول إلى العضو المجدول في البنية i للصفيف. وإذا قمت بكتابة _requestPool.pRequestDatas-> المجدولة ، فهذا سيعني الوصول إلى العضو المجدول في البنية الأولى للصفيف.

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

كما أفهمها ، يجب أن يبدو السطر الأخير كما يلي:

 _requestPool.pRequestDatas[reqIndex].scheduled = true; 

الخطأ التالي يكمن في وظيفة صغيرة ، لذلك سأقدمها بالكامل:

 /* Return true if the string " pcString" is found * inside the token pxTok in JSON file pcJson. */ static BaseType_t prvGGDJsoneq( const char * pcJson, const jsmntok_t * const pxTok, const char * pcString ) { uint32_t ulStringSize = ( uint32_t ) pxTok->end - ( uint32_t ) pxTok->start; BaseType_t xStatus = pdFALSE; if( pxTok->type == JSMN_STRING ) { if( ( uint32_t ) strlen( pcString ) == ulStringSize ) { if( ( int16_t ) strncmp( &pcJson[ pxTok->start ], // <= pcString, ulStringSize ) == 0 ) { xStatus = pdTRUE; } } } return xStatus; } 

تحذير PVS-Studio: V642 [CWE-197] يعد حفظ نتيجة دالة strncmp داخل متغير النوع "القصير" غير مناسب. يمكن أن تضيع البتات كبيرة كسر منطق البرنامج. aws_greengrass_discovery.c 637

دعنا نلقي نظرة على تعريف وظيفة strncmp:

 int strncmp( const char *lhs, const char *rhs, size_t count ); 

في المثال ، يتم تحويل نتيجة type int ، التي يبلغ حجمها 32 بت ، إلى متغير من النوع int16_t . مع مثل هذا التحويل "الضيق" ، سيتم فقد الأجزاء الأكثر أهمية من قيمة الإرجاع. على سبيل المثال ، إذا كانت دالة strncmp تُرجع 0x00010000 ، فستضيع الوظيفة أثناء التحويل ، وسيتم استيفاء الشرط.

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

سلوك غير محدد ومؤشرات


الآن سيكون هناك مثال كبير إلى حد ما. يخفي التراجع المحتمل لمؤشر فارغ:

 static void _networkReceiveCallback(....) { IotHttpsReturnCode_t status = IOT_HTTPS_OK; _httpsResponse_t* pCurrentHttpsResponse = NULL; IotLink_t* pQItem = NULL; .... /* Get the response from the response queue. */ IotMutex_Lock(&(pHttpsConnection->connectionMutex)); pQItem = IotDeQueue_PeekHead(&(pHttpsConnection->respQ)); IotMutex_Unlock(&(pHttpsConnection->connectionMutex)); /* If the receive callback is invoked * and there is no response expected, * then this a violation of the HTTP/1.1 protocol. */ if (pQItem == NULL) { IotLogError(....); fatalDisconnect = true; status = IOT_HTTPS_NETWORK_ERROR; goto iotCleanup; } .... iotCleanup : /* Report errors back to the application. */ if (status != IOT_HTTPS_OK) { if ( pCurrentHttpsResponse->isAsync && pCurrentHttpsResponse->pCallbacks->errorCallback) { pCurrentHttpsResponse->pCallbacks->errorCallback(....); } pCurrentHttpsResponse->syncStatus = status; } .... } 

تحذير PVS-Studio: V522 [CWE-690] قد يكون هناك إلغاء مرجعية لمؤشر فارغ محتمل "pCurrentHttpsResponse". iot_https_client.c 1184

dereferences المشكلة هي في أسفل إذا . دعونا نرى ما يحدث هنا.

في بداية الوظيفة ، تتم تهيئة متغيرات pCurrentHttpsResponse و pQItem إلى NULL ، ويتم تهيئة متغير الحالة إلى IOT_HTTPS_OK ، مما يعني أن كل شيء يسير بسلاسة.

بعد ذلك ، يتم تعيين pQItem القيمة التي يتم إرجاعها من دالة IotDeQueue_PeekHead ، والتي تُرجع مؤشرًا إلى بداية قائمة انتظار متصلة بشكل مضاعف.

ماذا يحدث إذا كانت قائمة الانتظار فارغة؟ في هذه الحالة ، ترجع الدالة IotDeQueue_PeekHead NULL :

 static inline IotLink_t* IotDeQueue_PeekHead (const IotDeQueue_t* const pQueue) { return IotListDouble_PeekHead(pQueue); } .... static inline IotLink_t* IotListDouble_PeekHead (const IotListDouble_t* const pList) /* @[declare_linear_containers_list_double_peekhead] */ { IotLink_t* pHead = NULL; if (pList != NULL) { if (IotListDouble_IsEmpty(pList) == false) { pHead = pList->pNext; } } return pHead; } 

بعد ذلك ، الشرط pQItem == NULL ، ويتم التحكم في تدفق التحكم عبر الانتقال إلى الجزء السفلي من الجسم الوظيفي. بحلول هذا الوقت ، سيظل مؤشر pCurrentHttpsResponse خاليًا ، ولن تكون الحالة مساوية لـ IOT_HTTPS_OK . نتيجة لذلك ، سنقع في هذا الفرع بالذات إذا ، و ... برودز! عواقب هذا dereferencing أنت نفسك تعرف.

حسنا. كان مثالا مزخرف قليلا. الآن أود أن ألفت انتباهكم إلى إلغاء التسجيل المحتمل البسيط والمفهوم:

 int PKI_mbedTLSSignatureToPkcs11Signature (uint8_t * pxSignaturePKCS, uint8_t * pxMbedSignature ) { int xReturn = 0; uint8_t * pxNextLength; /* The 4th byte contains the length of the R component */ uint8_t ucSigComponentLength = pxMbedSignature[ 3 ]; // <= if( ( pxSignaturePKCS == NULL ) || ( pxMbedSignature == NULL ) ) { xReturn = FAILURE; } .... } 

تحذير PVS-Studio: V595 [CWE-476] تم استخدام مؤشر "pxMbedSignature" قبل أن يتم التحقق منه ضد nullptr. خطوط التحقق: 52 ، 54. iot_pki_utils.c 52

تحصل هذه الوظيفة على مؤشرين إلى uint8_t . يتم التحقق من كلا المؤشرين لـ NULL ، وهي ممارسة جيدة - يجب حل هذه المواقف على الفور.

ولكن هذا هو الحظ السيئ: بحلول الوقت الذي يتم فيه تحديد pxMbedSignature ، سيتم بالفعل إلغاء تأجيله حرفيًا سطر واحد أعلاه. تا-DAA!

مثال آخر على كود المضاربة:

 CK_RV vAppendSHA256AlgorithmIdentifierSequence ( uint8_t * x32ByteHashedMessage, uint8_t * x51ByteHashOidBuffer ) { CK_RV xResult = CKR_OK; uint8_t xOidSequence[] = pkcs11STUFF_APPENDED_TO_RSA_SIG; if( ( x32ByteHashedMessage == NULL ) || ( x51ByteHashOidBuffer == NULL ) ) { xResult = CKR_ARGUMENTS_BAD; } memcpy( x51ByteHashOidBuffer, xOidSequence, sizeof( xOidSequence ) ); memcpy( &x51ByteHashOidBuffer[ sizeof( xOidSequence ) ], x32ByteHashedMessage, 32 ); return xResult; } 

تحذيرات PVS-Studio:

  • V1004 [CWE-628] تم استخدام مؤشر 'x51ByteHashOidBuffer' بطريقة غير آمنة بعد أن تم التحقق منه ضد nullptr. خطوط التحقق: 275 ، 280. iot_pkcs11.c 280
  • V1004 [CWE-628] تم استخدام مؤشر 'x32ByteHashedMessage' بطريقة غير آمنة بعد أن تم التحقق منه ضد nullptr. خطوط التحقق: 275 ، 281. iot_pkcs11.c 281

يحذر المحلل من أن معلمات الوظائف التي هي مؤشرات تستخدم بشكل غير آمن بعد أن تم اختبارها من أجل NULL . في الواقع ، يتم التحقق من الوسيطات ، ولكن إذا تبين أن أي منها فارغ ، فلا يتم اتخاذ أي إجراء ، باستثناء الكتابة إلى xResult . يبدو أن هذه الشفرة تقول: "نعم ، هذا يعني أن الحجج كانت سيئة. سنكتبها الآن ، بينما تستمر ، تابع. "

خلاصة القول: سيتم تمرير NULL إلى memcpy . ماذا يمكن أن يأتي من هذا؟ أين سيتم نسخ القيم وأيها؟ في الواقع ، التخمين حول هذا الأمر لا يستحق كل هذا العناء ، لأنه ينص المعيار بوضوح على أن مثل هذه الدعوة تؤدي إلى سلوك غير محدد (انظر الفقرة 1).

الشكل 2


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

صحيح! = 1


العديد من الأخطاء التي وجدتها كانت مرتبطة بنمط واحد ، والذي ، للأسف ، غالباً ما يتم نسيانه.

الحقيقة هي أن نوع bool (من C ++) يختلف عن نوع BOOL (يستخدم عادةً في C). الأول يمكن أن يحتوي فقط على صواب أو خطأ . والثاني هو typedef لبعض أنواع الأعداد الصحيحة ( int ، طويلة ، إلخ). بالنسبة له ، "false" هي القيمة 0 ، و "true" هي أي قيمة غير الصفر.

نظرًا لعدم وجود نوع منطقي مدمج في C ، يتم تعريف هذه الثوابت للراحة:

 #define FALSE 0 #define TRUE 1 

الآن النظر في مثال:

 int mbedtls_hardware_poll(void* data, unsigned char* output, size_t len, size_t* olen) { int lStatus = MBEDTLS_ERR_ENTROPY_SOURCE_FAILED; HCRYPTPROV hProv = 0; /* Unferenced parameter. */ (void)data; /* * This is port-specific for the Windows simulator, * so just use Crypto API. */ if (TRUE == CryptAcquireContextA( &hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { if (TRUE == CryptGenRandom(hProv, len, output)) { lStatus = 0; *olen = len; } CryptReleaseContext(hProv, 0); } return lStatus; } 

تحذيرات PVS-Studio:

  • V676 [CWE-253] من الخطأ مقارنة متغير نوع BOOL مع TRUE. aws_entropy_hardware_poll.c 48
  • V676 [CWE-253] من الخطأ مقارنة متغير نوع BOOL مع TRUE. التعبير الصحيح هو: 'FALSE! = CryptGenRandom (hProv ، len ، الإخراج)'. aws_entropy_hardware_poll.c 51

وجدت خطأ؟ وهو :) وظائف CryptAcquireContextA و CryptGenRandom هي وظائف قياسية من رأس wincrypt.h . إذا نجحت ، فإنها تُرجع قيمة غير صفرية. أؤكد - غير الصفر . لذلك ، من الناحية النظرية ، يمكن أن تكون هذه أي قيمة غير الصفر: 1 ، 314 ، 42 ، 420 .

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

مع ما هو الاحتمال الشرط TRUE == CryptGenRandom (....) لم يتحقق؟ من الصعب القول. ربما تقوم CryptGenRandom بإرجاع وحدة في كثير من الأحيان أكثر من القيم الأخرى ، وربما ترجع دائمًا وحدة واحدة فقط. لا يمكننا أن نعرف على وجه اليقين: تنفيذ هذه الوظيفة التشفير مخفي عن أعين المبرمجين البشر :)

من المهم أن نتذكر أن هذه المقارنات قد تكون خطيرة. وبدلاً من:

 if (TRUE == GetBOOL()) 

استخدم خيارًا أكثر أمانًا:

 if (FALSE != GetBOOL()) 

قضايا التحسين


ارتبطت العديد من تحذيرات المحلل ببنيات التشغيل البطيء. على سبيل المثال:

 int _IotHttpsDemo_GetS3ObjectFileSize(....) { .... pFileSizeStr = strstr(contentRangeValStr, "/"); .... } 

تحذير PVS-Studio: V817 يعتبر البحث عن حرف "/" أكثر فعالية من البحث عن سلسلة. iot_demo_https_common.c 205

باختصار وواضح ، أليس كذلك؟ يتم استخدام وظيفة strstr هنا للبحث عن حرف واحد فقط ، والذي يتم تمريره إلى المعلمة كسلسلة (يتم تضمينه في علامات اقتباس مزدوجة).

يمكن تحسين هذا المكان عن طريق استبدال strstr بـ strchr :

 int _IotHttpsDemo_GetS3ObjectFileSize(....) { .... pFileSizeStr = strchr(contentRangeValStr, '/'); .... } 

ثم البحث سوف تعمل بشكل أسرع قليلا. تافه ، ولكن لطيفة.

هذه التحسينات جيدة ، بالطبع ، وقد وجد المحلل مكانًا آخر يمكن تحسينه بشكل ملحوظ أكثر:

 void vRunOTAUpdateDemo(void) { .... for (; ; ) { .... xConnectInfo.cleanSession = true; xConnectInfo.clientIdentifierLength = (uint16_t)strlen(clientcredentialIOT_THING_NAME); xConnectInfo.pClientIdentifier = clientcredentialIOT_THING_NAME; .... } } 

تحذير PVS-Studio: V814 انخفاض الأداء. تم استدعاء الوظيفة "strlen" عدة مرات داخل جسم الحلقة. aws_iot_ota_update_demo.c 235

Hmmm ... داخل الحلقة ، في كل تكرار ، يتم استدعاء strlen ، والذي يقوم في كل مرة بحساب طول نفس الخط. ليست العملية الأكثر كفاءة :)

دعونا نلقي نظرة على تعريف clientcredentialIOT_THING_NAME :

 /* * @brief Host name. * * @todo Set this to the unique name of your IoT Thing. */ #define clientcredentialIOT_THING_NAME "" 

تتم مطالبة المستخدم بإدخال اسم الجهاز الخاص به هنا. بشكل افتراضي ، إنه فارغ ، وفي هذه الحالة ، كل شيء على ما يرام. ولكن ماذا لو أراد المستخدم إدخال اسم طويل وجميل هناك؟ على سبيل المثال ، أحب أن أسمي بنات أفكاري " آلة القهوة العاطفية والمتطورة BarBarista-N061E The Ultimate Edition ". هل يمكنك أن تتخيل ما سيكون مفاجأتي إذا بدأت بعد ذلك آلة القهوة الجميلة في العمل بشكل أبطأ قليلاً؟ اضطراب!

لإصلاح الخطأ ، يجب إخراج strlen من جسم الحلقة. بعد كل شيء ، لا يتغير اسم الجهاز أثناء تشغيل البرنامج. Ehhh ، هنا سيكون constexpr من C ++ ...

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

بضع كلمات عن ميسرا


يحتوي محلل PVS-Studio على مجموعة كبيرة من القواعد التي تسمح لك بالتحقق من الكود الخاص بك للتأكد من امتثاله لمعايير MISRA C و MISRA C ++. ما هي هذه المعايير؟

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

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

يستخدم MISRA في الصناعات الجوية والطبية والعسكرية - حيث تعتمد حياة البشر على جودة البرامج المدمجة.

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

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

لنبدأ مع وحدات الماكرو:

 #define FreeRTOS_ms_to_tick(ms) ( ( ms * configTICK_RATE_HZ + 500 ) / 1000 ) 

 #define SOCKETS_htonl( ulIn ) ( ( uint32_t ) \ ( ( ( ulIn & 0xFF ) << 24 ) | ( ( ulIn & 0xFF00 ) << 8 ) \ | ( ( ulIn & 0xFF0000 ) >> 8 ) | ( ( ulIn & 0xFF000000 ) >> 24 ) ) ) 

 #define LEFT_ROTATE( x, c ) ( ( x << c ) | ( x >> ( 32 - c ) ) ) 

تحذيرات PVS-Studio:

  • V2546 [MISRA C 20.7] يجب وضع الماكرو ومعلماته بين قوسين. النظر في فحص المعلمة "مللي ثانية" من الماكرو "FreeRTOS_ms_to_tick". FreeRTOS_IP.h 201
  • V2546 [MISRA C 20.7] يجب وضع الماكرو ومعلماته بين قوسين. النظر في فحص المعلمة 'ulIn' من الماكرو 'SOCKETS_htonl'. iot_secure_sockets.h 512
  • V2546 [MISRA C 20.7] يجب وضع الماكرو ومعلماته بين قوسين. ضع في اعتبارك فحص المعلمات 'x' و 'c' للماكرو 'LEFT_ROTATE'. iot_device_metrics.c 90

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

 val = LEFT_ROTATE(A[i] | 1, B); 

بعد ذلك ، سيتم فتح ماكرو "call" في:

 val = ( ( A[i] | 1 << B ) | ( A[i] | 1 >> ( 32 - B ) ) ); 

تذكر أولويات العمليات؟ أولاً ، يتم إجراء تحول bitwise ، وفقط بعد ذلك يكون "bitwise" أو "bit". لذلك ، سيتم انتهاك منطق البرنامج. مثال أبسط: ماذا يحدث إذا تم تمرير التعبير " x + y " إلى الماكرو FreeRTOS_ms_to_tick ؟ أحد الأهداف الرئيسية لـ MISRA هو منع حدوث مثل هذه الحالات.

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

أنتقل إلى مطوري Amazon FreeRTOS: وجدت PVS-Studio 12 أكثر من وحدات الماكرو غير الآمنة ، لذلك أنت أكثر حذرا معهم :)

انتهاك MISRA آخر مثير للاهتمام:

 /** * @brief Callback for an asynchronous request to notify * that the response is complete. * * @param[in] 0pPrivData - User private data configured * with the HTTPS Client library request configuration. * @param[in] respHandle - Identifier for the current response finished. * @param[in] rc - Return code from the HTTPS Client Library * signaling a possible error. * @param[in] status - The HTTP response status. */ static void _responseCompleteCallback(void* pPrivData, IotHttpsResponseHandle_t respHandle, IotHttpsReturnCode_t rc, uint16_t status) { bool* pUploadSuccess = (bool*)pPrivData; /* When the remote server response with 200 OK, the file was successfully uploaded. */ if (status == IOT_HTTPS_STATUS_OK) { *pUploadSuccess = true; } else { *pUploadSuccess = false; } /* Post to the semaphore that the upload is finished. */ IotSemaphore_Post(&(_uploadFinishedSem)); } 

يمكنك أن تجد الخطأ نفسك؟

تحذير PVS-Studio: V2537 [MISRA C 2.7] يجب ألا تحتوي الوظائف على معلمات غير مستخدمة. النظر في فحص المعلمة: 'rc'. iot_demo_https_s3_upload_async.c 234

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

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

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

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

الشكل 1


استنتاج


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

شكرا لقراءة مقالاتنا! نراكم في العدد القادم: د

ملحوظة: لقد حدث أن نُشرت هذه المقالة في 31 أكتوبر. لذلك ، أتمنى للجميع عيد سعيد!



إذا كنت ترغب في مشاركة هذا المقال مع جمهور ناطق باللغة الإنجليزية ، فيرجى استخدام الرابط الخاص بالترجمة: George Gribkov. بناءً على طلب المطورين المضمنين: اكتشاف الأخطاء في Amazon FreeRTOS .

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


All Articles