PKEYS - Linux手册页

时间:2019-08-20 18:02:00  来源:igfitidea点击:

Linux程序员手册 第7部分
更新日期: 2020-06-09

名称

pkeys-内存保护锁概述

说明

内存保护键(pkey)是现有基于页面的内存权限的扩展。使用页表的普通页权限在更改权限时需要昂贵的系统调用和TLB无效。内存保护键提供了一种更改保护的机制,而无需在每次许可更改时都修改页表。

要使用pkey,软件必须首先使用pkey在页表中"标记"页面。放置此标签后,应用程序只需更改寄存器的内容即可删除写访问或对标记页面的所有访问。

保护密钥与传递给系统调用(例如mprotect(2)和mmap(2))的现有PROT_READ / PROT_WRITE / PROT_EXEC权限一起使用,但始终会进一步限制这些传统的权限机制。

如果某个进程执行的访问违反了pkey限制,则它将收到SIGSEGV信号。有关该信号可用信息的详细信息,请参见sigaction(2)。

要使用pkeys功能,处理器必须支持它,并且内核必须在给定的处理器上包含对该功能的支持。截至2016年初,仅支持将来的Intel x86处理器,并且该硬件在每个进程中均支持16个保护密钥。但是,pkey 0被用作默认密钥,因此实际应用程序最多可以使用15个密钥。将默认密钥分配给尚未通过pkey_mprotect(2)显式分配pkey的任何内存区域。

保护锁有可能为应用程序增加一层安全性和可靠性。但是它们并不是主要设计为安全功能。例如,WRPKRU是完全无特权的指令,因此,在攻击者控制PKRU寄存器或可以执行任意指令的任何情况下,pkey都是无用的。

应用程序应非常小心,以确保它们不会"泄漏"保护锁。例如,在调用pkey_free(2)之前,应用程序应确保没有分配pkey的内存。如果应用程序分配了释放的pkey,则该pkey的未来用户可能会无意中更改无关数据结构的权限,这可能会影响安全性或稳定性。内核当前允许使用中的pkey调用pkey_free(2),因为它会影响处理器或内存性能,以执行不允许执行的其他检查。必要检查的实施由应用程序决定。应用程序可以通过在/ proc / [pid] / smaps文件中搜索分配了pkey的内存区域来实现这些检查。可以在proc(5)中找到更多详细信息。

任何想要使用保护锁的应用程序都必须能够在没有保护锁的情况下运行。它们可能不可用,因为运行应用程序的硬件不支持它们,内核代码不包含支持,内核支持已被禁用,或者因为密钥已全部分配,可能是由应用程序正在使用的库分配的。建议希望使用保护密钥的应用程序应该简单地调用pkey_alloc(2)并测试调用是否成功,而不是尝试以任何其他方式来检测对该功能的支持。

尽管没有必要,但可以使用cpuid指令枚举保护密钥的硬件支持。有关如何执行此操作的详细信息,请参阅《英特尔软件开发人员手册》。内核执行此枚举,并在"标志"字段下的/ proc / cpuinfo中公开信息。此字段中的字符串" pku"表示对保护密钥的硬件支持,字符串" ospke"表示内核包含并启用了保护密钥支持。

使用线程和保护锁的应用程序应格外小心。线程在进行clone(2)系统调用时继承父级的保护密钥权限。应用程序应确保在调用clone(2)时,其自己的权限适合于子线程,或者应确保每个子线程都可以执行其自己的保护密钥权限初始化。

Signal Handler Behavior

每次调用信号处理程序(包括嵌套信号)时,都会临时为线程分配一组新的默认保护密钥权限,这些权限将覆盖来自中断上下文的权限。这意味着,如果所需权限不同于默认值,则应用程序必须在进入信号处理程序后重新建立其所需的保护密钥权限。信号处理程序返回时,将恢复所有中断上下文的权限。

此信号行为异常,是由于使用与管理浮点寄存器相同的硬件机制(XSAVE)管理x86 PKRU寄存器(用于存储保护密钥访问权限)。信号行为与浮点寄存器相同。

Protection Keys system calls

Linux内核实现了以下与pkey相关的系统调用:pkey_mprotect(2),pkey_alloc(2)和pkey_free(2)。

仅当使用CONFIG_X86_INTEL_MEMORY_PROTECTION_KEYS选项配置和构建内核时,Linux pkey系统调用才可用。

示例

下面的程序分配具有读取和写入权限的内存页面。然后,它将一些数据写入内存并成功读回。之后,它将尝试分配保护密钥,并禁止使用WRPKRU指令访问该页面。然后,它尝试访问该页面,我们现在希望该页面对应用程序造成致命的信号。

$ ./a.out
buffer contains: 73
about to read buffer again...
Segmentation fault (core dumped)

Program source

#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>
#include <stdio.h>
#include <sys/mman.h>

static inline void
wrpkru(unsigned int pkru)
{
    unsigned int eax = pkru;
    unsigned int ecx = 0;
    unsigned int edx = 0;

    asm volatile(".byte 0x0f,0x01,0xef\n\t"
                 : : "a" (eax), "c" (ecx), "d" (edx));
}

int
pkey_set(int pkey, unsigned long rights, unsigned long flags)
{
    unsigned int pkru = (rights << (2 * pkey));
    return wrpkru(pkru);
}

int
pkey_mprotect(void *ptr, size_t size, unsigned long orig_prot,
              unsigned long pkey)
{
    return syscall(SYS_pkey_mprotect, ptr, size, orig_prot, pkey);
}

int
pkey_alloc(void)
{
    return syscall(SYS_pkey_alloc, 0, 0);
}

int
pkey_free(unsigned long pkey)
{
    return syscall(SYS_pkey_free, pkey);
}

#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
                           } while (0)

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)
        errExit("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();
    if (pkey == -1)
        errExit("pkey_alloc");

    /*
     * Disable access to any memory with "pkey" set,
     * even though there is none right now
     */
    status = pkey_set(pkey, PKEY_DISABLE_ACCESS, 0);
    if (status)
        errExit("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)
        errExit("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)
        errExit("pkey_free");

    exit(EXIT_SUCCESS);
}

另外参见

pkey_alloc(2),pkey_free(2),pkey_mprotect(2),sigaction(2)

出版信息

这个页面是Linux手册页项目5.08版的一部分。有关项目的说明、有关报告错误的信息以及此页面的最新版本,请访问https://www.kernel.org/doc/man-pages/