عنصر
اصلی در کدنویسی امن با زبان های مختلف برنامه نویسی، مستند سازی خوب و
استفاده از استانداردهای قابل اجرا است. استانداردهای کدنویسی، برنامه
نویسان را ترغیب به پیروی از مجموعه ای متحدالشکل از قوانین و راهنماییها
می کند که بر اساس نیازمندی های پروژه و سازمان تعیین شده است، نه بر اساس
سلایق و مهارت های مختلف برنامه نویسان. به محض تعیین استانداردهای مذکور،
می توان از آن به عنوان معیاری برای ارزیابی کدهای منبع، چه به صورت دستی و
چه به صورت اتوماتیک استفاده کرد. از استانداردهای معروف در این زمینه می
توان به استانداردCERT برای کدنویسی امن اشاره کرد که یک سری از قوانین و
پیشنهادات را برای کد نویسی امن با زبان های برنامه نویسی C، C++ و جاوا
ارائه می دهد. هدف از این قوانین و پیشنهادات، حذف عادت های کدنویسی ناامن و
رفتارهای تعریف نشده است که منجر به آسیب پذیری های قابل سوءاستفاده می
شود. به کارگیری استانداردهای مذکور منجر به تولید سیستم های با کیفیت
بالاتر می شود که در برابر حملات بالقوه، پایدارتر و مقاوم تر هستند. در
مقاله "آشنایی با استاندارد CERT برای برنامه نویسی امن"، کلیات استاندارد
CERT در زمینه مزبور را توضیح دادیم و در این مقاله به صورت تخصصی تر شیوه
برنامه نویسی امن با زبان C را مورد بررسی قرار می دهیم. قابل ذکر است که
در این استاندارد 89 قانون و 134 پیشنهاد برای برنامه نویسی امن با زبان C
ارائه شده است که در سری مقالات کدنویسی امن با زبان C، مهمترین آنها را که
در سطح یک قرار دارند، شرح خواهیم داد. برای کسب اطلاعات بیشتر در مورد
سطح بندی قوانین و پیشنهادات به مقاله "آشنایی با استاندارد CERT برای
برنامه نویسی امن" مراجعه فرمایید.
عبارات یا Expressions)EXP)
پیشنهاد EXP01-C : از طول اشاره گر برای تعیین کردن طول نوعی که به آن اشاره می کند، استفاده نکنید
از طول اشاره گر برای تعیین کردن طول نوعی که به آن اشاره می کند، استفاده نکنید. دقت کنید که گرفتن طول "اشاره گر به یک نوع خاص"، همواره طول اشاره گر را بر می گرداند و نه طول نوعی را که به آن اشاره می کند. این مسئله در زمان محاسبه طول آرایه ها بسیار مشکل ساز می شود. برای مثال کد زیر به اشتباه از طول اشاره گر به جای طول آرایه استفاده می کند:
از طول اشاره گر برای تعیین کردن طول نوعی که به آن اشاره می کند، استفاده نکنید. دقت کنید که گرفتن طول "اشاره گر به یک نوع خاص"، همواره طول اشاره گر را بر می گرداند و نه طول نوعی را که به آن اشاره می کند. این مسئله در زمان محاسبه طول آرایه ها بسیار مشکل ساز می شود. برای مثال کد زیر به اشتباه از طول اشاره گر به جای طول آرایه استفاده می کند:
کد مذکور به صورت زیر تصحیح می شود:
قانون EXP33-C: قبل از مقدار دهی اولیه از متغیرها استفاده نکنید
قبل از مقدار دهی اولیه از متغیرها استفاده نکنید، زیرا حاوی مقادیر پیش بینی نشده است. با وجود اینکه متغیرهای مقداردهی نشده اغلب حاوی بیت های صفر می باشند، ولی این امر تضمین شده نیست و ممکن است متغیرهای مقدار دهی نشده، حاوی آخرین مقدار ثبت شده در پشته برنامه باشند. به علاوه توابع حافظه مانند malloc() نباید قبل از مقداردهی اولیه شدن مورد استفاده قرار گیرند، زیرا محتویات آنها نیز نامشخص است. در بسیاری از موارد، کامپایلرها در مورد موارد استفاده نادرست از حافظه هشدار می دهند. لازم است به هشدارهای مذکور با دقت توجه شده و آنها را برطرف ساخت.
قانون EXP34-C: به اشاره گرهای تهی ارجاع مجدد ندهید
تلاش برای ارجاع مجدد به اشاره گرهای تهی منجر به رفتارهای تعریف نشده ای همچون خروج غیر منتظره از برنامه می شود. برای مثال در کد برنامه زیر input_str در یک متغیر رشته ای به نام str که به صورت پویا مقداردهی شده است، کپی می شود. در صورتی که تابع malloc() به درستی اجرا نشود یک اشاره گر تهی یا null را برگردانده و به str اختصاص می دهد. زمانی که به str در memcpy() ارجاع مجدد می شود، برنامه به شکل غیر منتظره ای رفتار می کند.
برای برطرف ساختن مشکل مذکور تنها کافی است با یک دستور IF از تهی نبودن اشاره گر اطمینان حاصل کرد:
اعداد صحیح یا Integers)INT)
قانون INT34-C: یک عدد منفی و یا مقداری بیش از بیت های موجود در عملوند را شیفت ندهید
این قانون تأکید می کند که نباید یک عدد منفی و یا مقداری بیش از حد بیت های موجود در عملوند، بیت ها را شیفت داد. هر تلاشی برای این کار منجر به خطای منطقی می شود و دقت کنید که این خطا متفاوت از خطای سرریز بافر است. شیفت دو طرفه دارای شیفت چپ با فرمت shift-expression<< additive-expression و شیفت راست با فرمت shift-expression<< additive-expression است. جابجایی بیت ها به اندازه عدد صحیح تعیین شده در عملگر، بر روی عملوند که آن نیز دارای فرمت عدد صحیح (integer) است، انجام می شود. برای مثال E1 << E2، E1 را به اندازه E2 شیفت چپ می دهد. بیت های خالی شده با صفر پر می شوند. در صورتی که E1 عدد علامت دار نباشد و یا علامت دار بوده و مقدار غیر منفی داشته باشد و همچنین E1 * 2E2 قابل نمایش در تعداد بیت های E1 باشد، E1، بیت به بیت به میزان E2 مطابق شکل زیر شیفت پیدا می کند. در غیر این صورت رفتار عملگر مذکور غیر قابل پیش بینی خواهد بود.
این قانون تأکید می کند که نباید یک عدد منفی و یا مقداری بیش از حد بیت های موجود در عملوند، بیت ها را شیفت داد. هر تلاشی برای این کار منجر به خطای منطقی می شود و دقت کنید که این خطا متفاوت از خطای سرریز بافر است. شیفت دو طرفه دارای شیفت چپ با فرمت shift-expression<< additive-expression و شیفت راست با فرمت shift-expression<< additive-expression است. جابجایی بیت ها به اندازه عدد صحیح تعیین شده در عملگر، بر روی عملوند که آن نیز دارای فرمت عدد صحیح (integer) است، انجام می شود. برای مثال E1 << E2، E1 را به اندازه E2 شیفت چپ می دهد. بیت های خالی شده با صفر پر می شوند. در صورتی که E1 عدد علامت دار نباشد و یا علامت دار بوده و مقدار غیر منفی داشته باشد و همچنین E1 * 2E2 قابل نمایش در تعداد بیت های E1 باشد، E1، بیت به بیت به میزان E2 مطابق شکل زیر شیفت پیدا می کند. در غیر این صورت رفتار عملگر مذکور غیر قابل پیش بینی خواهد بود.
برای رهایی از نتایج غیر منتظره در برنامه به جای نوشتن
می توان با استفاده از دستور IF به شکل زیر برنامه را اصلاح کرد:
برای شیفت راست، در صورتی که داشته باشیم E1 >> E2، این دستور E1 را به اندازه E2 شیفت راست می دهد. در صورتی که E1 عدد علامت دار نباشد و یا علامت دار بوده و مقدار غیر منفی داشته باشد، نتیجه قسمت صحیح معادله E1 / 2E2 خواهد بود. در صورتی که E1 علامت دار بوده و مقدار منفی داشته باشد، رفتار عملگر نامشخص بوده و ممکن است شیفت ریاضی علامت دار و یا شیفت منطقی بدون علامت انجام شود.
در شیفت راست نیز می توان با استفاده از دستور IF، یک کد امن ایجاد کرد و از بروز نتایج نامشخص جلوگیری به عمل آورد:
قانون INT35-C: عبارت های عدد صحیح را در هنگام مقایسه کردن و نسبت دادن به یک اندازه بزرگتر ارزیابی کنید
در صورتی که یک عبارت عدد صحیح با یک عدد صحیح با اندازه بزرگتر مقایسه شود و یا به متغیر عدد صحیح با اندازه بزرگتر نسبت داده شود، باید به صورت صریح با استفاده از دستورات تبدیل انواع (Casting) در اندازه بزرگتر مورد ارزیابی قرار گیرد. در قسمت بعدی به بررسی قوانین و پیشنهادات سطح یک در اعداد اعشاری و آرایه ها خواهیم پرداخت.