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