Linux中mprotect()函數詳解

一:API介紹

mprotect()函數可以修改調用進程內存頁的保護屬性,如果調用進程嘗試以違反保護屬性的方式訪問該內存,則內核會發出一個SIGSEGV信號給該進程。

#include <sys/mman.h>
int mprotect(void *addr, size_t len, int prot);
addr:修改保護屬性區域的起始地址,addr必須是一個內存頁的起始地址,簡而言之爲頁大小(一般是 4KB == 4096字節)整數倍。
len:被修改保護屬性區域的長度,最好爲頁大小整數倍。修改區域範圍[addr, addr+len-1]。
prot:可以取以下幾個值,並可以用“|”將幾個屬性結合起來使用:
1)PROT_READ:內存段可讀;
2)PROT_WRITE:內存段可寫;
3)PROT_EXEC:內存段可執行;
4)PROT_NONE:內存段不可訪問。
返回值:0;成功,-1;失敗(並且errno被設置)
1)EACCES:無法設置內存段的保護屬性。當通過 mmap(2) 映射一個文件爲只讀權限時,接着使用 mprotect() 標誌爲 PROT_WRITE這種情況就會發生。
2)EINVAL:addr不是有效指針,或者不是系統頁大小的倍數。
3)ENOMEM:內核內部的結構體無法分配。

二: 測試源碼

//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);
}

#else //**************示例2*************
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>

int *g_ps32Result;

void add(int a, int b)
{
    *g_ps32Result = a + b;
}

void subtract(int a, int b)
{
    *g_ps32Result = a - b;
}

int main()
{
    int ret;
    int l_s32PageSize;
    
    /* 獲取操作系統一個頁的大小, 一般是 4KB == 4096 */
    l_s32PageSize = sysconf(_SC_PAGE_SIZE);
    if (l_s32PageSize == -1) {
        perror("sysconf fail");
        return -1;
    }
    printf("One Page Size is:%d Byte\r\n", l_s32PageSize);

    /* 按頁對齊來申請一頁內存, g_ps32Result會是一個可以被頁(0x1000 == 4096)整除的地址 */
    ret = posix_memalign((void**)&g_ps32Result, l_s32PageSize, l_s32PageSize);
    if (ret != 0) {
        /* posix_memalign 返回失敗不會設置系統的errno, 不能用perror輸出錯誤 */
        printf("posix_memalign fail, ret %u\r\n", ret);
        return -1;
    }
    printf("posix_memalign mem %p\r\n", g_ps32Result);

    add(1, 1); // 結果寫入 *g_ps32Result
    printf("the g_ps32Result is %d\n", *g_ps32Result);

    /* 保護g_ps32Result指向的內存, 權限設爲只讀 mprotect區間開始的地址start
       必須是一個內存頁的起始地址,並且區間長度len必須是頁大小的整數倍 */
    ret = mprotect(g_ps32Result, l_s32PageSize, PROT_READ);
    if (ret == -1) {
        perror("mprotect");
        return -1;
    }

    subtract(1, 1); // 結果寫入 *g_ps32Result, 但 *g_ps32Result 的內存地址已設爲只讀, 所以會引發segment fault
    printf("the g_ps32Result is %d\n", *g_ps32Result);

    /* 申請一定記得釋放 */
    free(g_ps32Result);
    return 0;
}

#endif

三:編譯測試

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$ 
  • 由上輸出信息可知程序在內存地址0x902000處引發段錯誤退出,在buff起始地址0x900000偏移0x2000處,符合示例1程序邏輯。

最後希望新型冠狀病毒早日消除,感謝每位奮戰在一線的工作人員,你們辛苦了!相信政府有能力控制好疫情,全國人民攜手共度難關。祝大家在新的一年百毒不侵,心想事成。

Linux用backtrace定位程序異常退出詳解 請點擊!!!
Linux程序內存越界定位分析總結 請點擊!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章