Почему Linux на x86 использует разные сегменты для пользовательских процессов и ядра?

StackOverflow https://stackoverflow.com/questions/4575032

Вопрос

Итак, я знаю, что Linux использует четыре сегмента по умолчанию для процессора x86 (код ядра, данные ядра, код пользователя, пользовательские данные), но все они имеют одинаковую базу и ограничение (0x00000000 и 0xfffff), что означает, что каждый сегмент отображает одинаково. набор линейных адресов.

Учитывая это, зачем даже есть сегменты пользователя/ядра? Я понимаю, почему должны быть отдельные сегменты для кода и данных (просто из -за того, как процессор X86 занимается реестрами CS и DS), но почему бы не иметь единый сегмент кода и единый сегмент данных? Защита памяти осуществляется с помощью пейджинга, а сегменты пользователя и ядра в любом случае отображаются по тем же линейным адресам.

Это было полезно?

Решение

Архитектура x86 связывает тип и уровень привилегий с каждым дескриптором сегмента. Тип дескриптора позволяет выполнять только сегменты только для чтения, чтения/записи, исполняемого файла и т. Д., Но основная причина для различных сегментов, имеющих одинаковую базу и ограничение, состоит в том, чтобы позволить использовать другой уровень привилегий дескриптора (DPL).

DPL составляет два бита, что позволяет кодировать значения от 0 до 3. Когда уровень привилегий равен 0, тогда говорят Кольцо 0, который является наиболее привилегированным. Дескрипторы сегмента для ядра Linux являются кольцом 0, тогда как дескрипторы сегмента для пользовательского пространства являются кольцом 3 (наименьшая привилегированная). Это верно для большинства сегментированных операционных систем; Ядром операционной системы является кольцо 0, а остальное - кольцо 3.

Ядро Linux устанавливает, как вы упомянули, четыре сегмента:

  • __Kernel_cs (сегмент кода ядра, base = 0, limit = 4gb, type = 10, dpl = 0)
  • __Kernel_ds (сегмент данных ядра, base = 0, limit = 4GB, type = 2, dpl = 0)
  • __User_cs (сегмент кода пользователя, base = 0, limit = 4gb, type = 10, dpl = 3)
  • __User_ds (сегмент пользовательских данных, base = 0, limit = 4GB, type = 2, dpl = 3)

База и предел всех четырех одинаковы, но сегменты ядра являются DPL 0, сегментами пользователя являются DPL 3, сегменты кода являются исполняемыми и читаемыми (не доступны для записи), а сегменты данных читаемые и записываются (не исполняют исполнение) Анкет

Смотрите также:

Другие советы

Архитектура управления памятью x86 использует как сегментацию, так и пейджинг. Очень грубо говоря, сегмент - это разделение адресного пространства процесса, которое имеет свою собственную политику защиты. Таким образом, в архитектуре x86 можно разделить диапазон адресов памяти, которые процесс видит на несколько смежных сегментов, и назначить различные режимы защиты каждому. Пейджинг - это метод для картирования небольших (обычно 4 КБ) областей адресного пространства процесса на куски реальной физической памяти. Таким образом, пейджинг контролирует, как области внутри сегмента отображаются на физическом ОЗУ.

Все процессы имеют два сегмента:

  1. Один сегмент (адреса от 0x00000000 по 0xbfffffff) для данных, специфичных для процесса, таких как код программы, статические данные, куча и стек. Каждый процесс имеет свой независимый пользовательский сегмент.

  2. Один сегмент (адресов от 0XC0000000 по 0xffffffff), который содержит специфичные для ядра данных, такие как инструкции ядра, данные, некоторые стеки, на которых может выполняться код ядра, и, что более интересно, область в этом сегменте напрямую отображается в физическую память, так что это, так что это Ядро может непосредственно получить доступ к местоположениям физической памяти, не беспокоясь о переводе адреса. Один и тот же сегмент ядра отображается в каждом процессе, но процессы могут получить к нему доступ только при выполнении в режиме защищенного ядра.

Таким образом, в пользовательском режиме процесс может получить доступ только к адресам менее 0xc0000000; Любой доступ к адресу выше этого приводит к ошибке. Однако, когда процесс пользовательского режима начинает выполнять в ядре (например, после того, как сделал системный вызов), защитный бит в процессоре изменяется в режим супервизора (и некоторые регистры сегментации изменяются), что означает, что процесс является тем самым можно получить доступ к адресам выше 0xc0000000.

Обратитесь к: ЗДЕСЬ

В x86 - Регистры сегмента Linux используются для проверки переполнения буфера [см. Приведенный ниже фрагмент кода, которые определили некоторые массивы в стеке]:

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]);
}

Теперь, если мы увидим разборку кода, сгенерированного GCC,.

Дамп кода ассемблера для функции 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 

Теперь, если мы удалим массивы ChAR на основе стека из этой функции, GCC не генерирует эту защитную проверку.

Я видел то же, что и GCC даже для модулей ядра. По сути, я видел сбой, когда разбивал код ядра, и это было ошибочно с виртуальным адресом 0x28. Позже я подумал, что мысль я правильно инициализировал указатель стека и правильно загрузил программу, у меня нет правильных записей в GDT, что переводит смещение FS на основе действительного виртуального адреса.

Однако в случае кода ядра он просто игнорировал, ошибку вместо того, чтобы прыгать на что -то вроде __stack_chk_fail@plt>.

Соответствующая опция компилятора, которая добавляет эту охрану в GCC, является -фстак -протектор. Я думаю, что по умолчанию это включено, что компиляция пользовательского приложения.

Для ядра мы можем включить этот флаг GCC через опцию 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.

Соответствующий файл ядра, где этот GS/FS - Linux/Arch/x86/include/asm/stackprotector.h

Память ядра не должна быть читаемо из программ, работающих в пространстве пользователей.

Данные о программе часто не выполняются (DEP, функция процессора, которая помогает охранять не выполнять переполненный буфер и другие злонамеренные атаки).

Все дело в контроле доступа - разные сегменты имеют разные права. Вот почему доступ к неправильному сегменту даст вам «ошибку сегментации».

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top