5.3. Уровни привилегий
Привилегии - это свойство (обычно устанавливаемое при проектировании системы), которое определяет, какие компьютерные операции разрешаются в любой момент времени и какие доступы к памяти законны. Привилегии используются для обеспечения безопасности в компьютерной системе.
Защита на уровне сегментов представлена четырьмя уровнями привилегий. Уровень привилегий (Privilege Level) - один из четырех уровней привилегий микропроцессора. Привилегии реализуются путем присвоения значения от 0 до 3 ключевым объектам, которые опознаются процессором. Значение 0 соответствует наибольшим привилегиям, тогда как значение 3 - наименьшим.
![Уровни привилегий](pl.gif)
Четыре уровня привилегий можно интерпретировать в виде колец защиты. Центр - уровень 0 - предназначен для сегментов, содержащих наиболее критичные программы (обычно ядро операционной системы). Внешние кольца предназначены для сегментов с менее критичными программами или данными. Использование всех четырех уровней привилегий не является необходимым. Существующие системы, спроектированные с меньшим количеством уровней, могут просто игнорировать другие допустимые уровни. UNIX и Windows, например, используют только два уровня привилегий - 0 (для ядра системы) и 3 (для всего остального), а OS/2 использует уровни 0 (для ядра системы), 2 (для процедур ввода-вывода) и 3 (для прикладных программ).
Механизм контроля уровня привилегий микропроцессора оперирует следующими значениями:
CPL - текущий уровень привилегий (Current Privilege Level) : уровень привилегий, на котором в данный момент исполняется задача. Значение CPL хранится в поле RPL селектора сегмента кода, который помещен в регистр CS. Обычно это значение соответствует уровню привилегий дескриптора исполняемого сегмента кода. Уровень привилегий меняется, когда управление передается сегменту кода с другим значением DPL (за исключением подчиняемых сегментов кода).
DPL - уровень привилегий дескриптора (Descriptor Privilege Level) : наименее привилегированный уровень, на котором задача может получить доступ к сегменту или шлюзу, связанному с этим дескриптором. Уровень DPL определяется битами 46 и 45 дескриптора.
RPL - запрашиваемый уровень привилегий (Requested Privilege Level) используется для временного понижения своего уровня привилегий при обращении к памяти. RPL заносится в младшие биты селектора.
Механизм контроля уровня привилегий обычно сравнивает уровень привилегий дескриптора (DPL) с максимальным из двух чисел CPL и RPL. Наименее привилегированный из текущего уровня привилегий и запрашиваемого считается эффективным уровнем привилегий :
Контроль привилегий при доступе к данным
Контроль привилегий осуществляется при загрузке селектора в сегментный регистр DS, ES, FS, GS (либо при обращении к памяти, если селектор содержится в коде инструкции). Программа может обратиться к сегменту данных, который находится на том же или более низком уровне привилегий (с учетом RPL), т.е. доступ к данным разрешен, если
в противном случае генерируется нарушение общей защиты.
Контроль привилегий при доступе к стеку
Контроль привилегий осуществляется при загрузке селектора в регистр SS. Программа должна использовать сегмент стека, находящийся на том же уровне привилегий, т.е.
Контроль привилегий при передаче управления
Передача управления другому сегменту кода может происходить:
- при выполнении межсегментного перехода (FAR JMP);
- при выполнении межсегментного вызова (FAR CALL) и возврате из него (RET);
- при вызове прерывания (INT n), обработке исключения и возврате из них (IRET) (эти вопросы рассмотрены в главе "Прерывания и исключения");
- при выполнении специальных инструкций SYSENTER и SYSEXIT, предназначенных для быстрого вызова системных процедур (эти команды доступны в Pentium II и более поздних процессорах, здесь не рассматриваются).
При выполнении коротких переходов и вызовов сегмент кода не меняется, поэтому контроль привилегий не выполняется (контролируется лишь предел сегмента). Инструкции межсегментного перехода и вызова (JMP и CALL), меняющие сегмент кода, в качестве операнда могут содержать:
- селектор сегмента кода;
- селектор шлюза вызова (который, в свою очередь, указывает на сегмент кода);
- селектор сегмента состояния задачи TSS (который содержит селектор сегмента кода, при этом происходит переключение задач);
- селектор шлюза задачи (который содержит селектор TSS, при этом происходит переключение задач).
При выполнении перехода или вызова по селектору сегмента кода результат контроля привилегий зависит от того, подчиняемый ли сегмент кода назначения или нет.
Для передачи управления на обычный сегмент кода (S=1, типы 4 и 5) его уровень привилегий должен совпадать с текущим уровнем привилегий. Значение RPL должно быть не больше CPL, чтобы не вызывать исключения, но вне зависимости от значения RPL уровень привилегий не сменится.
Чтобы передать управление на подчиняемый сегмент кода (S=1, типы 6 и 7), он должен находиться на том же или более высоком уровне привилегий. Значение RPL не проверяется и может быть любым.
При передаче управления на подчиняемый сегмент кода не происходит смены уровня привилегий. Код в подчиняемом сегменте выполняется с теми же привилегиями, что и вызвавший его код.
Текущий уровень привилегий может измениться при передаче управления через селектор шлюза. Шлюзы бывают четырех типов: шлюз вызова, шлюз задачи, шлюз ловушки и шлюз прерывания (последние два типа не используются в командах перехода и вызова). При передаче управления через селектор шлюза задачи или селектор TSS происходит переключение задач. Одна задача может передать управление другой задаче, при этом контролируется, чтобы объект, через который передается управление, был на том же или более низком уровне привилегий, чем CPL (правило контроля как для сегментов данных). Более подробно вопросы переключения задач описаны в разделе "Многозадачность".
Шлюзы вызова используются для смены уровня привилегий без переключения задач, а также для передачи управления между 16-битными и 32-битными сегментами кода.
![Формат шлюза вызова](gate.gif)
Шлюз вызова определяет:
- сегмент кода, на который передается управление (Segment Selector);
- точку входа в процедуру в указанном сегменте кода (Offset);
- ограничение уровня привилегий для вызывающего сегмента кода (DPL);
- количество параметров, передаваемых вызываемой процедуре через стек (Param.Count);
- размер параметров передаваемых через стек (на рисунке показан 32-битный шлюз вызова, требующий 32-битных параметров в стеке, 16-битный шлюз вызова (S=0, тип 4) требует 16-битных параметров в стеке).
![Механизм вызова через шлюз](callgate.gif)
Для использования шлюза вызова его селектор надо указать в команде межсегментного перехода (FAR JMP) или вызова (FAR CALL) как часть длинного указателя (far pointer). Смещение в этом указателе процессором игнорируется, т.к. точку входа в процедуру (смещение в вызываемом сегменте) хранит сам шлюз.
При передаче управления через шлюз вызова правило контроля привилегий выглядит следующим образом:
Т.е. через шлюз вызова можно обратиться только к не менее привилегированному коду, а сам шлюз вызова должен быть не более привилегирован, чем вызывающий код. Примеры разрешенных вызовов:
![Примеры переходов через шлюз](jumps.gif)
С помощью команды JMP нельзя передать управление на код с другим уровнем привилегий (кроме подчиняемых сегментов кода). Передача управления на другой уровень привилегий всегда подразумевает возврат к исходному уровню привилегий. При обращении к подчиняемому сегменту кода через шлюз уровень привилегий не меняется.
Правило контроля привилегий для сегмента стека требует, чтобы уровень привилегий сегмента стека совпадал с уровнем привилегий сегмента кода, поэтому если уровни привилегий вызывающего и вызываемого кодов различаются, то происходит переключение стека. Каждая задача должна определить отдельный сегмент стека для каждого уровня привилегий, используемого системой. Для этой цели в сегменте состояния задачи отведены поля SS0:ESP0, SS1:ESP1, SS2:ESP2 и SS:ESP. Если система использует все четыре уровня привилегий, то, соответственно, все четыре указателя стека должны быть заданы. При переключении стека процессор копирует указанное в шлюзе число параметров из стека вызывающего кода в стек вызываемого.
![Пример переключения стека](st-sw.gif)
На рисунке показан пример переключения стека при вызове процедуры через шлюз с уровня привилегий 3 на нулевой уровень привилегий с передачей трех параметров.
Возврат из таких процедур должен происходить по команде RETF n,
где n - число параметров. При этом процессор контролирует, чтобы
возврат произошел на не более привилегированный уровень, и при необходимости
производит обратное переключение стека.