C语言之 mprotect

在Linux中,mprotect()函数可以用来修改一段指定内存区域的保护属性。

函数原型如下:

#include <unistd.h>   
#include <sys/mmap.h>   
int mprotect(const void *start, size_t len, int prot);

mprotect()函数把自start开始的、长度为len的内存区的保护属性修改为prot指定的值。

prot可以取以下几个值,并且可以用“|”将几个属性合起来使用:

1)PROT_READ:表示内存段内的内容可读;

2)PROT_WRITE:表示内存段内的内容可写;

3)PROT_EXEC:表示内存段中的内容可执行;

4)PROT_NONE:表示内存段中的内容根本没法访问。

需要指出的是,锁指定的内存区间必须包含整个内存页(4K)。区间开始的地址start必须是一个内存页的起始地址,并且区间长度len必须是页大小的整数倍。

如果执行成功,则返回0;如果执行失败,则返回-1,并且设置errno变量,说明具体因为什么原因造成调用失败。错误的原因主要有以下几个:

1)EACCES

该内存不能设置为相应权限。这是可能发生的,比如,如果你 mmap(2) 映射一个文件为只读的,接着使用 mprotect() 标志为 PROT_WRITE。

2)EINVAL

start 不是一个有效的指针,指向的不是某个内存页的开头。

3)ENOMEM

内核内部的结构体无法分配。

4)ENOMEM

进程的地址空间在区间 [start, start+len] 范围内是无效,或者有一个或多个内存页没有映射。

如果调用进程内存访问行为侵犯了这些设置的保护属性,内核会为该进程产生 SIGSEGV (Segmentation fault,段错误)信号,并且终止该进程。

#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

static int alloc_size;
static char *memory;

int main()
{
	int fd;
	fd = open("/dev/zero", O_RDONLY);
	alloc_size = getpagesize(); 
	memory = mmap(NULL, alloc_size, PROT_WRITE, MAP_PRIVATE, fd, 0); 
	close(fd);
	memory[0] = 1;
	
	memory[0] = 0;
	printf("all done\n");
	munmap(memory, alloc_size);
	return 0;
}
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

static int alloc_size;
static char* memory;

void segv_handler(int signal_number)
{
	printf("find memory accessed!\n");
	mprotect(memory, alloc_size, PROT_READ | PROT_WRITE);
	
	printf("set memory read write!\n");
}

int main()
{
	int fd;
	struct sigaction sa;

	
	memset(&sa, 0, sizeof(sa));
	sa.sa_handler = &segv_handler;
	sigaction(SIGSEGV, &sa, NULL);

	 
	alloc_size = getpagesize();
	fd = open("/dev/zero", O_RDONLY);
	memory = mmap(NULL, alloc_size, PROT_WRITE, MAP_PRIVATE, fd, 0);
	close(fd);
	
	memory[0] = 0;
	printf("memory[0] = 0\n");
	
	mprotect(memory, alloc_size, PROT_NONE);
	printf("memory[0] = 1 SIGSEGV\n");
	
	memory[0] = 1;
	printf("memory[0] = 2 ok\n");
	
	memory[0] = 2;
	
	printf("all done\n");
	munmap(memory, alloc_size);
	return 0;
}

上述程序按照如下步骤执行:

  1. 程序为 SIGSEGV 建立一个信号处理句柄。
  2. 程序通过映射 /dev/zero 分配一个内存分页,然后通过写入数据的方式获得一个私有复本。
  3. 程序通过调用带 PROT_NONE 权限的 mprotect 保护了内存。
  4. 当程序在后续执行中写入内存时,Linux 向进程发送 SIGSEGV,这个信号被 segv_handler 句柄接收处理。这个句柄将解除内存保护,因而程序内存访问得以继续。
  5. 当信号句柄执行完成时,程序控制权返回 main 函数,程序将使用 munmap 来释放内存。
posted @ 2021-01-12 12:22  Max_hhg  阅读(4199)  评论(0编辑  收藏  举报