IRCAR201106103
تاريخ: 19/03/90
عنصر اصلي در برنامهنويسي امن با زبانهاي مختلف برنامهنويسي، مستندسازي خوب و استفاده از استانداردهاي قابل اجرا است. استانداردهاي كدنويسي، برنامهنويسان را ترغيب به پيروي از مجموعهاي متحدالشكل از قوانين و راهنماييهايي ميكند كه بر اساس نيازمنديهاي پروژه و سازمان تعيين شده است، نه بر اساس سلايق و مهارتهاي مختلف برنامهنويسان. به محض تعيين استانداردهاي مذكور، ميتوان از آن به عنوان معياري براي ارزيابي كدهاي منبع، چه به صورت دستي و چه به صورت اتوماتيك استفاده كرد.
از استانداردهاي معروف در اين زمينه ميتوان به استانداردCERT براي كدنويسي امن اشاره كرد كه يك سري از قوانين و پيشنهادات را براي كدنويسي امن با زبانهاي برنامهنويسي C، C++ و جاوا ارائه ميدهد. هدف از اين قوانين و پيشنهادات، حذف عادتهاي كدنويسي ناامن و رفتارهاي تعريف نشده است كه منجر به آسيبپذيريهاي قابل سوءاستفاده ميشود. به كارگيري استانداردهاي مذكور منجر به توليد سيستمهاي با كيفيت بالاتر ميشود كه در برابر حملات بالقوه، پايدارتر و مقاومتر هستند.
در مقالههاي قبلي، كليات استاندارد CERT در زمينه مزبور را توضيح داديم و در سري مقالههاي برنامهنويسي امن به زبان C به صورت تخصصيتر شيوه برنامهنويسي امن با اين زبان را مورد بررسي قرار ميدهيم. قابل ذكر است كه در اين استاندارد 89 قانون و 134 پيشنهاد براي برنامهنويسي امن با زبان C ارائه شده است كه در اين سري مقالات، مهمترين آنها را كه در سطح يك قرار دارند، شرح خواهيم داد. براي كسب اطلاعات بيشتر در مورد سطحبندي قوانين و پيشنهادات به مقاله "آشنايي با استاندارد CERT براي برنامه نويسي امن" مراجعه فرماييد. سري مقالات حاضر با عنوان قوانين و پيشنهادات تكميلي (Miscellaneous) آخرين سري مقالات برنامهنويسي امن به زبان C است كه در آن قوانين و پيشنهاداتي توضيح داده ميشوند كه در دسته بنديهاي قبلي نگنجيدهاند.
42. MSC15-C – به رفتارهاي تعريف نشده وابسته نشويد.
در C99، بخش 3.4.3 رفتار نامشخص به صورت زير تعريف شده است:
"رفتاري كه بر اثر استفاده از برنامهاي داراي ساختار نادرست يا داده هاي نادرست به وجود ميآيد و اين استاندارد يك نيازمندي را در مورد آن اعمال نميكند."
در بخش چهارم استاندارد C99 در مورد چگونگي تشخيص رفتار تعريف نشده توضيح داده شده است:
" در صورتي كه نيازمندي "بايد" يا "نبايد" (shall or shall not) كه در بيرون يك محدوديت آمده است، نقض شود، رفتار توليد شده تعربفنشده خواهد بود. همچنين رفتار تعريفنشده در اين استاندارد يا با كلمات "undefined behaviour"مشخص شده است يا با حذف هر گونه تعريف روشني از رفتار معين ميشود. هيچ اولويتي در مورد اين سه روش وجود ندارد و هر سه نشاندهنده رفتار تعريفنشده هستند."
رفتار تعريفنشده ميتواند تحت شرايط بسيار متنوعي اتفاق بيفتد كه از آن جمله ميتوان به ناديده گرفتن برخي شرايط كه منجر به نتايج پيشبيني نشده ميشود اشاره كرد، براي مثال در نظر نگرفتن شرايط استثنائي. از آنجايي كه كامپايلرها ملزم به توليد كد براي رفتارهاي نامشخص نيستند، اين رفتارها كانديداي خوبي براي بهينهسازيها هستند. با اطمينان از اينكه رفتارهاي نامشخص اتفاق نخواهند افتاد، كامپايلرها ميتوانند كدهايي را با كارايي بالاتر توليد كنند.
متأسفانه رفتارهاي نامشخص اتفاق ميافتند به خصوص اگر مهاجمي حضور داشته باشد. بهينه سازيها تشخيص رفتار سيستم در حضور رفتارهاي نامشخص را مشكل ميسازند. اين مسئله همچنين براي كسي كه در حال مرور كد است، مشكل ايجاد ميكند زيرا در صورتي كه كد كامپايل و اجرا شود، تشخيص خطا بسيار سخت است. از طرف ديگر اينكه كامپايلر در حال حاضر كد اجرايي براي يك رفتار نامشخص توليد مي كند، دليل نميشود كه نسخههاي بعدي كامپايلر نيز اين كار را در آينده انجام دهند. همچنين كامپايلرها از اساس براي تشخيص رفتارهاي تعريف نشده طراحي نشدهاند و بنابراين همان طور كه گفتيم تشخيص اين نوع رفتارها را در كد سخت ميكنند.
همه مسائل فوق بار سنگيني را بر دوش برنامهنويس ميگذارند تا كدي را كه دقيقاً از استانداردهاي بينالمللي پيروي ميكند(strictly conforming)، توليد كند.
در زير نمونه كدي را مشاهده ميكنيد كه داراي يك رفتار نامشخص است كه مربوط به سرريز عدد صحيح علامتدار ميشود. اين برنامه به رفتار مذكور براي مديريت سرريزبافر وابسته است.
#include <assert.h> int foo(int a) { assert(a + 100 > a); printf("%d %d\n", a + 100, a); return a; } int main(void) { foo(100); foo(INT_MAX); }
اين برنامه از طريق دستور شرطي if a+1>a آزمايش ميكند كه آيا سرريز عدد صحيح اتفاق ميافتد يا خير. اين آزمون هيچگاه نتيجه نادرست را بر نميگرداند مگر اينكه سرريز عدد صحيح اتفاق بيفتد. از آنجايي كه يك پياده سازي استاندارد الزامي به توليد كد براي رفتار نامشخص ندارد و سرريز عدد صحيح علامتدار نيز يك رفتار نامشخص است، در نتيجه كد مي تواند كامپايل شود. براي مثال GCC نسخه 4.1.1 اين كد را براي همه سطوح بهينه سازي بهينه ميكند.
از طرف ديگر در برخي از سكوها، سرريز عدد صحيح، قبل از اينكه فرصت آزمون آن به وجود آيد، منجر به خروج از برنامه ميشود.
در زير كد اصلاح شده را مشاهده مي كنيد كه وابسته به رفتار تأييد نشده نيست:
#include <assert.h> int foo(int a) { assert(a < (INT_MAX - 100)); printf("%d %d\n", a + 100, a); return a; } int main(void) { foo(100); foo(INT_MAX); }
با وجود اينكه تقريباً محال است كه همه برنامه كاربردي كاملاً از استانداردها پيروي كند ولي لازم است هدف اين باشد كه اغلب برنامه اينگونه طراحي شده باشد (به اين معني كه از رفتارهاي نامشخص اجتناب كند) و همچنين برنامهنويس دقت داشته باشد كه ماژولها و قسمتهايي كه به پيادهسازي وابسته هستند و بايد با تغيير سكو تغيير پيدا كنند، حتماً تغييرات لازم بر آنها اعمال شده باشد.
- 6