pkeys - обзор
ключей
защиты
памяти
Ключи
защиты
памяти (pkeys) —
это
расширение
существующих
постраничных
прав на
память. Для
обычных
прав на
страницу
используются
страничные
таблицы,
требующие
для
изменении
прав
затратных
системных
вызовов и
аннулирования
TLB. Ключи
защиты
памяти
предоставляют
механизм
изменения
защиты без
необходимости
изменять
страничные
таблицы
при каждом
изменении
прав.
Чтобы
использовать
pkeys, ПО
сначала
должно
«пометить»
(tag) страницу
в
страничных
таблицах
значением pkey.
После
размещения
этой метки
для
удаления
прав на
запись или
весь
доступ к
помеченной
странице
приложению
нужно
изменить
только
содержимое
регистра.
Ключи
защиты
вместе с
существующими
правами
PROT_READ,
PROT_WRITE и
PROT_EXEC
передаются
в
системные
вызовы,
такие как
mprotect(2) и
mmap(2), но
всегда
считаются
как
дополнительное
ограничение
к
существующим
традиционным
механизмам
прав
доступа.
Если
процесс
осуществляет
доступ,
нарушающий
ограничения
pkey, то он
получает
сигнал
SIGSEGV.
Подробную
информацию
об этом
сигнале
смотрите в
sigaction(2).
Чтобы
использовать
свойство pkeys,
это должен
поддерживать
процессор,
а ядро
должно
включать
поддержку
этого
свойства
для этого
процессора.
К началу 2016
года это
относится
только к
будущим
процессорам
Intel x86, и данная
аппаратура
поддерживает
16 ключей
защиты на
каждый
процесс.
Однако pkey 0
используется
как ключ по
умолчанию,
поэтому
для
приложения
доступно
только 15.
Ключ по
умолчанию
назначается
любой
области
памяти, для
которой pkey не
был
назначен
явным
образом с
помощью
pkey_mprotect(2).
Потенциально,
ключи
защиты
могут
добавить
уровень
безопасности
и
надежности
приложений.
Но, прежде
всего, они
не
разрабатывались
как
средство
защиты.
Например, WRPKRU
— это
полностью
непривилегированная
инструкция,
поэтому pkeys
бесполезны,
когда
атакующий
контролирует
регистр PKRU
или может
выполнять
любые
инструкции.
Приложения
должны
следить за
тем, чтобы
их ключи
защиты не
«не
утекли».
Например,
перед
вызовом
pkey_free(2)
приложение
должно
проверить,
что pkey не
назначен
памяти.
Если
приложение
оставит
назначенным
освобождённый
pkey, то
будущий
пользователь
этого pkey
может
непреднамеренно
изменить
права на не
относящуюся
к делу
структуру
данных, что
может
привести к
проблемам
с
безопасностью
или
стабильностью.
В
настоящее
время ядро
позволяет
вызывать
pkey_free(2) для
задействованных
pkeys, так как
выполнение
дополнительных
проверок
повлияло
бы на
производительность
процессора
или памяти.
Реализация
необходимых
проверок
переложена
на
приложение.
Приложения
могут
найти
области
памяти,
которым
назначен pkey,
в файле
/proc/pid
/smaps.
Дополнительная
информация
представлена
в
proc(5).
Приложение,
которое
хочет
воспользоваться
ключами
защиты,
может
работать и
без них.
Ключи
могут быть
недоступны
из-за
отсутствия
аппаратной
поддержки
системе,
где
запускается
приложение,
в коде ядра
может не
быть
поддержки,
поддержка
в ядре
может быть
выключена
или все
ключи уже
задействованы
библиотекой,
которую
использует
приложение.
В
приложениях
рекомендуется
просто
вызывать
pkey_alloc(2) и
проверять
успешность
выполнения,
а не
пытаться
проверять
наличие
поддержки
каким-то
другим
образом.
Хотя и
необязательно,
поддержку
ключей
защиты в
аппаратуре
можно
определить
помощью
инструкции
cpuid. От том,
как это
сделать,
смотрите в
программном
руководстве
разработчика
Intel. Ядро
определяет
наличие
поддержки
и выводит
информацию
в
/proc/cpuinfo в поле
«flags». Строка
«pku» в этом
поле
означает,
что
аппаратура
поддерживает
ключи
защиты, а
строка «ospke»
означает,
что ядро
содержит
включённую
поддержку
защиты.
Если
приложение
использует
нити и
ключи
защиты, то
нужно быть
особенно
осторожным.
Нити
наследуют
права
ключей
защиты
родителя
при
выполнении
системного
вызова
clone(2).
Приложения
должны
убедиться,
что их
собственные
права
подходят
для
дочерних
нитей до
вызова
clone(2)
или
выполнять
инициализацию
прав
ключей
защиты в
самих
нитях.
Каждый раз,
когда
вызывается
обработчик
сигнала
(включая
вложенные
сигналы),
нити
временно
даётся
новый
набор прав
ключа
защиты по
умолчанию,
который
заменяет
права
прерванного
контекста.
Это
означает,
что
приложения
должны
переустанавливать
свои
желаемые
права
ключа
защиты при
входе в
обработчик
сигнала,
если
желаемые
права
отличаются
от
значения
по
умолчанию.
Права
любого
прерванного
контекста
восстанавливаются
при
завершении
обработчика
сигналов.
Данное
поведение
сигнала
необычно
из-за того,
что
регистр x86 PKRU
(который
хранит
права
доступа
ключа
защиты)
управляется
тем же
аппаратным
механизмом
(XSAVE) что и
регистры
плавающей
запятой.
Поведение
сигнала
такое же
как у
регистров
плавающей
запятой.
В ядре Linux
реализованы
следующие
системные
вызовы для
работы с pkey:
pkey_mprotect(2),
pkey_alloc(2) и
pkey_free(2).
Системные
вызовы Linux pkey
доступны
только,
если ядро
было
собрано с
включённым
параметром
CONFIG_X86_INTEL_MEMORY_PROTECTION_KEYS.
Программа,
представленная
далее,
выделяет
страницу
памяти с
правами на
чтение и
запись.
Затем она
записывает
кусок
данных в
памяти и
читает его.
После
этого она
пытается
выделить
ключ
защиты и
запретить
доступ к
странице с
помощью
инструкции
WRPKRU. Далее она
пытается
получить
доступ к
странице,
что, как мы
ожидаем,
вызовет
сигнал
завершения
приложения.
$ ./a.out
буфер содержит: 73
читаем буфер снова...
Segmentation fault (core dumped)
#define _GNU_SOURCE
#include <err.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
int
main(void)
{
int status;
int pkey;
int *buffer;
/*
* выделяем страницу памяти
*/
buffer = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (buffer == MAP_FAILED)
err(EXIT_FAILURE, "mmap");
/*
* пишем произвольные данные в страницу (чуть)
*/
*buffer = __LINE__;
printf("буфер содержит: %d\n", *buffer);
/*
* выделяем ключ защиты:
*/
pkey = pkey_alloc(0, 0);
if (pkey == -1)
err(EXIT_FAILURE, "pkey_alloc");
/*
* запрещаем доступ к памяти, на которой будет установлен «pkey»,
* хотя пока ничего не запрещено
*/
status = pkey_set(pkey, PKEY_DISABLE_ACCESS);
if (status)
err(EXIT_FAILURE, "pkey_set");
/*
* установим ключ защиты на «буфер»
* заметим, что он доступен пока не применён mprotect()
* и ключ не заменен созданным ранее pkey_set()
*/
status = pkey_mprotect(buffer, getpagesize(),
PROT_READ | PROT_WRITE, pkey);
if (status == -1)
err(EXIT_FAILURE, "pkey_mprotect");
printf("читаем буфер снова...\n");
/*
* приложение падает, так как мы запретили доступ
*/
printf("буфер содержит: %d\n", *buffer);
status = pkey_free(pkey);
if (status == -1)
err(EXIT_FAILURE, "pkey_free");
exit(EXIT_SUCCESS);
}
pkey_alloc(2),
pkey_free(2),
pkey_mprotect(2),
sigaction(2)
Русский
перевод
этой
страницы
руководства
был сделан
Alexey, Azamat Hackimov <
[email protected]>, kogamatranslator49
<
[email protected]>, Kogan, Max Is <
[email protected]>, Yuri
Kozlov <
[email protected]> и Иван
Павлов <
[email protected]>
Этот
перевод
является
бесплатной
документацией;
прочитайте
Стандартную
общественную
лицензию GNU
версии 3
или более
позднюю,
чтобы
узнать об
условиях
авторского
права. Мы не
несем
НИКАКОЙ
ОТВЕТСТВЕННОСТИ.
Если вы
обнаружите
ошибки в
переводе
этой
страницы
руководства,
пожалуйста,
отправьте
электронное
письмо на
[email protected]