Kernel etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
Kernel etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

5 Eylül 2017 Salı

Kernel ve Stack

Not : Bu yazı ile ilgili olarak Segmentation Fault başlıklı yazıya göz atabilirsiniz.


Stack Boyutu
Linux üzerinde stack boyutunu ayarlamak mümkün. Buradaki şekilde durum görülebilmektedir.

Address space layout randomization
Bu yöntemde stack'in başlangıç adresi, kullandığı guard page sayısı rastgele seçilerek, buffer overflow saldırılarına karşı tedbir alınıyor.

Stack'in Yönü
Çoğu mimaride stack aşağıya doğru büyür. Bunu görmek için şöyle yaparız.
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>

static ptrdiff_t
stack_probe(uintptr_t stack_addr_from_main)
{
  int var;
  uintptr_t stack_addr_from_me = (uintptr_t)&var;

  return ((intptr_t) stack_addr_from_me) - 
         ((intptr_t) stack_addr_from_main);
}

int main(void)
{
  int var;
  uintptr_t stack_addr_from_main = (uintptr_t)&var;
  ptrdiff_t stack_delta = stack_probe(stack_addr_from_main);
  printf("Stack offset from one function call = %td\n", stack_delta);
  return 0;
}

Linux ve ulimit komutu
Linux üzerinde stack boyutunu ayarlamak için ulimit komutu kullanılabilir.
Windows ve Enhanced Mitigation Experience Toolkit
Buradaki soruda, EMET aracı kullanılarak, kaç tane stack guard sayfasının konulabileceği ayarlanmış.

Stack Clash
Açıklaması şöyle
Stack Clash is an exploit based on a fairly old technique. The memory used by a process is divided into two regions - the stack and the heap. One generally imagines the stack as growing downwards and the heap as growing upwards. What happens when the either grows enough to clash with the other? More generally, what happens when the stack grows enough to encroach into unrelated memory spaces? The original vulnerability is 12 years old, and the Linux kernel developers fixed it temporarily by using a guard page. However, researchers at Qualys have managed to exploit this despite the guard page.
Stackoverflow Olursa - C#
C#'ta stackoverflow olursa, JIT için kod derleyecek yeterli yer kalmadığı için kurtulmak mümkün değil.

Stackoverflow Olursa -Java
Java JIT kullanmak zorunda olmadığı için stackoverflow'dan kurtulabilir. Örnek:
public class Test
{
    public static void main(String[] args)
    {
        try {
            foo();
        } catch (StackOverflowError e) {
            bar();
        }
        System.out.println("normal termination");
    }

    private static void foo() {
        System.out.println("foo");
        foo();
    }

    private static void bar() {
        System.out.println("bar");
    }
}
C++ Windows
Stack'te yaratılan değişkenlerde buffer overflow olursa yakalanabilir.
Proje / C7C++ / Code Generation / Basic Runtime Cheks = Both yapılır.  Aşağıdaki örnek çalıştırılırsa stack hatası yakalanır.
int main (int argc, char* argv[]){
 char buf [1];
 buf [1] = 'A';
 return 1;
}
Programdan çıkarken Run-Time Check Failure #2 - Stack around the variable 'buf' was corrupted pop-up mesajı gelir.

Stack ve API
Aşağıda stack bilgisini alma ve atamayla ilgili bazı API açıklamaları var.

Windows 8
Stack boyutunu öğrenmek mümkün.
ULONG_PTR lowLimit;
ULONG_PTR highLimit;
GetCurrentThreadStackLimits(&lowLimit, &highLimit);

Posix pthread_getattr_np metodu
pthread_getattr_np ve pthread_attr_getstack metodlarını kullanarak bir threadin kullandığı stack boyunu öğrenmek mümkün.

Posix pthread_attr_setstack metodu
pthread_attr_setstack metodu ile bir threadin kullandığı stack boyutunu atamak mümkün.
pthread_attr_getstack metodu ile bir threadin kullandığı stack boyutunu öğrenmek mümkün.

Stack boyutunu atamak için kullanılacak stack'i kullanıcının yaratması gerekiyor.

Posix pthread_attr_setguardsize metodu
Bazı derleyiciler Buffer Overflow hatalarını yakalamak için Heap ve Stack segmentleri arasına "Guard Page" denilen veri yapıları yerleştirirler. Böylece stack bölümündeki bir taşmayı yakalamak mümkün olur.

pthread_attr_setguardsize metodu ile stack için guard alanı yaratmak mümkün.
How to set stack_size, stack_addr and guardsize when creating a thread başlıklı soruda bu konu daha iyi açıklanmış. Bu konu ile ilgili olarak Segmentation Fault başlıklı yazıya göz atabilirsiniz.

Compiler her zaman Stack Kullanmak Zorunda Değildir
Bir metod çağırıldığı zaman eski metod içindeki her şey stack'e atılır ve yeni metod'a geçilen parametreler de dahil olmak üzere yeni bir stack yaratılır diye biliyordum ancak c & gcc : Stack growth and alignment - for a 64 bit machine sorunda da görülebildiği gibi compiler yeni çağırılan metoda geçilen parametreleri ESI ve EDI register'larına yazarak stack kullanmadan bir metod da çağırabiliyormuş.What is exactly the base pointer and stack pointer? To what do they point? sorusuna verilen cevaplardan birisi olan şu cümle ile ne kast ettiğim daha iyi anlaşılabilir.
Generally (and this may vary from compiler to compiler), all of the arguments to a function being called are pushed onto the stack (usually in the reverse order that they're declared in the function prototype, but this varies).

17 Ağustos 2017 Perşembe

Segmentation Fault

Not : Bu yazı da Address Space Layout Randamization konusuna değinmedim, ileride değinmeyi düşünüyorum.

Segmentation Fault Neden Oluşur
Segmentation Fault oluşmasının en önemli iki sebebi şunlardır.
  1. İmtiyaz seviyemizin dışında olan bir makine talimatını çalıştırmaya çalışmak
  2. Olmayan bir veriye erişmeye teşebbüs ederek Page Fault ve dolayısıyla Segmentation Fault almak
Bu yazının ağırlık noktası 2. madde olacak. Paging işletim sistemlerinde başlı başına bir konu. o yüzden yazının bir çok noktaya değinmesi kaçınılmaz oldu.

İmtiyazlı Talimatı Çalıştırmaya Çalışmak

page fault and segmentation fault sorusu konuyu çok güzel açıklıyor ve bir örnek te veriyor. Ayrıca yazının devamında CPU ring seviyelerini de okuyabilirsiniz.

Page Fault

Kernel Segmentation Fault'u nereden biliyor ?

Protected mode'da çalışan bir CPU fiziksel RAM'e erişmek için iki kademeli bir çevirim işlemine tabi tutulur. İlk kademeye Segmentation, ikincisine ise Paging denilir.

Paging, Segmentation kavramına göre daha kolaydır. Bu yüzden yazıda ilk olarak bu kavramı anlatılacak. Daha sonra da Segmentation kavramına değinilecek.


1. Paging
Memory Management Unit Nedir - MMU yazısına taşıdım.

Translation Lookaside Buffer (TLB)

TLB yukarıda bahsedilen page table veri yapısına erişimi hızlandırmak için kullanılan bir önbellek. Operating System - paging cevabına göz atmak faydalı olabilir.

TLB ile dönüşüm için kullanılan bir hafıza parçası. MIPs ve x86 üzerinde farklı şekillerde çalışabilir. Örneğin How do x86 page tables work? sorusunda TBL'nin MIPS mimarisi üzerinde yazılımla yönetilirken, x86 üzerinden donanıma bağlı olduğu açıklanmış.

En Basit Dönüşüm

Bu hesaplamayı temsil eden bir resimi buradan aldım ve aşağıya ekledim. Sanal adres iki parçaya bölünür ve ilk parça bir indeks gibi kullanılır. İkinci parça ise offset (sayfa eklemesi) olarak kullanılır.

İşlemi gösteren ve mutlaka okunması gereken bir diğer şekli ise Ulrich Drepper'in "What Every Programmer Should Know About Memory" isimli belgesinden aldım.


Bulunan sayfa numarası sayfa tablosunda aranır. Eğer sayfa hafızada mevcut ise memory bus kullanılarak erişilir. Sayfa disk üzerinde ise önce ana hafızaya yüklenir ve daha sonra erişilir. The Basics of Page Faults başlıklı yazıda anlatıldığına göre iki çeşit page fault var. Hard page fault ve Soft page fault. Hard page fault aranılan sayfanın page file'tan yüklenmesi demek. Soft page fault ise zaten RAM üzerinde bulunan bir sayfanın bizim uygulamamız tarafından da kullanılabilecek hale getirilmesi demek. Soft page fault ile örneğin yüklü bulunan bir DLL dosyasının bir kısmı bizim uygulamamız tarafından da kullanılabilir.

Bazen yeni bir sayfanın yüklenmesi için mevcut bir sayfanın diske yazılması gerekir. Bu durumda hangi sayfanın çıkarılacağını seçmek için bir algoritma kullanmalıdır. Page replacement algorithm başlıklı yazıda kullanlabilecek algoritmalarla ilgili bilgi mevcut.

Not : major page fault handler in Linux kernel sorusunda Linux çekirdeğindeki Hard page fault handler'ın nerede bulunduğuna dair bir açıklama var.



Şekil - Hard Page Fault Örneği
Linux  üzerinde bulunan /usr/bin/time komutu ile bir uygulamanın kaç tane hard/soft page fault'a maruz kaldığını görmek mümkün.



MMU tarafından verilen fiziksel adrese erişim için memory bus kullanılır.


MMU çalışan bir program tarafından istenen sanal bir adresi fiziksel adrese çeviremez ise işletim sistemini "Page Fault Exception" ile haberdar eder. İşletim sistem Page Fault Exception gelince buradan aldığım aşağıdaki akışı takip eder.

Erişilmeye çalışılan adresin uygulama adresleri içinde bulunmamasını gösteren bir şekil de aşağıda bulabilirsiniz.





İşletim sistemi bu hatayı yakalar ve o program için segmentation fault oluşturur. Bu hata genellikle core dump dosyasının oluşması ile sonlanır. Yani Invalid Page Fault oluşur.



Not : Windows'ta virtual memory istemek için VirtualAlloc metodu kullanılır.


2. Segmentation


Hafıza bir çok sayfadan oluşsa bile düz bir tabloymuş gibi de düşünülebilir. Segmentation ile ilgili olarak C Programlarının Hafıza Yerleşimi başlıklı yazıya göz atabilirsiniz.

CPU açısından logical adreslerde yukarıdaki şekilde bölündüğü için MMU'ya verilen logical adresleri hesaplamak için segmentation denilen işlem yapılır. Aşağıda buradan aldığım ve her hafıza bölümünün başladığı logical adresi hesaplayabilmemizi sağlayan segmentation işleminin anlatıldığı şekil var.



Bu bölümlerden bazılarının büyüklüğü işletim sistemi ayarları ile atanabilir.

Örneğin stack büyüklüğünün nasıl ayarlanacağını görmek için Kernel ve Stack başlıklı yazıya göz atabilirsiniz.

Bu arada memory mapping segment linux üzerinde Dynamic Linker ile yapılır. Bu durum gösteren bir şekli aşağıya buradan ekledim.

Dynamic Linker'ın ayarlarını gösteren bir başka şekli ise buradan ekledim.

Linux üzerinde bu atamalar ulimit komutuyla yapılabilir. Aşağıda buradan aldığım bir şekilde ulimit ile atanmış değerleri görmek mümkün. Örneğin stack size 10420 kb olarak atanmış.

  • "ulimit -s" komutuyla da uygulamanın en fazla ne kadar stack (kilobyte cinsinden) kullanabileceği  ayarlanabilir.
  •  
  • "ulimit -m" komutuyla da uygulamanın en fazla ne kadar fiziksel hafıza (kilobyte cinsinden) kullanabileceği ayarlanabilir. Eğer fiziksel hafıza sınırı aşılırsa virtual memory kullanılmaya başlanır.



2.1 Kernel ve Virtual Memory
Segment ve Page yapılarına baktıktan sonra kernel'ın tüm bu yapıların nerede başladığını nerede bittiğini takip etmesi gerektiğini görüyoruz. Bu iş için de vm_area_struct denilen yapı kullanılıyor. Çalışan bir uygulamanın kullandığı tüm hafıza bölümlerini takip edebilmek için aşağıdaki şekil kullanılıyor. task_struct çalışan bir uygulamanın bilgilerini tutar. Bu yapının içindeki mm_struct hafıza bilgilerini tutar. bu yapının içindeki vm_area_struct ise bir linked list'tir ve sanal adreslere erişimi sağlar.


How to force a user program to block 16GB of physical RAM? sorusunda da cevaplandığı gibi kernel bazı hafıza adreslerini RAM üzerinde tutmaya ve swap alanını kullanmamaya zorlanabilir. Bu iş için mlock fonksiyonu kullanılabiliyor. mlock fonksiyonunun tersi ise munlock.

2.2 Stack Overflow Koruması

Not : Bu konu ile ilgili olan Posix çağrıları için Kernel ve Stack başlıklı yazıya göz atabilirsiniz.

Stack ve heap uygulamanın içinde büyüklüğü değişen iki bölümdür. Bu bölümlerdeki taşmalar programda Buffer Overflow sebep olur. Bazı derleyiciler Buffer Overflow hatalarını yakalamak için Heap ve Stack segmentleri arasına "Guard Page" denilen veri yapıları yerleştirirler. Böylece stack bölümündeki bir taşmayı yakalamak mümkün olur.
Aşağıdaki şekli buradan aldım ve "Guard Page"'in nereye yerleştiğini gösteriyor.


2.2 Kernel'in Yüklenmesi 

Programlar arasındaki izolasyona tek istisna  kernel'a (çekirdeğe) erişim tekniğidir. Çekirdeğin çoğu kısmı sadece bir defa yüklenir. MMU her bir program için çekirdeğe erişim satırlarını ekler.

Örneğin Windows üzerinde kernel tarafından kullanılan Non-Paged Pool (Disksiz Bellek) sayfaları asla RAM'den çıkarılmaz!.

Linux Virtual Memory yazısında da anlatıldığı gibi Linux üzerind kernel 3 GB'tan yukarıdaki adresleri kullanır.


Burada bu konu ile ilgili güzel bir yazı var. Aşağıdaki şekli ise buradan aldım ve sürekli RAM'de tutulması gereken bellek miktarını gösteriyor.



Konuyla uzaktan ilgili ancak yine de yazmanın faydalı olduğunu düşündüm. Linux'ta cat /proc/meminfo komutuyla görülen bilgilerde memtotal alanı için redhat'in sayfasında şöyle bir açıklama var:
MemTotal: Total usable ram (i.e. physical ram minus a few reserved bits and the kernel binary code)
MemTotal alanını gösteren şekil ise aşağıda. Yukarıdaki kernel'in yüklenmesi şeklini de göz önüne alınca MemTotal'in aslında tüm fiziksel RAM'i değil, RAM'de bulunan kernel kodu hariç geri kalan büyüklüğü gösterdiğini anlamak daha kolay oluyor.


Windows'ta toplam fiziksel belleği görmek daha kolay. Bunu "Görev Yöneticisi" gösteriyor ve örnek bir şekli buradan aldım.

Kernel sadece bir defa yüklense bile diğer uygulamalardan farklı bir güvenlik seviyesinde çalışmaktadır. Bu güvenlik seviyesi Ring 0 olarak anılır.

Dolayısıyla çekirdek kodu daha fazla erişime sahiptir. Bir kodun Ring 0 seviyesinde çalışması virtual memory kullanmadığı anlamına gelmez.