عنصر اصلی در برنامه نویسی امن با زبان های مختلف برنامه نویسی، مستند سازی خوب و استفاده از استانداردهای قابل اجرا است. استانداردهای کدنویسی، برنامه نویسان را ترغیب به پیروی از مجموعه ای متحدالشکل از قوانین و راهنماییها می کند که بر اساس نیازمندی های پروژه و سازمان تعیین شده است، نه بر اساس سلایق و مهارت های مختلف برنامه نویسان. به محض تعیین استانداردهای مذکور، می توان از آن به عنوان معیاری برای ارزیابی کدهای منبع، چه به صورت دستی و چه به صورت اتوماتیک استفاده کرد.
از استانداردهای معروف در این زمینه می توان به استانداردCERT برای کدنویسی امن اشاره کرد که یک سری از قوانین و پیشنهادات را برای کد نویسی امن با زبان های برنامه نویسی C، C++ و جاوا ارائه می دهد. هدف از این قوانین و پیشنهادات، حذف عادت های کدنویسی ناامن و رفتارهای تعریف نشده است که منجر به آسیب پذیری های قابل سوءاستفاده می شود. به کارگیری استانداردهای مذکور منجر به تولید سیستم های با کیفیت بالاتر می شود که در برابر حملات بالقوه، پایدارتر و مقاوم تر هستند.
در مقاله "آشنایی با استاندارد CERT برای برنامه نویسی امن"، کلیات استاندارد CERT در زمینه مزبور را توضیح دادیم و در سری مقاله های برنامه نویسی امن به زبان C به صورت تخصصی تر شیوه برنامه نویسی امن با این زبان را مورد بررسی قرار می دهیم. قابل ذکر است که در این استاندارد 89 قانون و 134 پیشنهاد برای برنامه نویسی امن با زبان C ارائه شده است که در این سری مقالات، مهمترین آنها را که در سطح یک قرار دارند، شرح خواهیم داد. برای کسب اطلاعات بیشتر در مورد سطح بندی قوانین و پیشنهادات به مقاله "آشنایی با استاندارد CERT برای برنامه نویسی امن" مراجعه فرمایید. در مقاله قبلی در مورد قوانین سطح اول ارائه شده در مورد آشته ها صحبت کردیم و در مقاله حاضر به پیشنهادات ارائه شده سطح اول در مورد مدیریت حافظه خواهیم پرداخت.
مدیریت حافظه پویا یکی از منابع متداول ایجاد حفره های برنامه نویسی است که منجر به آسیب پذیری های امنیتی می شود. تصمیم گیری در مورد اینکه حافظه پویا چگونه اختصاص داده شود، استفاده شود و آزاد شود، باری بر روی دوش برنامه نویسان است. مدیریت حافظه ضعیف می تواند منجر به مشکلات امنیتی همچون سرریز بافر heap، اشاره گرهای بدون مرجع و خطاهای دو بار آزاد کردن حافظه شود. از دیدگاه برنامه نویسان، مدیریت حافظه، اختصاص حافظه، خواندن از و نوشتن در حافظه و آزاد کردن حافظه است.
در زیر مهمترین پیشنهادات و قوانین برنامه نویسی امن برای پیش گیری از بروز آسیب پذیری های مربوط به مدیریت حافظه، بر اساس استاندارد CERT آورده شده است.
24. MEM00-C – اختصاص دادن و آزاد کردن حافظه را در یک ماژول، در سطحی یکسان انجام دهید.
اختصاص دادن و آزاد کردن حافظه در ماژول ها و سطوح مختلف تجرید (abstraction)، تشخیص زمان و چگونگی آزادسازی حافظه را مشکل ساخته و منجر به نقص های برنامه نویسی همچون نشت اطلاعات حافظه، آسیب پذیری های دوبار آزاد سازی حافظه، دسترسی به حافظه های آزاد یا نوشتن در حافظه های آزاد شده و یا اختصاص داده نشده می شود.
برای پیشگیری از چنین وضعیت هایی، حافظه باید در سطح یکسانی از تجرید و به صورت ایده آل در یک ماژول اختصاص داده شده و آزاد گردد. این مسئله شامل تابع های تخصیص و آزاد سازی حافظه که در ISO/IEC 9899:1999 توضیح داده شده است می شود. توابع مذکور را در زیر مشاهده می کنید:
در زیر کدی را مشاهده می کنید که از پیشنهاد مذکور استفاده نکرده است. در این کد حافظه دو بار آزاد می شود، زیرا در سطوح مختلفی از تجرید اختصاص داده شده و آزاد گشته است. در این مثال، حافظه آرایه list در تابع process_list() اختصاص داده شده است. سپس آرایه مذکور به تابع verify_size() فرستاده شده است که مسئول بررسی وجود خطا در اندازه آرایه است. در صورتی که اندازه آرایه زیر یک مقدار حداقل باشد، حافظه ای که اختصاص داده شده آزاد می شود و کنترل برنامه به تابع فراخوان بر می گردد. تابع فراخوان نیز حافظه مذکور را برای بار دوم آزاد می کند. این رفتار به صورت بالقوه یک آسیب پذیری قابل سوءاستفاده است. همچنین فراخوانی آزاد کردن حافظه در تابع verify_size() اتفاق می افتد که زیرتابعی از process_list() است. لذا اختصاص و آزاد کردن حافظه در دو سطح متفاوت تجرید اتفاق می افتد و پیشنهاد مذکور نقض می شود.
برای اصلاح مشکل مذکور، کد رسیدگی به خطا در تابع verify_size به گونه ای تغییر پیدا کرده است که حافظه آرایه را آزاد نمی کند. این تغییر تضمین می کند که حافظه آرایه list تنها یک بار و در سطح یکسانی از تجرید، در تابع process_list() آزاد می شود.
25. MEM08-C – از realloc() تنها برای تغییر اندازه آرایه هایی که به صورت پویا حافظه دهی شده اند، استفاده کنید.
"شئ قدیمی را که اشاره گر ptr به آن اشاره می کند آزاد کرده و یک اشاره گر به شئ جدیدی را بر می گرداند که اندازه آن توسط پارامتر size مشخص شده است. محتویات شئ جدید باید مشابه محتویات شئ قدیمی قبل از آزاد سازی باشد. در صورتی که اندازه حافظه شئ جدید بیش از شئ قدیمی باشد، هر بایت اضافی حاوی محتویات نامشخصی خواهد بود."
در زیر یک کد را مشاهده می کنید که با این پیشنهاد همخوانی ندارد. در این مثال از realloc() برای اختصاص حافظه به شئ با یک نوع خاص استفاده شده است ولی آن را با یک نوع دیگر مقدار دهی اولیه می کند.
در اینجا فراخوانی realloc() برای تخصیص حافظه به widget است ولی آن را مانند یک gadget مقدار دهی اولیه می کند. این مسئله در بهترین حالت منجر به تبدیل انواع از char * به int و یا از int به char * می شود. حتی ممکن است یک double را با استفاده از محتویات اشاره گر، مقدار دهی اولیه کند.
یک برنامه تنها باید از realloc() برای حافظه گیری مجدد آرایه های پویا استفاده کند. همچنین لازم است از آرایه های با نوع یکسان اما با ابعاد متفاوت برای جابجایی استفاده کند. یک نمونه کد سازگار با این پیشنهاد را در زیر مشاهده می کنید:
در مقاله بعدی در مورد قوانین ارائه شده برای مدیریت حافظه صحبت خواهیم کرد.