| seccomp(2) | System Calls Manual | seccomp(2) |
seccomp - Agir sur l'état de calcul sécurisé (Secure Computing State) du processus
Bibliothèque C standard (libc, -lc)
#include <linux/seccomp.h> /* Définition des constantes SECCOMP_* */ #include <linux/filter.h> /* Définition de struct sock_fprog */ #include <linux/audit.h> /* Définition des constantes AUDIT_* */ #include <linux/signal.h> /* Définition des constantes SIG* */ #include <sys/ptrace.h> /* Définition des constantes PTRACE_* */ #include <sys/syscall.h> /* Définition des constantes SYS_* */ #include <unistd.h>
int syscall(SYS_seccomp, unsigned int opération, unsigned int flags,
void *args);
Remarque : la glibc ne fournit pas d'enveloppe pour seccomp(), imposant l'utilisation de syscall(2).
L'appel système seccomp() agit sur l'état de calcul sécurisé (seccomp) du processus appelant.
Actuellement, Linux gère les valeurs d'opération suivantes :
prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
prctl(PR_SET_NO_NEW_PRIVS, 1);
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, args);
struct seccomp_notif_sizes
__u16 seccomp_notif; /* Taille de la structure de notification */
__u16 seccomp_notif_resp; /* Taille de la structure de réponse */
__u16 seccomp_data; /* Taille de 'struct seccomp_data' */
};
Lors de l'ajout d'un filtre à l'aide de SECCOMP_SET_MODE_FILTER, args pointe vers un programme de filtrage :
struct sock_fprog {
unsigned short len; /* Nombre d'instructions BPF */
struct sock_filter *filter; /* Pointeur vers le tableau
d'instructions BPF */
};
Chaque programme doit contenir une ou plusieurs instructions BPF :
struct sock_filter { /* Filter block */
__u16 code; /* Code du filtre réel */
__u8 jt; /* Jump true (sauter le vrai) */
__u8 jf; /* Jump false (sauter le faux) */
__u32 k; /* Champ générique multiusages */
};
Lors de l'exécution des instructions, le programme BPF agit sur les informations de l'appel système qui sont rendues disponibles (c'est-à-dire qu'il utilise le mode d'adressage BPF_ABS) en tant que tampon (en lecture seule) ayant la forme suivante :
struct seccomp_data {
int nr; /* Numéro de l'appel système */
__u32 arch; /* Valeur AUDIT_ARCH_*
(voir <linux/audit.h>) */
__u64 instruction_pointer; /* pointeur vers l'instruction du processeur */
__u64 args[6]; /* Jusqu'à 6 paramètres de l'appel système */
};
Comme la numérotation des appels système varie entre les architectures et comme certaines (comme x86-64) autorisent du code de l'espace utilisateur à utiliser les conventions de l'appelant d'autres architectures (et comme cette convention peut varier pendant la vie d'un processus qui utilise execve(2) pour exécuter des binaires qui utilisent différentes conventions), il est généralement nécessaire de vérifier la valeur du champ arch.
Il est fortement recommandé d'utiliser une approche par liste d'autorisations autant que possible, parce qu'une telle approche est plus robuste et plus simple. Une liste d'interdictions devra être mise à jour à chaque fois qu'un appel système dangereux sera ajouté (ou un attribut ou une option si elles font partie de la liste des interdictions) et il est souvent possible de modifier la représentation d'une valeur sans changer sa signification, conduisant à contourner la liste d'interdictions. Voir aussi Mises en garde ci-dessous.
Le champ arch n'est pas unique pour toutes les conventions d'appelant. Les ABI x86-64 et x32 utilisent AUDIT_ARCH_X86_64 en tant que arch et elles fonctionnent sur les mêmes processeurs. Au contraire, le masque __X32_SYSCALL_BIT est utilisé sur le numéro d'appel système pour parler indépendamment aux deux ABI.
Cela veut dire qu'une politique peut soit interdire tous les appels système avec __X32_SYSCALL_BIT, soit elle doit les reconnaître avec le positionnement ou pas de __X32_SYSCALL_BIT. Une liste des appels système à interdire qui s'appuie sur nr et qui ne contient pas de valeurs nr où __X32_SYSCALL_BIT est positionné peut être contournée par un programme malveillant qui positionne __X32_SYSCALL_BIT.
En outre, les noyaux précédant Linux 5.4 autorisaient à tort nr dans les intervalles 512–547 ainsi que les appels système non x32 correspondants reliés (opération OU) avec __X32_SYSCALL_BIT. Par exemple, nr == 521 et nr == (101 | __X32_SYSCALL_BIT) créeraient des appels ptrace(2) avec une sémantique potentiellement confondue entre x32 et x86_64 dans le noyau. Les politiques prévues pour fonctionner sur des noyaux antérieurs à Linux 5.4 doivent garantir qu'elles interdisent ou qu'elles gèrent correctement ces appels système. Sur Linux 5.4 et plus récents, de tels appels système échoueront avec une erreur ENOSYS sans rien faire.
Le champ instruction_pointer fournit l'adresse de l'instruction en langage machine qui a effectué l'appel système. Cela pourrait être utile avec /proc/pid/maps pour effectuer des vérifications à partir de la région (projection) du programme qui a fait l'appel système (il est probablement raisonnable de verrouiller les appels système mmap(2) et mprotect(2) pour empêcher le programme de contourner de telles vérifications).
Lors de la vérification des valeurs de args, gardez en tête que les paramètres sont souvent tronqués silencieusement avant d'être traités mais après la vérification seccomp. Cela arrive par exemple si l'ABI i386 est utilisée sur un noyau x86-64 : bien que le noyau n'ira normalement pas regarder au-delà des 32 bits les plus faibles des paramètres, les valeurs des registres 64 bits complets seront présentes dans les données de seccomp. Un exemple moins surprenant est que si l'ABI x86-64 est utilisée pour effectuer un appel système prenant un paramètre de type int, la moitié du registre du paramètre la plus significative est ignorée par l'appel système mais visible dans les données de seccomp.
Un filtre seccomp renvoie une valeur 32 bits en deux parties : la plus significative, de 16 bits (correspondant au masque défini par la constante SECCOMP_RET_ACTION_FULL), contient une des valeurs « action » listée ci-dessous ; la moins significative, de 16 bits (définie par la constante SECCOMP_RET_DATA), contient des « data » à associer à ce code de retour.
Si plusieurs filtres existent, ils sont tous exécutés dans l'ordre inverse de leur apparition dans l'arbre des filtres – si bien que le filtre le plus récemment installé est exécuté en premier) (remarquez que tous les filtres seront appelés même si l'un des premiers appelés renvoie SECCOMP_RET_KILL, cela pour simplifier le code du noyau et pour fournir une petite accélération d’exécution d’ensembles de filtres en évitant la vérification de ce cas rare). La valeur renvoyée de l'évaluation d'un appel système donné est la première valeur vue de l'action de plus haute priorité (ainsi que ses données associées) renvoyée par l'exécution de tous les filtres.
Dans l'ordre décroissant de priorité, les valeurs d'action qui peuvent être renvoyées par un filtre seccomp sont :
Si on indique un code d'action différent de ceux ci-dessus, l'action de filtre est traitée soit comme un SECCOMP_RET_KILL_PROCESS (depuis Linux 4.14), soit comme un SECCOMP_RET_KILL_THREAD (dans Linux 4.13 et antérieurs).
Les fichiers du répertoire /proc/sys/kernel/seccomp offrent des informations et des configurations seccomp supplémentaires :
Depuis Linux 4.14, le noyau offre la possibilité d'enregistrer les actions renvoyées par des filtres seccomp dans le compte-rendu d'audit. Le noyau prend la décision d'enregistrer une action à partir du type d'action, de sa présence dans le fichier actions_logged et de l'activation de l'audit du noyau (par exemple avec l'option d'amorçage du noyau audit=1). Les règles sont les suivantes :
En cas de succès, seccomp() renvoie 0. En cas d'erreur, si SECCOMP_FILTER_FLAG_TSYNC a été utilisé, le code de retour est l'identifiant du thread à l'origine de l'échec de la synchronisation (cet identifiant est un identifiant de thread du noyau du type renvoyé par clone(2) et gettid(2)). Si une autre erreur arrive, -1 est renvoyé et errno est positionné pour indiquer l'erreur.
seccomp() peut échouer pour les raisons suivantes :
L'appel système seccomp() est apparu pour la première fois dans Linux 3.17.
L'appel système seccomp() est une extension Linux non standard.
Au lieu de coder à la main des filtres seccomp comme démontré dans l'exemple ci-dessous, vous pourriez préférer utiliser la bibliothèque libseccomp qui fournit une interface de génération de filtres seccomp.
Le champ Seccomp du fichier /proc/pid/status offre une méthode de visualisation du mode seccomp du processus ; voir proc(5).
seccomp() fournit un sur-ensemble de fonctionnalités de l'opération PR_SET_SECCOMP de prctl(2) (qui ne prend pas en charge les flags).
Depuis Linux 4.4, l'opération PTRACE_SECCOMP_GET_FILTER de ptrace(2) peut être utilisée pour obtenir les filtres seccomp d'un processus.
La gestion d'architecture pour le filtrage de BPF seccomp est disponible sur les architectures suivantes :
Il y a beaucoup de subtilités à prendre en compte lorsqu'on applique des filtres seccomp à un programme, notamment :
La conséquence des points ci-dessus est qu'il pourrait être nécessaire de filtrer un appel système autre que celui prévu. Plusieurs pages de manuel de la section 2 donnent des détails utiles sur les différences entre les fonctions enveloppe et les appels système sous-jacents dans les sous-sections intitulées Différences entre le noyau et la bibliothèque C.
En outre, remarquez que l'application de filtres seccomp risque même de provoquer des bogues dans une application, quand les filtres provoquent des échecs inattendus d'opérations légitimes que l'application a besoin d'effectuer. De tels bogues pourraient ne pas être facilement identifiés lors d'un test des filtres seccomp s'ils se produisent à des endroits du code rarement utilisés.
Remarquez que les détails BPF suivants sont spécifiques aux filtres seccomp :
Le programme ci-dessous accepte quatre paramètres ou plus. Les trois premiers sont un numéro d'appel système, un identifiant numérique d'architecture et un numéro d'erreur. Le programme utilise ces valeurs pour construire un filtre BPF utilisé lors de l'exécution pour effectuer les vérifications suivantes :
Les autres paramètres de la ligne de commande indiquent le chemin et les paramètres supplémentaires d'un programme que notre exemple doit essayer d'exécuter en utilisant execv(3) (une fonction de bibliothèque qui utilise l'appel système execve(2)). Certains exemples d’exécution du programme sont présentés ci-dessous.
Tout d'abord, on affiche l'architecture sur laquelle on est (x86-64) puis on construit une fonction d’interpréteur qui cherche les numéros d'appels système sur cette architecture :
$ uname -m
x86_64
$ syscall_nr() { cat /usr/src/linux/arch/x86/syscalls/syscall_64.tbl | \ awk '$2 != "x32" && $3 == "'$1'" { print $1 }' }
Quand le filtre BPF rejette un appel système (cas n° 2 ci-dessus), il fait échouer l'appel système avec le numéro d'erreur indiqué sur la ligne de commande. Dans les exemples présentés ici, nous utiliserons le numéro d'erreur 99 :
$ errno 99 EADDRNOTAVAIL 99 Ne peut pas affecter l'adresse demandée
Dans l'exemple suivant, on essaie d'exécuter la commande whoami(1), mais le filtre BPF rejette l'appel système execve(2), donc la commande n'est même pas exécutée :
$ syscall_nr execve
59
$ ./a.out
Utilisation : ./a.out <syscall_nr> <arch> <errno> <prog> [<args>]
Astuce pour <arch> : AUDIT_ARCH_I386: 0x40000003
AUDIT_ARCH_X86_64 : 0xC000003E
$ ./a.out 59 0xC000003E 99 /bin/whoami
execv : Ne peut pas affecter l'adresse demandée
Dans le prochain exemple, le filtre BPF rejette l'appel système write(2) pour que, même si elle a pu démarrer, la commande whoami(1) ne puisse pas écrire de sortie :
$ syscall_nr write 1 $ ./a.out 1 0xC000003E 99 /bin/whoami
Dans le dernier exemple, le filtre BPF rejette un appel système qui n'est pas utilisé par la commande whoami(1), elle peut donc s'exécuter et produire une sortie :
$ syscall_nr preadv 295 $ ./a.out 295 0xC000003E 99 /bin/whoami cecilia
#include <linux/audit.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <unistd.h>
#define X32_SYSCALL_BIT 0x40000000
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
static int
install_filter(int syscall_nr, unsigned int t_arch, int f_errno)
{
unsigned int upper_nr_limit = 0xffffffff;
/* On suppose que AUDIT_ARCH_X86_64 renvoie à l'ABI x86-64 normale
(dans l'ABI x32, tous les appels système ont le bit 30 positionné
dans le champ 'nr', donc les numéros sont >= X32_SYSCALL_BIT). */
if (t_arch == AUDIT_ARCH_X86_64)
upper_nr_limit = X32_SYSCALL_BIT - 1;
struct sock_filter filter[] = {
/* [0] Charger l'architecture depuis le tampon 'seccomp_data'
dans l'accumulateur. */
BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
(offsetof(struct seccomp_data, arch))),
/* [1] Revenir en arrière de 5 instructions si l'architecture
ne correspond pas à 't_arch'. */
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, t_arch, 0, 5),
/* [2] Charger le numéro d'appel système depuis le tampon
'seccomp_data' dans l'accumulateur. */
BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
(offsetof(struct seccomp_data, nr))),
/* [3] Vérifier l'ABI - nécessaire seulement pour x86-64 si on
utilise une liste d'interdictions. Utiliser BPF_JGT au lieu de
vérifier par rapport au masque de bits pour ne pas devoir
recharger le numéro d'appel système. */
BPF_JUMP(BPF_JMP | BPF_JGT | BPF_K, upper_nr_limit, 3, 0),
/* [4] Reculer d'une instruction si le numéro d'appel système
ne correspond pas à 'syscall_nr'. */
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, syscall_nr, 0, 1),
/* [5] Architecture et appel système correspondants : ne pas
exécuter l'appel système et renvoyer 'f_errno' dans
'errno'. */
BPF_STMT(BPF_RET | BPF_K,
SECCOMP_RET_ERRNO | (f_errno & SECCOMP_RET_DATA)),
/* [6] Cible du numéro d’appel système inadéquate :
autoriser d'autres appels système. */
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
/* [7] Cible de l’architecture inadéquate : tuer le processus. */
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
};
struct sock_fprog prog = {
.len = ARRAY_SIZE(filter),
.filter = filter,
};
if (syscall(SYS_seccomp, SECCOMP_SET_MODE_FILTER, 0, &prog)) {
perror("seccomp");
return 1;
}
return 0;
}
int
main(int argc, char *argv[])
{
if (argc < 5) {
fprintf(stderr, "Utilisation : "
"%s <syscall_nr> <arch> <errno> <prog> [<args>]\n"
"Astuce pour <arch> : AUDIT_ARCH_I386: 0x%X\n"
" AUDIT_ARCH_X86_64: 0x%X\n"
"\n", argv[0], AUDIT_ARCH_I386, AUDIT_ARCH_X86_64);
exit(EXIT_FAILURE);
}
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
perror("prctl");
exit(EXIT_FAILURE);
}
if (install_filter(strtol(argv[1], NULL, 0),
strtoul(argv[2], NULL, 0),
strtol(argv[3], NULL, 0)))
exit(EXIT_FAILURE);
execv(argv[4], &argv[4]);
perror("execv");
exit(EXIT_FAILURE);
}
bpfc(1), strace(1), bpf(2), prctl(2), ptrace(2), seccomp_unotify(2), sigaction(2), proc(5), signal(7), socket(7)
Plusieurs pages de la bibliothèque libseccomp, dont : scmp_sys_resolver(1), seccomp_export_bpf(3), seccomp_init(3), seccomp_load(3) et seccomp_rule_add(3).
Les fichiers Documentation/networking/filter.txt et Documentation/userspace-api/seccomp_filter.rst des sources du noyau (ou Documentation/prctl/seccomp_filter.txt avant Linux 4.13).
McCanne, S. et Jacobson, V. (1992) The BSD Packet Filter : une nouvelle architecture de captation de paquets au niveau utilisateur, colloque de la conférence USENIX à l'hiver 1993 http://www.tcpdump.org/papers/bpf-usenix93.pdf
La traduction française de cette page de manuel a été créée par Christophe Blaess <https://www.blaess.fr/christophe/>, Stéphan Rafin <stephan.rafin@laposte.net>, Thierry Vignaud <tvignaud@mandriva.com>, François Micaux, Alain Portal <aportal@univ-montp2.fr>, Jean-Philippe Guérard <fevrier@tigreraye.org>, Jean-Luc Coulon (f5ibh) <jean-luc.coulon@wanadoo.fr>, Julien Cristau <jcristau@debian.org>, Thomas Huriaux <thomas.huriaux@gmail.com>, Nicolas François <nicolas.francois@centraliens.net>, Florentin Duneau <fduneau@gmail.com>, Simon Paillard <simon.paillard@resel.enst-bretagne.fr>, Denis Barbier <barbier@debian.org>, David Prévot <david@tilapin.org>, Jean-Philippe MENGUAL <jpmengual@debian.org> et Jean-Pierre Giraud <jean-pierregiraud@neuf.fr>
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 à debian-l10n-french@lists.debian.org.
| 5 février 2023 | Pages du manuel de Linux 6.03 |