Mémoire · Section 2
mprotect(2)
Modifie la protection d'accès d'une région mémoire.
Signature
#include <sys/mman.h>
int mprotect(void * addr, size_t len, int prot);- addr
- Adresse de départ alignée page.
- len
- Longueur en octets. Arrondie au multiple supérieur de la taille de page.
- prot
- Protection souhaitée : OU bit-à-bit de PROT_READ, PROT_WRITE, PROT_EXEC ou PROT_NONE (seul).
Description
mprotect() modifie la protection d'accès des pages de la plage [addr, addr+len) vers prot. addr doit être aligné page ; len est arrondi au multiple supérieur. La plage doit se trouver dans des projections créées par mmap() (ou la pile/heap du processus). En succès retourne 0 ; -1 avec errno sinon. mprotect() est le levier d'exécution derrière chaque résolution de symboles par le chargeur dynamique (le linker écrit le GOT puis mprotect() en lecture seule — RELRO), derrière chaque JIT (écrire le code, basculer en PROT_READ|PROT_EXEC), et derrière les pages de garde qui basculent en PROT_NONE autour de zones chaudes. C'est aussi l'étape canonique où un processus compromis bascule un tampon de shellcode inscriptible en exécutable.
Numéros par architecture
| Architecture | Numéro | ABI | Point d'entrée |
|---|---|---|---|
| x86 (i386) | 125 | i386 | sys_mprotect |
| x64 (x86_64) | 10 | common | sys_mprotect |
| ARM64 (aarch64) | 226 | — | sys_mprotect |
Historique noyau
Introduit dans Linux 1.0.
1.0
mprotect() est présent dans Linux depuis 1.0 avec la sémantique POSIX.
4.9
pkey_mprotect() a été introduit (Linux 4.9, MPK Intel sur matériel compatible) pour attacher une clé de protection mémoire à une région — un thread active/désactive la protection d'un groupe de pages par une seule écriture de registre au lieu d'appels mprotect par syscall. Base de libmpk et des variantes PAuth arm64.
5.13
Le comportement de mprotect() à la frontière des VMA de pile (PROT_GROWSDOWN) a été durci pour empêcher les extensions ambiguës qui pouvaient leurrer d'anciens noyaux à étendre la pile sur des allocations voisines.
seccomp & conteneurs
Docker default profile
Autorisé
Podman default profile
Autorisé
mprotect() est dans tous les profils par défaut et ne peut être totalement bloqué — chaque programme l'appelle via le chargeur dynamique. Le durcissement à fort impact est le filtrage d'arguments sur PROT_EXEC : bloquer mprotect(..., PROT_EXEC) au niveau seccomp impose W^X dans le processus, bloquant l'étape canonique de chargement de shellcode (mmap RW → écriture → mprotect RX). À combiner avec le masque PROT_EXEC correspondant sur mmap(). Les charges qui JIT légitimement (Node, Java HotSpot, V8) nécessitent une exemption ; les manipulateurs de données purs (base de données, Nginx sans modules) non.
libseccomp
// Block mprotect(..., PROT_EXEC) to enforce W^X at the seccomp layer
seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(mprotect),
1, SCMP_A2(SCMP_CMP_MASKED_EQ, PROT_EXEC, PROT_EXEC));Exemple strace
$ strace -e mprotect /bin/true 2>&1 | head -5
mprotect(0x7f8c2a1d0000, 16384, PROT_READ) = 0
mprotect(0x7f8c2a212000, 8192, PROT_READ) = 0
mprotect(0x55d4e8e4a000, 4096, PROT_READ) = 0strace décode prot symboliquement (PROT_READ|PROT_WRITE|PROT_EXEC). Une séquence de démarrage normale montre ~10-30 appels mprotect() depuis ld.so pour RELRO et TLS ; le reste est activité applicative. Pour isoler l'activité JIT, filtrer -e trace=mprotect après les premières secondes de vie du processus.
Sécurité & observabilité
mprotect() est la primitive post-exploitation qui transforme un tampon inscriptible en exécutable — presque chaque loader de shellcode sous Linux se termine par mprotect(PROT_READ|PROT_EXEC). Le tracepoint eBPF sys_enter_mprotect avec prot=PROT_EXEC est un flux à fort signal ; le coupler à l'empreinte du binaire pour blanchir les JIT connus. Les écritures RELRO (linker rendant le GOT en lecture seule au démarrage) produisent des appels mprotect() pendant l'init ; bénins et prévisibles. /proc/<pid>/maps affiche les permissions actuelles — comparer les snapshots avant/après une activité suspecte révèle les régions modifiées.
Erreurs
- EACCES
- Protection demandée incompatible avec la projection (par ex. PROT_WRITE sur un fichier ouvert O_RDONLY en MAP_SHARED).
- EINVAL
- addr non aligné page, plage hors des projections existantes, ou prot invalide.
- ENOMEM
- Allocation noyau interne échouée (rare ; généralement vm.max_map_count épuisé parce que l'appel découperait un VMA).
Drapeaux
- PROT_READ
- 0x1
- Pages lisibles.
- PROT_WRITE
- 0x2
- Pages inscriptibles.
- PROT_EXEC
- 0x4
- Pages exécutables. Le drapeau le plus critique côté sécurité — voir W^X.
- PROT_NONE
- 0x0
- Aucun accès. SIGSEGV sur toute référence. Utile pour les pages de garde.
- PROT_GROWSDOWN
- 0x01000000
- —
- PROT_GROWSUP
- 0x02000000
- —