Ключи защиты памяти
Ключи защиты памяти обеспечивают механизм повышения надежности программ.
Ключи защиты применяются к страницам памяти и работают на уровне дискретности страниц, аналогично команде mprotect, которую можно использовать для для защиты от чтения или записи одной или нескольких страниц. Однако с помощью ключей памяти можно пометить разделы данных для конкретных уровней защиты от чтения и записи. Защита с помощью ключей памяти является функцией не только страницы данных, но также и функцией попытки доступа к нити. Можно включить определенные в исходном коде пути для доступа к данным, недоступные для более крупной программы, таким образом инкапсулируя важные данные программы и защищая их от случайных повреждений.
Поскольку доступ к защищенным ключами страницам является атрибутом выполняемой нити, этот механизм естественным образом распространяется на приложения с несколькими нитями, но с ограничением на использование этими приложениями только нитей pthread 1:1 (или области системы). Подход, реализуемый командой mprotect, не обеспечивает надежную работу в среде с несколькими нитями, поскольку приходится удалять защиту для всех нитей, если необходимо предоставить доступ для какой-либо одной нити. Можно одновременно использовать оба механизма; в этом случае, программа не может выполнить запись в защищенную от записи страницу даже если ключ защиты разрешает запись.
- Выполняется инкапсуляция всех частных данных программы, ограничивая доступ только выбранными путями исходного кода.
- Выполняется защита частных данных программы от случайного повреждения. При этом программа всегда выполняется с предоставленными правами на чтение, однако права на запись предоставляются только в том случае, если требуется изменить данные. Это может быть особенно полезным в том случае, если код в базовом механизме позволяет вызовы незащищенного кода.
- Если доступны несколько ключей защиты, то возможен дополнительный уровень дискретности при защите данных.
Можно упростить процесс отладки, если разрабатывать приложение, учитывая защиту ключами. Установка ключа защиты страницы и задание набора ключей активного пользователя являются системными вызовами, и поэтому являются относительно затратными операциями. Следует разрабатывать программу таким образом, чтобы частота этих операций не была избыточной.
Пользовательские ключи защиты
- Страницы, экспортированные из ядра только для чтения, по-прежнему будут видны для вашей программы. Эти страницы имеют ключ защиты UKEY_SYSTEM. Этот ключ защиты не находится под управлением программы, однако программа всегда имеет к нему доступ.
- Все страницы памяти вашей программы изначально имеют присвоенный им пользовательский общий ключ. Как было указано выше, доступ к памяти с ключом 0 предоставляется всегда, что делает этот ключ пользовательским общим ключом.
- Можно задать ключи защиты только для обычных и общих данных. Нельзя защитить, например, данные библиотеки, общую с ядром низшую память или текст программы.
- В зависимости от базового аппаратного обеспечения и административного выбора, доступно только ограниченное число пользовательских ключей защиты (обычно один). Когда программа присваивает ключ защиты одной или нескольким страницам, по умолчанию данные на этих страницах становятся недоступными. Необходимо явным образом предоставить права доступа к этим данным на чтение или запись для окружающих кодовых путей, которым требуется доступ, путем вызова новой службы для управления действующим пользовательским набором ключей.
- Физическое аппаратное обеспечение, вероятно, поддерживает дополнительные ключи защиты, которые не доступны для использования в качестве пользовательских ключей защиты.
- Для присвоения странице ключей защиты не требуются специальные права доступа. Единственным требованием является наличие текущих прав на запись для этой страницы.
- Ключи защиты не управляют правами на выполнение.
Если программа обращается к защищенным ключом данным в нарушение прав доступа, определенных в действующем наборе пользовательских ключей, она получит сигнал SIGSEGV, как в случае несанкционированного доступа к защищенным от чтения или записи страницам. Если вы указали, что этот сигнал следует обрабатывать, следует иметь в виду, что обработчики сигналов вызываются без доступа к частным ключам. Обрабатывающий сигнал код должен добавлять все необходимые права доступа к текущему пользовательскому набору ключей до обращения к защищенным ключом данным.
Дочерние процессы, созданные системным вызовом fork, логически наследуют состояние памяти и выполнения родительского процесса. Сюда входят ключи защиты, связанные с каждой страницей, а также действующий пользовательский набор ключей родительской нити во время вызова fork.
Области, защищенные пользовательскими ключами
- Область данных
- Область стека по умолчанию
- Области mmap
- Общая память, присоединенная с помощью команды shmat(), за исключением перечисленных ниже случаев
- Для следующих категорий страниц нельзя использовать ключи защиты:
- Файлы shmat и закрепленная общая память
- Большие (нелистаемые) страницы
- Текст программы
- Общая с ядром нижняя область память с разрешением чтения
Системные предварительные требования для защиты ключами
- Система должна выполняться на физическом аппаратном обеспечении, предоставляющем защиту памяти ключами
- Система должна выполняться в 64-разрядном ядре
- В системе должно быть включено применение пользовательских ключей защиты
Предварительные требования к программе для защиты ключами
- Программа должна объявить себя поддерживающей пользовательские ключи и с помощью команды ukey_enable определить, сколько пользовательских ключей защиты доступно.
- Программа должна организовать защищенные данные в пределах страницы.
- Программа должна с помощью команды ukey_protect присвоить частный ключ для каждой страницы, которую необходимо защитить.
- Если данные malloc'd защищены, то перед высвобождением необходимо снять защиту.
- Программа должна с помощью команды ukeyset_init подготовить один или несколько наборов ключей.
- Программа, возможно, должна добавить необходимые ключи в набор ключей с помощью команды ukeyset_add_key, для включения в будущем прав на чтение или запись.
- Программа должна с помощью команды ukeyset_activate активировать набор ключей, чтобы предоставить права доступа, определенные в наборе.
- Включать какие либо нити pthread M:N (области процесса)
- Иметь возможность выполнения контрольной точки (например, при условии CHECKPOINT=yes в среде)
- Обработчики сигналов, получающие структуру ucontext_t. Предыдущий действующий пользовательский набор ключей находится в ucontext_t.__extctx.__ukeys, массиве из двух элементов, содержащем 64-разрядное значение пользовательского набора ключей
- Пользовательские контекстные структуры, откомпилированные с определенным __EXTABI__ (которое применяется setcontext, getcontext, makecontext, swapcontext)
Функции
| Функция | Описание |
|---|---|
| sysconf | _SC_AIX_UKEYS позволяет определить число поддерживаемых пользовательских ключей (эта функция может быть вызвана в предыдущих версиях AIX) |
| ukey_enable | Включает для процесса программную среду, поддерживающую пользовательские ключи, и сообщает, сколько пользовательских ключей доступно |
| ukeyset_init | Инициализирует пользовательский набор ключей, который будет представлять набор прав доступа для частного ключа (ключей) |
| ukeyset_add_key | Добавляет в набор ключей права на чтение или запись для указанного ключа |
| ukeyset_remove_key | Удаляет из набора ключей права на чтение и/или запись для указанного ключа |
| ukeyset_add_set | Добавляет все права доступа из одного набора ключей в другой |
| ukeyset_remove_set | Удаляет все права доступа одного набора ключей из другого |
| ukeyset_activate | Применяет права доступа набора ключей к выполняемой нити |
| ukeyset_ismember | Проверяет, содержатся ли данные права доступа в наборе ключей |
| ukey_setjmp | Расширенная форма setjmp, которая сохраняет действующий набор ключей (использует структуру ukey_jmp_buf) |
| pthread_attr_getukeyset_np | Получает атрибут набора ключей нити pthread |
| pthread_attr_setukeyset_np | задает атрибут набора ключей нити pthread |
| ukey_protect | Задает пользовательский ключ защиты для страничного диапазона пользовательской памяти |
| ukey_getkey | Получает пользовательский ключ защиты для указанного адреса |
Отладка
- При отладке выполняемой программы:
- Команда ukeyset отображает действующий набор ключей.
- Команда ukeyvalue отображает ключ защиты, связанный с данным расположением памяти.
- При отладке файла дампа команда ukeyexcept сообщает действующий набор ключей, эффективный адрес исключительной ситуации ключа и ключ памяти.
Сведения об аппаратном обеспечении
- AMR - это 64-разрядный регистр, содержащий 32-разрядные пары (одна пара на ключ)
для максимум 32 ключей, пронумерованных от 0 до 31.
- Первый бит каждой пары представляет права на запись в соответствующий ключ.
- Аналогично, второй бит каждой пары представляет права на чтение соответствующего ключа.
- Значение бита, равное 0, предоставляет соответствующий доступ, а значение, равное 1, запрещает доступ.
- Пара битов, предоставляющая права доступа для ключа 0, не управляется программой. Пользовательский ключ 0 является пользовательским общим ключом, и все нити всегда имеют полный доступ к данным в этом ключе, независимо от параметров действующего пользовательского набора ключей.
- Все остальные пары битов представляют пользовательские частные ключи, которые (если доступны) можно использовать для защиты ваших данных.
Пример программы
Ниже приведен пример программы,поддерживающей пользовательские ключи:
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <sys/ukeys.h>
#include <sys/syspest.h>
#include <sys/signal.h>
#include <sys/vminfo.h>
#define ROUND_UP(size,psize) ((size)+(psize)-1 & ~((psize)-1))
/*
* Это пример каркаса программы, поддерживающей пользовательские ключи.
*
* Структура private_data_1 отображает защищенную ключом область памяти,
* к которой основная программа имеет свободный доступ, а "незащищенная"
* команда имеет только права на чтение.
*/
struct private_data_1 {
int some_data;
};
struct private_data_1 *p1; /* указатель на структуру защищенных данных */
ukeyset_t keyset1RW; /* набор ключей для предоставления доступа обработчику сигнала */
/*
* Незащищенная функция должна успешно прочитать защищенные данные.
*
* Если значение счетчика равно 0, оно просто возвращается, так что инициатор может записать
* увеличенное на единицу значение обратно в защищенное поле.
*
* Если значение счетчика равно 1, он пытается самостоятельно обновить защищенное поле.
* Результатом должно являться SIGSEGV.
*/
int untrusted(struct private_data_1 *p1) {
int count = p1->some_data; /* Можно прочитать защищенные данные */
if (count == 1)
p1->some_data = count; /* Однако запись запрещена */
return count + 1;
}
/*
* Обработчик сигнала обнаруживает умышленное нарушение защиты в
* незащищенной функции выше при значении count == 1.
* Обратите внимание, что обработчик вводится БЕЗ прав доступа к частным данным.
*/
void handler(int signo, siginfo_t *sip, void *ucp) {
printf("siginfo: signo %d code %d\n", sip->si_signo, sip->si_code);
(void)ukeyset_activate(keyset1RW, UKA_REPLACE_KEYS);
exit(1);
}
main() {
int nkeys;
int pagesize = 4096; /* аппаратный размер страницы данных */
int padded_protsize_1; /* размер страницы защищенных данных */
struct vm_page_info page_info;
ukey_t key1 = UKEY_PRIVATE1;
ukeyset_t keyset1W, oldset;
int rc;
int count = 0;
struct sigaction sa;
/*
* Попытка узнать о пользовательском ключе.
*/
nkeys = ukey_enable();
if (nkeys == -1) {
perror("ukey_enable");
exit(1);
}
assert(nkeys >= 2);
/*
* Определяется размер страницы области данных.
*/
page_info.addr = (long)&p1; /* адрес в области данных */
rc = vmgetinfo(&page_info, VM_PAGE_INFO, sizeof(struct vm_page_info));
if (rc)
perror("vmgetinfo");
else
pagesize = page_info.pagesize; /* получаем фактический размер страницы */
/*
* Необходимо выделить соответствующий странице объем памяти
* для области, которую необходимо защитить ключом.
*/
padded_protsize_1 = ROUND_UP(sizeof(struct private_data_1), pagesize);
rc = posix_memalign((void **)&p1, pagesize, padded_protsize_1);
if (rc) {
perror("posix_memalign");
exit(1);
}
/*
* Инициализация частных данных.
* Это можно сделать до установки защиты.
*
* Обратите внимание, что указатель на частные данные находится в общей памяти.
* Мы защищаем только сами данные.
*/
p1->some_data = count;
/*
* Создание наборов ключей для применения для доступа к защищенной структуре.
* Обратите внимание, что эти наборы ключей будут размещены в общей памяти.
*/
rc = ukeyset_init(&keyset1W, 0);
if (rc) {
perror("ukeyset_init");
exit(1);
}
rc = ukeyset_add_key(&keyset1W, key1, UK_WRITE); /* WRITE */
if (rc) {
perror("ukeyset_add_key 1W");
exit(1);
}
keyset1RW = keyset1W;
rc = ukeyset_add_key(&keyset1RW, key1, UK_READ); /* R/W */
if (rc) {
perror("ukeyset_add_key 1R");
exit(1);
}
/*
* Доступ к частным данным запрещен с помощью применения частного ключа
* к содержащей их странице (страницам).
*/
rc = ukey_protect(p1, padded_protsize_1, key1);
if (rc) {
perror("ukey_protect");
exit(1);
}
/*
* Разрешить всему основному коду ссылаться на чтение/запись защищенных данных.
*/
oldset = ukeyset_activate(keyset1RW, UKA_ADD_KEYS);
if (oldset == UKSET_INVALID) {
printf("ukeyset_activate failed\n");
exit(1);
}
/*
* Задается обработчик сигнала для SIGSEGV, для обнаружения преднамеренного
* нарушения ключа в незащищенном коде.
*/
sa.sa_sigaction = handler;
SIGINITSET(sa.sa_mask);
sa.sa_flags = SA_SIGINFO;
rc = sigaction(SIGSEGV, &sa, 0);
if (rc) {
perror("sigaction");
exit(1);
}
/*
* Основной цикл выполнения программы.
*/
while (count < 2) {
/*
* Когда необходимо выполнить "незащищенный" код, изменяем права доступа
* к частным данным на R/O, удалив права на запись.
*/
(void)ukeyset_activate(keyset1W, UKA_REMOVE_KEYS);
/*
* Вызов незащищенной команды. Она может только читать
* переданные ей защищенные данные.
*/
count = untrusted(p1);
/*
* Восстанавливается полный доступ к частным данным.
*/
(void)ukeyset_activate(keyset1W, UKA_ADD_KEYS);
p1->some_data = count;
}
ukey_protect(p1, padded_protsize_1, UKEY_PUBLIC);
free(p1);
exit(0);
}