mprotect(2)

๐Ÿฆ‹ NAME ๐Ÿฆ‹

okay so bestie, welcome to mprotect(2) โ€” this is the System Calls Manual page, section 2, and it's all about how you change the protection on a region of memory ๐Ÿ’…

  • mprotect, pkey_mprotect โ€” set protection on a region of memory

๐Ÿ’œ LIBRARY ๐Ÿ’œ

Standard C library (libc, -lc) ๐Ÿซถ


โœจ SYNOPSIS โœจ

#include <sys/mman.h>

int mprotect(size_t size;
             void addr[size], size_t size, int prot);

#define _GNU_SOURCE             /* See feature_test_macros(7) */
#include <sys/mman.h>

int pkey_mprotect(size_t size;
             void addr[size], size_t size, int prot, int pkey);

๐ŸŒธ DESCRIPTION ๐ŸŒธ

so mprotect() changes the access protections for the calling process's memory pages that contain any part of the address range in the interval [addr, addr+size-1]. and listen โ€” addr must be aligned to a page boundary, no exceptions, she's not flexible about this ๐Ÿ˜ญ

and if your process tries to access memory in a way that violates the protections? the kernel just generates a SIGSEGV signal for the process. like. it will simply end you ๐Ÿ’€

prot is a combination of these access flags: either PROT_NONE, or a bitwise OR of the other values in the list below ๐Ÿ‘‡

  • PROT_NONE โ€” the memory cannot be accessed at all. full lockdown, no touching, not even a peek ๐Ÿ™ˆ
  • PROT_READ โ€” the memory can be read ๐Ÿ‘€
  • PROT_WRITE โ€” the memory can be modified โœ๏ธ
  • PROT_EXEC โ€” the memory can be executed ๐Ÿƒ
  • PROT_SEM (since Linux 2.5.7) โ€” the memory can be used for atomic operations. this flag got introduced as part of the futex(2) implementation (the whole point was to guarantee the ability to perform atomic operations that commands like FUTEX_WAIT need), but it is not currently used on any architecture. iconic in theory, unused in practice ๐Ÿ’…
  • PROT_SAO (since Linux 2.6.26) โ€” the memory should have strong access ordering. this one is specific to the PowerPC architecture (version 2.06 of the architecture specification adds the SAO CPU feature, and it's available on POWER 7 or PowerPC A2, for example) โšก

additionally (since Linux 2.6.0), prot can have one of the following flags set:

  • PROT_GROWSUP โ€” apply the protection mode up to the end of a mapping that grows upwards. (mappings like this get created for the stack area on architectures โ€” HP-PARISC, for example โ€” that have an upwardly growing stack โฌ†๏ธ)
  • PROT_GROWSDOWN โ€” apply the protection mode down to the beginning of a mapping that grows downward (which should be a stack segment, or a segment mapped with the MAP_GROWSDOWN flag set โฌ‡๏ธ)

and just like mprotect(), pkey_mprotect() changes the protection on the pages specified by addr and size. the pkey argument specifies the protection key (see pkeys(7)) to assign to the memory. lowkey important: that protection key must be allocated with pkey_alloc(2) before you pass it to pkey_mprotect(). for an example of how this system call gets used, see pkeys(7) ๐ŸŒˆ


๐ŸŽ€ RETURN VALUE ๐ŸŽ€

on success, mprotect() and pkey_mprotect() return zero. on error, these system calls return -1, and errno is set to indicate the error ๐Ÿšจ


๐Ÿ’… ERRORS ๐Ÿ’…

  • EACCES โ€” the memory cannot be given the specified access. this can happen, for example, if you mmap(2) a file that you only have read-only access to, and then ask mprotect() to mark it PROT_WRITE. bestie. you cannot write to what you cannot write to ๐Ÿซ 
  • EINVAL โ€” addr is not a valid pointer, or not a multiple of the system page size.
  • EINVAL โ€” (pkey_mprotect()) pkey has not been allocated with pkey_alloc(2).
  • EINVAL โ€” both PROT_GROWSUP and PROT_GROWSDOWN were specified in prot. pick a lane, not both ๐ŸŒš
  • EINVAL โ€” invalid flags specified in prot.
  • EINVAL โ€” (PowerPC architecture) PROT_SAO was specified in prot, but the SAO hardware feature is not available.
  • ENOMEM โ€” internal kernel structures could not be allocated.
  • ENOMEM โ€” addresses in the range [addr, addr+size-1] are invalid for the address space of the process, or specify one or more pages that are not mapped. (before Linux 2.4.19, the error EFAULT was incorrectly produced for these cases ๐Ÿซข)
  • ENOMEM โ€” changing the protection of a memory region would result in the total number of mappings with distinct attributes (read versus read/write protection, for example) exceeding the allowed maximum. (for example: making the protection of a range PROT_READ in the middle of a region currently protected as PROT_READ|PROT_WRITE would result in three mappings โ€” two read/write mappings at each end, and a read-only mapping in the middle ๐Ÿฅด)

๐ŸŒŸ VERSIONS ๐ŸŒŸ

POSIX says that the behavior of mprotect() is unspecified if it's applied to a region of memory that wasn't obtained via mmap(2).

on Linux though, it's always permissible to call mprotect() on any address in a process's address space (except for the kernel vsyscall area). in particular, it can be used to change existing code mappings to be writable. yeah, she really said that ๐Ÿ˜ณ

whether PROT_EXEC has any effect different from PROT_READ depends on processor architecture, kernel version, and process state. if READ_IMPLIES_EXEC is set in the process's personality flags (see personality(2)), then specifying PROT_READ will implicitly add PROT_EXEC โœจ

on some hardware architectures (e.g., i386), PROT_WRITE implies PROT_READ.

POSIX.1 says that an implementation may permit access other than that specified in prot, but at a minimum it can allow write access only if PROT_WRITE has been set, and it must not allow any access if PROT_NONE has been set. no cap ๐Ÿงข

applications should be careful when mixing use of mprotect() and pkey_mprotect(). on x86, when mprotect() is used with prot set to PROT_EXEC, a pkey may be allocated and set on the memory implicitly by the kernel โ€” but only when the pkey was 0 previously ๐Ÿ‘‘

on systems that do not support protection keys in hardware, pkey_mprotect() may still be used, but pkey must be set to -1. when called this way, the operation of pkey_mprotect() is equivalent to mprotect() ๐Ÿช„


๐Ÿ† STANDARDS ๐Ÿ†

  • mprotect() โ€” POSIX.1-2024.
  • pkey_mprotect() โ€” Linux.

๐Ÿ“œ HISTORY ๐Ÿ“œ

  • mprotect() โ€” SVr4, SUSv1, POSIX.1-1996.
  • pkey_mprotect() โ€” Linux 4.9, glibc 2.27.

๐Ÿซถ NOTES ๐Ÿซถ

(ngl bestie, this section is literally empty in the original. a vibe, honestly ๐Ÿ’€)


๐Ÿ“ EXAMPLES ๐Ÿ“

the program below demonstrates the use of mprotect(). what it does: it allocates four pages of memory, makes the third of those pages read-only, and then executes a loop that walks upward through the allocated region modifying bytes ๐Ÿšถโ™€๏ธ

an example of what we might see when running the program is the following:

$ ./a.out
Start of region:        0x804c000
Got SIGSEGV at address: 0x804e000

๐ŸŒˆ Program source ๐ŸŒˆ

#include <err.h>
#include <malloc.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>

static char *buffer;

static void
handler(int sig, siginfo_t *si, void *unused)
{
    /* Note: calling printf() from a signal handler is not safe
       (and should not be done in production programs), since
       printf() is not async-signal-safe; see signal-safety(7).
       Nevertheless, we use printf() here as a simple way of
       showing that the handler was called.  */

    printf("Got SIGSEGV at address: %p\n", si->si_addr);
    exit(EXIT_FAILURE);
}

int
main(void)
{
    int               pagesize;
    struct sigaction  sa;

    sa.sa_flags = SA_SIGINFO;
    sigemptyset(&sa.sa_mask);
    sa.sa_sigaction = handler;
    if (sigaction(SIGSEGV, &sa, NULL) == -1)
        err(EXIT_FAILURE, "sigaction");

    pagesize = sysconf(_SC_PAGE_SIZE);
    if (pagesize == -1)
        err(EXIT_FAILURE, "sysconf");

    /* Allocate a buffer aligned on a page boundary;
       initial protection is PROT_READ | PROT_WRITE.  */

    buffer = memalign(pagesize, 4 * pagesize);
    if (buffer == NULL)
        err(EXIT_FAILURE, "memalign");

    printf("Start of region:        %p\n", buffer);

    if (mprotect(buffer + pagesize * 2, pagesize,
                 PROT_READ) == -1)
        err(EXIT_FAILURE, "mprotect");

    for (char *p = buffer ; ; )
        *(p++) = 'a';

    printf("Loop completed\n");     /* Should never happen */
    exit(EXIT_SUCCESS);
}
lowkey note: that printf("Loop completed\n"); line should literally never happen โ€” if you ever see it, something has gone very wrong ๐Ÿ’€

๐Ÿ‘‘ SEE ALSO ๐Ÿ‘‘

mmap(2), sysconf(3), pkeys(7)


and that's the whole page bestie, you ate that ๐Ÿซถ this was Linux man-pages 6.20, dated 2026-02-11. go forth and protect your memory regions responsibly, mwah ๐Ÿ’…โœจ

๐Ÿ“ฆ where this page comes from
  • arch rolling: man-pages 6.20-1 ยท BSD-2-Clause AND BSD-3-Clause AND BSD-4.3TAHOE AND BSD-4-Clause-UC AND GPL-1.0-or-later AND GPL-2.0-only AND GPL-2.0-or-later AND GPL-3.0-or-later AND LGPL-3.0-linking-exception AND LGPL-3.0-or-later AND LicenseRef-Public-Domain AND LicenseRef-UltraPermissive AND Linux-man-pages-1-para AND Linux-man-pages-copyleft AND Linux-man-pages-copyleft-2-para AND Linux-man-pages-copyleft-var AND MIT AND Spencer-94
  • nixpkgs unstable: man-pages 6.20
  • ๐Ÿ“œ the page's license: Linux-man-pages-copyleft
  • ๐Ÿ’– girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms