一: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程序邏輯。