内存越界定位mprotect

Posted 叮咚咕噜

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内存越界定位mprotect相关的知识,希望对你有一定的参考价值。

mprotect()函数可以修改调用进程内存页的保护属性,设置某个地址区域为只可读不可写的,如果调用进程尝试以违反保护属性的方式访问该内存,则内核会发出一个SIGSEGV信号给该进程。

函数介绍

  • 头文件:#include <sys/mman.h>
  • 函数定义:
int mprotect(void *addr, size_t len, int prot);
  • 入参:
    • addr:内存地址要求是一个内存页的首地址,简而言之为页大小(一般是 4KB == 4096字节)整数倍。
    • len:被修改保护属性区域的长度,页大小整数倍。修改区域范围[addr, addr+len-1]。
    • prot:可以取以下几个值,并可以用“|”将几个属性结合起来使用:
      • PROT_READ:内存段可读;
      • PROT_WRITE:内存段可写;
      • PROT_EXEC:内存段可执行;
      • PROT_NONE:内存段不可访问。
  • 返回值:0;成功,-1;失败(并且errno被设置)
    • EACCES:无法设置内存段的保护属性。当通过 mmap(2) 映射一个文件为只读权限时,接着使用 mprotect() 标志为 PROT_WRITE这种情况就会发生。
    • EINVAL:addr不是有效指针,或者不是系统页大小的倍数。
    • ENOMEM:内核内部的结构体无法分配。
  • 注意 : 入参1函数的地址要是页对齐的,保护的长度也需要时页对齐的

测试代码

//mmc.cpp 以下两个例子都是可以使用,只是逻辑处理有点差异
#if 1 //**************示例1*************
#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)
{
   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 = (char *)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++) = 'a';
	
#if 0
	/* 测试发现len需要为页大小倍数,如果不为页大小倍数情况下,系统会匹配最大页大小倍数,
	   比如页大小为4k,len小于4k,修改范围[addr, addr+4k-1],如果大于4k小于8k,修改范围[addr, addr+8k-1],
	   以此类推。
	*/
	if (mprotect(buffer, 4097,PROT_READ) == -1)
		  handle_error("mprotect");
	int i = 0; 
	/* i<0x4000 (4 * pagesize) */
	for (p = buffer ;i<0x4000 ;p++)
	{
		if (i++ < 8192)
			continue;
		*p = 'a';
	}
	printf("End of region:        0x%lx\\n", (long) buffer+i);
#endif
	printf("Loop completed\\n");     /* Should never happen */
	exit(EXIT_SUCCESS);
}

测试结果:

qiuhui@ubuntu:~/work/share/mprotect-gdb$ g++ -g mmc.cpp 
qiuhui@ubuntu:~/work/share/mprotect-gdb$ ./a.out 
Start of region:        0x900000
Got SIGSEGV at address: 0x902000
qiuhui@ubuntu:~/work/share/mprotect-gdb$ 
  • 上述程序,buff起始地址0x900000,保护地址为0x902000,当对0x902000进行写操作时触发死机

核心思想

在死机使用的过程中,死机的地址没办法做到正好是页对齐的,所以需要我们对代码进行修改:

  • 在被踩的内存前添加一段“替死鬼”内存,并在上面设置“陷阱”揪出踩内存的罪魁祸首:被保护的内存是只读属性,发生内存被写则中断操作。

参考

真是的案例可以参考下:
Linux程序内存越界定位分析总结
定位多线程内存越界问题实践总结

以上是关于内存越界定位mprotect的主要内容,如果未能解决你的问题,请参考以下文章

Asan快速定位内存越界内存泄漏

Asan快速定位内存越界内存泄漏

踩内存问题定位手段汇总

内存越界定位magic number

多线程内存问题分析之mprotect方法

内存越界定位MALLOC_CHECK_