Container security · syscall filtering
seccomp & filtrage de syscalls dans les conteneurs
seccomp-BPF est le mécanisme noyau qui permet à un processus de restreindre l'ensemble des appels système qu'il (et ses enfants) peut effectuer. C'est la colonne vertébrale de tout runtime de conteneur moderne et une atténuation peu coûteuse et durable contre des classes entières de mouvements post-exploitation.
Qu'est-ce que seccomp
Un filtre seccomp est un petit programme BPF classique attaché à un processus. À chaque appel système, le noyau exécute le filtre avec le numéro d'appel, l'architecture et les arguments en entrée ; le filtre renvoie une action — autoriser, renvoyer une erreur, tuer le thread, ou notifier un superviseur userspace. Une fois installé (via prctl(PR_SET_SECCOMP) ou seccomp()), le filtre s'applique pour la durée de vie du processus et de tous ses descendants ; il ne peut être que durci, jamais relâché.
Actions de filtrage
SECCOMP_RET_ALLOW
Permet l'appel système normalement. Comportement par défaut pour les appels non explicitement listés.
SECCOMP_RET_ERRNO
Retourne -1 avec errno positionné à la valeur indiquée, sans entrer dans l'appel. Utile pour refuser élégamment une fonctionnalité optionnelle (par ex. mlock pour un conteneur qui ne devrait pas verrouiller de pages).
SECCOMP_RET_TRAP
Délivre SIGSYS au thread appelant. Le gestionnaire peut inspecter siginfo_t->si_syscall pour journaliser et décider.
SECCOMP_RET_KILL_PROCESS
Tue immédiatement tout le groupe de threads. Réglage le plus fort ; préféré en production où un appel refusé indique toujours une compromission.
SECCOMP_RET_LOG / NOTIFY
RET_LOG enregistre l'appel dans l'audit sans bloquer. RET_USER_NOTIF (Linux 5.0+) bloque l'appel et le transmet à un superviseur userspace qui peut l'inspecter et l'émuler — utilisé par les bacs à sable type gVisor.
Profil par défaut Docker / Podman
Docker, Podman et les principaux runtimes Kubernetes livrent un profil seccomp par défaut qui autorise environ 300 appels et bloque le reste. Les blocages par défaut sont essentiellement des choses dont une charge n'a jamais légitimement besoin : manipulation de modules noyau, ptrace avancé, bpf() brut, création d'espaces de noms, administration bas niveau, et variantes ABI obsolètes ou compat.
Bloqué par le profil par défaut (4)
Intégration Kubernetes
Kubernetes expose seccomp via le champ securityContext.seccompProfile sur les Pods et conteneurs (GA en 1.19). RuntimeDefault délègue au profil par défaut du kubelet (le même que Docker / containerd). Localhost pointe vers un profil JSON personnalisé sur le nœud. Unconfined désactive complètement seccomp ; ne jamais utiliser en production.
# Pod-level seccomp via securityContext (Kubernetes 1.19+)
apiVersion: v1
kind: Pod
spec:
securityContext:
seccompProfile:
type: RuntimeDefault # use kubelet's default (Docker-like) profile
containers:
- name: app
image: my-app:latest
securityContext:
seccompProfile:
type: Localhost
localhostProfile: my-profiles/app.jsonÉcrire un filtre personnalisé
Refuser par défaut avec une petite liste d'autorisations est le motif le plus solide. Le helper libseccomp le rend lisible ; un BPF équivalent peut être généré pour intégration directe :
#include <seccomp.h>
int harden(void) {
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_ERRNO(EPERM));
/* allow only what we need */
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(close), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
/* W^X: deny PROT_EXEC anonymous mmap */
seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(mmap),
1, SCMP_A2(SCMP_CMP_MASKED_EQ, PROT_EXEC, PROT_EXEC));
return seccomp_load(ctx);
}Toujours tester contre la vraie charge : un appel manquant produit un EPERM difficile à diagnostiquer quelque part en profondeur. Des outils comme docker-slim et falco peuvent extraire l'ensemble utilisé à l'exécution.
Syscalls à filtrer avec soin
- mmap(2) Projette un fichier ou de la mémoire anonyme dans l'espace d'adressage du processus.
- execve(2) Remplace l'image du processus courant par un nouveau programme.
- clone(2) Crée un nouveau processus qui peut partager sélectivement mémoire, descripteurs, espaces de noms et autres ressources avec son parent.
- ptrace(2) Observer et contrôler un autre processus — la primitive noyau derrière gdb, strace et l'injection malveillante.
- ioctl(2) Appel système de contrôle de périphérique fourre-tout : exécute une opération définie par le pilote sur un descripteur.