mprotect, pkey_mprotect -
контролирует
доступ к
области
памяти
Standard C library (
libc,
-lc)
#include <sys/mman.h>
int mprotect(void addr[.len], size_t len, int prot);
#define _GNU_SOURCE /* смотрите feature_test_macros(7) */
#include <sys/mman.h>
int pkey_mprotect(void addr[.len], size_t len, int prot, int pkey);
Вызов
mprotect()
изменяет
параметры
доступа
страниц
памяти
вызывающего
процесса,
которые
содержатся,
даже
частично, в
адресном
диапазоне [
addr,
addr+
len-1].
Значение
addr
должно
быть
выровнено
на границу
страницы.
Если
вызывающий
процесс
нарушает
защиту
доступа к
памяти, то
ядро
посылает
процессу
сигнал
SIGSEGV.
Значение
prot
представляет
собой
комбинацию
следующих
флагов
доступа:
PROT_NONE
или
побитово
сложенные
другие
значения
из
следующего
списка:
- PROT_NONE
- Доступ к
памяти
запрещён.
- PROT_READ
- Память
можно
читать.
- PROT_WRITE
- Память
можно
изменять.
- PROT_EXEC
- Память
можно
выполнять.
-
PROT_SEM
(начиная с Linux
2.5.7)
- Память
можно
использовать
для
атомарных
операций.
Этот флаг
появился
как часть
реализации
futex(2) (для
гарантии
способности
выполнять
атомарные
операции,
требуемые
таким
командам
как FUTEX_WAIT), но
пока не
используется
ни в одной
архитектуре.
-
PROT_SAO
(начиная с Linux
2.6.26)
- Память
должна
иметь
строгий
порядок
доступа.
Это
свойство
есть
только в
архитектуре
PowerPC (в
спецификации
архитектуры
версии 2.06
добавлено
свойство
ЦП SAO и оно
доступно,
например,
на POWER 7 или PowerPC A2).
Также
(начиная с Linux
2.6.0),
prot может
содержать
один из
следующих
установленных
флагов:
- PROT_GROWSUP
- Apply the protection mode up to the end of a mapping that
grows upwards. (Such mappings are created for the stack area on
architectures—for example, HP-PARISC—that have an upwardly
growing stack.)
- PROT_GROWSDOWN
- Применить
режим
защиты до
начала
отображения,
которое
растёт
вниз
(которое
должно
быть
сегментом
стека или
сегментом,
отображённым
с
установленным
флагом
MAP_GROWSDOWN).
Подобно
mprotect(),
вызов
pkey_mprotect()
изменяет
защиту
страниц,
указанных
addr и
len.
Аргумент
pkey
содержит
ключ
защиты
(смотрите
pkeys(7)),
назначаемый
памяти.
Ключ
защиты
должен
быть
выделен с
помощью
pkey_alloc(2)
до
передачи в
pkey_mprotect(). Пример
использования
этого
системного
вызова
смотрите в
pkeys(7).
On success,
mprotect() and
pkey_mprotect() return zero. On error,
these system calls return -1, and
errno is set to indicate the error.
- EACCES
- Нельзя
задать
этот вид
доступа.
Например,
это может
случиться,
если при
вызове mmap(2)
файл
доступен
только на
чтение, а
запрос mprotect()
был PROT_WRITE.
- EINVAL
- Значение
addr не
является
правильным
указателем
или не
кратен
размеру
системной
страницы.
- EINVAL
- (pkey_mprotect()) pkey не
был
выделен с
помощью
pkey_alloc(2).
- EINVAL
- В prot
указаны
оба флага,
PROT_GROWSUP и PROT_GROWSDOWN.
- EINVAL
- Указано
неверное
значение в
prot.
- EINVAL
- (архитектура
PowerPC ) В prot
указан PROT_SAO,
но
недоступно
аппаратное
свойство SAO.
- ENOMEM
- Не
удалось
выделить
место под
внутренние
структуры
ядра.
- ENOMEM
- Addresses in the range [addr,
addr+len-1] are invalid for the address space of the
process, or specify one or more pages that are not mapped. (Before Linux
2.4.19, the error EFAULT was incorrectly produced for these
cases.)
- ENOMEM
- Изменение
защиты
области
памяти
привело бы
к
превышению
разрешённого
максимума
на
количество
отображений
с
различающимися
атрибутами
(защита на
чтение и на
чтение/запись).
Например,
защита
диапазона
PROT_READ в
середине
области,
которая
сейчас
защищена
PROT_READ|PROT_WRITE,
привела бы
к трём
отображениям:
два
отображения
на концах,
доступных
на
чтение/запись
и
доступное
только для
чтение
отображение
посередине.
Вызов
pkey_mprotect()
впервые
появился в
Linux 4.9;
поддержка
в
библиотеке
glibc добавлена
в версии 2.27.
mprotect(): В POSIX.1-2001, POSIX.1-2008, SVr4
сказано,
что
поведение
mprotect() не
определено,
если
переданная
область
памяти не
получена
через
mmap(2).
Вызов
pkey_mprotect
является
непереносимым
расширением
Linux.
В Linux всегда
можно
вызвать
mprotect()
с любым
адресом из
адресного
пространства
процесса
(за
исключением
области
ядра vsyscall). В
частности,
это можно
использовать
для
изменения
отображений
существующего
кода на
записываемые.
Отличается
ли
действие
PROT_EXEC от
PROT_READ
зависит от
архитектуры
процессора,
версии
ядра и
состояния
процесса.
Если в
флагах
специализаций
процессора
установлен
READ_IMPLIES_EXEC
(смотрите
personality(2)), то
указание
PROT_READ
подразумевает
добавление
PROT_EXEC.
На
некоторых
аппаратных
архитектурах
(например, i386)
PROT_WRITE
подразумевает
PROT_READ.
В POSIX.1 сказано,
что
реализация
может
разрешить
доступ
отличный
от
указанного
в
prot, но для
доступа на
запись
должен
быть
обязательно
установлен
флаг
PROT_WRITE, и
любой
доступ
должен
быть
запрещён,
если
установлен
флаг
PROT_NONE.
В
приложениях
нужно
осторожно
использовать
mprotect() и
pkey_mprotect()
вместе. На x86,
если
mprotect()
используется
с
установленным
в
prot
значением
PROT_EXEC, то pkey
может быть
выделен и
установлен
ядром в
память
неявным
образом, но
только
если до
этого pkey был
равен 0.
В системах
без
аппаратной
поддержки
ключей
защиты
pkey_mprotect()
всё ещё
можно
использовать,
но
значение
pkey
должно
быть равно -1.
При таком
вызове
операция
pkey_mprotect()
эквивалентна
mprotect().
Программа,
представленная
далее,
показывает
использование
mprotect(). Она
выделяет
четыре
страницы
памяти,
делает
третью
доступной
только на
чтение, а
затем
запускает
цикл,
который
проходит
по
выделенной
области,
меняя
байты.
Результат
работы
программы:
$ ./a.out
Начало области: 0x804c000
Получен SIGSEGV при адресе: 0x804e000
#include <malloc.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
static char *buffer;
static void
handler(int sig, siginfo_t *si, void *unused)
{
/* Замечание: вызов printf() из обработчика сигнала небезопасен
(и не должен выполняться в готовых программах), так как
printf() не async-signal-safe; смотрите signal-safety(7).
Тем не менее, здесь мы используем printf(), так как это простой
способ показать когда вызывается обработчик. */
printf("Получен SIGSEGV при адресе: %p\n", si->si_addr);
exit(EXIT_FAILURE);
}
int
main(void)
{
int pagesize;
struct sigaction sa;
sa.sa_flags = SA_SIGINFO;
sigemptyset(&sa.sa_mask);
sa.sa_sigaction = handler;
if (sigaction(SIGSEGV, &sa, NULL) == -1)
handle_error("sigaction");
pagesize = sysconf(_SC_PAGE_SIZE);
if (pagesize == -1)
handle_error("sysconf");
/* выделить буфер с выравниванием на границу страницы;
начальная защита: PROT_READ | PROT_WRITE */
buffer = memalign(pagesize, 4 * pagesize);
if (buffer == NULL)
handle_error("memalign");
printf("Начало области: %p\n", buffer);
if (mprotect(buffer + pagesize * 2, pagesize,
PROT_READ) == -1)
handle_error("mprotect");
for (char *p = buffer ; ; )
*(p++) = 'a';
printf("Цикл завершён\n"); /* никогда не должно случиться */
exit(EXIT_SUCCESS);
}
mmap(2),
sysconf(3),
pkeys(7)
Русский
перевод
этой
страницы
руководства
был сделан
aereiae <
[email protected]>, Alexey <
[email protected]>, Azamat
Hackimov <
[email protected]>, Dmitriy S. Seregin
<
[email protected]>, Dmitry Bolkhovskikh <
[email protected]>,
ITriskTI <
[email protected]>, Max Is <
[email protected]>, Yuri
Kozlov <
[email protected]>, Иван
Павлов <
[email protected]>
и Малянов
Евгений
Викторович
<
[email protected]>
Этот
перевод
является
бесплатной
документацией;
прочитайте
Стандартную
общественную
лицензию GNU
версии 3
или более
позднюю,
чтобы
узнать об
условиях
авторского
права. Мы не
несем
НИКАКОЙ
ОТВЕТСТВЕННОСТИ.
Если вы
обнаружите
ошибки в
переводе
этой
страницы
руководства,
пожалуйста,
отправьте
электронное
письмо на
[email protected]