mprotect, pkey_mprotect - Définir la protection d'une partie de la
mémoire
Bibliothèque C standard (
libc,
-lc)
#include <sys/mman.h>
int mprotect(void addr[.len], size_t len, int prot);
#define _GNU_SOURCE /* Consultez feature_test_macros(7) */
#include <sys/mman.h>
int pkey_mprotect(void addr[.len], size_t len, int prot, int pkey);
mprotect() change les protections d'accès pour la (les) page(s) de
mémoire du processus appelant contenant tout ou une partie de
l'intervalle [
addr,
addr+
len-1].
addr doit
être aligné sur une limite de page.
Si le processus appelant essaie d'accéder à la mémoire en
violant la protection, le noyau génère un signal
SIGSEGV
pour ce processus.
prot est une combinaison des attributs d'accès suivants :
PROT_NONE ou le résultat d’une opération OU bit
à bit parmi les autres valeurs de la liste suivante :
- PROT_NONE
- On ne peut pas accéder du tout à la zone de
mémoire.
- PROT_READ
- On peut lire la zone de mémoire.
- PROT_WRITE
- On peut modifier la zone de mémoire.
- PROT_EXEC
- La zone de mémoire peut contenir du code
exécutable.
-
PROT_SEM (depuis Linux 2.5.7)
- La mémoire peut être utilisée pour des
opérations atomiques. Cet attribut a été introduit
dans l'implémentation de futex(2) (afin de garantir la
possibilité d'effectuer des opérations atomiques
exigées par des commandes comme FUTEX_WAIT), mais il n'est
actuellement utilisé sur aucune architecture.
-
PROT_SAO (depuis Linux 2.6.26)
- La mémoire devrait avoir une forte organisation de
son accès. Cette fonctionnalité est spécifique
à l'architecture PowerPC (la version 2.06 de la
spécification de l'architecture ajoute la fonction SAO du
processeur, disponible par exemple sur POWER 7 ou PowerPC A2).
En outre (depuis Linux 2.6.0), il est possible de positionner les attributs
suivants sur
prot :
- PROT_GROWSUP
- Appliquer le mode de protection jusqu'à la fin d'une
projection qui grandit vers le haut (de telles projections sont
créées pour la zone de la pile sur une architecture
— par exemple HP-PARISC — dont la pile a
tendance à s'accroître vers le haut).
- PROT_GROWSDOWN
- Appliquer le mode de protection vers le bas jusqu'au
début d'une projection qui grandit vers le bas (il pourrait s'agir
d'un segment de pile ou d'un segment projeté avec un drapeau
MAP_GROWSDOWN positionné).
Comme
mprotect(),
pkey_mprotect() modifie la protection des pages
indiquées par
addr et
len. Le paramètre
pkey indique la clé de protection (voir
pkeys(7)))
à assigner à la mémoire. La clé de protection doit
être allouée avec
pkey_alloc(2) avant d'être
passée à
pkey_mprotect(). Pour un exemple d'utilisation
de cet appel système, voir
pkeys(7).
mprotect() et
pkey_mprotect() renvoient
0 s'ils
réussissent. En cas d'erreur, ces appels système renvoient
-1 et
errno est défini pour indiquer l'erreur.
- EACCES
- L'accès spécifié n'est pas possible
sur ce type de mémoire. Cela se produit par exemple si vous
utilisez mmap(2) pour représenter un fichier en lecture
seule en mémoire, et puis demandez de marquer cette zone avec
PROT_WRITE.
- EINVAL
-
addr n'est pas un pointeur valable, ou ce n'est pas
un multiple de la taille de page du système.
- EINVAL
- (pkey_mprotect()) pkey n'a pas
été alloué avec pkey_alloc(2)
- EINVAL
-
PROT_GROWSUP et PROT_GROWSDOWN étaient
indiqués tous les deux dans prot.
- EINVAL
- Drapeaux non valables indiqués dans
prot.
- EINVAL
- (Architecture PowerPC) PROT_SAO était
indiqué dans prot, mais la fonctionnalité
matérielle SAO n'est pas disponible.
- ENOMEM
- Impossible d'allouer des structures internes au noyau.
- ENOMEM
- Les adresses dans l'intervalle [addr,
addr+len-1] ne sont pas valables dans l'espace d'adressage
du processus, ou l'intervalle s'étend sur des pages non
projetées (avant Linux 2.4.19, l'erreur EFAULT
était produite à tort dans ce cas).
- ENOMEM
- La modification de la protection d'une zone de la
mémoire ferait dépasser le nombre maximal autorisé de
projections avec des attributs différents (comme la protection en
lecture vs lecture/écriture) (par exemple, positionner une
protection d'une plage PROT_READ au milieu d'une zone
protégée par PROT_READ|PROT_WRITE donnerait trois
projections : deux en lecture/écriture aux
extrémités et une en lecture seule au milieu).
pkey_mprotect() est apparu dans Linux 4.9 ; la bibliothèque
glibc le gère depuis la version 2.27.
mprotect() : POSIX.1-2001, POSIX.1-2008, SVr4. POSIX indique que
le comportement de
mprotect() n'est pas spécifié s'il
s'applique à une zone de mémoire non obtenue à l'aide de
mmap(2).
pkey_mprotect() est une extension Linux non portable.
Sous Linux, il est toujours autorisé d'appeler
mprotect() sur une
adresse de l'espace d'adressage du processus (excepté pour la zone
vsyscall du noyau). En particulier, il peut être utilisé pour
rendre une projection de code existante accessible en écriture.
La différence entre
PROT_EXEC et
PROT_READ dépend de
l'architecture, de la version du noyau et de l'état du processus. Sur
certaines, si
READ_IMPLIES_EXEC est positionné dans les drapeaux
de la personnalité d'un processus (voir
personality(2)), le fait
d'indiquer
PROT_READ ajoutera implicitement
PROT_EXEC.
Sur certaines architectures matérielles (comme i386),
PROT_WRITE
implique
PROT_READ.
POSIX.1 indique qu'une implémentation peut autoriser un accès
autre que celui donné dans
prot, mais doit au minimum autoriser
l'accès en écriture si
PROT_WRITE était
passé, et ne doit autoriser aucun accès si
PROT_NONE
était passé.
Les applications devraient faire attention quand elles mélangent
l'utilisation de
mprotect() et de
pkey_mprotect(). Sur x86,
quand
mprotect() est utilisé avec
prot positionné
sur
PROT_EXEC, une pkey peut être allouée et
positionnée implicitement sur la mémoire par le noyau, mais
uniquement quand la pkey était de
0 précédemment.
Sur les systèmes qui ne gèrent pas les clés de protection
dans le matériel,
pkey_mprotect() peut toujours être
utilisé, mais
pkey doit être positionné sur
-1. Si elle est appelée ainsi, l'opération
pkey_mprotect() est équivalente à
mprotect().
Le programme ci-dessous montre l'utilisation de
mprotect(). Il alloue
quatre pages de mémoire, rend la troisième accessible en lecture
seule, puis exécute une boucle qui se déplace en avançant
dans la région allouée et en modifiant son contenu.
Voici un exemple d'exécution de ce programme :
$ ./a.out
Début de la région : 0x804c000
Reçu SIGSEGV à l'adresse : 0x804e000
#include <malloc.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.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)
{
/* Remarque : appeler printf() à partir d'un gestionnaire de signal
n'est pas sûr (vous ne devriez pas le faire dans des programmes en
production) car printf() n'est pas async-signal-safe ; voir
signal-safety(7). Cependant, nous utilisons printf() ici comme façon
simple de montrer que le gestionnaire a été appelé. */
static void
printf("Reçu SIGSEGV à l'adresse : %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)
handle_error("sigaction");
pagesize = sysconf(_SC_PAGE_SIZE);
if (pagesize == -1)
handle_error("sysconf");
/* Allouer un tampon aligné sur une limite de page ;
la protection initiale est PROT_READ | PROT_WRITE. */
buffer = memalign(pagesize, 4 * pagesize);
if (buffer == NULL)
handle_error("memalign");
printf("Début de la région : %p\n", buffer);
if (mprotect(buffer + pagesize * 2, pagesize,
PROT_READ) == -1)
handle_error("mprotect");
for (char *p = buffer ; ; )
*(p++) = 'a';
printf("Boucle terminée\n"); /* Ne devrait jamais arriver */
exit(EXIT_SUCCESS);
}
mmap(2),
sysconf(3),
pkeys(7)
La traduction française de cette page de manuel a été
créée par Christophe Blaess
<
https://www.blaess.fr/christophe/>, Stéphan Rafin
<
[email protected]>, Thierry Vignaud
<
[email protected]>, François Micaux, Alain Portal
<
[email protected]>, Jean-Philippe Guérard
<
[email protected]>, Jean-Luc Coulon (f5ibh)
<
[email protected]>, Julien Cristau
<
[email protected]>, Thomas Huriaux <
[email protected]>,
Nicolas François <
[email protected]>, Florentin
Duneau <
[email protected]>, Simon Paillard
<
[email protected]>, Denis Barbier
<
[email protected]>, David Prévot <
[email protected]> et
Jean-Philippe MENGUAL <
[email protected]>
Cette traduction est une documentation libre ; veuillez vous reporter
à la
GNU
General Public License version 3 concernant les conditions de copie
et de distribution. Il n'y a aucune RESPONSABILITÉ LÉGALE.
Si vous découvrez un bogue dans la traduction de cette page de manuel,
veuillez envoyer un message à
[email protected]