برنامه‌نويسی امن با زبان C – مديريت خطاها

برنامه‌نويسی امن با زبان C – مديريت خطاها

تاریخ ایجاد

IRCAR201105100
عنصر اصلي در برنامه‌نويسي امن با زبان‌هاي مختلف برنامه‌نويسي، مستندسازي خوب و استفاده از استانداردهاي قابل اجرا است. استانداردهاي كدنويسي، برنامه نويسان را ترغيب به پيروي از مجموعه‌اي متحدالشكل از قوانين و راهنمايي‌هايي مي‌كند كه بر اساس نيازمندي‌هاي پروژه و سازمان تعيين شده است، نه بر اساس سلايق و مهارت‌هاي مختلف برنامه‌نويسان. به محض تعيين استانداردهاي مذكور، ميتوان از آن به عنوان معياري براي ارزيابي كدهاي منبع، چه به صورت دستي و چه به صورت اتوماتيك استفاده كرد.
از استانداردهاي معروف در اين زمينه مي‌توان به استانداردCERT براي كدنويسي امن اشاره كرد كه يك سري از قوانين و پيشنهادات را براي كدنويسي امن با زبان‌هاي برنامه‌نويسي C، C++ و جاوا ارائه مي‌دهد. هدف از اين قوانين و پيشنهادات، حذف عادت‌هاي كدنويسي ناامن و رفتارهاي تعريف نشده است كه منجر به آسيب‌پذيري‌هاي قابل سوءاستفاده مي‌شود. به كارگيري استانداردهاي مذكور منجر به توليد سيستم‌هاي با كيفيت بالاتر مي‌شود كه در برابر حملات بالقوه، پايدارتر و مقاوم‌تر هستند.
در مقاله هاي قبلي، كليات استاندارد CERT در زمينه مزبور را توضيح داديم و در سري مقاله‌هاي برنامه‌نويسي امن به زبان C به صورت تخصصي‌تر شيوه برنامه‌نويسي امن با اين زبان را مورد بررسي قرار مي‌دهيم. قابل ذكر است كه در اين استاندارد 89 قانون و 134 پيشنهاد براي برنامه‌نويسي امن با زبان C ارائه شده است كه در اين سري مقالات، مهمترين آنها را كه در سطح يك قرار دارند، شرح خواهيم داد. براي كسب اطلاعات بيشتر در مورد سطح‌بندي قوانين و پيشنهادات به مقاله "آشنايي با استاندارد CERT براي برنامه نويسي امن" مراجعه فرماييد. در مقاله حاضر به يك قانون سطح اول مديريت خطاها خواهيم پرداخت.

41. ERR33-C – خطاها را تشخيص داده و برطرف سازيد.
بسياري از توابع يك مقدار معتبر را بر مي‌گردانند و يا مقداري را بر مي‌گردانند كه نشان‌دهنده بروز خطا است، براي مثال مي توان به -1 يا اشاره گر تهي (null pointer) اشاره كرد. فرض را بر اين گذاشتن كه توابع همواره اجراي موفقي خواهند داشت يكي از اشتباهات خطرناكي است كه منجر به بروز رفتار تعريف نشده و يا خارج از انتظار مي شود. بنابراين ضروري است كه برنامه ها خطاهاي احتمالي را تشخيص داده و به صورت مناسب آنها را با توجه به سياست رسيدگي به خطاها مديريت كنند.

استفاده ناصحيح از setlocale()
در مثال زير برنامه اي را مشاهده مي كنيد كه از قانون فوق پيروي نكرده است. تابع utf8_to_ucs() سعي دارد تا يك ترتيب از كاراكترهاي UTF-8 را به UCS تبديل كند. اين برنامه ابتدا تابع setlocale() را فراخواني مي‌كند تا تنظيمات را به طور كلي بر روي en_US.UTF-8 تنظيم كند، اما آزمون شكست تابع را در نظر نمي‌گيرد. تابع مذكور ممكن است به دلايل مختلفي از جمله كمبود منابع به درستي اجرا نشود و مقدار تهي (null) را برگرداند. در اينجا با توجه به توالي كاراكترهاي ارسال شده، فراخواني تابع بعدي، mbstowcs() ممكن است با شكست مواجه شده و يا منجر به ايجاد رشته اي ناخواسته از كاراكترها در بافر ucs شود.

size_t utf8_to_ucs(wchar_t *ucs, size_t n, const char *utf8) {
setlocale(LC_CTYPE, "en_US.UTF-8");
return mbstowcs(ucs, utf8, n);
}

استفاده صحيح از setlocale():
در برنامه اصلاح شده زير، برخلاف برنامه بالا، مقدار برگشتي از تابع setlocale() بررسي شده و در صورتي كه تابع به صورت موفق اجرا نشده باشد، از فراخواني mbstowcs() اجتناب مي كند. اين برنامه همچنين در انتها وضعيت locale را به حالت اوليه بازمي گرداند.

size_t utf8_to_ucs(wchar_t *ucs, size_t n, const char *utf8) {
const char *save;
save = setlocale(LC_CTYPE, "en_US.UTF-8");
if (NULL == save) {
/* Propagate error to caller */
return (size_t)-1;
}
n = mbstowcs(ucs, utf8, n);
if (NULL == setlocale(LC_CTYPE, save))
n = -1;
return n;
}
}

استفاده نادرست از malloc():
در اين برنامه كه از قانون فوق پيروي نكرده است، input_string در يك حافظه پويا به نام str ذخيره مي شود. در اينجا نتيجه بازگشتي malloc() قبل از اينكه str مقداردهي گردد، بررسي نمي شود. در نتيجه در صورتي كه malloc() موفق نباشد، برنامه رفتار نامشخصي خواهد داشت. در عمل معمولاً اين نوع خطاها منجر به خروج غير عادي از برنامه مي شود كه زمينه اي را براي حملات انكار سرويس فراهم مي آورد.

void f(char *input_string) {
size_t size = strlen(input_string) + 1;
char *str = (char *)malloc(size);
strcpy(str, input_string);
/* ... */
}

استفاده صحيح از malloc():
تابع malloc() نيز همچون ديگر توابع اختصاص دهي حافظه، يا يك اشاره گر تهي را برمي گرداند و يا اشاره گري كه به فضاي اختصاص داده شده اشاره مي كند. همواره مقدار برگشتي اين نوع توابع را قبل از ارجاع دادن به اشاره گر مذكور بررسي كنيد و وضعيت خطا را در صورتي كه null برگردانده شود به خوبي مديريت كنيد. زماني كه بازيافت از حالت شكست ممكن نيست اين مشكل را به تابع فراخوان انتقال دهيد.

int f(char *input_string) {
size_t size = strlen(input_string) + 1;
char *str = (char *)malloc(size);
if (str == NULL) {
/* Handle allocation failure and return error status */
return -1;
}
strcpy(str, input_string);
/* ... */
free(str);
return 0;
}
برچسب‌ها