pkeys(7) Miscellaneous Information Manual pkeys(7)

ИМЯ

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;
    /*
     * Allocate one page of memory.
     */
    buffer = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE,
                  MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
    if (buffer == MAP_FAILED)
        err(EXIT_FAILURE, "mmap");
    /*
     * Put some random data into the page (still OK to touch).
     */
    *buffer = __LINE__;
    printf("buffer contains: %d\n", *buffer);
    /*
     * Allocate a protection key:
     */
    pkey = pkey_alloc(0, 0);
    if (pkey == -1)
        err(EXIT_FAILURE, "pkey_alloc");
    /*
     * Disable access to any memory with "pkey" set,
     * even though there is none right now.
     */
    status = pkey_set(pkey, PKEY_DISABLE_ACCESS);
    if (status)
        err(EXIT_FAILURE, "pkey_set");
    /*
     * Set the protection key on "buffer".
     * Note that it is still read/write as far as mprotect() is
     * concerned and the previous pkey_set() overrides it.
     */
    status = pkey_mprotect(buffer, getpagesize(),
                           PROT_READ | PROT_WRITE, pkey);
    if (status == -1)
        err(EXIT_FAILURE, "pkey_mprotect");
    printf("about to read buffer again...\n");
    /*
     * This will crash, because we have disallowed access.
     */
    printf("buffer contains: %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 <azamat.hackimov@gmail.com>, kogamatranslator49 <r.podarov@yandex.ru>, Kogan, Max Is <ismax799@gmail.com>, Yuri Kozlov <yuray@komyakino.ru> и Иван Павлов <pavia00@gmail.com>

Этот перевод является бесплатной документацией; прочитайте Стандартную общественную лицензию GNU версии 3 или более позднюю, чтобы узнать об условиях авторского права. Мы не несем НИКАКОЙ ОТВЕТСТВЕННОСТИ.

Если вы обнаружите ошибки в переводе этой страницы руководства, пожалуйста, отправьте электронное письмо на man-pages-ru-talks@lists.sourceforge.net.

3 мая 2023 г. Linux man-pages 6.05.01