MPROTECT - Linux手册页

时间:2019-08-20 17:59:00  来源:igfitidea点击:

Linux程序员手册 第2部分
更新日期: 2020-04-11

名称

mprotect,pkey_mprotect-在内存区域设置保护

语法

#include <sys/mman.h>

int mprotect(void *addr, size_t len, int prot);

#define _GNU_SOURCE             /* See feature_test_macros(7) */
#include <sys/mman.h>

int pkey_mprotect(void *addr, 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(since Linux 2.5.7)
该存储器可用于原子操作。此标志是作为futex(2)实现的一部分引入的(为了保证具有执行诸如FUTEX_WAIT之类的命令所需的原子操作的能力),但是当前尚未在任何体系结构中使用。
PROT_SAO(since Linux 2.6.26)
内存应具有较强的访问顺序。此功能特定于PowerPC体系结构(体系结构规范的2.06版添加了SAO CPU功能,例如在POWER 7或PowerPC A2上可用)。

此外(自Linux 2.6.0起),prot可以设置以下标志之一:

PROT_GROWSUP将保护模式应用到向上增长的映射的末尾。 (此类映射是针对堆栈向上增长的体系结构(例如,HP-PARISC)上的堆栈区域创建的。)
PROT_GROWSDOWN
将保护模式向下应用于向下增长的映射的开头(应该是堆栈段或设置了MAP_GROWSDOWN标志的段)。

mprotect()一样,pkey_mprotect()更改由addr和len指定的页面上的保护。 pkey参数指定要分配给内存的保护密钥(请参阅pkeys(7))。保护密钥必须先通过pkey_alloc(2)分配,然后才能传递给pkey_mprotect()。有关使用此系统调用的示例,请参见pkeys(7)。

返回值

成功时,mprotect()和pkey_mprotect()返回零。出错时,这些系统调用将返回-1,并正确设置了errno。

错误说明

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_SAO在prot中指定,但是SAO硬件功能不可用。
ENOMEM
内部内核结构无法分配。
ENOMEM
范围[addr,addr + len-1]内的地址对于进程的地址空间无效,或指定一个或多个未映射的页面。 (在内核2.4.19之前,对于这些情况错误地产生了错误EFAULT。)
ENOMEM
改变存储器区域的保护将导致具有不同属性(例如,读取保护与读取/写入保护)的映射总数超过允许的最大值。 (例如,在当前被保护为PROT_READ | PROT_WRITE的区域中间保护范围PROT_READ将导致三个映射:在两端分别有两个读/写映射,在中间有一个只读映射。)

版本

pkey_mprotect()最早出现在Linux 4.9中。库支持在glibc 2.27中添加。

遵循规范

mprotect():POSIX.1-2001,POSIX.1-2008,SVr4。 POSIX表示,如果将mprotect()的行为应用于不是通过mmap(2)获得的内存区域,则未指定。

pkey_mprotect()是不可移植的Linux扩展。

备注

在Linux上,始终允许在进程地址空间中的任何地址(内核vsyscall区域除外)上调用mprotect()。特别是,它可用于将现有代码映射更改为可写。

PROT_EXEC是否具有与PROT_READ不同的效果,取决于处理器体系结构,内核版本和进程状态。如果在进程的个性标志中设置了READ_IMPLIES_EXEC(请参见个性(2)),则指定PROT_READ将隐式添加PROT_EXEC。

在某些硬件体系结构(例如i386)上,PROT_WRITE表示PROT_READ。

POSIX.1表示,实现可以允许除prot中指定的访问之外的访问,但是,至少在设置了PROT_WRITE的情况下,才可以允许写访问,而如果设置了PROT_NONE,则不得允许任何访问。

混合使用mprotect()和pkey_mprotect()时,应用程序应小心。在x86上,当将prot设置为PROT_EXEC时使用mprotect()时,内核可以隐式分配pkey并将其设置在内存上,但前提是先前的pkey为0。

在不支持硬件保护键的系统上,仍可以使用pkey_mprotect(),但必须将pkey设置为-1。以这种方式调用时,pkey_mprotect()的操作等效于mprotect()。

示例

下面的程序演示了mprotect()的用法。程序将分配四页内存,使这些页中的第三页变为只读,然后执行循环,向上循环遍历分配的区域,以修改字节。

下面是运行程序时可能看到的示例:

$ ./a.out
Start of region:        0x804c000
Got SIGSEGV at address: 0x804e000

Program source

#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)
{
    /* Note: calling printf() from a signal handler is not safe
       (and should not be done in production programs), since
       printf() is not async-signal-safe; see signal-safety(7).
       Nevertheless, we use printf() here as a simple way of
       showing that the handler was called. */

    printf("Got SIGSEGV at address: 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");

    /* Allocate a buffer aligned on a page boundary;
       initial protection is PROT_READ | PROT_WRITE */

    buffer = memalign(pagesize, 4 * pagesize);
    if (buffer == NULL)
        handle_error("memalign");

    printf("Start of region:        0x%lx\n", (long) buffer);

    if (mprotect(buffer + pagesize * 2, pagesize,
                PROT_READ) == -1)
        handle_error("mprotect");

    for (p = buffer ; ; )
        *(p++) = aqaaq;

    printf("Loop completed\n");     /* Should never happen */
    exit(EXIT_SUCCESS);
}

另外参见

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

出版信息

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