عنصر
اصلی در برنامه نویسی امن با زبان های مختلف برنامه نویسی، مستند سازی خوب
و استفاده از استانداردهای قابل اجرا است. استانداردهای کدنویسی، برنامه
نویسان را ترغیب به پیروی از مجموعه ای متحدالشکل از قوانین و راهنماییها
می کند که بر اساس نیازمندی های پروژه و سازمان تعیین شده است، نه بر اساس
سلایق و مهارت های مختلف برنامه نویسان. به محض تعیین استانداردهای مذکور،
می توان از آن به عنوان معیاری برای ارزیابی کدهای منبع، چه به صورت دستی و
چه به صورت اتوماتیک استفاده کرد.
از استانداردهای معروف در این زمینه می توان به استانداردCERT برای کدنویسی امن اشاره کرد که یک سری از قوانین و پیشنهادات را برای کد نویسی امن با زبان های برنامه نویسی C، C++
و جاوا ارائه می دهد. هدف از این قوانین و پیشنهادات، حذف عادت های
کدنویسی ناامن و رفتارهای تعریف نشده است که منجر به آسیب پذیری های قابل
سوءاستفاده می شود. به کارگیری استانداردهای مذکور منجر به تولید سیستم های
با کیفیت بالاتر می شود که در برابر حملات بالقوه، پایدارتر و مقاوم تر
هستند.
در مقاله "آشنایی با استاندارد CERT برای برنامه نویسی امن"، کلیات استاندارد CERT در زمینه مزبور را توضیح دادیم و در سری مقاله های برنامه نویسی امن به زبان C
به صورت تخصصی تر شیوه برنامه نویسی امن با این زبان را مورد بررسی قرار
می دهیم. قابل ذکر است که در این استاندارد 89 قانون و 134 پیشنهاد برای
برنامه نویسی امن با زبان C
ارائه شده است که در این سری مقالات، مهمترین آنها را که در سطح یک قرار
دارند، شرح خواهیم داد. برای کسب اطلاعات بیشتر در مورد سطح بندی قوانین و
پیشنهادات به مقاله "آشنایی با استاندارد CERT برای برنامه نویسی امن" مراجعه فرمایید. در مقاله قبلی در مورد پیشنهادات سطح اول ارائه شده در مورد مدیریت حافظه صحبت کردیم و در مقاله حاضر به قوانین ارائه شده سطح اول در مورد مدیریت حافظه خواهیم پرداخت.
مدیریت حافظه
مدیریت حافظه پویا یکی از منابع متداول ایجاد حفره های برنامه نویسی است که منجر به آسیب پذیری
های امنیتی می شود. تصمیم گیری در مورد اینکه حافظه پویا چگونه اختصاص
داده شود، استفاده و آزاد شود، باری بر روی دوش برنامه نویسان است. مدیریت
حافظه ضعیف میتواند منجر به مشکلات امنیتی همچون سرریز بافر heap،
اشارهگرهای بدون مرجع و خطاهای دو بار آزاد کردن حافظه شود. از دیدگاه
برنامه نویسان، مدیریت حافظه، اختصاص حافظه، خواندن از و نوشتن در حافظه و
آزاد کردن حافظه است.
در زیر مهمترین قوانین برنامه نویسی امن برای پیشگیری از بروز آسیب پذیریهای مربوط به مدیریت حافظه، بر اساس استاندارد CERT آورده شده است.
قوانین مدیریت حافظه
26. MEM30-C – به حافظه های آزاد شده مراجعه نکنید.
با توجه به ISO/IEC 9899-1999 رفتار برنامه ای که از یک اشاره گر استفاده می کند که حافظه ارجاعی به آن توسط توابع free() یا realloc()
آزاد سازی شده، تعریف نشده است. خواندن از اشارهگری که حافظه مربوط به آن
آزاده شده، تعریف نشده است، زیرا مقدار اشاره گر غیر قابل تعیین بوده و می
تواند یک مقدار اشتباه و غلط انداز را نمایش دهد. همچنین این کار در برخی
از موارد منجر به مشکلات سخت افزاری می شود.
در واقع استفاده از حافظه بعد از آزاد سازی آن، منجر به تخریب ساختمان دادههایی میشود که برای مدیریت Heap
مورد استفاده قرار میگیرند. ارجاع به حافظههای آزادشده به عنوان مشکل
"اشارهگرهای معلق" شناخته میشود و دسترسی به این نوع اشارهگرها منجر به
آسیبپذیری های قابل سوءاستفاده میشود.
زمانی
که حافظهای آزاد میشود، ممکن است محتوای آن دست نخورده بماند و قابل
دسترسی باشد زیرا حافظه مذکور به صلاحدید مدیر حافظه پاک شده و یا مقداردهی
مجدد می شود. داده موجود در حافظه پاک شده ممکن است به نظر صحیح برسد، با
این وحود هر لحظه امکان تغییر غیرمنتظره آن وجود دارد که منجر به رفتار
نامشخص برنامه خواهد شد. به همین دلیل بسیار مهم است که تضمین شود، در
حافظه آزاد شده چیزی نوشته و یا از آن چیزی خوانده نمی شود.
در زیر یک نمونه برنامه که این قانون را نقض می کند مشاهده می کنید:
این قطعه برنامه به صورت زیر اصلاح می شود:
27. MEM31-C – تنها یک بار حافظه های پویا را آزاد کنید.
چندین
بار آزاد کردن حافظه دارای پیامدهای مشابه با دسترسی به حافظه ای است که
قبلاً آزاد شده است. اولاً خواند اشاره گر به حافظه ای که آزاد شده، تعریف
نشده است زیرا مقدار اشاره گر غیر قابل تعیین است و می تواند گمراه کننده
باشد. ثانیاً انجام چنین کاری ممکن است منجر به گیر انداختن سخت افزار شود.
زمانی که خواندن یک اشاره گر آزاد شده منجر به از کار افتادن برنامه نشود،
ممکن است ساختارهای داده زیربنایی که مدیریت heap
را به عهده دارند، به صورتی تخریب شوند که منجر به بروز آسیبپذیری های
امنیتی در برنامه شوند. به این نوع مشکلات آسیبپذیریهای آزادسازی مجدد (double-free) گفته میشود. در عمل، این نوع آسیبپذیریها در صورتی که مورد سوءاستفاده قرار گیرند منجر به اجرای کد دلخواه توسط هکر می شوند.
برای
حذف آسیبپذیری های آزادسازی مجدد، باید تضمین شود که حافظه های پویا تنها
یک بار آزاد می شوند. برنامه نویسان باید در آزاد کردن حافظه ها در
دستورات شرطی و یا حلقه ها بسیار محتاط عمل کنند، زیرا در صورتی که حافظه
ها به صورت نادرست آزاد شوند می توانند منجر به آسیبپذیری آزاد سازی مجدد
شوند. همچنین استفاده نادرست از realloc() می تواند منجر به آسیبپذیری های آزادسازی مجدد شود. برای کسب اطلاعات بیشتر در مورد این موضوع به MEM04 مراجعه شود.
در زیر کدی را مشاهده می کنید که در آن امکان دارد حافظه اختصاص داده شده دوبار، یک بار در شرط if و یک بار نیز در بدنه برنامه آزاد شود.
در زیر کد اصلاح شده را مشاهده می کنید:
28. MEM32-C – خطاهای تخصیص حافظه را شناسایی کرده و آنها را برطرف کنید.
مقادیر بازگشتی از روتین های تخصیص حافظه نشان دهنده شکست یا موفقیت اختصاص حافظه است. با توجه به C99، توابع calloc()، malloc() و realloc() در صورتی که تخصیص حافظه به درستی انجام نشود، مقدار null را بر می گردانند. برنامه نویسان نباید تصور کنند که heap برنامه نامحدود است و در برخی موارد تخصیص حافظه ناموفق به علت پر شدن Heap
بر اثر استفاده نامحدود پردازه ها از آن اتفاق می افتد. شکست در شناسایی و
مدیریت صحیح خطاهای تخصیص حافظه می تواند منجر به رفتارهای غیر قابل پیش
بینی و ناخواسته برنامه شود. در نتیجه، بسیار ضروری است که وضعیت نهایی
روتین مدیریت حافظه مشخص شده و خطاها به درستی و مطابق با استاندارد
برنامهنویسی امن مدیریت شوند. جدول زیر نشان دهنده خروجی توابع مدیریت
حافظه است:
29. MEM34-C – تنها حافظه های پویا را آزاد کنید.
آزاد
کردن حافظه ای که به صورت پویا تخصیص داده نشده است، می تواند منجر به
خطاهای جدی مشابه آنهایی که در قانون 31 توضیح داده شد، شود. نتایج مخصوص
این خطا بستگی به جزئیات پیاده سازی دارد، ممکن است هیچ اتفاقی نیفتد و
ممکن است برنامه به طرز غیر منتظره ای پایان یابد! با صرف نظر از پیاده
سازی، از تابع free() برای هیچ چیزی به جز اشارهگری که توسط یک تابع تخصیص حافظه پویا همچون malloc() و calloc() و یا realloc() برگردانده شده است، استفاده نکنید.
وضعیت مشابهی در مورد تابع realloc() پیش می آید زمانی که از آن برای متغیری که به صورت پویا حافظه دهی نشده است، استفاده می شود. تابع realloc() برای تغییر اندازه حافظه پویا استفاده می شود.
در زیر نمونه کدی را مشاهده می کنید که از قانون مذکور سرپیچی کرده است:
در اینجا نیز کد اصلاح شده را مشاهده می کنید: