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

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

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

Ответы [ 4 ]

15 голосов
/ 01 января 2011

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

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

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

  • __ KERNEL_CS (сегмент кода ядра,база = 0, лимит = 4 ГБ, тип = 10, DPL = 0)
  • __ KERNEL_DS (сегмент данных ядра, база = 0, лимит = 4 ГБ, тип = 2, DPL = 0)
  • __USER_CS (сегмент кода пользователя, база = 0, лимит = 4 ГБ, тип = 10, DPL = 3)
  • __ USER_DS (сегмент данных пользователя, база = 0, лимит = 4 ГБ, тип = 2, DPL = 3)

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

См. также:

1 голос
/ 01 января 2011

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

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

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

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

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

См. Ред .: ЗДЕСЬ

0 голосов
/ 18 марта 2014

в 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 

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

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

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

Соответствующей опцией компилятора, которая добавляет эту защиту в gcc, является -fstack-protector. Я думаю, что это включено по умолчанию, которое компилирует пользовательское приложение.

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

0 голосов
/ 01 января 2011

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

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

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

...