IRCAR201008072
عنصر اصلي در برنامه نويسي امن با زبان هاي مختلف برنامه نويسي، مستند سازي خوب و استفاده از استانداردهاي قابل اجرا است. استانداردهاي كدنويسي، برنامه نويسان را ترغيب به پيروي از مجموعه اي متحدالشكل از قوانين و راهنماييها مي كند كه بر اساس نيازمندي هاي پروژه و سازمان تعيين شده است، نه بر اساس سلايق و مهارت هاي مختلف برنامه نويسان. به محض تعيين استانداردهاي مذكور، مي توان از آن به عنوان معياري براي ارزيابي كدهاي منبع، چه به صورت دستي و چه به صورت اتوماتيك استفاده كرد.
از استانداردهاي معروف در اين زمينه مي توان به استانداردCERT براي كدنويسي امن اشاره كرد كه يك سري از قوانين و پيشنهادات را براي كد نويسي امن با زبان هاي برنامه نويسي C، C++ و جاوا ارائه مي دهد. هدف از اين قوانين و پيشنهادات، حذف عادت هاي كدنويسي ناامن و رفتارهاي تعريف نشده است كه منجر به آسيب پذيري هاي قابل سوءاستفاده مي شود. به كارگيري استانداردهاي مذكور منجر به توليد سيستم هاي با كيفيت بالاتر مي شود كه در برابر حملات بالقوه، پايدارتر و مقاوم تر هستند.
در مقاله آشنايي با استاندارد CERT براي برنامه نويسي امن، كليات استاندارد CERT در زمينه مزبور را توضيح داديم و در سري مقاله هاي برنامه نويسي امن به زبان C به صورت تخصصي تر شيوه برنامه نويسي امن با اين زبان را مورد بررسي قرار مي دهيم. قابل ذكر است كه در اين استاندارد 89 قانون و 134 پيشنهاد براي برنامه نويسي امن با زبان C ارائه شده است كه در اين سري مقالات، مهمترين آنها را كه در سطح يك قرار دارند، شرح خواهيم داد. براي كسب اطلاعات بيشتر در مورد سطح بندي قوانين و پيشنهادات به مقاله "آشنايي با استاندارد CERT براي برنامه نويسي امن" مراجعه فرماييد. در مقاله قبلي در مورد قوانين و پيشنهادات سطح اول ارائه شده در مورد آرايه ها و همچنين پيشنهادات سطح اول ارائه شده براي رشته ها صحبت كرديم و در مقاله حاضر به قوانين ارائه شده سطح اول در مورد رشته ها خواهيم پرداخت.
رشته ها
رشته ها يكي از مفاهيم پايه در مهندسي نرم افزار هستند، اما در زبان C به صورت پيش فرض تعريف نشده اند. فرمتي كه براي پشتيباني متغيرهاي رشته اي در C استفاده مي شودNull-terminated byte string يا NTBS ناميده مي شود. اين نوع، حاوي توالي پيوسته اي از كاراكترها است كه اولين و آخرين آنها null است. زبان برنامه نويسي C رشته هاي كاراكتري تك بايتي، چند بايتي و Wide را پشتيباني مي كند. هر دو رشته هاي كاراكتري تك بايتي و چند بايتي به عنوان رشته هاي منتهي به null شناخته مي شوند و اصطلاحاً به آنها رشته هاي كاراكتري narrow گفته مي شود.
رشته ها در زبان برنامه نويسي C همچون آرايه اي كاراكترها پياده سازي مي شوند و لذا بسياري از مشكلات آنها همانند آرايه ها است. در نتيجه، قوانين و پيشنهادات ارائه شده براي آرايه ها در اينجا نيز بايد به كار گرفته شوند. در ادامه به توضيح قوانين ارائه شده در استاندارد برنامه نويسي امنCERT براي رشته ها مي پردازيم.
قوانين
18. قانون STR31-C: از كافي بودن فضاي حافظه تخصيص داده شده براي رشته ها و كاراكتر پاياني null اطمينان حاصل كنيد.
كپي كردن داده در يك بافر كه به اندازه كافي براي نگهداري داده مذكور بزرگ نيست، منجر به خطاي سرريز بافر مي شود. با وجودي كه خطاي مذكور به رشته هاي null-terminated byte (NTBS) محدود نمي شود، سرريز بافر معمولاً در دستكاري داده هاي NTBS رخ مي دهد. براي پيشگيري از خطاي مذكور لازم است از كافي بودن فضاي مقصد براي نگهداري داده هاي كاراكتري اطمينان حاصل كنيد.
برنامه آسيب پذيري كه در زير آمده حاوي خطايي است كه به Off-By-one مشهور است. در اين برنامه، حلقه For داده ها را از src در dest كپي مي كند. در اينجا بايت null پاياني ممكن است به اشتباه بعد از dest نوشته شود زيرا حلقه، كاراكتر پاياني null را كه بايد در انتهاي dest قرار داده شود، محاسبه نكرده است.
char dest[ARRAY_SIZE]; char src[ARRAY_SIZE]; size_t i; /* … */ for (i=0; src[i] && (i < sizeof(dest)); i++) { dest[i] = src[i]; } dest[i] = ‘’; /* … */
براي اصلاح كد مذكور، لازم است شرط پايان يافتن حلقه به صورتي تغيير كند تا كاراكتر پاياني null را نيز در نظر گيرد:
char dest[ARRAY_SIZE]; char src[ARRAY_SIZE]; size_t i; /* ... */ for (i=0; src[i] && (i < sizeof(dest)-1); i++) { dest[i] = src[i]; } dest[i] = ''; /* ... */
19. قانون STR32-C: استفاده از بايت null-terminate در صورت نياز.
رشته هاي NTBS بايد حاوي يك كاراكتر null در آخرين عنصر آرايه و يا قبل از آن باشند تا بتوان آنها را به طرز صحيحي به عنوان آرگومان توابع دستكاري رشته ها همچون strcpy() يا strlen() ارسال كرد. اين التزام به اين خاطر است كه توابع مذكور همچون ديگر توابع تعريف شده در C99، براي مشخص كردن طول يك رشته، وابسته به وجود كاراكتر null در انتهاي رشته هستند. همچنين انواع NTBS، قبل از اينكه بر روي يك آرايه كاراكتري تكرار شوند كه در آن شرط پايان حلقه به وجود كاراكتر Null در انتهاي رشته بستگي دارد، بايد حاوي كاراكتر Null در انتهاي رشته باشند، مانند آنچه در حلقه زير مشاهده مي كنيد كه يكي از شروط پايان حلقه مشاهده كاراكتر null است:
size_t i; char ntbs[16]; /* ... */ for (i = 0; i < sizeof(ntbs); ++i) { if (ntbs[i] == '') break; /* ... */ }
20. قانون STR33-C: طول رشته هاي كاراكتري wide را صحيح اندازه بگيريد.
اغلب ممكن است رشته هاي كاراكتري wide به صورت نادرست اندازه گيري شوند به اين دليل كه با رشته هاي narrow و يا رشته هاي كاراكتري چند بايتي اشتباه گرفته مي شوند. اندازه گيري اشتباه مي تواند منجر به خطاي سرريز بافر شود.
براي مثال در برنامه زير از strlen() براي مشخص كردن طول رشته كاراكتري wide استفاده شده است:
wchar_t wide_str1[] = L"0123456789"; wchar_t *wide_str2 = (wchar_t *)malloc(strlen(wide_str1) + 1); if (wide_str2 == NULL) { /* Handle Error */ } /* ... */ free(wide_str2); wide_str2 = NULL;
تابع strlen() تعداد كاراكترها در يك رشته منتهي به null را مي شمرد، در حالي كه كاراكترهاي wide حاوي بايت هاي null هستند، به خصوص زماني كه از مجموعه كاراكترهاي ASCII استفاده مي كنند. در نتيجه تابع strlen() تعداد كاراكترهاي قبل از اولين كاراكتر null را بر مي گرداند.
براي اصلاح كد مذكور بايد از توابع مخصوص رشته هاي كاراكتري wide استفاده كرد:
wchar_t wide_str1[] = L"0123456789"; wchar_t *wide_str2 = (wchar_t *)malloc( (wcslen(wide_str1) + 1) * sizeof(wchar_t) ); if (wide_str2 == NULL) { /* Handle Error */ } /* ... */ free(wide_str2); wide_str2 = NULL;
21. قانون STR35-C: داده ها را از يك منبع با اندازه نامشخص در يك آرايه با اندازه مشخص كپي نكنيد.
توابعي كه كپي هاي نامحدود انجام مي دهند، اغلب بر روي منطقي بودن اندازه ورودي خود تكيه مي كنند. ثابت شده است كه چنين فرض هايي اشتباه است و منجر به خطاي سرريز بافر مي شود. به همين دليل در استفاده از توابعي كه كپي هاي نامحدود انجام مي دهند بايد دقت كافي را مبذول داشت.
استفاده از توابع strcat() و strcpy() براي كپي كردن يك رشته با اندازه نامشخص در يك بافر با اندازه محدود يكي از متداول ترين دلايل بروز خطاي سرريز بافر و يكي از منابع اصلي ايجاد آسيب پذيري هاي امنيتي است. كد آسيب پذير زير فرض مي كند كه طول آرگومان هاي ورودي خط فرمان از 4096 بايت فراتر نمي رود و آرگومان هاي برنامه را در يك بافر با اندازه ثابت، بدون اينكه احتمال بروز سرريز بافر را بررسي كند، به هم متصل مي كند. فراخواني strcat() و يا هر تابع دستكاري رشته ديگر كه تلاش مي كند وراي اندازه مشخص شده بافر بنويسد، منجر به رفتارهاي تعريف نشده براي برنامه مي شود.
int main(int argc, char *argv[]) { char cmdline [4096]; cmdline[0] = ''; for (int i = 1; i < argc; ++i) { strcat(cmdline, argv [i]); strcat(cmdline, " "); } /* ... */ return 0; }
در كد اصلاح شده زير، فرضي در مورد طول حداكثر آرگومان هاي خط فرمان در نظر گرفته نشده است و به جاي آن از تركيب توابع malloc() و memset() براي اختصاص اتوماتيك فضاي حافظه كافي براي همه آرگومان ها استفاده شده است.
int main(int argc, char *argv[]) { size_t bufsize = 0; size_t buflen = 0; char* cmdline = NULL; for (int i = 1; i < argc; ++i) { const size_t len = strlen(argv[i]); if (bufsize - buflen <= len) { bufsize = (bufsize + len) * 2; cmdline = realloc(cmdline, bufsize); if (NULL == cmdline) return 1; /* realloc failure */ } memcpy(cmdline + buflen, argv[i], len); buflen += len; cmdline[buflen++] = ' '; } cmdline[buflen] = ''; /* ... */ free(cmdline); return 0; }
22. قانون STR36-C: براي يك آرايه كاراكتري كه با string literal مقداردهي اوليه شده است، حد بالا تعيين نكنيد.
زبان برنامه نويسي C استاندارد، اجازه مي دهد يك آرايه متغير هم توسط يك ايندكس محدود كننده و هم توسط يك عبارت اوليه تعريف شود. عبارت اوليه همچنين اندازه آرايه را با توجه به تعداد عناصر مشخص مي كند. براي رشته ها، اندازه آرايه، توسط تعداد كاراكترهاي موجود در رشته اوليه به اضافه يك كاراكتر Null پاياني محاسبه مي شود. در زبان C مصطلح است كه يك آرايه متغير از رشته ها توسط يك عبارت اوليه با اندازه مشخص كه مطابق تعداد كاراكترهاي موجود در رشته اوليه است، تعريف شود. با اين وجود اين مسئله در مورد كاراكترهايي كه با يك بايت null پايان مي پذيرند، منجر به بروز آسيب پذيري مي شود. بهترين راهكار براي پيشگيري از آسيب پذيري هاي مذكور، مشخص نكردن اندازه رشته در تعريف آرايه است، زيرا كامپايلر به صورت اتوماتيك فضاي حافظه كافي با توجه به كاراكترهاي Null پاياني را براي در بر گرفتن كل عبارت رشته اي، به آن اختصاص مي دهد.
در زير كدي را مي بينيد كه از يك عبارت اوليه رشته اي استفاده مي كند كه يك كاراكتر بيش از حد تعيين شده دارد:
const char s[3] = "abc";
براي اصلاح كد مذكور به راحتي مي توان حدي را براي آرايه مذكور در نظر نگرفت:
const char s[] = "abc";
23. قانون STR38-C: از توابع مربوط به رشته هاي كاراكتري wide بر روي رشته هاي كاراكتري narrow و بالعكس استفاده نكنيد.
كاراكترهاي wide اغلب حاوي كاراكترهاي null هستند به خصوص وقتي از مجموعه كاراكتر ASCII استفاده مي كنند. در نتيجه استفاده از توابع كاراكترهاي narrow كه بر روي كاراكتر پاياني Null تكيه مي كنند، منجر به رفتارهاي غيرقابل پيش بيني مي شود. همين طور، از توابع كاراكترهاي wide نيز نبايد بر روي رشته هاي كاراكتري narrow كه داراي بايت پاياني null هستند، استفاده كرد. استفاده نادرست از رشته هاي كاراكتري wide و narrow منجر به خطاي سرريز بافر مي شود.
- 37