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 :
- SECCOMP_SET_MODE_STRICT
- Les seuls appels système que le thread appelant est autorisé
à faire sont read(2), write(2), _exit(2) (mais
pas exit_group(2)) et sigreturn(2). Les autres appels
système aboutissent à la fin du thread appelant ou à
la fin du processus complet avec le signal SIGKILL quand il n'y a
qu'un seul thread. Le mode de calcul sécurisé strict est
utile pour les applications de traitement de nombres qui peuvent avoir
besoin d'exécuter un code à octets non fiable, obtenu
peut-être en lisant un tube ou un socket.
- Remarquez que si le thread appelant ne peut plus appeler
sigprocmask(2), il peut utiliser sigreturn(2) pour bloquer
tous les signaux, sauf ceux provenant de SIGKILL et de
SIGSTOP. Cela veut dire que alarm(2) (par exemple) n'est pas
suffisant pour restreindre la durée d'exécution d'un
processus. Pour terminer de manière fiable un processus,
SIGKILL doit être utilisé. On peut le faire en
utilisant timer_create(2) avec SIGEV_SIGNAL et
sigev_signo positionné à SIGKILL ou en
utilisant setrlimit(2) pour positionner la limite ferme de
RLIMIT_CPU.
- Cette fonctionnalité n'est disponible que si le noyau a
été construit avec l'option CONFIG_SECCOMP
activée.
- La valeur de flags doit être de 0 et args doit
être NULL.
- Cette opération est fonctionnellement identique à
l'appel :
-
prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
- SECCOMP_SET_MODE_FILTER
- Les appels système autorisés sont définis par un
pointeur vers un filtre Berkeley Packet (BPF) fourni à l'aide de
args. Ce paramètre est un pointeur vers une
struct sock_fprog ; il peut être conçu
pour filtrer des appels système de votre choix ainsi que des
paramètres d'appel système. Si le filtre n'est pas valable,
seccomp() échoue en renvoyant EINVAL dans
errno.
- Si fork(2) ou clone(2) est autorisé par le filtre,
les processus enfant seront contraints par les mêmes filtres
d'appel système que leur parent. Si execve(2) est
autorisé, les filtres existants seront préservés lors
d'un appel à execve(2).
- Pour utiliser l'opération SECCOMP_SET_MODE_FILTER, soit le
thread appelant doit avoir la capacité CAP_SYS_ADMIN dans
son espace de noms utilisateur, soit il doit avoir déjà le
bit no_new_privs défini. Si ce bit n'a pas
déjà été positionné par un ascendant du
thread, le thread doit effectuer l'appel suivant :
-
prctl(PR_SET_NO_NEW_PRIVS, 1);
- Sinon, l'opération SECCOMP_SET_MODE_FILTER échoue et
renvoie EACCES dans errno. Cette exigence garantit qu'un
processus non privilégié ne peut pas appliquer un filtre
malveillant et appeler un programme set-user-ID ou avec d'autres
privilèges en utilisant execve(2), compromettant ainsi le
programme (un tel filtre malveillant pourrait, par exemple, conduire
setuid(2) à essayer de définir les identifiants
utilisateur de l'appelant à des valeurs non nulles pour renvoyer
plutôt 0 sans faire d'appel système. Ainsi, le
programme pourrait être bidouillé pour garder les
privilèges du super-utilisateur à des moments où il
est possible de l'influencer pour faire des choses dangereuses vu qu'il
n'a pas abandonné ses privilèges).
- Si prctl(2) ou seccomp() est autorisé par le filtre
rattaché, d'autres filtres peuvent être ajoutés. Cela
augmentera le temps d'évaluation mais permet d'autres
réductions de la surface d'attaque lors de l'exécution d'un
thread.
- L'opération SECCOMP_SET_MODE_FILTER n'est disponible que si
le noyau a été configuré avec
CONFIG_SECCOMP_FILTER.
- Quand flags vaut 0, cette opération est
fonctionnellement identique à l'appel :
-
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, args);
- Les paramètres reconnus de flags sont :
- SECCOMP_FILTER_FLAG_LOG
(depuis Linux 4.14)
- Toutes les actions de renvoi des filtres, sauf SECCOMP_RET_ALLOW,
doivent être journalisées. Un administrateur peut
outrepasser cet attribut de filtre en empêchant des actions
spécifiques d'être journalisées à l'aide du
fichier /proc/sys/kernel/seccomp/actions_logged.
- SECCOMP_FILTER_FLAG_NEW_LISTENER
(depuis Linux 5.0)
- Après une installation réussie du programme de filtrage,
renvoyer un nouveau descripteur de fichier de notification pour l'espace
utilisateur. (L'attribut close-on-exec est défini pour le
descripteur de fichier.) Quand le filtre renvoie
SECCOMP_RET_USER_NOTIF, une notification sera envoyée
à ce descripteur de fichier.
- Pour un thread, au maximum un seul filtre de seccomp utilisant l'attribut
SECCOMP_FILTER_FLAG_NEW_LISTENER peut être
installé.
- Consultez seccomp_unotify(2) pour plus de détails.
- SECCOMP_FILTER_FLAG_SPEC_ALLOW
(depuis Linux 4.17)
- Désactiver la mitigation Speculative Store Bypass.
- SECCOMP_FILTER_FLAG_TSYNC
- Lors de l'ajout d'un filtre, synchroniser tous les autres threads du
processus appelant avec la même arborescence de filtres seccomp.
Une « arborescence de filtres » est une liste
ordonnée de filtres rattachée à un thread (le
rattachement de filtres identiques dans des appels seccomp()
distincts génère différents filtres depuis cette
perspective).
- Si aucun thread ne peut pas se synchroniser avec l'arborescence de
filtres, l'appel ne rattachera pas le nouveau filtre seccomp et
échouera en renvoyant le premier identifiant de thread qui n'a pas
pu se synchroniser. La synchronisation échouera si un autre thread
du même processus est en SECCOMP_MODE_STRICT ou si des
nouveaux filtres seccomp lui sont rattachés en propre, en
décalage par rapport à l'arborescence de filtres du thread
appelant.
- SECCOMP_GET_ACTION_AVAIL
(depuis Linux 4.14)
- Tester pour savoir si une action est prise en charge par le noyau. Cette
opération peut aider à confirmer que le noyau connaît
l'action de renvoi d'un filtre récemment ajouté puisque le
noyau traite toutes les actions inconnues comme des
SECCOMP_RET_KILL_PROCESS.
- La valeur de flags doit être de 0 et args doit
être un pointeur vers une action de renvoi de filtre 32 bits
non signé.
- SECCOMP_GET_NOTIF_SIZES
(depuis Linux 5.O)
- Obtenir la taille des structures de notification de l'espace utilisateur
de seccomp. Comme ces structures peuvent évoluer et croître
avec le temps, cette commande peut être utilisée pour
déterminer quelle quantité de mémoire allouer pour
envoyer et recevoir des notifications.
- La valeur de flags doit être de 0 et args doit
être un pointeur vers un struct seccomp_notif_sizes de la
forme suivante :
-
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' */
};
- Consultez seccomp_unotify(2) pour plus de détails.
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 :
- SECCOMP_RET_KILL_PROCESS
(depuis Linux 4.14)
- Cette valeur aboutit à la fin immédiate du processus, avec
un vidage mémoire. L'appel système n'est pas
exécuté. Contrairement à
SECCOMP_RET_KILL_THREAD ci-dessous, tous les threads du groupe de
threads sont terminés (pour un point sur les groupes de thread,
voir la description de l'attribut CLONE_THREAD de
clone(2)).
- Le processus se termine parce que il a été tué
par un signal SIGSYS. Même si un gestionnaire de signal a
été enregistré pour SIGSYS, le gestionnaire
sera ignoré dans ce cas et le processus se termine toujours. Le
processus parent qui attend ce processus (en utilisant waitpid(2)
ou équivalent) reçoit wstatus qui indique que son
enfant s'est terminé suite à un signal SIGSYS.
- SECCOMP_RET_KILL_THREAD
(ou SECCOMP_RET_KILL)
- Cette valeur provoque la fin immédiate du thread qui a
effectué l'appel système. L'appel système n'est pas
exécuté. Les autres threads du même groupe de threads
continueront à s'exécuter.
- Le thread s'est terminé comme tué par un signal
SIGSYS. Voir SECCOMP_RET_KILL_PROCESS ci-dessus.
- Avant Linux 4.11, tout processus qui se terminait de cette
manière ne générait pas de vidage mémoire
(bien que SIGSYS soit documenté dans signal(7) pour
avoir comme action par défaut celle de terminer avec un vidage
mémoire). Depuis Linux 4.11, un processus d'un seul thread
créera un vidage mémoire s'il se termine dans ce cadre.
- Avec l'apparition de SECCOMP_RET_KILL_PROCESS dans
Linux 4.14, SECCOMP_RET_KILL_THREAD a été
ajouté comme synonyme de SECCOMP_RET_KILL, afin de
distinguer plus clairement les deux actions.
- Remarque : l'utilisation de SECCOMP_RET_KILL_THREAD
pour tuer un thread unique d'un processus de plusieurs threads va
probablement mettre le processus dans un état incohérent et
corrompre pour toujours son état.
- SECCOMP_RET_TRAP
- Cette valeur fait envoyer par le noyau un signal SIGSYS
adressé au thread déclencheur (l'appel système n'est
pas exécuté). Divers champs seront positionnés dans
la structure siginfo_t (voir sigaction(2)) associée
au signal :
- si_signo contiendra SIGSYS.
- si_call_addr affichera l'adresse de l'instruction de l'appel
système.
- si_syscall et si_arch indiqueront l'appel système qui
a été tenté.
- si_code contiendra SYS_SECCOMP.
- si_errno contiendra la partie SECCOMP_RET_DATA du code de
retour du filtre.
- Le compteur du programme sera arrêté comme si l'appel
système a été fait (c'est-à-dire que le
compteur du programme ne pointera pas vers l'instruction de l'appel
système). Le registre du code de retour contiendra une valeur
dépendante de l'architecture ; en cas de relance de
l'exécution, positionnez-la sur quelque chose adapté
à l'appel système (la dépendance de l'architecture
provient du fait que son remplacement par ENOSYS écraserait
des informations utiles).
- SECCOMP_RET_ERRNO
- Cette valeur fait passer la partie SECCOMP_RET_DATA du code de
retour du filtre à l'espace utilisateur en tant que valeur
errno sans exécuter l'appel système.
- SECCOMP_RET_USER_NOTIF
(depuis Linux 5.0)
- Faire suivre l'appel système à un processus de superviseur
attaché de l'espace utilisateur attaché pour permettre
à ce processus de décider quoi faire de l'appel
système. Si il n'y a pas de superviseur attaché (soit parce
que le filtre n'a pas été installé avec l'attribut
SECCOMP_FILTER_FLAG_NEW_LISTENER ou parce que le descripteur de
fichier était fermé), le filtre renvoie ENOSYS (c'est
similaire à ce qui se produit quand un filtre renvoie
SECCOMP_RET_TRACE et qu'il n'y a pas d'observateur). Consultez
seccomp_unotify(2) pour plus de détails.
- Remarquez que le processus de superviseur ne sera pas notifié si un
autre filtre renvoie une valeur d'action ayant une priorité
supérieure à SECCOMP_RET_USER_NOTIF.
- SECCOMP_RET_TRACE
- Quand cette valeur est renvoyée, le noyau essaiera de notifier
à un observateur basé sur ptrace(2) avant
d'exécuter l'appel système. Si aucun observateur n'est
présent, l'appel système n'est pas exécuté et
renvoie un échec en positionnant errno sur
ENOSYS.
- Un observateur sera notifié s'il demande
PTRACE_O_TRACESECCOMP en utilisant
ptrace(PTRACE_SETOPTIONS). Il sera notifié d'un
PTRACE_EVENT_SECCOMP et la partie SECCOMP_RET_DATA du code
de retour du filtre sera mise à la disposition de l'observateur
à l'aide de PTRACE_GETEVENTMSG.
- L'observateur peut ignorer l'appel système en modifiant le
numéro de l'appel système à -1. Autrement,
l'observateur peut modifier l'appel système demandé en le
passant à un numéro d'appel système valable. Si
l'observateur demande à ignorer l'appel système, ce dernier
renverra la valeur que l'observateur place dans le registre du code de
retour.
- Avant Linux 4.8, la vérification seccomp ne sera pas refaite
après que l'observateur ait reçu une notification (cela
signifie que sur les anciens noyaux, les conteneurs basés sur
seccomp ne doivent pas autoriser l'utilisation de ptrace(2)
– même sur d'autres processus
encapsulés – sans une prudence
extrême ; les ptracers peuvent utiliser ce mécanisme
pour sortir d'un conteneur seccomp).
- Remarquez que le processus d'un observateur ne sera pas notifié si
un autre filtre renvoie une valeur d'action ayant une priorité
supérieure à SECCOMP_RET_TRACE.
- SECCOMP_RET_LOG
(depuis Linux 4.14)
- Cette valeur fait exécuter l'appel système après
l'enregistrement de l'action de retour du filtre. Un administrateur peut
supplanter la journalisation de cette action à l'aide du fichier
/proc/sys/kernel/seccomp/actions_logged.
- SECCOMP_RET_ALLOW
- Cette valeur provoque l'exécution de l'appel système.
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 :
- actions_avail
(depuis Linux 4.14)
- Une liste ordonnée en lecture seule d'actions de renvoi de filtre
seccomp sous la forme d'une chaîne. L'ordre, de gauche à
droite, est décroissant pour la priorité. La liste
représente l'ensemble des actions de renvoi de filtre seccomp
gérées par le noyau.
- actions_logged
(depuis Linux 4.14)
- Une liste ordonnée en lecture-écriture d'actions de renvoi
de filtre seccomp autorisées pour la journalisation. Les
écritures dans le fichier n'ont pas besoin d'être
ordonnées, mais les lectures se feront dans le même ordre
que pour actions_avail.
- Il est important de remarquer que la valeur de actions_logged
n'empêche pas certaines actions de renvoi de filtre d'être
enregistrées quand le sous-système d'audit est
configuré pour auditer une tâche. Si l'action n'est pas
retrouvée dans le fichier actions_logged, la décision
finale d'auditer l'action de cette tâche revient au
sous-système d'audit pour toutes les actions de renvoi de filtre
autres que SECCOMP_RET_ALLOW.
- La chaîne « allow » n'est pas
acceptée dans le fichier actions_logged car il n'est pas
possible d'enregistrer les actions SECCOMP_RET_ALLOW. Essayer
d'écrire « allow » dans le fichier
échouera avec l'erreur EINVAL.
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 :
- Si l'action est SECCOMP_RET_ALLOW, l'action n'est pas
enregistrée.
- Sinon, si l'action est SECCOMP_RET_KILL_PROCESS ou
SECCOMP_RET_KILL_THREAD et si elle apparaît dans le fichier
actions_logged, l'action est enregistrée.
- Sinon, si le filtre a demandé l'enregistrement (l'attribut
SECCOMP_FILTER_FLAG_LOG) et si elle apparaît dans le fichier
actions_logged, l'action est enregistrée.
- Sinon, si l'audit du noyau est activé et si le processus doit
être audité (autrace(8)), l'action est
enregistrée.
- Sinon, l'action n'est pas enregistrée.
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 :
- EACCES
- L'appelant n'avait pas la capacité CAP_SYS_ADMIN dans son
espace de noms utilisateur ou n'avait pas positionné
no_new_privs avant d'utiliser SECCOMP_SET_MODE_FILTER.
- EBUSY
- Pendant l'installation d'un nouveau filtre, l'attribut
SECCOMP_FILTER_FLAG_NEW_LISTENER a été
indiqué, mais un filtre précédent a
déjà été installé avec cet
attribut.
- EFAULT
- args n'était pas une adresse valable.
- EINVAL
- L'opération est inconnue ou n'est pas prise en charge par
cette version ou cette configuration du noyau.
- EINVAL
- Les flags spécifiés ne sont pas valables pour
l'opération donnée.
- EINVAL
- L'opération comprenait BPF_ABS, mais la position
indiquée n'était pas alignée sur une limite
32 bits ou elle dépassait
sizeof(struct seccomp_data).
- EINVAL
- Un mode de calcul sécurisé a déjà
été défini et l'opération
diffère du paramétrage existant.
- EINVAL
- opération indiquait SECCOMP_SET_MODE_FILTER mais le
programme de filtre vers lequel pointait args n'était pas
valable ou sa longueur était de zéro ou dépassait
BPF_MAXINSNS instructions (4096).
- ENOMEM
- Plus assez de mémoire.
- ENOMEM
- La taille totale de tous les programmes de filtre rattachés au
thread appelant dépasserait MAX_INSNS_PER_PATH instructions
(32768). Remarquez qu'afin de calculer cette limite, chaque programme de
filtre déjà existant intègre une
pénalité de dépassement de 4
instructions.
- EOPNOTSUPP
- opération indiquait SECCOMP_GET_ACTION_AVAIL mais le
noyau ne gère pas l'action de renvoi de filtre indiquée par
args.
- ESRCH
- Un autre thread a provoqué un échec pendant la
synchronisation, mais son identifiant n'a pas pu être
déterminé.
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 :
- x86-64, i386, x32 (depuis Linux 3.5)
- ARM (depuis Linux 3.8)
- s390 (depuis Linux 3.8)
- MIPS (depuis Linux 3.16)
- ARM-64 (depuis Linux 3.19)
- PowerPC (depuis Linux 4.3)
- Tile (depuis Linux 4.3)
- PA-RISC (depuis Linux 4.6)
Il y a beaucoup de subtilités à prendre en compte
lorsqu'on applique des filtres seccomp à un programme,
notamment :
- Certains appels système traditionnels ont des
implémentations dans l'espace utilisateur dans le vdso(7) de
nombreuses architectures. Parmi les exemples remarquables, se trouvent
clock_gettime(2), gettimeofday(2) et time(2). Sur de
telles architectures, le filtrage seccomp de ces appels système
sera sans effet (il y a cependant des cas où les
implémentations vdso(7) se rabattent sur le véritable
appel système, alors les filtres seccomp verraient l'appel
système).
- Le filtrage seccomp s'appuie sur les numéros d'appel
système. Cependant, les applications n'appellent
généralement pas directement les appels système, mais
plutôt les fonctions enveloppe de la bibliothèque C
qui appellent à leur tour les appels système. Par
conséquent, vous devez garder en tête ce qui
suit :
- Les enveloppes de la glibc pour certains appels système
traditionnels peuvent utiliser des appels système ayant des noms
différents dans le noyau. Par exemple, la fonction enveloppe
exit(2) utilise en fait l'appel système exit_group(2)
et la fonction fork(2) utilise en réalité les appels
clone(2).
- Le comportement des fonctions enveloppe peut changer en fonction des
architectures, selon la plage d'appels système fournie sur ces
architectures. Autrement dit, la même fonction enveloppe peut
appeler différents appels système selon les
architectures.
- Enfin, le comportement des fonctions enveloppe peut changer selon les
versions de la glibc. Par exemple, dans d'anciennes versions, la fonction
enveloppe de la glibc de open(2) appelait l'appel système du
même nom, mais à partir de la 2.26,
l'implémentation est passée à l'appel
openat(2) sur toutes les architectures.
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 :
- Les modificateurs de taille BPF_H et BPF_B ne sont pas pris
en charge : toutes les opérations doivent charger et stocker
des mots (4 octets) (BPF_W).
- Pour accéder au contenu du tampon seccomp_data, utilisez le
modificateur du mode d'adressage BPF_ABS.
- Le modificateur du mode d'adressage BPF_LEN produit un
opérande de mode immédiatement dont la valeur est la taille
du tampon seccomp_data.
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 :
- Si le programme ne tourne pas sur l'architecture indiquée, le
filtre BPF fait échouer les appels système avec l'erreur
ENOSYS.
- Si le programme essaie d'exécuter l'appel système ayant le
numéro indiqué, le filtre BPF fait échouer l'appel
système en positionnant errno sur le numéro d'erreur
indiqué.
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.