‫ برنامه نويسي امن با زبان C – ورودي / خروجي (IO) – قسمت آخر

IRCAR201102088
 
 
عنصر اصلي در برنامه‌نويسي امن با زبان‌هاي مختلف برنامه‌نويسي، مستندسازي خوب و استفاده از استانداردهاي قابل اجرا است. استانداردهاي كدنويسي، برنامه نويسان را ترغيب به پيروي از مجموعه‌اي متحدالشكل از قوانين و راهنمايي‌ها مي‌كند كه بر اساس نيازمندي‌هاي پروژه و سازمان تعيين شده است، نه بر اساس سلايق و مهارت‌هاي مختلف برنامه‌نويسان. به محض تعيين استانداردهاي مذكور، مي توان از آن به عنوان معياري براي ارزيابي كدهاي منبع، چه به صورت دستي و چه به صورت اتوماتيك استفاده كرد.
از استانداردهاي معروف در اين زمينه مي‌توان به استانداردCERT براي كدنويسي امن اشاره كرد كه يك سري از قوانين و پيشنهادات را براي كدنويسي امن با زبان‌هاي برنامه‌نويسي C، C++ و جاوا ارائه مي‌دهد. هدف از اين قوانين و پيشنهادات، حذف عادت‌هاي كدنويسي ناامن و رفتارهاي تعريف نشده است كه منجر به آسيب‌پذيري‌هاي قابل سوءاستفاده مي‌شود. به كارگيري استانداردهاي مذكور منجر به توليد سيستم‌هاي با كيفيت بالاتر مي‌شود كه در برابر حملات بالقوه، پايدارتر و مقاوم‌تر هستند.
در مقاله "آشنايي با استاندارد CERT براي برنامه‌نويسي امن"، كليات استاندارد CERT در زمينه مزبور را توضيح داديم و در سري مقاله‌هاي برنامه‌نويسي امن به زبان C به صورت تخصصي‌تر شيوه برنامه‌نويسي امن با اين زبان را مورد بررسي قرار مي‌دهيم. قابل ذكر است كه در اين استاندارد 89 قانون و 134 پيشنهاد براي برنامه‌نويسي امن با زبان C ارائه شده است كه در اين سري مقالات، مهمترين آنها را كه در سطح يك قرار دارند، شرح خواهيم داد. براي كسب اطلاعات بيشتر در مورد سطح‌بندي قوانين و پيشنهادات به مقاله "آشنايي با استاندارد CERT براي برنامه نويسي امن" مراجعه فرماييد. در مقاله حاضر به پيشنهادات و قوانين ارائه شده سطح اول در مورد ورودي - خروجي خواهيم پرداخت. در دو مقاله قبل يك سري از اين قوانين و پيشنهادات را شرح داديم و در اين قسمت آخرين سري از آنها را توضيح مي دهيم.
 
34. FIO36-C – در استفاده از fgets() فرض نكنيد كه كاراكتر خط جديد خوانده شده است.
تابع fgets() به طور پيش فرض براي خواندن يك خط از جريان ورودي كه با كاراكتر خط جديد (new-line character) پايان يافته، طراحي شده است. اين تابع يك پارامتر (size) مي گيرد كه در آن اندازه بافر مقصد معين شده است و حداكثر به اندازه size-1 كاراكتر را از جريان به رشته كپي مي‌كند. در صورتي كه برنامه‌نويس فرض كند كه آخرين كاراكتر رشته مقصد newline است، منجر به خطاهاي truncation ميشود.
تابع fgetws() نيز به به طور مشابه تحت تأثير قرار مي گيرد.
 
در زير يك نمونه كد آورده شده است كه از قانون فوق پيروي نكرده است.
 
 

در اين برنامه در صورتي كه آخرين كاراكتر buf يك new-line نباشد، برنامه بر روي يك كاراكتر معتبر از طرف ديگر بازنويسي مي‌كند.
در زير كد اصلاح شده برنامه مذكور آورده شده كه در آن از تابع strchr() براي جابجا كردن كاراكتر خط جديد در رشته جديد (در صورت وجود) استفاده شده است. راه حل معادل براي تابع fgetws() مي تواند از wcschr() استفاده كند.
 
 
يك راه حل جايگزين بسيار واضح ديگر نيز كه به ذهن متبادر مي شود، قرار دادن يك خانه اضافي در بافر مقصد براي يك كاراكتر بيشتر است. در اين صورت اگر هيچ كاراكتر New-line در مبدأ وجود نداشته باشد، بايد يك new-line به اضافه يك كاراكتر null-termination به مقصد ارسال شود. اين روش ناامن است زيرا در واقع يك ورودي را قبول مي كند كه ورودي واقعي مورد نظر نيست و ممكن است منجر به نتايج ناشناخته‌اي شود.
 
35. FIO37-C – فرض نكنيد كه تابع fgets() زماني كه موفق بوده است، يك رشته ناتهي را بر مي‌گرداند.
 
پيش فرض در مورد نوع داده اي كه خوانده مي شود، منجر به بروز خطا مي شود، زيرا اين فرض ها ممكن است نقض شوند. براي مثال حالتي را در نظر بگيريد كه يك داده باينري از فايل به جاي يك داده متني از ترمينال كاربر خوانده شود. در برخي از سيستم ها نيز امكان دارد كه بايت null (مشابه ديگر كدهاي باينري) به عنوان ورودي از كيبورد گرفته شود.
در C99، بخش 7.19.7.2 پاراگراف سوم درباره fgets() مي گويد:
 
" تابع fgets()، در صورتي كه موفق عمل كند "s" را برمي‌گرداند. در صورتي كه به پايان خط برسد و هيچ كاراكتري پيش از آن وارد آرايه نشده باشد، محتويات آرايه دست نخورده باقي مي ماند و يك اشاره گر null برگردانده مي شود."
 
بنابراين، در صورتي كه fgets() يك اشاره گر غير null را برگرداند، مي توانيم فرض كنيم كه واقعاً آرايه را با داده پر كرده است. با اين وجود، اين فرض كه تابع مذكور آرايه را با بايت غيرتهي پايان يافته با null (non-empty null-terminated (NTBS)) پر كرده است، خطا است زيرا داده هايي كه تابع fgets() در آرايه قرار مي دهد، مي تواند حاوي كاراكترهاي Null نيز باشد.
 
در زير نمونه كدي را مي بينيد كه قانون مذكور را ناديده گرفته است.
 
 
در اين مثال تلاش شده است تا كاراكتر خط جديد "\n" از ورودي حذف شود. تابع fgets() به طور پيش فرض براي خواندن يك خط از جريان ورودي كه با كاراكتر خط جديد (new-line character) پايان يافته، طراحي شده است. اين تابع يك پارامتر (size) مي گيرد كه در آن اندازه بافر مقصد معين شده است و حداكثر به اندازه size-1 كاراكتر را از جريان به رشته كپي مي‌كند. تابع strlen() طول رشته را با شمردن تعداد كاراكترهاي پيش از كاراكتر null مشخص مي كند. مشكل زماني ايجاد مي شود كه اولين كاراكتر خوانده شده null باشد. براي مثال مي توان به يك فايل داده باينري اشاره كرد كه توسط تابع fgets() خوانده شود و در صورتي كه اولين كاراكتر خوانده شده از buf يك كاراكتر null باشد، strlen(buf) مقدار 0 را بر مي‌گرداند و يك خطاي نوشتن خارج از محدوده آرايه اتفاق مي‌افتد.
در زير كد اصلاح شده فوق را با استفاده از strchr() مشاهده مي كنيد كه براي از بين بردن كاراكتر خط جديد در صورت وجود، استفاده شده است.
 
 
36. FIO43-C – فايل هاي موقتي را در دايركتوري هاي مشترك ايجاد نكنيد.
برنامه نويسان معمولاً عادت دارند فايل هاي موقتي خود را در دايركتوري هايي كه قابليت نوشتن براي هر كسي را دارند، ايجاد كنند كه معمولاً نيز در يك بازه زماني معين براي مثال هر شب و يا در هر بار راه اندازي مجدد پاك مي شوند. براي نمونه از دايركتوري هاي مذكور مي توان به /tmp و يا /var/tmp در يونيكس و به c:\TEMP بر روي ويندوز اشاره كرد.
فايل هاي موقتي معمولاً به عنوان يك حافظه كمكي براي داده ها استفاده مي شوند كه نيازي به ذخيره كردن آنها و يا از طرف ديگر اصلاً قابليت قرار گرفتن بر روي حافظه برنامه را ندارند. از اين فايل ها معمولاً براي ارتباط با ديگر پردازه ها از طريق انتقال داده از طريق فايل سيستمي استفاده مي شود. براي مثال، يك پردازه يك فايل موقتي را در يك دايركتوري با نامي شناخته شده ايجاد مي كند تا پردازه هايي كه با هم همكاري دارند، از آن استفاده كنند.
اين يك كار خطرناك است زيرا يك فايل شناخته شده در يك دايركتوري مشترك مي تواند به آساني توسط يك مهاجم سرقت شده و يا دستكاري شود. استراتژي كاهش اين خطر شامل موارد زير مي شود:
1-      از مكانيزم هاي سطح پايين IPC ديگري همچون socket و يا حافظه مشترك استفاده كنيد.
2-      از مكانيزم هاي سطح بالاي IPC ديگري همچون فراخواني تابع از راه دور (RPC) مشترك استفاده كنيد.
3-      از يك دايركتوري امن استفاده كنيد كه فقط توسط نسخه هاي برنامه قابل دسترسي است (البته اطمينان حاصل كنيد كه چندين نسخه از يك برنامه كاربردي بر روي يك سكو با يكديگر رقابت نمي كنند).
 
تعداد زيادي مكانيزم ارتباط بين پردازه ها (IPC) وجود دارد كه برخي از آنها نيازمند استفاده از فايلهاي موقتي هستند، در حالي كه برخي از آنها نيز چنين نيازي ندارند. براي مثال از مكانيزمي كه از فايل هاي موقتي استفاده مي‌كند مي توان به POSIX mmap() اشاره كرد. در مقابل Berkeley Sockets، POSIX Local IPC Sockets و System V Shared Memory نيازي به فايل هاي موقتي ندارند.
از آنجايي كه طبيعت چند كاربره دايركتوري هاي مشترك، ذاتاً داراي ريسك امنيتي است، لذا استفاده از فايل هاي موقتي مشترك در IPC توصيه نمي شود. زماني كه دو يا چند كاربر يا گروهي از كاربران اجازه نوشتن در يك دايركتوري را دارند، خطر بسيار بيشتر از زماني است كه چندين كاربر به چند فايل محدود دسترسي مشترك دارند، بنابراين فايل هاي موقتي در دايركتوري هاي مشترك بايد موارد زير را رعايت كنند:
 
1-      نام فايلها يكتا و غير قابل پيش بيني باشد.
2-      با دسترسي انحصاري باز شود.
3-      قبل از اينكه برنامه تمام شود، پاك شود.
4-      با اجازه هاي مناسب باز شود.
 
در جدول زير توابع مربوط به فايلهاي موقت متداول و همخواني آنها با معيار فوق آورده شده است:
 
 
 
در مقاله هاي بعدي در مورد قوانين و پيشنهادات محيط صحبت خواهيم كرد.
 
 

نظرات

بدون نظر
شما برای نظر دادن باید وارد شوید

نوشته

 
تاریخ ایجاد: 18 مرداد 1393

دسته‌ها

امتیاز

امتیاز شما
تعداد امتیازها:0