Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

На главную -> MyLDP -> Программирование и алгоритмические языки


Ulrich Drepper "Как писать разделяемые библиотеки"
Назад Оглавление Вперед

2.6. Улучшение безопасности

Все, что до сих пор рассказывалось об ELF, должно было показать исключительную важность структур данных GOT и PLT, используемых динамическоим компоновщиком во время работы приложения. Поскольку эти структуры данных используются для прямого доступа к памяти, на функциях, которые к ним обращаются, также лежит ответственность за безопасность. Если ошибка в программе позволит злоумышленнику записать в одном слове адресного пространства значение, которое он выберет, то запись одной из достойных целей может быть запись в таблице GOT. В некоторых вариантах архитектуры измененное значение GOT может перенаправить обращение к функции, которая вызывается через таблицу PLT, в любое другое место. Аналогичным образом, если будет модифицирована таблица PLT в тот момент, когда когда она перемещается и, следовательно, когда она доступна для записи, то может быть изменено содержимое памяти.

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

Этот вид атаки был бы невозможным в случае, если бы данные в таблицах GOT и PLT не могли бы модифицироваться программой пользователя. Для некоторых платформ, таких как IA-32, таблица PLT уже доступна только для чтения. Но таблицу GOT можно модифицировать во время выполнения программы. Динамический компоновщик является частью обычной программы, и поэтому невозможно потребовать, чтобы траблица GOT была записана в таком месте, где для динамического компоновщика запись была разрешена, а для остальной части программы — нет. Совсем другое дело, если была бы возможность иметь динамический компоновщик, который всякий раз, когда нужно было изменить значение, изменял бы права доступа для страниц памяти, содержащих GOT и PLT. Требуемые для этого вызовы mprotect слишком дорогие, что исключает такое решение в любой системе, где необходимо, чтобы система хорошо работала.

В этом месте мы должны вспомнить, как работает динамический компоновщик и как используется таблица GOT. Каждая запись GOT принадлежит определенному символу и, в зависимости от того, как используется символ, динамический компоновщик будет выполнять перемещение во время запуска или когда это потребуется, т. е. когда будет использоваться символ. Нас здесь нас интересуют перемещения, относящиеся к первой группе. Мы точно знаем когда выполняются все перемещения, выполнение которых не откладывается. Так что мы могли бы изменить права доступа для той части GOT, которая изменяется во время запуска, запретив доступ на запись после того, как перемещение будет сделано. Можно разрешить создавать объекты таким способом, если использовать параметр компоновщика -z relro. Компоновщику указывается поместить секции, которые модифицируются только при перемещении, в отдельную страницу памяти и создать новую заголовочную запись программы PT GNU RELRO, которая указывать динамическому компоновщику на эти специальные страницы. Во время выполнения приложения динамический компоновщик может после того, как будут созданы эти страницы, удалить для них доступ на запись.

Это только частичное решение, но и оно уже очень полезное. С помощью параметра компоновщика -z now можно за счет повышения расходов при запуске отключить возможность отложенных перемещений и сделать так, чтобы все перемещения могли быть обработаны таким образом. Для долго работающего приложения, для которого важна безопасность, такое решение, безусловно, привлекательно: затраты при запуске не столь важны в сравнении с проблемой безопасности. Кроме того, если приложение и объекты DSO написаны хорошо, то в них отсутствуют перемещения. Например, все объекты DSO в библиотеке GNU языка C собираются с параметрами -z relro и -z now.

Таблицы GOT и PLT не единственное место в приложении, в которым можно достичь преимуществ от использования этой возможности. В разделе 2.4.2 мы видели, что добавление квалификатора const везде, где это возможно в определение объектов данных, дает выигрыш, когда происходит обращение к данным. Но это не все. Рассмотрим следующий код:

const char *msgs1[ ] = {
   "one", "two", "three"
};
const char *const msgs2[ ] = {
   "one", "two", "three"
};

Уже объяснялось, что массив нельзя перемещать в секцию данных, доступную только для чтения и, следовательно, разделяемую секцию данных, в случае, если файл компонуется в DSO. Адреса строк, на которые указывают элементы, известны только во время выполнения приложения. Однако, как только адреса будут занесены, элементы массива msgs2 не должны никогда изменяться, поскольку сам массив определяен как const. Поэтому gcc запоминает массив msgs1 в секции .data, в то время как массив msgs2 запоминается в секции .data.rel. Эта секция .data.rel точно такая же, как секция .data, но динамический компоновщик может отключить доступ на запись после того, как будет выполнено перемещение. Ранее описанная обработка таблицы GOT является просто частным случаем именно этой функции. Поэтому если добавление столько const, столько будет возможным, и одновременно использовать параметр компоновщика -z relrore, то это позволит защитить в программе даже данные. Это даже поможет поймать ошибки, когда из-за неправильного кода изменяются данные, объявляемые как const.

Абсолютно другой темой, но которую в контексте безопасности все же стоит упомянуть, являются текстовые перемещения. Создание таких объектов DSO, где необходимы текстовые перемещения (смотрите раздел 2), означает, что динамический компоновщик должен создавать страницы памяти, которые временно были бы доступны для записи, хотя в другое время они доступны только для чтения. Время, в течение которого страницы доступны для записи, как правило, короткое — только до тех пор, пока обрабатываются все неоткладываемые перемещения. Но даже этот короткий промежуток времени период может использоваться злоумышленником. Во время атаки в области код может быть перезаписан кодом, выбранный злоумышленником, и программа, если она достигнет этих адресов, слепо выполнит этот код.

Во время запуска программы это невозможно, поскольку нет никакого другого потока, который мог бы выполнять атаку в то время, когда страницы доступны для записи. Дело обстоит иначе, если позже, когда программа уже выполняет обычный код и, возможно, запускает другие потоки, некоторые объекты DSO с помощью dlopen загружаются динамически. По этой причине создание объектов DSO с текстовыми перемещениями неизбежно означает увеличения проблем с безопасностью системы.

Более важным является то, что при наличии Security Enhanced Linux (SELinux) использование текстовых перемещений будет более ответственным. По умолчанию расширения SELinux запрещают делать исполняемые страницы данных доступными для записи. Поскольку в ядре нет возможности различать, выполняет ли это динамический компоновщик и программа или изменения вносит атакующий, то также ограничиваются возможности динамического компоновщика. Единственным выходом из этого является либо предоставление программе разрешения изменять среду (и, следовательно, разрешить атакующим делать то же самое), либо удалить из объектов DSO и PIE текстовые перемещения. Первый вариант должен использоваться только как самая последняя возможность.


Предыдущий раздел:   Следующий раздел:
Назад Оглавление Вперед