Mikroİşlemcilerdeki Güvenlik Açığı (Meltdown ve Spectre)

2017 yılından bu yana bildiğimiz bir güvenlik açığı var. Bu açık bir yazılım firmasının ürettiği yazılımda bulunan bir açık değil, bilgisayarlarımızın beynini oluşturan işlemcilerdeki bir açık.

Önce işlemcilere bir bakalım. Ama her yerin bir gaz ve toz bulutundan ibaret olduğu en eski dönemlere kadar gitmeyelim ve 80386 işlemcisinden başlayalım, çokgörevlilik, dallanma tahmini gibi konuları ele alalım.

1985 yılında çıkan 80386 işlemcisi büyük bir ilerlemeydi. 8086 ile başlayan, 80286 ile tökezleyen bir dizinin en olgun haliydi. 32 bitlik bir işlemci olan 80386 çok daha büyük bir belleğe doğrudan ulaşabiliyor, işlemleri de çok daha hızlı bir şekilde yapabiliyordu.

8086, 80286 ve 80386 genel amaçlı işlemcilerdi. Yani, hemen her türlü işi yapabiliyorlardı; matematiksel işlemler, dosya işlemleri, görüntü işlemleri vb. Matematik işlemler bu işlemcilerin yapabildiği işlerin bir tanesiydi ama en önemlisi değildi. Bu nedenle, matematik ağırlıklı işlerde onlara yardımcı olmak için matematiksel işlemciler üretildi: 8086 işlemcisi için 8087 matematik işlemcisi, 80286 için 80287 ve 80386 için de 80387. Matematiksel işlemcilerin kullanılması zorunlu değildi ama kullanıldığı zaman matematik ağırlıklı işlemlerin, örneğin mimari tasarım işlemlerinin performansını 3-4 kat arttırabiliyorlardı.

Matematik işlemciler her zaman genel amaçlı işlemcilerden pahalıydı, bu yüzden her makinede onlara rastlamamız söz konusu olmuyordu.

80386’lı bilgisayarlar işlemci ile RAM bellek arasında tampon bir bellek de kullanabiliyordu. RAM bellekten daha hızlı olan Tampon Bellek, sık kullanılan verileri RAM bellekten alıyor ve depoluyordu. Böylece işlemci genel olarak, daha yavaş olan RAM belleğe gitmek zoruna kalmıyordu.

Matematik işlemci ve tampon bellek ile bir anakart üzerinde şu şekilde bir düzen görüyorduk:

Bu arada, yarıiletken teknolojisinin sürekli geliştiğini ve ucuzladığını unutmayalım. Intel’in kurucularından birisi olan Gordon Moore dile getirdiği için onun adıyla anılan Moore Yasası’na göre, bir tümleşik devre üzerindeki transistörlerin sayısı her 24 ayda bir iki katına çıkıyor. Bu nedenle, gelişen teknoloji sayesinde, Intel 386 sonrasındaki işlemcisinde çok daha fazla işleve yer vermek istedi ve 80486 adını verdiği bu yeni işlemciye matematiksel işlemciyi ve 8 KB’lık bir tampon belleği ekledi. Yani, 486 işlemcisini bir 386, bir 387 ve bir tampon bellek devresinin toplamı şeklinde düşünebiliriz:

İşlemci üzerindeki tampon bellek hem işlenecek verileri hem de çalıştırılacak komutları içerebiliyordu. 80486 İşlemcisinin büyük başarı kazandığını söylemeye gerek yok.

Yukarıdaki haliyle işlemci alanında daha başka bir şey yapmak zor görünüyor; matematik işlemcisi ve tampon bellek ana işlemciyle birlikte yan yana, aynı tümleşik devre üzerinde. Daha ne olsun?

Intel’in “Daha ne olsun?” sorusuna yanıtı Pentium işlemci oldu. Pentium’un yapısına bir bakalım:

Gördüğünüz gibi, Pentium aslında iki ayrı 486 işlemci içeriyor gibi düşünülebilir. Intel gelişen teknoloji sayesinde aynı alana yerleştirebildiği transistör sayısını arttırınca fırsatı değerlendirip iki 486 işlemcisini aynı alana sıkıştırabilmiş. 486 işlemci derken işlemcinin fiilen işlem yaptığı kısımlarını kastediyoruz. Bu yapının sonraki Hyperthreading ve Core işlemci yapılarına dönüşeceğini öngörebilirsiniz. Tampon bellek de aslında bir değil iki tane; veriler için bir tampon bellek, işlenecek komutlar için başka bir tampon bellek. Komut tampon belleği ileride başımıza bela olacak.

Peki, iki işlemci içeren bir işlemci nasıl kullanılabilir? Birincisi, bilgisayarda birden fazla programı daha iyi çalıştırabiliriz. Aynı anda birden fazla işi yapabilmek (program çalıştırabilmek) “Çok görevlilik” (Multitasking) olarak adlandırılıyor. Tek işlemciyle bile çokgörevliliği sağlayabiliyoruz: İşlemcinin zamanını çalışan programlar arasında bölüştürürsek, işlemciler çok hızlı olduğu için, iki program aynı anda çalışıyor görünebilir. Ama birden fazla işlemci varsa o zaman bir görevi birine, bir başka görevi diğerine verip daha iyi bir çokgörevlilik sağlayabiliriz.

Şimdilik bilgisayar üzerinde tek bir program çalıştığın varsayalım. Bu durumda çokgörevlilik diye bir şey yok (işletim sisteminin kendisinin görevlerini bir kenara bırakıyoruz). O zaman birden fazla işlemcinin ne yararı olacak?

Eğer çalışan program birden fazla işlemciyi kullanabilecek yapıdaysa, yani paralel çalışmayı destekleyebiliyorsa işimiz kolay: Program işlemciye paralel çalışacak kodları verir, işlemci de bu kodları farklı işlemcilerde çalıştırır.

Bazı uygulamalar, bazı ortamlar paralel çalışmayı destekler. Örneğin, Powershell içinde paralel çalışabilecek kodları belirtebiliyoruz. Önce, paralel özellik içermeyen bir Powershell koduna bakalım:

$number=1

while($true){

$name=$number.tostring()

new-aduser -name $name

$number=$number+1

}

Yukarıdaki kod, Ctrl-C ile kesilene kadar “birbiri ardına” yeni kullanıcı yaratıyor. Kod içinde paralellik yok dolayısıyla işletim sistemi bu kodu büyük olasılıkla hep aynı işlemci üzerinde çalıştıracak.

Şimdi de şu koda bakalım:

workflow Create

{

$numbers = 4997970..5200000

foreach -parallel ($number in $numbers)

{

$name=$number.tostring()

new-aduser -name $name

}

}

Create

Bu, Powershell içinde bir iş akışı kodu örneği ve aynen yukarıdaki kod gibi kullanıcı yaratıyor ama bunu paralel şekilde yapıyor. İşletim sisteminin bunu fark ettiğinde işlemleri farklı işlemcilere ataması mümkün.

Ama Powershell paralel çalışmayı destekleyen yeni bir ortam. Pentium’un çıktığı 1990’lı yılların başındaysa paralel çalışmayı gözeterek yazılmış program yok gibi bir şey.

Neyse ki Intel’in çantasında performansı arttırmak için başka numaralar da var. Burada karşımıza, şimdilerde başımıza bela olacak Branch Prediction (Dallanma Tahmini) teknolojisi çıkıyor. İster paralel çalışmayı destekleyen programlar olsun, isterse klasik programlar olsun, dallanma tahmini ile performansı arttırabiliriz.

Branch Prediction (Dallanma Tahmini)

Programların içinde çok sayıda koşul ifadesi bulunur: Bir değer şu sayıdan büyükse şunu yap, değilse bunu yap gibi. Aşağıdaki Powershell koduna bir göz atalım:

$i=1

If ($i –lt 1000) {write-host “i değişkeni 1000den küçüktür”}

else {write-host “i değişkeni 1000den küçük değildir”}

Kodun ilk satırında $i değişkenine bir değer atıyoruz. Sonra da If-else yapısında bu değişkenin değerini denetliyoruz ve verili bir sayıdan küçük olup olmadığına bakıp ona göre işlemler yapıyoruz.

Tüm programlar çok sayıda If-else yapısından oluşur, sürekli olarak bir şeyler başka şeylerle karşılaştırılır, karşılaştırma sonucuna göre farklı işlemler yapılır. Burada bir itiraz gelebilir; “If-else dışında da çok yapı var, örneğin For döngüsü ya da Do while döngüsü, vb.” Bu itiraza şu yanıtı verebiliriz: If-else dışındaki yapılar aslında hep If-else yapılarına dönüştürülür!

Aşağıdaki örnek C koduna bakalım:

int c;

int main () {

int i, j;

for (i=0; i<1000; i++) {

for (j=0; j<4; j++) {

c++;

}

}

return c;

}

Bu C kodunda hiç If-Else döngüsü yok gibi görünüyor. Ama yukarıdaki gibi bir kod, derleyici (compiler) tarafından şuna benzer bir şekle dönüşür:

main:

leal 4(%esp), %ecx ;

andl $-16, %esp

pushl -4(%ecx)

pushl %ecx

xorl %ecx, %ecx ; i = 0 (%ecx)

.L2:

xorl %edx, %edx ; j = 0 (%edx)

.L3:

movl c, %eax ; %eax = c

addl $1, %edx ; j++

addl $1, %eax ; %eax++

movl %eax, c ; c = %eax

cmpl $4, %edx ; if j < 4 then goto L3

jne .L3 ; INNER LOOP BRANCH

addl $1, %ecx ; i++

cmpl $1000, %ecx ; if i < 1000 then goto L2

jne .L2 ; OUTER LOOP BRANCH

movl c, %eax ; return c

popl %ecx ; function overhead

leal -4(%ecx), %esp

ret

Dolayısıyla programlar çok sayıda If-Else koşul cümlesinden oluşur diyebiliriz. Yine örnek C kodumuza bakalım. Bu kod i değişkenine önce 0 değerini atıyor, i değişkenini her seferinde bir arttırıyor, ta ki i’nin değeri 1000 oluncaya kadar. Bu döngü If-else yapısında tam 1000 sefer If kısmını doğrulayacak (if i<1000). Dolayısıyla 1000 sefer boyunca ilgili koşulun doğru olacağını varsayarak işlem yapsak işlemleri hızlandırabiliriz. If kısmındaki komutları bir Komut Tampon Belleği’ne (Instruction Cache) koyarız ve kodu 1000 kez çalıştırırız. Bu varsayım i değeri 1000’e ulaştığında yanlışlanacak ve bizim Instruction Cache’i boşaltıp “Else” bölümündeki işlemleri oraya yerleştirmemiz gerekecek, bu da bir miktar gecikme demek. Gecikme ama 1000 sefer komutları hızlı bir şekilde işlerken yalnızca bir sefer gecikme yaşayacağız. Değer mi? Değer. O zaman yürüyelim arkadaşlar. Herkes mutlu.

Mutluluk 22 yaşındaki genç bir araştırmacının durumu fark etmesine kadar uzun yıllar sürdü. Herkesin başına bela olan bulgunun sorumlusu aşağıda fotoğrafı görülen Jann Horn.

Aslında işlemcilerde ve özellikle de dallanma tahmini teknolojisinde açık olabileceğini düşünüp bu alanda çalışan başkaları vardı. Bu kişiler geniş olanaklara da sahipti ama onlarcan önce dallanma tahminindeki olası sorunu bulan kişi bu genç adam oldu.

Jann Horn daha lisede okurken arkadaşlarıyla birlikte yaptığı projeyle Almanya’da beşinci olmuştu ve başbakan Angela Merkel’in kabul edip kutladığı 64 gelecek vaat eden genç arasına girmişti. Yaptığı proje de hoşaf projesi falan değildi: Arkadaşlarıyla zor bir konuyu ele almışlardı; ikili sarkaç. Yaptıkları proje ikili sarkacın hareketinde ortaya çıkabilecek düzensizlikleri “tahmin” ediyor ve mıknatıslar yardımıyla bunları gideriyordu.

Horn 2017 yılında Google’ın Siber Güvenlik Bölümü’nde staj yaparken Intel işlemcilerine ilişkin binlerce sayfalık İşlemci Kılavuzları’nı okudu dallanma tahmini konusuna odaklandı. Şunu buldu: İşlemci yanlış bir tahminde bulunduğunda, yanlış tahmine ilişkin kodları Komut Tampon Belleği’nden silmiyor, orada bırakıyordu! Eğer dikkatlice bir kod yazılırsa bu durum kötüye kullanılabilirdi ve kötü amaçlı kod bilgisayardaki tüm RAM belleğe, oradan da bilgisayardaki tüm dosyalara ulaşabilirdi! Kıyamet koptu tabii.

Dallanma Tahmini 1990’lı yılların başından bu yana tüm işlemcilerde kullanılıyor: Bu da milyarlarca işlemci demek. Milyarlarca kötüye kullanıma açık işlemci! Karabasan gibi.

Horn’un bulgusu bu konuda çalışan diğer ekiplerin bulgularıyla birleştirildi ve bulunan açıklar Meltdown ve Spectre olarak adlandırıldı.

Bulunan şeyler bir açık. Ama açığı kullanılabilecek kötü yazılımları üretmek kolay değil. Kolay olmasa da olanaksız değil. Bu yüzden hem Intel gibi işlemci üreticileri hem de Microsoft gibi yazılım üreticileri hemen bu açığı kapatmaya yöneldiler. Bunu büyük oranda da başardıkları söylenebilir.

İşlemcim Meltdown ve Spectre Açıklarına Karşı Korumalı Mı?

Microsoft, işlemcimizin söz konusu açıklara sahip olup olmadığını ve işletim sistemimizin gerekli yamalara sahip olup olmadığını görebilmemiz için Speculationcontrol adında bir Powershell modülü üretti. Bu modülü, yönetici olarak açtığımız bir Powershell ortamında, aşağıdaki komutla sistemimize yükleyebiliriz:

Install-module speculationcontrol

Bu modülün içinde tek bir komut var: Get-SpeculationControlSettings. Bu komutu çalıştırdığımızda çok sayıda bilgi alıyoruz:

Raporda görüldüğü gibi, işlemcimiz yukarıda anlatılan açığa (vulnerability) sahip olabilir ama işletim sisteminde buna yönelik önlem (mitigation) varsa sorun yoktur.

Bir Yanıt to “Mikroİşlemcilerdeki Güvenlik Açığı (Meltdown ve Spectre)”

  1. Yavuz Selim Seckin Says:

    Teşekkürler Murat Hocam bilgilendirici bir yazı olmuş, kodları anlayabilmeyi de isterdim doğrusu.

Bir Cevap Yazın

Aşağıya bilgilerinizi girin veya oturum açmak için bir simgeye tıklayın:

WordPress.com Logosu

WordPress.com hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Google fotoğrafı

Google hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Twitter resmi

Twitter hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Facebook fotoğrafı

Facebook hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Connecting to %s


%d blogcu bunu beğendi: