MPROTECT(2) Руководство программиста Linux MPROTECT(2)

ИМЯ

mprotect, pkey_mprotect - контролирует доступ к области памяти

ОБЗОР

#include <sys/mman.h>
int mprotect(void *addr, size_t len, int prot);
int pkey_mprotect(void *addr, size_t len, int prot, int pkey);

ОПИСАНИЕ

Вызов mprotect() изменяет параметры доступа страниц памяти вызывающего процесса, которые содержатся, даже частично, в адресном диапазоне [addraddr+len-1]. Значение addr должно быть выровнено на границу страницы.

Если вызывающий процесс нарушает защиту доступа к памяти, то ядро посылает процессу сигнал SIGSEGV.

Значение prot представляет собой комбинацию следующих флагов доступа: PROT_NONE или побитово сложенные другие значения из следующего списка:

Доступ к памяти запрещён.
Память можно читать.
Память можно изменять.
Память можно выполнять.
Память можно использовать для атомарных операций. Этот флаг появился как часть реализации futex(2) (для гарантии способности выполнять атомарные операции, требуемые таким командам как FUTEX_WAIT), но пока не используется ни в одной архитектуре.
Память должна иметь строгий порядок доступа. Это свойство есть только в архитектуре PowerPC (в спецификации архитектуры версии 2.06 добавлено свойство ЦП SAO и оно доступно, например, на POWER 7 или PowerPC A2).

Также (начиная с Linux 2.6.0), prot может содержать один из следующих установленных флагов:

Применить режим защиты до конца отображения, которое растёт вверх (такие отображения создаются для области стека например в архитектуре HP-PARISC, где стек растёт вверх).
Применить режим защиты до начала отображения, которое растёт вниз (которое должно быть сегментом стека или сегментом, отображённым с установленным флагом MAP_GROWSDOWN).

Подобно mprotect(), вызов pkey_mprotect() изменяет защиту страниц, указанных addr и len. Аргумент pkey содержит ключ защиты (смотрите pkeys(7)), назначаемый памяти. Ключ защиты должен быть выделен с помощью pkey_alloc(2) до передачи в pkey_mprotect(). Пример использования этого системного вызова смотрите в pkeys(7).

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ

При успешном выполнении mprotect() и pkey_mprotect() возвращают 0. В случае ошибки возвращается -1, а errno устанавливается в соответствующее значение.

ОШИБКИ

Нельзя задать этот вид доступа. Например, это может случиться, если при вызове mmap(2) файл доступен только на чтение, а запрос mprotect() был PROT_WRITE.
Значение addr не является правильным указателем или не кратен размеру системной страницы.
(pkey_mprotect()) pkey не был выделен с помощью pkey_alloc(2).
В prot указаны оба флага, PROT_GROWSUP и PROT_GROWSDOWN.
Указано неверное значение в prot.
(архитектура PowerPC ) В prot указан PROT_SAO, но недоступно аппаратное свойство SAO.
Не удалось выделить место под внутренние структуры ядра.
Адреса в диапазоне [addr, addr+len-1] некорректны для адресного пространства процесса, или одна или более указанных страниц не отображена (до ядра версии 2.4.19 в этих случаях некорректно возвращалась ошибка EFAULT).
Изменение защиты области памяти привело бы к превышению разрешённого максимума на количество отображений с различающимися атрибутами (защита на чтение и на чтение/запись). Например, защита диапазона 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 должно быть равно 0. При таком вызове операция pkey_mprotect() эквивалентна mprotect().

ПРИМЕР

Программа, представленная далее, показывает использование mprotect(). Она выделяет четыре страницы памяти, делает третью доступной только на чтение, а затем запускает цикл, который проходит по выделенной области, меняя байты.

Результат работы программы:

$ ./a.out
Начало области:             0x804c000
Получен SIGSEGV при адресе: 0x804e000

Исходный код программы

#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/mman.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 при адресе: 0x%lx\n",

            (long) si->si_addr);

    exit(EXIT_FAILURE);
}
int
main(int argc, char *argv[])
{

    char *p;

    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("Начало области:             0x%lx\n", (long) buffer);

    if (mprotect(buffer + pagesize * 2, pagesize,

                PROT_READ) == -1)

        handle_error("mprotect");

    for (p = buffer ; ; )

        *(p++) = 'a';

    printf("Цикл завершён\n");     /* никогда не должно случиться */

    exit(EXIT_SUCCESS);
}

СМОТРИТЕ ТАКЖЕ

mmap(2), sysconf(3), pkeys(7)

2019-03-06 Linux