Dlaczego Linux na x86 używa różnych segmentów dla procesów użytkownika i jądra?

Tak więc, Wiem, że Linux używa czterech domyślnych segmentów dla procesora x86( kod jądra, dane jądra, kod użytkownika, Dane użytkownika), ale wszystkie mają tę samą bazę i limit (0x00000000 i 0xfffff), co oznacza, że każdy segment mapuje ten sam zestaw adresów liniowych.

Biorąc to pod uwagę, po co w ogóle mieć segmenty user / kernel? Rozumiem, dlaczego powinny być osobne segmenty dla kodu i danych (właśnie ze względu na to, jak procesor x86 radzi sobie z rejestrami cs i ds), ale dlaczego nie mieć pojedynczy segment kodu i pojedynczy segment danych? Ochrona pamięci odbywa się poprzez stronicowanie, a segmenty użytkownika i jądra mapują do tych samych adresów liniowych.

4 answers

Architektura x86 kojarzy typ i poziom uprawnień z każdym deskryptorem segmentu. Typ deskryptora pozwala na tworzenie segmentów tylko do odczytu, odczytu/zapisu, wykonywalnych, itp., ale głównym powodem, dla którego różne segmenty mają tę samą podstawę i limit, jest umożliwienie użycia innego poziomu uprawnień deskryptora (DPL).

DPL to dwa bity, umożliwiające zakodowanie wartości od 0 do 3. Gdy poziom uprawnień wynosi 0, to mówi się, że jest to pierścień 0 , który jest najbardziej uprzywilejowany. Deskryptory segmentów dla jądra Linuksa to pierścień 0, podczas gdy deskryptory segmentów dla przestrzeni użytkownika to pierścień 3 (najmniej uprzywilejowany). Jest to prawdą dla większości segmentowych systemów operacyjnych; rdzeń systemu operacyjnego to pierścień 0, a reszta to pierścień 3.

Jądro Linuksa tworzy, jak wspomniałeś, cztery segmenty:

  • __KERNEL_CS (Kernel code segment, base=0, limit=4GB, type=10, DPL=0)
  • _ _ KERNEL _ DS (Kernel data segment, base=0, limit=4GB, type=2, DPL=0)
  • __USER_CS (User code segment, base=0, limit=4GB, type=10, DPL=3)
  • _ _ USER _ DS (segment danych użytkownika, baza=0, limit=4GB, Typ=2, DPL=3)

Baza i limit wszystkich czterech są takie same, ale segmenty jądra to DPL 0, segmenty użytkownika to DPL 3, segmenty kodu są wykonywalne i czytelne (nie można zapisać), a segmenty danych są czytelne i zapisywalne (nie można wykonać).

Zobacz też:

 15
Author: Daniel Trebbien,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-01 19:16:26

Architektura zarządzania pamięcią x86 wykorzystuje zarówno segmentację, jak i stronicowanie. Z grubsza mówiąc, segment jest partycją przestrzeni adresowej procesu, która ma własną politykę ochrony. Tak więc w architekturze x86 można podzielić zakres adresów pamięci widzianych przez proces na wiele sąsiadujących ze sobą segmentów i przypisać do każdego z nich różne tryby ochrony. Stronicowanie jest techniką mapowania małych (Zwykle 4KB) regionów przestrzeni adresowej procesu do fragmentów rzeczywistych, fizycznych pamięć. W ten sposób stronicowanie kontroluje sposób mapowania regionów wewnątrz segmentu na fizycznej pamięci RAM.

Wszystkie procesy mają dwa segmenty:

  1. Jeden segment (adresy 0x00000000 do 0xbfffffff) dla danych specyficznych dla procesu na poziomie użytkownika, takich jak kod programu, dane statyczne, sterta i stos. Każdy proces ma swój własny, niezależny segment użytkownika.

  2. Jeden segment (adresy od 0xC0000000 do 0xFFFFFFFF), który zawiera dane specyficzne dla jądra, takie jak jądro instrukcje, dane, niektóre stosy, na których może być wykonywany kod jądra, a co ciekawsze, region w tym segmencie jest bezpośrednio mapowany do pamięci fizycznej, tak że jądro może bezpośrednio uzyskać dostęp do lokalizacji pamięci fizycznej bez konieczności martwienia się o tłumaczenie adresów. Ten sam segment jądra jest mapowany do każdego procesu, ale procesy mogą uzyskać do niego dostęp tylko podczas wykonywania w trybie chronionego jądra.

Tak więc, w trybie użytkownika, proces może uzyskać dostęp tylko do adresów mniejszych niż 0xC0000000; każdy dostęp do adresu wyższego niż ten powoduje usterkę. Jednakże, gdy proces w trybie użytkownika rozpoczyna wykonywanie w jądrze (na przykład po wywołaniu systemowym), bit ochrony w procesorze jest zmieniany na tryb nadzorcy (a niektóre rejestry segmentacji są zmieniane), co oznacza, że proces jest w stanie uzyskać dostęp do adresów powyżej 0xC0000000.

Poleć ed z: tutaj

 1
Author: Vikram.exe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-01 18:49:10

W x86-linux rejestry segmentów są używane do sprawdzania przepełnienia bufora [patrz poniższy fragment kodu, który zdefiniował niektóre tablice znaków w stosie]:

static void
printint(int xx, int base, int sgn)
{
    char digits[] = "0123456789ABCDEF";
    char buf[16];
    int i, neg;
    uint x;

    neg = 0;
    if(sgn && xx < 0){
        neg = 1;
        x = -xx;
    } else {
        x = xx;
    }

    i = 0;
    do{
        buf[i++] = digits[x % base];
    }while((x /= base) != 0);
    if(neg)
        buf[i++] = '-';

    while(--i >= 0)
        my_putc(buf[i]);
}

Teraz, jeśli zobaczymy dis-assembly kodu wygenerowanego przez gcc.

Zrzut kodu asemblera dla funkcji printint:

 0x00000000004005a6 <+0>:   push   %rbp
   0x00000000004005a7 <+1>: mov    %rsp,%rbp
   0x00000000004005aa <+4>: sub    $0x50,%rsp
   0x00000000004005ae <+8>: mov    %edi,-0x44(%rbp)


  0x00000000004005b1 <+11>: mov    %esi,-0x48(%rbp)
   0x00000000004005b4 <+14>:    mov    %edx,-0x4c(%rbp)
   0x00000000004005b7 <+17>:    mov    %fs:0x28,%rax  ------> obtaining an 8 byte guard from based on a fixed offset from fs segment register [from the descriptor base in the corresponding gdt entry]
   0x00000000004005c0 <+26>:    mov    %rax,-0x8(%rbp) -----> pushing it as the first local variable on to stack
   0x00000000004005c4 <+30>:    xor    %eax,%eax
   0x00000000004005c6 <+32>:    movl   $0x33323130,-0x20(%rbp)
   0x00000000004005cd <+39>:    movl   $0x37363534,-0x1c(%rbp)
   0x00000000004005d4 <+46>:    movl   $0x42413938,-0x18(%rbp)
   0x00000000004005db <+53>:    movl   $0x46454443,-0x14(%rbp)

...
...
  // function end

   0x0000000000400686 <+224>:   jns    0x40066a <printint+196>
   0x0000000000400688 <+226>:   mov    -0x8(%rbp),%rax -------> verifying if the stack was smashed
   0x000000000040068c <+230>:   xor    %fs:0x28,%rax  --> checking the value on stack is matching the original one based on fs
   0x0000000000400695 <+239>:   je     0x40069c <printint+246>
   0x0000000000400697 <+241>:   callq  0x400460 <__stack_chk_fail@plt>
   0x000000000040069c <+246>:   leaveq 
   0x000000000040069d <+247>:   retq 

Teraz, jeśli usuniemy tablice znaków oparte na stosie z tej funkcji, gcc nie wygeneruje tego sprawdzenia.

Widziałem to Samo generowane przez gcc nawet dla modułów jądra. Zasadniczo I widział awarię podczas botrapowania kodu jądra i powodował błąd z wirtualnym adresem 0x28. Później pomyślałem, że myślałem, że zainicjowałem poprawnie wskaźnik stosu i poprawnie załadowałem program, nie mam odpowiednich wpisów w gdt, które tłumaczyłyby przesunięcie oparte na fs na prawidłowy adres wirtualny.

Jednak w przypadku kodu jądra było to po prostu ignorowanie, błąd zamiast przeskakiwania do czegoś w rodzaju _ _ stack _ chk _ fail@plt>.

Odpowiedni kompilator opcją dodającą tę ochronę w gcc jest -fstack-protector . Myślę, że jest to domyślnie włączone, co kompiluje aplikację użytkownika.

Dla jądra możemy włączyć tę flagę gcc poprzez opcję Config CC_STACKPROTECTOR.

config CC_STACKPROTECTOR
 699        bool "Enable -fstack-protector buffer overflow detection (EXPERIMENTAL)"
 700        depends on SUPERH32
 701        help
 702          This option turns on the -fstack-protector GCC feature. This
 703          feature puts, at the beginning of functions, a canary value on
 704          the stack just before the return address, and validates
 705          the value just before actually returning.  Stack based buffer
 706          overflows (that need to overwrite this return address) now also
 707          overwrite the canary, which gets detected and the attack is then
 708          neutralized via a kernel panic.
 709
 710          This feature requires gcc version 4.2 or above.

Odpowiedni plik jądra, gdzie GS / fs to linux / arch / x86/include/asm / stackprotector.h

 0
Author: jithu83,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-18 17:37:28

Pamięć jądra nie powinna być odczytywana z programów uruchomionych w przestrzeni użytkownika.

Dane programu często nie są wykonywalne (Dep, funkcja procesora, która pomaga chronić przed wykonywaniem przepełnionego bufora i innymi złośliwymi atakami).

Chodzi o kontrolę dostępu-różne segmenty mają różne prawa. Dlatego dostęp do niewłaściwego segmentu spowoduje "błąd segmentacji".

 -2
Author: Borealid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-01 18:19:24