وبسایت شخصی حسن هاشمی

برنامه نویس. ایران. قم :))

بررسی حمله امنیتی DOS به CPU های چند هسته ای

به احتمال زیاد نوع حمله DOS که امروز می خوام بررسی کنم رو، تا حالا به گوشتون نخورده.

توی این مطلب می خوایم نحوه حمله امنیتی DOS به ماشین های با پردازنده چند هسته ای با استفاده از نقطه ضعف سرویس دهی  Memory Controller رو بررسی کنیم یا شایدم باید بگم نقطه قوت.

ابتدا بذارید مطمئن بشیم اعتبار این مطلب به نویسنده اصلی میرسه:

مطالب این پست در واقع چکیده از  مقاله ای با عنوان Memory Performance Attack: Denial of memory service in multi core Systems نوشته پروفسور Onur Mutlu عضو تیم تحقیقاتی مایکروسافت هست.

 به دلیل اینکه خودم از مطالعه این مقاله خیلی لذت بردم گفتم خوبه که تلخیصش کنم و یه سری توضیحات بهش اضافه کنم تا شما رو هم بی بهره نذارم. :)

پس بیاید محض یادآروی یه سری مفاهیم رو مرور کنیم:

DRAM: مخفف کلمه dynamic random access memory هست یا همون رم خودمون اما به دلیل اینکه این نوع رم ها بار موجود داخلشون رو (که همون دیتای ما باشه) به صورت دوره ای refresh می کنن بهشون DRAM گفته میشه.

نکته: به دلیل اینکه حتی خازن هایی که نارسانا هستن هم کمی نشتی دارن این Refresh نیاز هست تا بار موجود از دست نره.


Memory Controller: مداری هست که بین پردازنده اصلی و DRAM قرار میگیره و درخواست هایی که CPU از حافظه داره رو سرویس دهی می کنه.


Thread: یه جریان از کد در حال اجرا ( به صورت ساده تر یه دنباله ای کد در حال اجرا) برای حالا به همین تعریف بسنده می کنیم.


از نظر مهندسی سخت افزار تولید پردازنده ای با یک هسته که دارای سرعت بالا باشه بسیار سخت از تولید یک پردازنده با چند هسته و سرعت های پایین تر هست در نتیجه توی چند سال اخیر شاهد رشد ماشین هایی که CPU های چند هسته ای دارن بودیم.


ابتدا بیاین عملکرد CPU و Memory Controller رو یک ماشین با یک پردازنده تک هسته ای در نظر بگیریم:

1- CPU یک آدرس خاص از حافظه مثلا 0xefef رو از Memory Controller در خواست می کنه. در این حالت بلاکی از حافظه که دیتای درخواستی توش هست داخل یه حافظه cach که سایزش اندازه یک بلاک از حافظه هست منتقل میشه و اسمش هم Row buffer هست و بعد اون قسمت حافظه که درخواست شده رو از buffer استخراج می کنه و به CPU برمی گردونه.

به عملیات انتقال بلاک به cach اصظلاحاً row buffer hit گفته میشه.

توجه داشته باشید که مدت زمان انجام این عملیات نسبت به سرعت CPU خیلی خیلی پایین تره.


به عنوان مثال اگه شما حافظه رو به عنوان یک ماتریس 2 بعدی در نظر بگیرید، آدرسی که من درخواست می کنم هم مثلا ردیف 5 و ستون 6 هست.

با توجه به مراحل بالا ابتدا ردیف 5 به طور کامل به row buffer منتقل میشه ( تا اینجای کار ما یه row buffer hit انجام دادیم) و بعد ستون 6شم ازش استخراج میشه و به memory controller برگردونده میشه.

برای یک ماشین با پردازنده چند هسته ای این داستان کمی فرق می کنه. اگرچه چند تا هسته اضافه شده اما هنوز همه پردازنده ها از یک Memory Controller مشترک استفاده می کنن در نتیجه این معنی رو میده که Memory Controller باید انتخاب کنه که به درخواست کدوم Thread پاسخ بده و اینجاست که مشکل خودش رو نشون میده.

فرض کنیم ما دوتا Thread داریم که هر کدوم یه درخواستی از Memory Controller انجام میده در این میان Memory controller برای اینکه زمان رو برای یک row buffer hit دیگه هدر نده به درخواستی جواب میده که row درخواستیش توی row buffer قرار داره.

به عبارت دیگه اگر آدرس های درخواستی یک Thread به هم نزدیک باشه به نحویکه همشون در یک row قرار بگیرن الگوریتم فعلی Memory Controller همیشه اولویت رو به این thread میده! یعنی بقیه Thread ها با کندی سرعت تا 14 برابر مواجه میشن.

این اتفاق در بسیاری موارد باعث افزایش سرعت میشه که البته همون چیزیه که ما می خوایم. اما در مواردی یه کاربر خطرناک می تونه از این مرد استفاده کنه و باعث دردسر بشه.

از اونجایی که آرایه دنباله ای عناصر که پشت سر هم در حافظه ذخیره شدن در نتیجه Memory Controller نیازی به row buffer hit نداره و هر بار از روی buffer cache دیتا رو به CPU بر می گردونه.

دو تا متد که وضعیت های فوق رو توضیح می دم نوشتم، تا خودتون به عمق فاجعه پی ببرید. :)



  1. const int arrayLength = int.MaxValue / 10;
  2. // این تابع به صورت تصادفی بلاک های مخلتف حافظه رو می خونه  
  3. private static void DoRandomly()
  4. {
  5. int[] a = new int[arrayLength];
  6. float j;
  7. Stopwatch watch = new Stopwatch();
  8. var random = new Random();
  9. watch.Start();
  10. Console.WriteLine("Started Random");
  11. for (int i = 0; i < arrayLength; i++)
  12. {
  13. j = a[random.Next(0, arrayLength)];
  14. }
  15. watch.Stop();
  16. Console.WriteLine("Random Indexing ended after {0} ", watch.Elapsed);
  17. }
  18.  
  19. /// این تابع به صورت پشت سر هم
  20. public static void DoSequential()
  21. {
  22. int[] a = new int[arrayLength];
  23. int j;
  24. Stopwatch watch = new Stopwatch();
  25. watch.Start();
  26. Console.WriteLine("Start sequential");
  27. for (int i = 0; i < arrayLength; i++)
  28. {
  29. j = a[i];
  30. }
  31. watch.Stop();
  32. Console.WriteLine("Sequential Indexig ened after {0} ", watch.Elapsed);
  33. }

متد اول چون به صورت تصادفی اندیش آرایه رو انتخاب می کنه Memory Controller رو مجبور به انجام یه Buffer hit می کنه ولی دومی چون به صورت پشت سر هم اینکارو می کنه در نتیجه دیتا رو از روی Row buffer می خونه که باز هم در نتیجه باعث میشه که Memory Controller اولویت رو بهش بده.
جای تعجبه که متد اول در 60 ثانیه اجرا میشه و متد دوم در 1ثانیه. (حتی اگه از زمان اجرای متد random.next(int, int صرف نظر کنیم.
پس فکر می کنم متوجه شده باشید که چه پتانسیل خرابکاری داره و البته چه کاربردهایی می تونه برای ما به عنوان برنامه نویس داشته باشه تا بتونیم سرعت اجرای نرم افزار رو ارتقاء بدیم.
در حاضر و در سال 2016 که دارم این مطلب رو می نویسم برخی راه های جایگزین در مرحله کاربرد رسیده ولی هنوز اغلب پردازنده ها همین الگوریتم اولویت دهی رو دنبال می کنن.
Loading