mprotect: 設置內存訪問權限
mmap 的第三個參數指定對內存區域的保護,由標記讀、寫、執行權限的 PROT_READ、PROT_WRITE 和 PROT_EXEC 按位與操作獲得,或者是限制沒有訪問權限的 PROT_NONE。如果程序嘗試在不允許這些權限的本地內存上操作,它將被 SIGSEGV 信號(Segmentation fault,段錯誤)終止。
在內存映射完成后,這些權限仍可以被 mprotect 系統調用所修改。mprotect 的參數分別為內存區間的地址,區間的大小,新的保護標志設置。所指定的內存區間必須包含整個頁:區間地址必須和整個系統頁大小對齊,而區間長度必須是頁大小的整數倍。這些頁的保護標記被這里指定的新保護模式替換。
獲得頁面對齊的內存
應注意的是, malloc 返回的內存區域通常并不與內存頁面對齊,甚至在內存的大小是頁大小整數倍的情況下也一樣。如果您想保護從 malloc 獲得的內存,您不得不分配一個更大的內存區域并在其中找到一個與頁對齊的區間。
您可以選擇使用 mmap 系統調用來繞過 malloc 并直接從 Linux 內核中分配頁面對齊內存。
mmap通過映射 /dev/zero 來分配內存頁。內存將被初始化為可讀和可寫模式。
int fd = open (“/dev/zero”, O_RDONLY); char* memory = mmap (NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); close (fd);
然后,您的程序可以使用 mprotect 把它變成只讀:
mprotect (memory, page_size, PROT_READ);
有一種監控內存訪問的高級技巧,可以通過利用 mmap 和 mprotect 保護目標內存區間,然后當程序訪問時候接收并處理 Linux 系統發送的 SIGSEGV 信號。代碼 展示了這個技巧。
代碼使用mprotect檢測內存訪問
#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 (“memory accessed!\n”); mprotect (memory, alloc_size, PROT_READ | PROT_WRITE); } int main () { int fd; struct sigaction sa; /* 初始化segv_handler為SIGSEGV的句柄。*/ memset (&sa, 0, sizeof (sa)); sa.sa_handler = &segv_handler; sigaction (SIGSEGV, &sa, NULL); /* 使用映射/dev/zero分配內存頁。最初映射的內存為只寫。*/ 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; /* 使內存為不可寫。 */ mprotect (memory, alloc_size, PROT_NONE); /* 寫分配內存區域。 */ memory[0] = 1; /* 所有工作都結束;unmap內存映射。 */ printf (“all done\n”); munmap (memory, alloc_size); return 0; }
上述程序按照如下步驟執行:
- 程序為 SIGSEGV 建立一個信號處理句柄。
- 程序通過映射 /dev/zero 分配一個內存分頁,然后通過寫入數據的方式獲得一個私有復本。
- 程序通過調用帶 PROT_NONE 權限的 mprotect 保護了內存。
- 當程序在后續執行中寫入內存時,Linux 向進程發送 SIGSEGV,這個信號被 segv_handler 句柄接收處理。這個句柄將解除內存保護,因而程序內存訪問得以繼續。
- 當信號句柄執行完成時,程序控制權返回 main 函數,程序將使用 munmap 來釋放內存。