21 Haziran 2017 Çarşamba

x86 ve x64 mimaride koruma mekanizması

Bu yazı x86 mimari ve x64 mimarinin koruma mekanizmasını anlatmayı amaçlamıştır.

Koruma mekanizmasının olmadığı işlemcilerde sistemi kullanan kişiler basit hatalarla sistemin
çalışma akışını bozarak çökmesine neden olabilir. Bu bilinçsiz bir şekilde gerçekleşebileceği gibi bilinçli bir şekilde de gerçekleştirilebilir.Örneğin bazı emirlerin kullanıcılar tarafından çalıştırılması durumunda sistem askıda kalabilir.

Bu gibi sebeplerden dolayı x86 mimari protected modu geliştirmiştir.
işlemcinin protected moda sokulması için  CR0 registerının 0.biti 1'e setlenir.


Privilege level 
~~~~~~~~~~~~~~~~~~~~~~~

x86 mimari güvenlik için ayrıcalık seviyeleri ( privilege level ) tanımlamıştır. Protected modda 4 seviye bulunmaktadır. Bu seviyeler sırasıyla 0,1,2,3 seviyelerdir. Ayrıcalığı en yüksek olan seviye 0 seviyesidir. İşletim sisteminin kendisi 0 seviyesinde çalışmaktadır.

3.seviye ise en düşük ayrıcalık seviyesine sahiptir ve  kullanıcı uygulamaları bu seviyede çalışır. Modern işletim sistemlerinin çoğu yalnızca 2 seviye kullanmaktadırlar. Bunlar işletim sisteminin kernelının(körnılının) bulunduğu 0.seviye ve kullanıcı uygulamalarının bulunduğu 3.seviye.

Genel olarak 0.seviye kernel space olarak ve 3.seviye ise user space olarak bilinmektedir.


Bu seviye farkları ile bir takım emirler 1,2,3 seviyelerinde yasaklanmıştır. 0.seviye en ayrıcalıklı seviye olduğundan tüm emirleri  icra edebilir. 

• LGDT — Load GDT register.
• LLDT — Load LDT register.
• LTR — Load task register.
• LIDT — Load IDT register.
• MOV (control registers) — Load and store control registers.
• LMSW — Load machine status word.
• CLTS — Clear task-switched flag in register CR0.
• MOV (debug registers) — Load and store debug registers.
• INVD — Invalidate cache, without writeback.
• WBINVD — Invalidate cache, with writeback.
• INVLPG —Invalidate TLB entry.
• HLT— Halt processor.
• RDMSR — Read Model-Specific Registers.
• WRMSR —Write Model-Specific Registers.
• RDPMC — Read Performance-Monitoring Counter.
• RDTSC — Read Time-Stamp Counter.

Eğer bu emirler 0.seviye dışında icra edilirse işlemci tarafından general-protection exception fırlatılır.

GDT(Global Descriptor Table) ve LDT(Local Descriptor Table)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Bu tabloların adres çevriminde x86 ( i386 ve sonrası)  işlemciler için çok bir anlam ifade etmediğini biliyoruz(base address = 0 ). Bu tabloların Koruma mekanizmasında nasıl kullanıldığını incelemek için öncelikle bir kaç şeyi tekrar hatırlayalım. 

Segment registerlarımızın kendisi protected modda segment selector olarak bilinmektedir ve bunların biçimleri ise şu şekildedir.



(*)Görsel guastovo duarte adında bir şahısa ait ve oldukça güzel bir çizim. 

CPL veya Current Privilege Level , CS(Code Segment) registerının son 2 bitine karşı düşer.

CPL değerini değiştirmek için kod akışının değişmesi gerekmektedir. x86 mimari code segment registerının içeriğinin  kod akışı dışında değiştirilmesine müsade etmemektedir.(jmp,call emirleri gibi).

RPL veya Requested Privilege Level, DS,ES,SS,FS,GS registerlarının son 2 bitine karşı düşer.

RPL değerinin değişmesi için MOV gibi emirler  kullanılabilir. Fakat işlemci buna izin verirse bu değişim gerçekleşir. Yazının ilerleyen kısmında  buna değinilecektir.


Genel olarak sistemde bir tane GDT bulunurken, her bir proses için ise LDT tabloları yaratılabilir dikkat edin yaratılabilir diyoruz çünkü mimari bunu sağlasa da kimi işletim  sistemleri yalnızca GDT tablosunu kullanırken LDT tablosuna ihtiyaç duymamaktadır.Her prosese ait LDT için GDT içerisinde bir kayıt tutulur. LDT ve GDT tablolarının her ikisinin de kayıt formatları aynıdır.

Bu iki tablonun  bellekte nerede olduğunu bildiren iki register mevcuttur. Bunlar GDTR(Global Descriptor Table Register) ve LDTR(Local Descriptor Table Register).


Segment Descriptorlar 
~~~~~~~~~~~~~~~~~~~~~~~

GDT veya LDT içerisindeki kayıtların her birine segment descriptor diyoruz ve descriptorlar bir segmentin karakteristiğini ifade eder.

Her bir descriptor 8-bayt uzunluğundadır. Üç tür segment descriptordan bahsedilebilir. Bunlar Code Segment Descriptor,Data Segment Descriptor ve System Segment Descriptor.




Koruma mekanizmasına göz attığımız için burada bizim için önemli olan alan DPL.

DPL, Descriptor Privilege Level kısaltmasıdır. Segmentin Seviyesini belirtir. Çoğu modern
işletim sistemi ya 0(kernel mod) ya da 3(user mod) olarak segmentleri seviyelendirir. Aslında segmentli memory ile çalışmamamıza rağmen segment kabulü üzerinden konuşmaktayız.


Linux
~~~~~~~~~~~~~~

Koruma mekanizmasının linux işletim sistemi tarafından nasıl kullanıldığını görerek olayları daha iyi kavrayabiliriz. Tabi ki Windows işletim sisteminin veya diğer modern işletim sistemlerinin koruma mekanizmasını uygulayış biçimi çok farklılık göstermeyecektir.

Linux işletim sisteminde LDT tablosu her process için yaratılabilme  imkanına sahip olduğu halde linux tarafından kullanılmamıştır.(modify_ldt() sistem çağrısına göz atabilirsiniz).

Aşağıdaki tablo x86 linux işletim sisteminin tanımladığı GDT tablosudur. 


Bu tabloda bizim için önemli olan 4 descriptordan bahsedeceğiz. Bunlar __KERNEL_CS , __KERNEL_DS , __USER_CS, __USER_DS  ile tanımlı descriptorlardır.

__KERNEL_CS ve __KERNEL_DS(bunlar birer makro ama kolay olduğundan  descriptor olarak ifade ediyorum) descriptorları kernel tarafından kullanılmaktayken __USER_CS ve __USER_DS descriptorları ise linux tarafından sistemdeki tüm user processleri tarafından kullanılmaktadır.

Sistemdeki tüm processler bu 4 descriptorı paylaşarak birbirinden kesin bir biçimde  ayrılmaktadır.
(Kernel space ve User Space ayrımı.)


Bu 4 descriptorın içeriği ise şu şekildedir.

__KERNEL_CS Desriptorı, (Kernel Mode Code Segment)

o Limit = 0xfffff
o Base = 0x00000000
o G (granularity flag) = 1, page boyutunda hizalama olduğunu belirtir.
o S (system flag) = 1, normal code veya data segment
o Type = 0xa, Code segmenti olduğunu belirtir.(executable,read) 
o DPL (Descriptor Privilege Level) = 0, Kernel Mode olduğunu belirtir.
o D/B (32-bit address flag) = 1, offsetlerin 32-bit adres olduğunu belirtir.


(*)Eğer G biti set edilmişse Segmentin boyutu LIMIT * PAGE_SIZE olarak hesaplanır. 
PAGE boyutunu 4K olarak ele alırsanız Segmentin boyutunu 4GB olarak hesaplandığını görebilirsiniz.(2^20 * 2^12 = 2^32 ) 

__KERNEL_DS Descriptorı,  ( Kernel Mode Data Segment)

o Limit = 0xfffff
o Base = 0x00000000
o G (granularity flag) = 1, page boyutunda hizalama olduğunu belirtir.
o S (system flag) = 1, normal code veya data segment
o Type = 2, Data segmenti olduğunu belirtir.(read,write)
o DPL (Descriptor Privilege Level) = 0, Kernel mod olduğunu belirtir.
o D/B (32-bit address flag) = 1, offsetlerin 32-bit adres olduğunu belirtir.


__USER_CS Descriptorı,  ( User Mode Code Segment)

o Limit = 0xfffff
o Base = 0x00000000
o G (granularity flag) = 1, page boyutunda hizalama olduğunu belirtir.
o S (system flag) = 1, normal code veya data segment
o Type = 0xa, Code segment olduğunu belirtir.(executable,read)
o DPL (Descriptor Privilege Level) = 3, User mode olduğunu belirtir.
o D/B (32-bit address flag) = 1, offsetlerin 32-bit adres olduğunu belirtir.


__USER_DS Descriptorı, ( User Mode Data Segment) 

o Base = 0x00000000
o Limit = 0xfffff
o G (granularity flag) = 1, page boyutunda hizalama olduğunu belirtir.
o S (system flag) = 1,  normal code veya data segment
o Type = 2, Data segmenti olduğunu belirtir.(read,write)
o DPL (Descriptor Privilege Level) = 3, User Mod olduğunu belirtir.
o D/B (32-bit address flag) = 1, offsetlerin 32-bit adres olduğunu belirtir.


Korumanın Sağlanması
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

CPL,DPL,RPL değerlerine bağlı olarak işlemci ya exception fırlatır ya da olağan akışı bozmaz.



(*)Görsel guastovo duarte adında bir şahısa ait ve oldukça güzel bir çizim. 

Korumanın nasıl gerçekleştirildiğini açıklayacak olursak Data segment selectorüne herhangi bir değer yüklendiği zaman Current Privilege Level( CS registerın son 2 biti) ile Data segment selectorüne ait  Descriptor Privilege Level(DPL) değeri karşılaştırılır ve yukarıdaki belirtilen eşitsizliğe bağlı olarak ya exception fırlatılır ya da olağan akış bozulmaz.

Bir başka exception sebepi ise çalışılan seviyenin değişmesidir. Bu da  CS registerının değişmesi ile mümkün olmaktadır.Bunu yapmaya kalkıştığınızda yine x86 mimarinin privilege level kontrolü gerçekleştirilerek segmentation fault hatası alırsınız.Fakat bazı durumlarda User moddan Kernel Moda geçiş yapılması gerekebilir.(sistem çağrıları gibi)

Bu durumlar kontrollü bir şekilde işletim sistemleri tarafından gerçekleştirilmektetir. Bu durum işletim sisteminde interruptlar(başka bir yazının konusu) aracılığı ile gerçekleştirilir.  



User modda çalışan bir uygulamanın segment registerlarının  ilgili değerlere setlendiği görülmektedir.


CS registerının içeriği değiştirilmeye çalışıldığında oluşacak hata yukarıdaki gibidir.


Paging
~~~~~~~~~~~~~~

Son olarak korumaların pageler üzerinde nasıl gerçekleştirdiğini de anlatarak  konuyu bitirebiliriz. Yalnız burada paging nedir veya page nedir gibi sorulara cevap vermeyeceğiz zaten önceki yazılarda bunlardan bahsetilmişti.(Virtual Memory yazısı).

Buraya kadar bahsettiğimiz korumalar fark edebileceğiniz gibi linear adres dönüşümleri(tam olarak dönüşümden bahsedilemez fakat elde edilen adrese intel terminolojisinde linear adres olarak bilinir amma  da uzun parantez içi oldu ) sırasında uygulanmaktatır. Herhangi bir linear adresin fiziksel adrese dönüşümü sırasında bir takım kontroller gerçekleştirilmektedir.

(*)Linear adresin fiziksel adrese dönüşümü belleğe erişimin olduğunu gösterir.

Segmentlerin karakteristiğini belirten  bir tablo olduğu gibi sistemdeki her pagein karakteristiğini belirten page tablosu mevcuttur. Page tablosundaki kayıtların formatları aşağıdaki gibidir. Bu format x64 mimarinin kullandığı kayıt formatıdır. x86 mimari de benzer kayıt formatına sahiptir.
x64 ve x86 mimarinin page tablolarının kayıtları arasındaki fark, N/X biti ve kayıtların uzunluğudur..




NX biti Never Execute karşı gelir. Bu bit ile işlemci bu sayfa üzerinde asla kod çalıştırmaz.

(*)Kernelın kendisine ait olan Pageler Supervisor diğer pageler ise user page olarak bilinir.

U/S(User/Supervisor) biti sayesinde CPL değerine bağlı olarak sayfaya erişim kontrolü yapılır. CPL=3 iken Supervisor Sayfasına erişim gerçekleştirilmesi durumunda  Page-fault exception fırlatılır. 


R/W(Read/Write) biti sayesinde bir page ya yalnızca okunabilir ya da yazma ve okuma hakkının ikisine birden sahip olur. Bu bit de page protection için  kullanılır. Yalnızca okunabilir bir sayfaya yazma gerçekleştirildiğinde Page-fault exception fırlatılır.

Umarım faydalı olmuştur.

Referanslar :

Understanding The Linux Kernel
AMD64 Architecture Programmer's Manual Volume 2 : System Programming

EOF

Hiç yorum yok:

Yorum Gönder