На 4 януари 2018 година излезе информация за мащабна хардуерна уязвимост в процесорите на Intel, която дава възможност на непривилегирована програма да получи достъп до данните в ядрото на компютърната система. Тази уязвимост бе наречена Meltdown. По-късно бе открит начин за използване на подобна уязвимост в почти всички процесори, който получи името Spectre.

С какво започна всичко

Началото на тази история съвсем не е 4-ти януари. Още в далечната 2008 година, руският специалист по компютърна безопасност Крис Касперски разкри критична уязвимост в процесорите на Intel, която дава възможност за отдалечено хакване на компютърната система чрез JavaScript или директно чрез TCP/IP, независимо от операционната система. Крис възнамеряваше да представи работещ код за Windows и Linux, но после всичко затихна за цели десет години. Сега няма как да намерим цялата информация, понеже Крис загина при скок с парашут.

За втори път тази уязвимост бе разкрита от няколко независими групи от експерти и по-точно, от Google Project Zero, Cyberus Technology и други. Всичко бе пазено в тайна, за да могат разработчиците да създадат и пуснат пачове за защита от Meltdown И Spectre. През цялото това време водещите Windows и Linux програмисти съвместно работиха върху върху решението на проблема. Плановете бяха данните за Meltdown и Spectre да бъдат публикувани на 9 януари 2018 година, но информацията изтече по-рано – The Register някак разбра за тези уязвимости и публикува всичко.

Уязвимостта Meltdown

Да разгледаме натрупаната досега информация за Meltdown, и да се опитаме да разберем, дали наистина всичко е толкова страшно. Първоначално накратко да разгледаме как работи процесора.

През последните десетилетия, започвайки от 1992 година, когато се появи първият Pentium, Intel непрекъснато развиваше своята суперскаларна архитектура на своите процесори. Идеята е да се създадат бързи процесори, запазвайки обратната съвместимост. Получава се една сложна структура, в която компилаторът подрежда и опакова инструкциите по такъв начин, че да се изпълняват в един поток, а процесорът вътре в себе си разделя кода на отделни команди и започва да ги изпълнява паралелно, стига да е възможно, и дори разменя тяхната последователност. Причината за тези действия е, че хардуерните блокове за изпълняването на командите в процесора са много и всяка инструкция обикновено задейства само един от тях. И още, тактовата честота на процесорите с излизането на нови модели, нараства много по-бързо, отколкото скоростта на оперативната памет. Това доведе до появата на кешове от 1, 2 и 3 ниво. Да се прочете или запише информация от и в оперативната памет са необходими повече от 100 процесорни такта, а достъпът до кеш паметта от 1 ниво става само за единици тактове – изпълняването на операция от типа на аритметично събиране на операнди например, става само за два такта.

Като резултат от всичко това, докато една инструкция чака получаването на данни от паметта или освобождаването на floating point блока, или още нещо, процесорът спекулативно обработва следващите инструкции. Съвременните процесори по този начин могат паралелно да обработват около 100 инструкции (97 в Sky Lake, ако трябва да бъдем точни). Всяка подобна инструкция работи със свое копие на регистрите (това става в reservation station) и по време на изпълнението, те по този начин не си влияят един на друг. След като въпросната инструкция е изпълнена, процесорът се опитва да нареди резултатите от изпълнението на тези инструкции в блока retirement, сякаш тази суперскаларност я е нямало (компилаторът не знае за това и счита, че това си е последователно изпълнение на кода). Ако по някаква причина процесорът реши, че някоя от тези инструкции е изпълнена неправилно – например, опитва се да използва значението на регистъра, променено от предишна инструкция, то текущата инструкция ще бъде изхвърлена. Същото става и при промените в клетките на паметта, ако предсказването на прехода се е оказало грешно.

Паметта в операционната система

В 64-битов режим всяко приложение разполага със собствен блок памет, но всъщност паметта на ядрото също присъства в адресното пространство на процеса с цел повишаване производителността на системните извиквания, но е защитена от достъп от страна на потребителския код. Ако инструкция от потребителския код се опита да получи достъп към защитената памет, то се генерира грешка на ниво защитните механизми на процесора и неговите защитни пръстени Ring 0, Ring 1, Ring 2 и т.н.

Side-channel атаки

Когато не се получава прочитането на някакви данни, винаги се разглеждат страничните ефекти от работата на атакувания обект. Класическият пример за този подход е хакването на KeeLoq чиповете за отключване на автомобилите чрез измерване консумацията на електрическа енергия при изпълнението на различните операции, като по този начин бе постигнато тяхното разпознаване и разгадаване на алгоритъма.

В случая с Meltdown, подобен страничен информационен канал е времето за четене на данните. Ако байтът данни е записан в кеша, то той ще бъде прочетен много по-бързо, отколкото ако се намираше в оперативната памет и се налага да бъде поставен в кеша.

Да обединим всичко в едно

Атаката е съвсем проста и може да се каже дори красива

1. Чистим кеша на процесора

char userspace_array[256*4096];
for (i = 0; i < 256*4096; i++) {
_mm_clflush(&userspace_array[i]);
}

2. Прочитаме интересуващата ни променлива от защитеното пространство на ядрото. Това естествено, ще предизвика прекъсване, което обаче няма да се обработи веднага

const char* kernel_space_ptr = 0xBAADF00D;
char tmp = *kernel_space_ptr;

3. Извършваме спекулативно четене от масива, който се намира в нашето адресно пространство, като използваме значението на променливата от втората точка

char not_used = userspace_array[tmp * 4096];

4. Внимателно четем масива и точно измерваме времето за достъп. Всички елементи, освен един ще се четат бавно, а елементът, който бъде прочетен бързо е именно променливата от защитеното адресно пространство, понеже ние го вкарахме в кеша.

for (i = 0; i < 256; i++) {
if (is_in_cache(userspace_array[i*4096])) {
// Got it! *kernel_space_ptr == i
}
}

Въпреки че това е псевдокод, добре се вижда, че обект на атаката е самата архитектура на процесора, а подобно нещо със софтуер не може да се оправи.

Кодът на атаката:

; rcx = kernel address
; rbx = probe array
retry:
mov al, byte [rcx]
shl rax, 0xc
jz retry
mov rbx, qword [rbx + rax]

Да се спрем по-подробно:

mov al, byte [rcx] – това е четенето на интересуващия хакера защитена от ядрото област, което ще генерира прекъсване. Най-важното тук е, че прекъсването няма да се обработи в момента на четенето, а малко по-късно.
shl rax, 0xc – умножение на 4096 за да се избегнат проблемите със механизма за зареждането на данните в кеша
mov rbx, qword [rbx + rax] – „запомняне“ на прочетеното значение и работа с кеша. Информацията на експертите на Google Project Zero спира дотук, за да няма злоупотреби. Но идеята е ясна.

Защитените данни могат да се прочетат дори с JavaScript код, с който са претъпкани съвременните уеб-страници. А това прави проблема глобален.

Ясно е, че за да се запуши тази уязвимост в операционните системи, може просто да се раздели виртуалното адресно пространство на паметта и на ОС ядрото. Но това сериозно ще забави операционната система, понеже системните извиквания ще отнемат много повече време. Точно това се случва в компютърните системи с вече инсталирани пачове.

Meltdown получи индекс CVE-2017-5754 и засяга:

  • Всички процесори на Intel от фамилиите Core, Xeon, Celeron и Pentium с Core ядра
  • ARM процесорите с най-новите ядра Cortex-A75
  • Всички iOS устройства с ARM базирани процесори

Ето един интересен пример за използването на Meltdown:

Какво е Spectre

Meltdown получи индекс CVE-2017-5754 и засяга всички процесори на Intel от последните 10 и дори 15 години. На втората уязвимост Spectre, открита не само в процесорите на Intel, но и в ARM чиповете, бяха присвоени два идентификатора: CVE-2017-5753 (секретен байт) и CVE-2017-5715 (еднакви последователни инструкции), като последният важи и за някои процесори на AMD.

Spectre не може да прониква в паметта на ядрото, но дава достъп до паметта на другите приложения.

Тази технология може да застави всеки процес самостоятелно да покаже съдържимото на своята памет. Използва се поредица повтарящи се инструкции, с които се тренира блока за предсказване на преходите, а след това му се подава съвсем различна, специално подбрана инструкция. По този начин границите между изолираните приложения в тези критични случаи се размиват и става възможно прочитането на защитените данни на друго приложение, но не и от ядрото.

Няма универсална защита от Spectre: разкритият механизъм може да използва цял спектър от варианти, което усложнява пускането на универсален пач. Налага се приложението само да защитава своята памет или непрекъснато да чисти кеша. Това означава прекомпилиране на всички приложения и сериозно понижаване на производителността. А и едва ли програмистите ще се заемат просто така да прекомпилират приложенията си.

Spectre засяга следните процесори:

  • Всички процесори на Intel
  • Всичко нови ядра на ARM, поддържащи спекулативно изпълнение на кода
  • Някои процесори на AMD

Но днес AMD официално съобщи, че за използването на тази уязвимост в нейните процесори, е необходим физически достъп до компютърното устройство, но за това ще стане дума в друга статия.

 


Безспорно, Meltdown и Spectre са сериозни грешки и ще трябва по-внимателно да следим за безопасността на нашите устройства. Но всеки сам решава за себе си. Вече започнаха да излизат инструкции, как да се провери, дали системата е уязвима към Meltdown и Spectre и как да се изключат фиксовете за тях в различните операционни системи. Това също е тема за друг материал.

ДОБАВИ КОМЕНТАР

8 коментара за "Как именно работят Meltdown и Spectre"

Извести ме за
avatar
Сортиране по:   нови | стари | оценка