In Section 5.3, "Mapped Memory," we showed how to use the mmap system call to map a file into memory. Recall that the third argument to mmap is a bitwise or of memory protection flags PROT_READ, PROT_WRITE, and PROT_EXEC for read, write, and execute permission, respectively, or PROT_NONE for no memory access. If a program attempts to perform an operation on a memory location that is not allowed by these permissions, it is terminated with a SIGSEGV (segmentation violation) signal.
After memory has been mapped, these permissions can be modified with the mprotect system call. The arguments to mprotect are an address of a memory region, the size of the region, and a set of protection flags. The memory region must consist of entire pages: The address of the region must be aligned to the system's page size, and the length of the region must be a page size multiple. The protection flags for these pages are replaced with the specified value.
Note that memory regions returned by malloc are typically not page-aligned, even if the size of the memory is a multiple of the page size. If you want to protect memory obtained from malloc, you will have to allocate a larger memory region and find a page-aligned region within it.
Alternately, you can use the mmap system call to bypass malloc and allocate page-aligned memory directly from the Linux kernel. See Section 5.3, "Mapped Memory," for details.
For example, suppose that your program allocates a page of memory by mapping /dev/zero, as described in Section 5.3.5, "Other Uses for mmap." The memory is initially both readable and writable.
int fd = open ("/dev/zero", O_RDONLY);
char* memory = mmap (NULL, page_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE, fd, 0);
Later, your program could make the memory read-only by calling mprotect:
mprotect (memory, page_size, PROT_READ);
An advanced technique to monitor memory access is to protect the region of memory using mmap or mprotect and then handle the SIGSEGV signal that Linux sends to the program when it tries to access that memory. The example in Listing 8.7 illustrates this technique.
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 ()
struct sigaction sa;
/* Install segv_handler as the handler for SIGSEGV. */
memset (&sa, 0, sizeof (sa));
sa.sa_handler = &segv_handler;
sigaction (SIGSEGV, &sa, NULL);
/* Allocate one page of memory by mapping /dev/zero. Map the memory
as write-only, initially. */
alloc_size = getpagesize ();
fd = open ("/dev/zero", O_RDONLY);
memory = mmap (NULL, alloc_size, PROT_WRITE, MAP_PRIVATE, fd, 0);
/* Write to the page to obtain a private copy. */
memory = 0;
/* Make the memory unwritable. */
mprotect (memory, alloc_size, PROT_NONE);
/* Write to the allocated memory region. */
memory = 1;
/* All done; unmap the memory. */
printf ("all done\n");
munmap (memory, alloc_size);
The program follows these steps:
2. The program allocates a page of memory by mapping /dev/zero and writing a value to the allocated page to obtain a private copy.
3. The program protects the memory by calling mprotect with the PROT_NONE permission.
4. When the program subsequently writes to memory, Linux sends it SIGSEGV, which is handled by segv_handler. The signal handler unprotects the memory, which allows the memory access to proceed.
5. When the signal handler completes, control returns to main, where the program deallocates the memory using munmap.