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() изменяет параметры доступа страниц памяти вызывающего процесса, которые содержатся, даже частично, в адресном диапазоне [addr, addr+len-1]. Значение addr должно быть выровнено на границу страницы.
Если вызывающий процесс нарушает защиту доступа к памяти, то ядро посылает процессу сигнал SIGSEGV.
Значение prot представляет собой комбинацию следующих флагов доступа: PROT_NONE или побитово сложенные другие значения из следующего списка:
Также (начиная с Linux 2.6.0), prot может содержать один из следующих установленных флагов:
Подобно mprotect(), вызов pkey_mprotect() изменяет защиту страниц, указанных addr и len. Аргумент pkey содержит ключ защиты (смотрите pkeys(7)), назначаемый памяти. Ключ защиты должен быть выделен с помощью pkey_alloc(2) до передачи в pkey_mprotect(). Пример использования этого системного вызова смотрите в pkeys(7).
При успешном выполнении mprotect() и pkey_mprotect() возвращают 0. В случае ошибки возвращается -1, а errno устанавливается в соответствующее значение.
Вызов 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); }
2019-03-06 | Linux |