Подтвердить что ты не робот

Что такое Ring 0 и Ring 3 в контексте операционных систем?

Я изучаю основы разработки драйверов в Windows. Я продолжаю находить термины Ring 0 и Ring 3. Что они означают? Являются ли они такими же, как режим ядра и пользовательский режим?

4b9b3361

Ответ 1

Обзор использования кольца Linux x86

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

В защищенном режиме x86 процессор всегда находится в одном из 4 звонков. Ядро Linux использует только 0 и 3:

  • 0 для ядра
  • 3 для пользователей

Это наиболее сложное и быстрое определение ядра и пользовательского пространства.

Почему Linux не использует кольца 1 и 2: Кольца привилегий ЦП. Почему кольца 1 и 2 не используются?

Как определяется текущее кольцо?

Текущее кольцо выбирается с помощью комбинации:

  • таблица глобальных дескрипторов: таблица в памяти записей GDT, и каждая запись имеет поле Privl, которое кодирует кольцо.

    Инструкция LGDT устанавливает адрес для текущей таблицы дескрипторов.

    Смотрите также: http://wiki.osdev.org/Global_Descriptor_Table

  • в сегменте регистрируются CS, DS и т.д., которые указывают на индекс записи в GDT.

    Например, CS = 0 означает, что первая запись GDT в данный момент активна для исполняемого кода.

Что может сделать каждое кольцо?

Чип процессора физически построен так, чтобы:

  • кольцо 0 может делать что угодно

  • кольцо 3 не может выполнить несколько инструкций и записать в несколько регистров, в частности:

    • не может изменить свое собственное кольцо! В противном случае он мог бы установить себе кольцо 0, и кольца были бы бесполезны.

      Другими словами, нельзя изменить текущий дескриптор сегмента, который определяет текущее кольцо.

    • невозможно изменить таблицы страниц: Как работает подкачка x86?

      Другими словами, нельзя изменить регистр CR3, а само разбиение на страницы предотвращает изменение таблиц страниц.

      Это препятствует тому, чтобы один процесс видел память других процессов из соображений безопасности/простоты программирования.

    • не может зарегистрировать обработчики прерываний. Они настраиваются путем записи в ячейки памяти, что также предотвращается подкачкой страниц.

      Обработчики работают в кольце 0 и нарушают модель безопасности.

      Другими словами, нельзя использовать инструкции LGDT и LIDT.

    • не может выполнять инструкции ввода-вывода, такие как in и out, и поэтому имеет произвольный доступ к оборудованию.

      В противном случае, например, права доступа к файлам будут бесполезны, если какая-либо программа сможет напрямую читать с диска.

      Точнее, благодаря Майклу Петчу: на самом деле ОС может разрешить инструкции ввода-вывода для кольца 3, это фактически контролируется сегментом состояния задачи.

      Невозможно, чтобы кольцо 3 дало себе разрешение сделать это, если у него его не было.

      Linux всегда запрещает это. См. также: Почему Linux не использует аппаратное переключение контекста через TSS?

Как программы и операционные системы переходят между кольцами?

  • когда процессор включен, он запускает исходную программу в кольце 0 (что-то вроде, но это хорошее приближение). Вы можете считать эту исходную программу ядром (но обычно это загрузчик, который затем вызывает ядро все еще в кольце 0).

  • когда пользовательский процесс хочет, чтобы ядро сделало что-то для него, например, запись в файл, он использует инструкцию, которая генерирует прерывание, например int 0x80 или syscall, чтобы подать сигнал ядру. x86-64 Linux hello world пример:

    .data
    hello_world:
        .ascii "hello world\n"
        hello_world_len = . - hello_world
    .text
    .global _start
    _start:
        /* write */
        mov $1, %rax
        mov $1, %rdi
        mov $hello_world, %rsi
        mov $hello_world_len, %rdx
        syscall
    
        /* exit */
        mov $60, %rax
        mov $0, %rdi
        syscall
    

    скомпилируйте и запустите:

    as -o hello_world.o hello_world.S
    ld -o hello_world.out hello_world.o
    ./hello_world.out
    

    GitHub upstream.

    Когда это происходит, ЦП вызывает обработчик обратного вызова прерывания, который ядро зарегистрировало во время загрузки. Вот конкретный пример из чистого металла, который регистрирует обработчик и использует его.

    Этот обработчик работает в кольце 0, который решает, разрешит ли ядро это действие, выполняет действие и перезапускает программу userland в кольце 3. x86_64

  • когда используется системный вызов exec (или когда ядро запустит /init), ядро подготавливает регистры и память нового процесса пользовательского пространства, а затем переходит к точка входа и переключает процессор на кольцо 3

  • Если программа пытается сделать что-то непослушное, например, запись в запрещенный регистр или адрес памяти (из-за подкачки), ЦП также вызывает некоторый обработчик обратного вызова ядра в кольце 0.

    Но поскольку пользовательская область была непослушной, ядро на этот раз может убить процесс или выдать ему предупреждение с сигналом.

  • Когда ядро загружается, оно устанавливает аппаратные часы с некоторой фиксированной частотой, которая периодически генерирует прерывания.

    Эти аппаратные часы генерируют прерывания, которые запускают кольцо 0, и позволяют ему планировать, какие процессы пользователя активизируются.

    Таким образом, планирование может происходить, даже если процессы не выполняют никаких системных вызовов.

Какой смысл иметь несколько колец?

Существует два основных преимущества разделения ядра и пользовательского пространства:

  • проще создавать программы, так как вы уверены, что одно не будет мешать другому. Например, один пользовательский процесс не должен беспокоиться о перезаписи памяти другой программы из-за подкачки страниц или о переводе оборудования в недопустимое состояние для другого процесса.
  • это более безопасно. Например. права доступа к файлам и разделение памяти могут помешать хакерскому приложению читать ваши банковские данные. Это предполагает, конечно, что вы доверяете ядру.

Как поиграть с этим?

Я создал "голую железную" установку, которая должна быть хорошим способом манипулирования кольцами напрямую: https://github.com/cirosantilli/x86-bare-metal-examples

К сожалению, у меня не хватило терпения сделать пример пользовательского пространства, но я дошел до настройки пейджинга, поэтому пользовательское пространство должно быть выполнимым. Мне бы очень хотелось увидеть запрос на выдачу.

В качестве альтернативы модули ядра Linux работают в кольце 0, поэтому вы можете использовать их для проверки привилегированных операций, например, прочитайте управляющие регистры: Как получить доступ к управляющим регистрам cr0, cr2, cr3 из программы? Получение ошибки сегментации

Вот удобная настройка QEMU + Buildroot, чтобы попробовать его, не убивая своего хоста.

Недостатком модулей ядра является то, что другие kthreads работают и могут мешать вашим экспериментам. Но в теории вы можете взять на себя все обработчики прерываний с вашим модулем ядра и владеть системой, на самом деле это был бы интересный проект.

Отрицательные кольца

Хотя отрицательные кольца фактически не упоминаются в руководстве Intel, на самом деле существуют режимы ЦП, которые имеют более широкие возможности, чем само кольцо 0, и поэтому хорошо подходят для имени "отрицательного кольца".

Одним из примеров является режим гипервизора, используемый в виртуализации.

Подробнее см.: https://security.stackexchange.com/questions/129098/what-is-protection-ring-1

ARM

В ARM кольца называются уровнями исключения, но основные идеи остаются прежними.

В ARMv8 существует 4 уровня исключений, которые обычно используются как:

  • EL0: пользовательское пространство

  • EL1: ядро ("супервизор" в терминологии ARM).

    Введено с инструкцией svc (SuperVisor Call), ранее известной как swi до унифицированной сборки, которая является инструкцией, используемой для выполнения системных вызовов Linux. Пример Hello World ARMv8:

    hello.S

    .text
    .global _start
    _start:
        /* write */
        mov x0, 1
        ldr x1, =msg
        ldr x2, =len
        mov x8, 64
        svc 0
    
        /* exit */
        mov x0, 0
        mov x8, 93
        svc 0
    msg:
        .ascii "hello syscall v8\n"
    len = . - msg
    

    GitHub upstream.

    Проверьте это с QEMU на Ubuntu 16.04:

    sudo apt-get install qemu-user gcc-arm-linux-gnueabihf
    arm-linux-gnueabihf-as -o hello.o hello.S
    arm-linux-gnueabihf-ld -o hello hello.o
    qemu-arm hello
    

    Вот конкретный пример из чистого металла, который регистрирует обработчик SVC и выполняет вызов SVC.

  • EL2: гипервизоры, например Xen.

    Введено с инструкцией hvc (вызов HyperVisor).

    Гипервизор для ОС, то же самое, что ОС для пользовательского пространства.

    Например, Xen позволяет вам запускать несколько ОС, таких как Linux или Windows, в одной и той же системе одновременно, и он изолирует ОС друг от друга для обеспечения безопасности и простоты отладки, как это делает Linux для пользовательских программ.

    Гипервизоры являются ключевой частью современной облачной инфраструктуры: они позволяют нескольким серверам работать на одном оборудовании, поддерживая использование оборудования всегда близким к 100% и экономя много денег.

    Например, AWS использовала Xen до 2017 года, когда о его переходе на KVM стало известно.

  • EL3: еще один уровень. Пример TODO.

    Введено с помощью инструкции smc (вызов в безопасном режиме)

Эталонная модель архитектуры ARMv8 DDI 0487C.a - Глава D1 - Модель программиста на уровне системы AArch64 - Рисунок D1-1 прекрасно иллюстрирует это:

enter image description here

Обратите внимание, что ARM, возможно, благодаря ретроспективе, имеет лучшее соглашение об именах для уровней привилегий, чем x86, без необходимости использования отрицательных уровней: 0 - нижний, а 3 - самый высокий. Более высокие уровни, как правило, создаются чаще, чем более низкие.

Текущий EL может быть запрошен с помощью инструкции MRS: каков текущий режим выполнения/уровень исключения и т.д.?

ARM не требует наличия всех уровней исключений, чтобы обеспечить реализации, которым не требуется эта функция для сохранения площади микросхемы. ARMv8 "Уровни исключений" гласит:

Реализация может не включать все уровни исключений. Все реализации должны включать EL0 и EL1. EL2 и EL3 являются необязательными.

Например, QEMU по умолчанию имеет значение EL1, но EL2 и EL3 можно включить с помощью параметров командной строки: qemu-system-aarch64 вводит el1 при эмуляции включения питания a53

Фрагменты кода, протестированные на Ubuntu 18.10.

Ответ 2

Процессоры Intel (x86 и другие) позволяют приложениям ограничить полномочия. Для ограничения (защиты) критически важных ресурсов, таких как IO, память, порты и т.д., Процессор, поддерживающий связь с ОС (в этом случае Windows) предоставляет уровни привилегий (0 - наименьшая привилегия на 3), которые соответствуют режиму ядра и пользовательскому режиму соответственно.

Таким образом, ОС запускает код ядра в кольце 0 - высокий уровень привилегий (0), предоставляемый ЦП, и код пользователя в кольце 3.

Подробнее см. http://duartes.org/gustavo/blog/post/cpu-rings-privilege-and-protection/

Ответ 3

Ну, это довольно широкий вопрос. Однако вы можете использовать Google или просто прочитать статьи в Википедии об этих вещах, чтобы получить первый обзор.