Существует несколько моделей предоставления прав доступа к файлам и другим объектам. Наиболее простая модель используется в системах семейства Unix.
В этих системах каждый файл или каталог имеют идентификаторы хозяина и группы. Определено три набора прав доступа: для хозяина, группы (т.е., для пользователей, входящих в группу, к которой принадлежит файл) и всех остальных. Пользователь может принадлежать к нескольким группам одновременно, файл всегда принадлежит только одной группе.
Бывают три права: чтения, записи и исполнения. Для каталога право исполнения означает право на поиск файлов в этом каталоге. Каждое из прав обозначается битом в маске прав доступа, т.е. все три группы прав представляются девятью битами или тремя восьмеричными цифрами.
Права на удаление или переименование файла не существует; вообще, в
Unix не определено операции удаления файла как таковой, а существует лишь
операция удаления имени unlink
. Для удаления или изменения имени достаточно
иметь право записи в каталог, в котором это имя содержится.
В традиционных системах семейства Unix все глобальные объекты -
внешние устройства и именованные программные каналы - являются файлами (точнее,
имеют имена в файловой системе), и управление доступом к ним охватывается
файловым механизмом. В современных версиях Unix адресные пространства
исполняющихся процессов также доступны как файлы в специальной файловой (или
псевдофайловой, если угодно) системе proc
. Файлы в этой ФС могут
быть использованы, например, отладчиками для доступа к коду и данным
отлаживаемой программы. Управление таким доступом также осуществляется
стандартным файловым механизмом. Кроме доступа к адресному пространству, над
процессом в Unix определена, по существу, только операция посылки
сигнала. Обычный пользователь может посылать сигналы только своим процессам;
только суперпользователь (root
) может посылать их чужим процессам.
Все остальные операции осуществимы только между процессами, связанными
отношением родитель/потомок, и распределение прав доступа в этой ситуации вообще
не нужно. Таким образом, файловые права доступа используются для управления
доступом к практически любым объектам ОС.
В Unix System V появились объекты, не являющиеся файлами и идентифицируемые численными ключами доступа вместо имен. Все эти объекты являются средствами межпроцессного взаимодействия: это семафоры, очереди сообщений и сегменты разделяемой памяти. Каждый такой объект имеет маску прав доступа, аналогичную файловой, и доступ к ним контролируется точно так же, как и к файлам.
Основное преимущество этого подхода состоит в его простоте. Фактически это наиболее простая из систем привилегий, пригодная для практического применения. Иными словами, более простые системы непригодны вообще. Кроме того, практика эксплуатации систем, использующих эту модель, показывает, что она вполне адекватна подавляющему большинству реальных ситуаций.
Многие современные системы, не входящие в семейство Unix, а также и некоторые версии Unix, например, HP/UX или SCO UnixWare 2.x, используют более сложную и гибкую систему управления доступом, основанную на списках управления доступом (Access Control Lists - ACL). С каждым защищаемым объектом, кроме идентификатора его хозяина, связан список записей. Каждая запись состоит из идентификатора пользователя или группы и списка прав для этого пользователя или группы. Понятие группы в таких системах не играет такой большой роли, как в Unix, а служит лишь для сокращения ACL, позволяя задать права для многих пользователей одним элементом списка. Многие системы, использующие эту модель, например Novell Netware, даже не ассоциируют с файлом идентификатора группы.
Обычно список возможных прав включает в себя право на изменение ACL. Таким образом, не только хозяин объекта может изменять права доступа к нему. Это право может быть дано и другим пользователям системы или даже группам пользователей, если это окажется для чего-то необходимо.
Однако за дополнительную гибкость приходится платить снижением производительности. В системах семейства Unix проверка прав доступа осуществляется простой битовой операцией над маской прав. В системах же, использующих ACL, необходим просмотр списка, который может занять намного больше времени. Самое плохое состоит в том, что мы не можем гарантировать завершения этого поиска за какое-либо время, не устанавливая ограничений на длину списка.
В современных системах накладные расходы, связанные с использованием ACL, считаются достаточно малыми, поэтому эта модель распределения прав приобретает все большую популярность.
Кроме прав доступа к объектам, система должна управлять выдачей некоторых привилегий. Так, для выполнения резервного копирования и восстановления файлов необходим пользователь, способный осуществлять доступ к файлам и операции на ними, не обращая внимания на права доступа.
Во всех системах необходимы пользователи, имеющие право изменять конфигурацию системы, заводить новых пользователей и группы и т.д. Если система обеспечивает запуск процессов реального времени, создание таких процессов тоже должно контролироваться, поскольку процесс РВ имеет более высокий приоритет, чем все процессы разделенного времени, и может, просто не отдавая процессор, заблокировать все остальные задачи.
В большинстве современных многопользовательских ОС, не входящих в семейство
Unix, с каждым пользователем ассоциирован список привилегий, которыми
этот пользователь обладает. В системах семейства Unix все гораздо проще:
обычные пользователи не обладают никакими привилегиями. Для выполнения
привилегированных функций существует пользователь c численным идентификатором 0
- суперпользователь (superuser), который обладает, подобно
христианскому Господу Богу, всеми мыслимыми правами, привилегиями и атрибутами.
По традиции суперпользователь имеет символьное имя root
-
``корень''.
Система списков привилегий предоставляет большую гибкость, чем один сверхпривилегированный суперпользователь, потому что позволяет администратору системы передать часть своих функций, например выполнение резервного копирования, другим пользователям, не давая им при этом других привилегий. Однако, как и ACL, эта схема приводит к большим накладным расходам при контроле прав доступа: вместо сравнения идентификатора пользователя с нулем мы должны сканировать список привилегий, что несколько дольше.
В некоторых ситуациях нужен более тонкий контроль за доступом, чем управление доступом на уровне файлов. Например, для изменения информации о пользователе необходим доступ на запись к соответствующей базе данных, но не ко всей, а только к определенной записи. Вполне естественно и даже необходимо дать пользователю возможность менять пароль, не обращаясь к администратору. С другой стороны, совершенно недопустима возможность менять пароли других пользователей. Одним из решений было бы хранение пароля для каждого из пользователей в отдельном файле, но это во многих отношениях неудобно. Другое решение может состоять в использовании модели клиент-сервер с процессом-сервером, исполняющимся с привилегиями администратора, который является единственным средством доступа к паролям. Например, в Windows NT весь доступ к пользовательской базе данных осуществляется через системные вызовы, то есть функции процесса-сервера исполняет само ядро системы. Этот подход позволяет решить проблему контроля доступа именно к пользовательской базе данных, но аналогичная проблема возникает и в других ситуациях.
В системах семейства Unix для этой цели был предложен оригинальный механизм, известный как setuid (setting of user id - установка [эффективного] идентификатора пользователя).
В Unix каждая задача имеет два пользовательских
идентификатора: реальный и эффективный. Реальный идентификатор
обычно совпадает с идентификатором пользователя, запустившего задание. Для
проверки прав доступа к файлам и другим объектам, однако, используется
эффективный идентификатор. При запуске обычных задач реальный и эффективный
идентификаторы совпадают. Несовпадение может возникнуть при запуске программы с
установленным признаком setuid
. При этом эффективный идентификатор
для задачи устанавливается равным идентификатору хозяина файла, содержавшего
программу.
*
Признак setuid
является атрибутом файла, содержащего
загрузочный модуль программы. Только хозяин может установить этот признак, таким
образом передавая другим пользователям право исполнять эту программу от своего
имени. При модификации файла или при передаче его другому пользователю признак
setuid
автоматически сбрасывается.
*
Так, например, программа /bin/passwd
принадлежит пользователю
root
, и у нее установлен признак setuid
. Любой
пользователь, запустивший эту программу, получает задачу, имеющую право
модификации пользовательской базы данных - файлов /etc/passwd
и
/etc/shadow
. Однако прежде чем произвести модификацию, программа
passwd
проверяет допустимость модификации. Например, при смене
пароля она требует ввести старый пароль. Сменить пароль пользователю, который
его забыл, может только root
.
Другим примером setuid
-программы может служить программа
/bin/ps
(process status - состояние процессов). Системы семейства
Unix не предоставляют системных вызовов для получения статистики об
исполняющихся процессах. Программа /bin/ps
анализирует виртуальную
память ядра системы, доступную как файл устройства /dev/kmem
,
находит в ней список процессов и выводит содержащиеся в списке данные.
Естественно, только привилегированная программа может осуществлять доступ к
/dev/kmem
.
Механизм setuid был изобретен одним из отцов-основателей Unix Деннисом Ритчи и запатенован фирмой AT&T в 1975 г. Через несколько месяцев после получения патенту был дан статус public domain. Официально фирма AT&T объяснила это тем, что плату за использование данного патента нецелесообразно делать высокой, а сбор небольшой платы с большого числа пользователей неудобен и тоже нецелесообразен.
Механизм setuid позволяет выдавать привилегии, доступные только
суперпользователю, как отдельным пользователям (при этом
setuid
-программа должна явным образом проверять реальный
идентификатор пользователя и сравнивать его с собственной базой данных), так и
группам (при этом достаточно передать setuid
-программу
соответствующей группе и дать ей право исполнения на эту программу), таким
образом отчасти компенсируя недостаточную гибкость стандартной системы прав
доступа и привилегий в системе Unix.
Легко понять, что разделение доступа к данным невозможно без контроля над доступом к другим объектам системы, в первую очередь к оперативной памяти, физическим внешним устройствам и драйверам этих устройств. Без такого контроля все средства управления доступом к данным во внешней памяти оказываются бессмысленными, как запертая дверь в сарае без одной стены. Поэтому нельзя всерьез говорить о безопасности в системах, не разделяющих доступ задач к физической памяти, таких как DOS, MS Windows 3.x, Windows 95, MacOS и др.
При этом пользовательские задачи должны иметь возможность обмениваться данными с модулями ОС и друг с другом.
В современных системах чаще всего используется аппаратно реализованное разделение системного и пользовательского уровней привилегий. Процессор может работать в одном из двух режимов: пользовательском и системном. Иногда системный режим называют режимом супервизора (supervisor - надзиратель). Обычно такую архитектуру называют кольцами защиты: структуру привилегий изображают в виде двух концентрических кругов, где пользовательский круг является внешним и менее привилегированным, а системный - внутренним. В ряде современных систем количество колец может быть увеличено, например процессоры семейства x86 имеют четыре уровня привилегий.
В системном режиме разрешено управление уровнем прерываний процессора и доступ к регистрам диспетчера памяти. Если процессор использует отдельное адресное пространство для ввода/вывода, то команды доступа к этому адресному пространству также обычно разрешены только в системном режиме. Если же используется отображение регистров устройств на адреса памяти, система может управлять доступом к внешним устройствам более простым и гибким способом, отображая страницы ввода/вывода только привилегированным задачам.
Обычно программа, исполняющаяся в системном режиме, имеет доступ к адресным пространствам пользовательских программ.
Напротив, в пользовательском режиме процессор может исполнять только обычные команды обработки данных и не имеет доступа к системному адресному пространству. Переключение из пользовательского режима в системный осуществляется специальной командой. Обычно такая команда сразу же передает управление одному из модулей ядра системы.
Например, в PDP-11 системные вызовы реализуются
через команду EMT
- Emulate Trap (программное прерывание). Эта
команда имеет 6-битовое поле операнда, которое, однако, не интерпретируется
процессором. Пользовательская программа проталкивает параметры системного вызова
в стек и исполняет EMT
с операндом, равным коду исполняемого
системного вызова. При этом вызывается обработчик прерывания по вектору 8.
Прерывание 8 обрабатывается диспетчером системных вызовов. Обработчики
прерываний в PDP-11 всегда исполняются в системном режиме, поэтому при
вызове диспетчера автоматически происходит переключение в режим ядра. Диспетчер
извлекает из пользовательского стека адрес команды EMT
и параметры.
Затем он извлекает код команды EMT
, анализирует ее операнд и в
зависимости от результатов анализа вызывает соответствующую процедуру ядра. Для
копирования параметров используются специальные команды доступа к
пользовательскому адресному пространству: MFPI/MFPD
(Move From
Previous Instruction/Data - копировать из предыдущего [адресного пространства]
команд/данных).
В RSX-11, использующей разделение памяти, fork-процесс драйвера все
время имеет доступ к пользовательскому адресному пространству через команды
MFPI/MFPD
, поэтому не возникает необходимости копировать данные в
системные буфера, как в Linux.
Несколько иная схема обмена данными используется в Windows NT и некоторых микроядерных системах: вместо прямого отображения пользовательских адресов в системные или специальных команд копирования между адресными пространствами используются небольшие (4 кбайта в случае NT) разделяемые буфера. Такое решение позволяет отчасти защитить пользовательские программы от ошибок в модулях ОС и упрощает синхронизацию доступа к разделяемым данным, но приводит к значительным накладным расходам.
В Windows NT для обмена данными между пользовательскими программами и ядром используются буфера, однако многие модули ядра обмениваются данными путем прямого разделения частей адресного пространства для повышения производительности. Несмотря на это, NT близка к статусу чемпиона среди современных ОС по потребностям в памяти и накладным расходам, хотя и трудно определить, с чем это связано: с буферизацией или с чем-либо еще, например, с плохо сбалансированным дисковым кэшем.
Легко понять, что такие модули, как планировщик, менеджер памяти и драйверы внешних устройств, могут работать только в системном режиме. Обычно в системном режиме исполняется все ядро ОС, в том числе и те модули, для которых это необязательно - файловые системы, сервисные модули разного рода и т.д.
В результате мы получаем двухуровневую систему прав, напоминающую систему привилегий в ОС семейства Unix: пользовательские программы имеют доступ только к своим внутренним объектам и тем внешним объектам, доступ к которым им разрешен ядром, ядро же обладает всеми мыслимыми правами и привилегиями.
При этом пользовательские программы вынуждены полагаться на порядочность модулей ядра. Ядро же обычно даже не может эффективно защитить себя от ошибок в собственных модулях.
Каждая система обычно исполняет по крайней мере несколько дополнительных модулей, которые хотя и являются частью ядра, но разрабатывались и отлаживались отдельно от самой ОС. В первую очередь такими модулями являются драйверы внешних устройств, которые обычно разрабатываются фирмами-изготовителями аппаратуры, а не поставщиками ОС. Кроме того, система может использовать внешние драйверы файловых систем, сетевых протоколов и т.д. Все эти модули исполняются с привилегиями системного режима и, таким образом, могут в случае ошибки нарушить работу ядра. Есть основания утверждать, что ошибки в таких модулях являются основной причиной сбоев современных ОС общего назначения.
Желание защитить ядро и пользовательские программы от ошибок в системных модулях вынуждает разработчиков современных процессоров усложнять структуру аппаратных привилегий. Некоторые процессоры, например семейство x86, предлагают несколько уровней привилегий в отличие от двух в модели пользователь/супервизор, но и они сохраняют ``концентрическую'' структуру привилегий, когда более привилегированный уровень автоматически получает доступ ко всем объектам предыдущего уровня. Таким образом, код, исполняющийся в нулевом кольце защиты, мало чем отличается по возможностям от кода супервизора в системах c двумя уровнями привилегий.
Альтернативный подход состоит в том, чтобы, не накладывая дополнительных требований на аппаратуру, как можно сильнее упростить само ядро и дополнительные модули, исполняющиеся в режиме ядра. Этот подход, реализованный, например, в Unix, не решает проблемы в принципе, но по крайней мере позволяет уменьшить вероятность опасных ошибок в ядре и системных модулях.
Описанная архитектура с различными вариациями используется в большинстве систем семейства Unix, OS/2, Windows NT, VAX/VMS-OpenVMS и ОС для больших компьютеров фирмы IBM: MVS, VM/ESA, OS/390. В совокупности эти системы образуют подавляющее большинство современных ОС общего назначения.
Отчасти похожую архитектуру памяти использует и Windows 95, но эта ОС не защищает значительную часть системных адресов от пользовательских задач. Никак не защищен оказывается первый мегабайт физической памяти, который зачем-то отображается в адресные пространства всех 32-разрядных программ, значительная часть ядра и даже глобальная таблица дескрипторов.
Поражают воображение заявления фирмы Microsoft по этому поводу. В одном из пресс-релизов, опубликованных еще до выпуска системы, официальные представители фирмы на полном серьезе утверждали, что на самом деле защищать надо лишь первые 64 килобайта адресного пространства, поскольку, по их измерениям (?!), 90 % ошибочных доступов приходятся именно на эти адреса. Комментарии излишни.
Широкая распространенность обсуждаемой архитектуры вовсе не означает, что она лишена недостатков. Безусловно, она лучше систем с открытой памятью, которые во многих случаях оказываются неприменимы или неудобны из-за низкой надежности и невозможности создания эффективных систем безопасности, но этого недостаточно, чтобы утверждать, что такая архитектура оказалась лучше всех альтернатив.
Важным недостатком раздельных адресных пространств является относительная сложность разделения памяти между задачами, что особенно заметно при разделении кода. Прослеживание множественных ссылок на разделяемую библиотеку подпрограмм приводит к снижению производительности алгоритмов подкачки и поиска жертвы, а также к более сложному управлению файлом подкачки. Из-за этого у систем с динамической сборкой (OS/2 и Windows NT) виртуальная память заметно менее эффективна, чем у систем семейства Unix, использующих статические загрузочные модули. Статические же загрузочные модули обеспечивают гораздо меньшую гибкость, чем динамическая сборка. Такой выбор между ``бедным, но здоровым'' и ``богатым, но больным'' трудно назвать привлекательным.
Большинство альтернативных решений, в том числе и i432, так или иначе восходят к системам Burroughs, первые из которых были разработаны в конце 60-х гг. В этих системах использовалась сегментная виртуальная память, но все задачи разделяли общую таблицу дескрипторов сегментов. Таким образом, можно сказать, что все задачи имели общее адресное пространство. Разделение же доступа к сегментам осуществлялось путем избирательной выдачи селекторов.
В Burroughs пользовательская программа не могла самостоятельно формировать указатели, так как эти машины имели теговую архитектуру, когда каждая ячейка данных снабжена описателем-тегом (tag), указывающим на тип данных, хранящихся в данной ячейке.
*
Благодаря тегам команды модификации скалярных данных не могут
оперировать с указателями. Команды же создания и модификации указателей являются
привилегированными и доступны только специализированному модулю ядра.
*
Простая передача указателя между задачами в Burroughs приводила к тому, что задача-получатель получала доступ к указуемому объекту, что в системах с раздельными таблицами дескрипторов соответствовало бы отображению объекта в два адресных пространства, но без связанных с отображением сложностей и накладных расходов. Это делает архитектуру типа Burroughs очень привлекательной альтернативой системам с разделением адресных пространств.
Однако на машинах, не использующих тегов, такая архитектура приводит фактически к полному отказу от защиты памяти. Теги же, в свою очередь, приводят к значительному усложнению аппаратуры процессора и системы команд, что привело к почти полному вытеснению теговых архитектур с рынка в 70-е гг. Несмотря на это, идея использовать контроль за передачей указателей вместо разделения адресных пространств продолжала интриговать разработчиков и исследователей на протяжении 70-х гг. Неясно было только, как эффективно реализовать такой контроль.
Одна из остроумных идей состояла в том, чтобы выполнять контроль за модификацией указателей статически, еще на этапе ассемблирования или компиляции. Получалось, что система доверяет языковым процессорам в том, что они всегда генерируют только безопасный код. Этот подход был реализован во многих экспериментальных системах, но оказался непригоден для реальных приложений, где часто возникает необходимость использовать языковые процессоры, разработанные третьими организациями.
Успехи микропрограммирования привели во второй половине 70-х - начале 80-х гг. к буйному расцвету CISC-архитектур. В микрокод переносили интерпретаторы Lisp, SmallTalk и целые модули операционных систем. На фоне этого расцвета возникла новая волна интереса к архитектурам с контролем за передачей указателя. Например, идею i432, кратко обсуждаемого в п. 9.4.3 тоже можно проследить вплоть до желания улучшить и усовершенствовать архитектуру Burroughs.
Среди возникшей волны CISC-архитектур такого типа нельзя не упомянуть System/38 фирмы IBM, кратко описанную в [23] и ряде других работ.
Значительная часть ОС System/38 реализована микропрограммно. Точнее, в этой системе трудно сказать, где кончается ОС и начинается система команд. Система использует одноуровневое адресное пространство для оперативной и внешней памяти. Все адресное пространство разбито на объекты различных типов. Каждый объект имеет набор атрибутов, включая имя объекта и идентификатор его владельца. Структура большинства объектов скрыта от прикладных программ. Доступ к объектам осуществляется специальными командами. Операции делятся на типовые, приложимые к любым объектам, и уникальные, приложимые только к объектам определенного типа.
Доступ к объектам может осуществляться как по адресам, так и по символьным именам. Роль каталогов при преобразовании имен в адреса выполняют специальные объекты, называемые контекстами (context). Адрес, называемый системным указателем, состоит из расположения объекта (т.е. собственно указателя) и прав доступа к объекту. Изменение прав доступа осуществляется специальными командами. Пользователь не может изменить эти права прямыми битовыми манипуляциями. Таким образом, System/38 вместо разделения адресных пространств использует контроль передачи указателя, как и Burroughs. Указатель с ограниченными правами приблизительно соответствует мандату в i432.
Пользовательская программа может собирать составные объекты из нескольких элементарных объектов. Например, реляционная база данных может состоять из нескольких таблиц данных, индексов для каждой из таблиц, курсора и сегмента памяти для скалярных переменных. Пользователь может рассматривать такую сложную составную сущность как единый объект.
Часть типов объектов может быть сопоставлена с сегментами кода и данных в традиционных архитектурах - это объекты, содержащие скалярные данные и код. Другие объекты, такие как описатели логических устройств и сетевых портов или пользовательские профили, лишь с большой натяжкой сопоставляются с обычными сегментами. Многие типы объектов ориентированы на работу с реляционными базами данных. Это такие типы, как:
Концепция System/38 получила несколько неожиданное развитие в машинах серии AS/400 (Application Server - сервер приложений). System/38 использовала микропрограммное обеспечение большого объема. Большая часть микрокода хранилась в основной памяти, что в значительной мере размывало границу между микрокодом и обычными программами. При разработке AS/400 разработчики фирмы IBM сделали смелый шаг, переместив эту границу из процессора в загрузчик программ.
В традиционных CISC-системах центральный процессор извлекает из основной памяти команды машинного языка и преобразует их в последовательности микрокоманд. В традиционных RISC-системах микрокоманд как таковых нет, и аппаратура процессора непосредственно интерпретирует машинный язык. В AS/400, как и в CISC-системах, существует различие между ``внешним'' машинным языком, команды которого хранятся в бинарных загрузочных модулях, и командами аппаратуры ЦПУ. Однако преобразование внешнего машинного кода в язык команд процессора происходит в момент загрузки программы. Загрузчик считывает из загрузочного модуля последовательность высокоуровневых объектно-ориентированных команд и преобразует их в последовательность команд реального ЦПУ. В современных моделях AS/400 используется RISC-процессор с системой команд Power, в основном аналогичный процессорам, используемым в RS/6000, PowerPC и PowerMAC. Пользователь не может сформировать и запустить произвольную последовательность команд этого ЦПУ и вообще не имеет доступа к реальному процессору.
Можно сказать, что AS/400 представляет собой удачный компромисс между упоминавшейся выше идеей о статическом контроле за передачей указателей на этапе компиляции/ассемблирования и требованиями практики эксплуатации вычислительных систем. Языковые процессоры в AS/400 генерируют высокоуровневые команды, реальный же процессор исполняет код, созданный системным загрузчиком на основе этих команд. Большая часть проверок ``безопасности'' исполняемых операций выполняется загрузчиком статически и не оказывает влияния на производительность системы.
Такой подход имеет еще одно большое преимущество: теперь оказывается возможным произвольно менять систему команд центрального процессора, не теряя совместимости со старыми загрузочными модулями.
Next: Пользовательский интерфейс Up: Contents
Т.Б.Большаков: tbolsh@inp.nsk.su