Introduction

Contexte

Le durcissement des systèmes répond à quatre enjeux différents :

  1. Business : identifier les menaces On formalise un threat model :

    Que cherche-t-on à éviter, par quel biais ?

    Et on conduit une analyse de risque.

  2. Gouvernance : définir une security policy pour réduire les risques

  3. Architecture, design et implémentation

    • Quelles options à notre disposition pour implémenter la police de sécurité sur le système cible ?
    • Quelles sont les adaptations nécessaires en termes de faisabilité, coût et utilisabilité ?
  4. Correction assurantielle

Threat Modeling

Un modèle de risque identifie :

  • biens à protéger ;
  • adversaires probables ;
  • menaces, ce qui serait tenté ;
  • vecteurs d’attaques, la façon dont cela serait tenté ;
  • limites du système et partis pris (en termes de confiance).

Quel modèle de risque en tant qu'opérateur cloud ?

On cherche à protéger les data centers de cyberattaquants (étatiques, individuels, concurrents) ; notamment afin de prévenir une intrusion destinée à porter atteinte à l’intégrité, la disponibilité et la confidentialité des données. Une telle compromission peut advenir si des systèmes de contrôle et d’administration sont exposés et pas assez sécurisés. Ou-bien, si un prestataire de service faillit à sa conformité.

Exemples de polices de sécurité

Multilevel security (MLS)

Le Multilevel Security est un schéma de sécurité qui applique le modèle d’accès obligatoire Bell-LaPadula. Cette politique repose sur l’étiquetage des sujets (processus, utilisateurs) et des objets (fichiers, périphériques) avec des niveaux de sécurité.

  • Structure des étiquettes Chaque niveau de sécurité est composé d’une sensibilité (hiérarchique, par exemple non classifié, confidentiel, secret, top secret) et, éventuellement, d’une catégorie (non hiérarchique, pour le « besoin d’en connaître »).

  • Règles de confidentialité Pour garantir la confidentialité, le modèle Bell-LaPadula impose deux propriétés majeures :

    • no read up (Simple Security Property) : un sujet ne peut lire que si son niveau de sécurité est supérieur ou égal à celui de l’objet ;
    • no write down (*-Property) : un sujet ne peut écrire dans un objet que si le niveau de sécurité de ce dernier est supérieur ou égal à celui du sujet.

    Objectif : éviter la fuite de données.

  • Applications

Multilateral security

Le Multilateral Security est une approche visant à compartimenter les données afin de restreindre l’accès à des sous-ensembles d’informations spécifiques.

  • Mécanisme Contrairement au MLS qui suit souvent une hiérarchie de pouvoir ou de secret, la sécurité multilatérale se base sur des étiquettes (tags) afin de séparer les informations entre différents groupes ou domaines.

  • Objectifs Elle vise à assurer l’accès aux données relativement à ce qui concerne strictement les individus ou processus. De ce fait, la compromission d’un secteur n’entraîne pas la compromission de l’ensemble du système.

Définitions

Confiance ( trust)

Un système ou composant de confiance est tel qu’il peut mettre en péril la police de sécurité.

Surface d'attaque ( attack surface)

La surface d’attaque d’un système est la somme des vulnérabilités, chemins d’accès ou méthodes — parfois appelés vecteurs d’attaque — qu’un attaquant peut exploiter afin d’accéder illégalement à ce système.

Reference monitor

Reference monitor

Composant d’un système d’exploitation chargé de contrôler tous les accès aux ressources. Il agit comme un arbitre externe ne faisant pas confiance aux sujets, et se base uniquement sur une liste de permission, souvent appelée liste de contrôle des accès (ACL).

  • Propriétés Pour qu’un système soit considéré comme réellement sécurisé, son moniteur de référence doit respecter trois garanties essentielles :

    • médiation complète : le système doit assurer que chaque opération sensible est interceptée et vérifiée sans exception ;
    • inviolabilité : le moniteur lui-même, ainsi que sa base de règles, doivent être protégés contre toute modification par des processus non fiables ;
    • vérifiabilité : le mécanisme doit être suffisamment petit et simple afin de pouvoir être analysé et testé rigoureusement.
  • Exemples

    • Linux : le framework Linux Security Modules (LSM) définit une interface de moniteur de référence pour le noyau Linux, derrière laquelle des modules comme SELinux ou AppArmor implémentent la logique de décision.
    • Solaris : les Trusted Extensions appliquent une politique MLS sur Solaris.

Trusted Computing Base (TCB)

Définition

Ensemble de composants (hardware, software, humain, etc.) dont le fonctionnement correct est suffisant pour assurer que la police de sécurité est mise en œuvre. En d’autres termes, la mise en péril de l’un d’eux est susceptible de causer une brèche au sein de la police de sécurité.

WARNING

Le TCB doit être aussi petit que possible.

L’exemple de gVisor

gVisor is an open-source Linux-compatible sandbox that runs anywhere existing container tooling does. It enables cloud-native container security and portability.

  • Motivations : gVisor was created in order to provide additional defense against the exploitation of kernel bugs by untrusted userspace code.

Attack Vectors — gVisor

A sandbox is not a substitute for a secure architecture.

  • Principes
    • Minimizes the possibility of direct exploitation of the host System API by applications.
    • Minimizes indirect exploitability, which is the exploitation by an exploited or buggy Sentry (e.g. chaining an exploit).

Complexité

Si la complexité est parfois nécessaire afin d’implémenter une fonctionnalité donnée, il faut tenir compte du fait qu’elle augmente mécaniquement la surface d’attaque.

Linux Kernel Security — Linux Foundation

Le noyau Linux, c’est 81 500 fichiers, 36 millions de lignes de code. Pourtant, un système Linux commun n’utilisera probablement que 5 à 10 % du code de ce noyau.

Principes généraux du durcissement

  1. Minimiser le système.
  2. Principe de moindre privilège :
    • compartimenter les tâches ;
    • accorder le minimum d’autorisations aux objets ou aux entités pour qu’elles puissent agir.
  3. Défense en profondeur :
    • sécuriser les configurations par défaut ;
    • mettre en place plusieurs couches de défense et de contrôle.

Linux hardening et application sandboxing

mindmap
  root((Sécurité : de l'Application au Hardware))
    Reminders
      Syscalls
      Process execution
      File permissions
      Users and groups
    Minimization
      Dependencies
      Network services
    Least privilege
      Compartmentalization
      Example of Qmail
    Security in depth
    Access Control
      DAC - Discretionary Access Control
      MAC - Mandatory Access Control
    Process execution supervision
      Capabilities
      Seccomp
      Debuggers
        ptrace
      Linux security modules
        SELinux
        AppArmor
        LandLock
      eBPF
    Virtualization
      Linux Containers
        Linux Namespaces
        Gvisor
      Linux heavy virtualization
        Type 1
        Type 2
    Data protection
      Confidentiality
        FDE - Full Disk Encryption
        FBE - File Based Encryption
      Integrity
        DM-verity
    Network
      Network stack
      Network services
    Hardware mechanisms
      Trusted Platform Modules - TPM
      Intel SGX
      ARM TrustZone

Syscalls

Un syscall, c’est un appel à une fonction de l’API du noyau. Ce dernier provoque une interruption logicielle, et donc est coûteux en temps.

  • Traçage : on peut suivre les syscalls avec n’importe quelle fonction dotée d’un mécanisme Linux Exec hooking ; notamment ptrace, eBPF, LSM.

  • On peut tracer l’exécution d’un processus avec strace :

dl@localhost:~$ strace ls
execve("/usr/bin/ls", ["ls"], 0x7ffe0c1f5f80 /* 21 vars */) = 0
brk(NULL)                               = 0x55a12f8e5000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe0f6db1000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=11979, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 11979, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fe0f6dae000
close(3)                                = 0

Rappels sur les OS

Processus

Définition

Un processus est un programme en cours d’exécution dans un contexte spécifique.

  • Exécution Le SE permet à plusieurs processus de s’exécuter de manière concurrente. Ainsi, même si un processus utilise le processeur à un instant T, plusieurs autres sont dans divers états d’exécution. Sous Linux, les processus sont créés via l’appel système clone (mécanisme CoW).

  • Management Certains syscalls permettent à certains processus d’en créer et gérer d’autres. On compte entre autres :

    • exit pour terminer le processus courant ;
    • fork pour cloner le processus courant. À l’issue d’un fork, les deux processus sont identiques (même pile, même tas) ;
    • exec remplace le processus courant par un nouvel exécutable. Il n’y a ni création, ni destruction de processus ;
    • waitpid attend jusqu’à ce qu’un autre processus ne se termine.
  • Isolation des processus C’est le mécanisme par lequel le SE sépare les applications les unes des autres en leur attribuant des ressources distinctes.

    • Chaque processus dispose de son propre espace mémoire privé et ne peut pas accéder à la mémoire d’un autre processus.
    • Cette isolation assure la robustesse (si un processus comme un onglet de navigateur plante, il n’entraîne pas tout le système) et la sécurité (empêche un programme malveillant d’interférer avec un autre).
  • Limites de l’isolation En pratique, l’isolation est imparfaite car les processus partagent toujours des ressources physiques comme le cache du processeur, la carte graphique ou le routeur Wi-Fi, ce qui peut créer des canaux auxiliaires (side-channels) permettant de fuiter des informations.

Files permissions

Les permissions de fichier définissent qui peut accéder à quelle information, et de quelle manière. Linux utilise le contrôle d’accès discrétionnaire (DAC), où le propriétaire décide des permissions.

  • Typologie de permissions : Read (R), Write (W), Execute (X).

  • Catégorie d’utilisateurs : Utilisateur, Groupe, Autres.

  • Mécanismes avancés

    • setuid : ce bit spécial permet à un utilisateur d’exécuter un programme avec les privilèges du propriétaire du fichier (souvent root). Par exemple, la commande passwd nécessite ce bit pour modifier le fichier /etc/shadow que seul root peut normalement écrire.
    • Access Control Lists (ACL) : conçues pour affiner les permissions fichiers en octroyant la possibilité d’autoriser n’importe qui à accéder à n’importe quelle ressource du disque.
      setfacl -m "u:user:permissions" <file/dir>
      getfacl <file/dir>
      Ressource : Red Hat Blog.

Utilisateurs et groupes

Dans les systèmes d’exploitation de type UNIX et Linux, l’identité d’un processus ne se résume pas à un seul numéro, mais à un ensemble d’identifiants (UID) ayant des rôles distincts pour la sécurité et la gestion des droits.

  • Le Real ID (RUID)

    • Le Real User ID (RUID) identifie l’utilisateur qui a réellement lancé le processus.
    • Il est utilisé pour déterminer à qui appartient le processus et à qui envoyer des signaux.
    • Par exemple, si vous ouvrez une session, votre UID habituel (souvent 1000 pour le premier utilisateur) est votre Real ID.
  • L’Effective User ID (EUID)

    • L’Effective User ID (EUID) est l’identifiant que le noyau examine pour vérifier les permissions lors de l’accès à des ressources comme les fichiers ou les sockets réseau.
    • La plupart du temps, le RUID et l’EUID sont identiques.
    • Cependant, il peut changer lors de l’exécution d’un programme disposant du bit setuid.
  • Nuances et interaction (le mécanisme setuid)

    • La nuance principale réside dans la séparation entre la propriété (qui je suis) et le privilège (ce que je peux faire à cet instant).
    • Un exemple classique est la commande /usr/bin/passwd : bien qu’elle soit lancée par un utilisateur standard (RUID = 1000), elle doit pouvoir modifier le fichier /etc/shadow qui appartient à root.
    • Grâce au bit setuid, le processus voit son EUID devenir 0 (root) le temps de l’exécution, alors que son RUID reste 1000.
    • Cela permet au programme de bénéficier des droits de root pour écrire le mot de passe, tout en sachant quel utilisateur a initié l’action.

Autres identifiants comptabilisés

Il existe d’autres identifiants pour gérer des scénarios plus complexes :

  • Saved Set-User-ID (SSUID) : il permet à un processus privilégié de basculer son EUID entre une valeur privilégiée et une valeur non privilégiée, tout en conservant une copie de l’ID privilégié pour pouvoir le récupérer plus tard.
  • File System User ID (FSUID) : spécifique à Linux, cet ID est utilisé exclusivement pour les vérifications de droits sur le système de fichiers, sans affecter les autres privilèges.
  • Identifiants de groupe (GID) : chaque processus possède également un Real Group ID (RGID), un Effective Group ID (EGID) et souvent un Saved GID, fonctionnant sur le même principe que les UID pour gérer les permissions de groupe.
  • Groupes supplémentaires : un processus peut aussi appartenir à une liste de groupes additionnels pour étendre ses droits d’accès.

Groupes

Les groupes permettent une gestion simplifiée des permissions (DAC), un partage sélectif et une vérification de l’identité (par le biais du GID ou de l’un des groupes supplémentaires) afin d’accorder les droits associés.

  • Sécurité et privilèges Les groupes jouent également un rôle crucial dans la gestion des capacités du système :
    • impersonation : la capacité Linux CAP_SETGID permet à un processus de changer son GID effectif, ce qui peut être utilisé pour usurper l’identité d’un groupe privilégié ;
    • vecteurs d’attaque : si un utilisateur parvient à intégrer ou à usurper un groupe sensible (comme le groupe docker ou shadow), il peut accéder à des ressources protégées (comme la socket Docker ou le fichier des mots de passe) et réaliser une escalade de privilèges.

sudo

L’utilisation de sudo permet :

  1. d’assurer l’attribution de l’opération d’administration par le biais du logging dans auditd et dans les journaux ;
  2. de limiter les actions qui peuvent être effectuées en tant qu’administrateur avec le fichier sudoer.

Ainsi, un programme requérant des droits root afin d’exécuter telle ou telle action doit renoncer à ses privilèges une fois les actions nécessaires effectuées.

Monolithic design

Un seul module implémente toutes les fonctionnalités. Si l’une d’elles est compromise, c’est tout le système qui l’est.

Différence entre un thread et un processus

Une application se compose d’un ou plusieurs processus. Dans ce contexte, un ou plusieurs threads s’exécutent. Ces derniers correspondent à l’unité de base à laquelle le système d’exploitation alloue du temps processeur. Si les threads (d’un même processus) s’exécutent au sein d’une mémoire partagée, ça n’est pas le cas des processus qui, quant à eux, s’exécutent dans des espaces mémoire séparés.

Chaque processus fournit les ressources nécessaires à l’exécution d’un programme. Un processus a un espace d’adressage virtuel, du code exécutable, des handles ouverts pour les objets système, un contexte de sécurité, un identificateur de processus unique, des variables d’environnement, une classe de priorité, des tailles minimales et maximales de jeu de travail et au moins un thread d’exécution. Chaque processus est démarré avec un thread unique, souvent appelé thread principal, mais peut créer des threads supplémentaires à partir de l’un de ses threads. Un thread est l’entité au sein d’un processus qui peut être planifiée pour l’exécution. Tous les threads d’un processus partagent son espace d’adressage virtuel et les ressources système. En outre, chaque thread conserve des gestionnaires d’exceptions, une priorité de planification, un stockage local de thread, un identificateur de thread unique et un ensemble de structures que le système utilisera pour enregistrer le contexte de thread jusqu’à ce qu’il soit planifié. Le contexte du thread inclut l’ensemble des registres d’ordinateur du thread, la pile du noyau, un bloc d’environnement de thread et une pile utilisateur dans l’espace d’adressage du processus du thread. Les threads peuvent également avoir leur propre contexte de sécurité, qui peut être utilisé pour emprunter l’identité des clients.

  • Fichier /proc Le système de fichiers /proc permet d’accéder à l’état de chaque processus actif et de chaque unité d’exécution du système.

    The proc filesystem is a pseudo-filesystem which provides an interface to kernel data structures. Typically, it is mounted automatically by the system, but it can also be mounted manually using a command such as :

mount -t proc proc /proc

Most of the files in the proc filesystem are read-only, but some files are writable, allowing kernel variables to be changed.

/proc/pid/maps :

A file containing the currently mapped memory regions and their access permissions. See mmap(2) for some further information about memory mappings.

ps aux # équiv. à `ps -ef` et permet de lister tous les processus

Compartmentalized design

Sendmail MTA design

Initialement basé sur la confiance, la problématique sécurité est omise du cadre de développement. De fait, les fonctionnalités de réseau, les entrées utilisateur, les affichages, les I/O fichiers passent tous par un processus root.

Qmail design

  • Isolation basée sur l’isolation de l’OS ; à savoir :
    • chaque module s’exécute en tant qu’utilisateur séparé ;
    • chaque utilisateur a accès à des ressources spécifiques.
  • Moindres privilèges
    • pour chaque UID, un minimum de privilèges ;
    • un seul setuid program ;
    • un seul programme exécuté en tant que root.

Google’s rule of two

Quelles sont nos approches pour sécuriser un module ?

Lorsqu’on a besoin de faire du code parsé, une entrée potentiellement qui n’est pas de confiance, il faut éviter de se retrouver à l’intersection de :

  • code qui traite d’un code non sûr ;
  • code écrit dans un langage non sûr (C, C++) ;
  • code qui ne tourne pas dans un sandbox, par exemple un navigateur.

Minimizing attack surface

  1. Le nombre d’erreurs augmente avec la taille du code.
  2. Des routines simples et réutilisables sont préférables à un seul bloc de code conséquent.
  3. De même concernant les dépendances :
    • simplicité de la librairie ;
    • utilisation du strict nécessaire.

Minimizing Linux system

La minimisation d’un système Linux s’opère selon trois axes :

  1. minimiser les packages installés ;
  2. minimiser les services en cours d’exécution ;
  3. minimiser les fonctionnalités configurées par service.

Minimizing Linux installation

Il existe deux familles de distributions Linux :

  • package-based distributions : Debian-based, Redhat-based ;
  • source-based distributions : Gentoo.

TIP

Using minimal servers is common for containers environments :

  • limits the size of the artefact (alpine linux goal),
  • security as well.

Objectif

Prévenir l’attaque « Living-Off-The-Land ». GTFOBins LOTL Exploits

BusyBox.

Compiling / providing only necessary programs and features in your systems

On peut :

  • utiliser des packages alternatifs (par exemple vim-tiny) ;
  • supprimer doc et compilateurs ;
  • supprimer les modules kernel inutiles / créer un kernel monolithique.

En savoir plus sur l’approche de Gentoo Kernel Minimization.

Minimiser la surface d’attaque réseau en pratique

  1. Supprimer les services inutiles. On peut lister les services avec la commande suivante :
systemctl list-units --type=service
  1. Concernant ceux qui écoutent sur le réseau :
ss -latupn | grep LISTEN

Modèle de permission

Un modèle allow list doit être préféré, car il évite les omissions, tout en suivant les conditions suivantes :

  • deny all par défaut ;
  • fine grained permissions, nominative allow list.

WARNING

Attention aux wildcards au sein des allow lists.

Access control

  • DAC
  • MAC
  • role-based AC

Capabilities

Les capabilities Linux constituent un mécanisme de sécurité conçu pour appliquer le principe du moindre privilège en divisant les pouvoirs omnipotents de l’utilisateur « root » en unités distinctes et plus petites. Traditionnellement, un processus était soit privilégié (root), soit non privilégié, mais les capabilities permettent d’accorder à un processus uniquement le sous-ensemble de privilèges nécessaire à sa tâche.

Fonctionnement interne : les ensembles (sets)

Le noyau gère les capabilities à travers cinq ensembles distincts pour chaque processus, ainsi que des attributs sur les fichiers binaires :

  • Permitted (CapPrm) : il s’agit du plafond maximum de privilèges qu’un processus peut posséder ; il sert de limite supérieure à ce que le processus peut élever dans son ensemble Effective.
  • Effective (CapEff) : c’est l’ensemble réellement utilisé par le noyau pour effectuer les vérifications de droits lors d’opérations sensibles (par exemple, ouvrir une socket brute).
  • Inheritable (CapInh) : cet ensemble détermine quelles capabilities sont transmises du processus parent au processus enfant lors d’une exécution.
  • Bounding (CapBnd) : il agit comme une barrière de sécurité ultime en mettant un plafond sur les capabilities qu’un processus pourra jamais acquérir, même s’il tente d’exécuter un binaire privilégié.
  • Ambient (CapAmb) : introduit plus récemment, il permet à des programmes non root de conserver certains privilèges après un appel à execve, ce qui était auparavant difficile sans utiliser le bit setuid.

Sur le plan technique, ces informations sont stockées dans les attributs étendus des fichiers sur le disque, tandis qu’en mémoire, elles sont maintenues par thread et consultables via /proc/<pid>/status.

Usage et manipulation

L’usage des capabilities se fait principalement via une suite d’outils dédiés :

  • getcap / setcap : utilisés pour lire ou attribuer des privilèges à des fichiers binaires spécifiques (par exemple setcap cap_net_raw+ep ./ping).
  • capsh : un utilitaire puissant pour décoder les valeurs hexadécimales des capabilities ou pour lancer des shells avec des privilèges restreints pour test.
  • getpcaps : permet d’afficher les capabilities d’un processus en cours d’exécution à partir de son PID.

Exemples d’application

  • Utilitaires réseau : le binaire ping utilise cap_net_raw pour envoyer des paquets ICMP sans avoir besoin des pleins pouvoirs de root.
  • Conteneurs (Docker/Kubernetes) : par défaut, Docker restreint fortement les capabilities. Il est recommandé de tout supprimer (--cap-drop all) puis d’ajouter uniquement le nécessaire, comme cap_net_bind_service pour permettre à un serveur web de se lier au port 80.
  • Sécurisation des services : un administrateur peut configurer un serveur web pour qu’il démarre avec uniquement le droit de se lier à un port privilégié, tout en lui retirant le droit de modifier les fichiers systèmes.

Risques de sécurité

Une mauvaise configuration peut mener à une escalade de privilèges. Par exemple, accorder cap_setuid à un interpréteur comme Python permet à n’importe quel utilisateur de forcer l’UID du processus à 0 et d’obtenir un shell root. De même, la capacité CAP_SYS_ADMIN est considérée comme « presque root » en raison de l’étendue des privilèges administratifs qu’elle confère.

Confused-deputy problem

La vulnérabilité

Un sujet, mandaté pour opérer telle ou telle action par un client, utilise ses droits au détriment des droits du client. Par exemple, le client demande au sujet de lire un document, tandis que les permissions du sujet le permettent, et que celles du client, non.

Le fond du problème

Les députés peuvent être dupés lorsque la sécurité est gérée par une partie du système, externe au logiciel. En l’occurrence, la sécurité est gérée par un reference monitor, dont la tâche est de décider quel sujet est autorisé à utiliser quel autre sujet.

Le problème fondamental est que le programme ne peut pas distinguer quelle autorité il doit utiliser pour une tâche spécifique.

La solution

L’utilisation de capacités plutôt que de simples noms de fichiers :

  • Identification + autorisation : une capacité est un jeton qui désigne à la fois un objet et le droit d’effectuer une action dessus.
  • Désignation explicite : au lieu de manipuler des noms de fichiers (chaînes de caractères ASCII), le compilateur utiliserait une capacité spécifique pour le fichier STAT et une capacité fournie par l’utilisateur pour le fichier de débogage.
  • Résolution : si l’utilisateur tente de désigner le fichier BILL sans en avoir la capacité, il ne peut pas la transmettre au compilateur, et l’opération échoue naturellement sans que le compilateur n’ait à effectuer de vérifications complexes.

Résumé

Le problème survient lorsque l’autorité est transmise de manière invisible à un programme qui ne peut pas différencier ses propres privilèges de ceux de son utilisateur.

SECCOMP

Seccomp (SECure COMPuting mode) est un mécanisme de sécurité du noyau Linux conçu pour restreindre les appels système (syscalls) qu’un processus est autorisé à effectuer. Son objectif principal est de minimiser la surface d’attaque du noyau en limitant les points d’entrée vers ce dernier, car de nombreux bugs exploitables se trouvent dans les interfaces des appels système.

Fonctionnement de Seccomp

Le mécanisme se décline principalement en deux modes distincts :

  • Le mode « Strict » : dans ce mode initial, le processus est limité à seulement quatre appels système — read(), write(), _exit() et sigreturn(). Toute tentative d’exécuter un autre appel système provoque la terminaison immédiate du processus par un signal SIGKILL.
  • Le mode « Filter » (Seccomp-BPF) : ce mode, plus flexible, utilise des programmes Berkeley Packet Filter (BPF) pour analyser les appels système. Il permet de définir des règles plus fines basées sur le numéro de l’appel système et la valeur de ses arguments immédiats (valeurs de registre).

Lorsqu’un appel système est intercepté, le filtre peut renvoyer différentes actions selon la politique définie :

  • SECCOMP_RET_KILL_PROCESS : termine le processus immédiatement.
  • SECCOMP_RET_ERRNO : refuse l’appel et renvoie un code d’erreur (errno) spécifique à l’application.
  • SECCOMP_RET_TRAP : envoie un signal SIGSYS pour que l’application puisse gérer l’exception.
  • SECCOMP_RET_ALLOW : autorise l’exécution normale de l’appel système.

Mise en place technique

La configuration de Seccomp s’effectue généralement via l’appel système prctl(). Voici les étapes clés pour son implémentation :

  1. Verrouillage des privilèges : avant d’appliquer un filtre, un processus non privilégié doit appeler prctl(PR_SET_NO_NEW_PRIVS, 1). Cette étape est obligatoire pour garantir qu’un processus ne peut pas utiliser Seccomp pour forcer un binaire privilégié (comme un binaire setuid) à s’exécuter de manière unsafe.
  2. Définition du filtre : pour le mode filtre, on prépare une structure sock_fprog contenant les instructions BPF de filtrage.
  3. Activation : on active le filtrage avec la commande prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, prog). Une fois activés, les filtres sont immuables et hérités par tous les processus enfants créés via fork() ou clone().

Limitations et points d’attention

  • Pointeurs et mémoire : Seccomp ne peut pas inspecter le contenu des pointeurs (par exemple, le chemin d’un fichier dans open()) car il n’est pas autorisé à déréférencer la mémoire de l’espace utilisateur.
  • Dépendance à l’architecture : les numéros d’appels système varient d’une architecture à l’autre (x86 vs ARM) ; un filtre doit donc impérativement vérifier le champ arch dans les données seccomp_data pour éviter d’autoriser par erreur un appel système malveillant sur une architecture différente.
  • Échappement via vDSO : certains appels système « virtuels » comme gettimeofday() sont gérés directement en espace utilisateur via le mécanisme vDSO et ne passent pas par le noyau, échappant ainsi totalement au contrôle de Seccomp.

Syscall TOCTTOU

Le terme TOCTTOU signifie Time-Of-Check To Time-Of-Use (temps de vérification contre temps d’utilisation). Il s’agit d’une vulnérabilité de concurrence qui survient lorsque l’état d’une ressource système est modifié entre le moment où le système vérifie les permissions d’accès et le moment où il effectue réellement l’opération.

Voici les détails concernant ce problème appliqué aux appels système (syscalls), d’après les sources.

1. Le mécanisme de la vulnérabilité

Dans le contexte des syscalls, l’attaque repose sur une condition de course (race condition). Elle se produit généralement de la manière suivante :

  • La vérification (check) : un programme privilégié ou le noyau vérifie qu’une opération est autorisée pour un sujet donné (par exemple, vérifier si un utilisateur a le droit d’ouvrir un fichier via son nom de chemin).
  • L’intervalle : un attaquant utilise un processus concurrent pour modifier la ressource ou l’argument juste après la vérification.
  • L’utilisation (use) : le noyau effectue l’opération sur une ressource qui n’est plus celle qui a été validée.

2. Exemple classique : le système de fichiers

L’exemple le plus courant concerne l’accès aux fichiers via un chemin (pathname). Un processus privilégié utilise l’appel système access() pour vérifier les droits d’un utilisateur sur un fichier temporaire (par exemple /tmp/X). Juste après cette vérification, mais avant l’appel à open(), l’attaquant remplace /tmp/X par un lien symbolique pointant vers un fichier sensible comme /etc/shadow. Le système ouvre alors le fichier sensible, croyant travailler sur le fichier temporaire validé précédemment.

3. Risques pour les moniteurs de référence et les sandboxes

Les systèmes qui interceptent les appels système (comme certains débogueurs ou sandboxes basées sur ptrace) sont particulièrement vulnérables. Si les arguments du syscall résident en mémoire utilisateur (cas des chaînes de caractères pour les chemins de fichiers ou des structures complexes), un thread malveillant peut modifier le contenu de cette mémoire après que le moniteur a fini sa vérification mais avant que le noyau ne l’utilise.

4. Solutions et remédiations

Les sources décrivent plusieurs approches pour contrer ce problème :

  • L’approche LSM (Linux Security Modules) : pour éviter le TOCTTOU, le framework LSM place ses points de contrôle (hooks) à l’intérieur du noyau, après que le nom du fichier a été résolu en un objet concret (l’inode). La vérification porte ainsi sur l’objet réel que le noyau va utiliser, et non sur un nom de chemin susceptible d’être détourné.
  • Le mécanisme Seccomp-BPF : pour se protéger, les filtres Seccomp ne sont pas autorisés à déréférencer des pointeurs. Ils ne peuvent examiner que les arguments passés directement par les registres du processeur (valeurs immédiates comme les numéros de descripteurs de fichiers), ce qui rend impossible la modification de l’argument par un thread tiers pendant le traitement.
  • La stratégie de Chromium : pour les arguments en mémoire impossibles à vérifier directement, le bac à sable de Chromium utilise un processus de confiance qui copie les arguments dans son propre espace d’adressage privé. Une fois copiés, ces arguments sont immunisés contre toute modification par le code non fiable.
  • Précautions avec eBPF : les programmes eBPF peuvent aussi souffrir de TOCTTOU car ils s’exécutent de manière concurrente sur différents cœurs CPU. La recommandation est de placer les points d’attache (hooks) immédiatement après que les données utilisateur ont été lues (copiées) vers l’espace noyau.

Side channel

https://jackcook.com/2024/11/09/bigger-fish.html

eBPF

L’eBPF (extended Berkeley Packet Filter) est une technologie révolutionnaire intégrée au noyau Linux qui permet d’exécuter des programmes sandboxés dans un contexte privilégié sans modifier le code source du noyau ni charger de modules noyau traditionnels. On peut comparer son rôle à celui de JavaScript pour le HTML : là où le noyau était auparavant statique, l’eBPF permet d’écrire de mini-programmes qui s’exécutent de manière dynamique lors d’événements spécifiques (comme des entrées/sorties disque ou des appels système).

1. Architecture et fonctionnement interne

L’eBPF est fondamentalement une architecture de jeu d’instructions (ISA) exécutée par une machine virtuelle interne au noyau Linux, dotée de ses propres registres, instructions et pile.

Le cycle de vie d’un programme eBPF suit généralement ces étapes :

  • Développement : le programme est écrit dans un sous-ensemble restreint du langage C, puis compilé en bytecode eBPF (souvent via Clang/LLVM).
  • Chargement et vérification : le bytecode est chargé dans le noyau via l’appel système bpf(). Avant d’être autorisé, un vérificateur (verifier) analyse le code pour s’assurer qu’il est sûr : il vérifie l’absence de boucles infinies, la validité des accès mémoire et le respect des limites de complexité.
  • Compilation JIT : une fois vérifié, le bytecode est compilé Just-In-Time (JIT) en instructions natives du processeur pour atteindre une vitesse d’exécution proche du natif.
  • Attachement (hooks) : le programme est attaché à des points d’entrée spécifiques appelés hooks. Il peut s’agir de points de trace (tracepoints), de sondes noyau (kprobes) ou utilisateur (uprobes), ou encore de points spécifiques à la pile réseau comme XDP (eXpress Data Path).
  • Communication de données : pour échanger des données entre l’espace noyau et l’espace utilisateur, l’eBPF utilise des maps (stockage clé-valeur partagé) ou des tampons circulaires (ring buffers).

2. Principaux domaines d’application

Initialement conçu pour le filtrage réseau, l’eBPF a étendu ses capacités à de nombreux domaines :

  • Observabilité et tracing : il permet une analyse de performance extrêmement fine (comme l’outil tcplife ou opensnoop) avec un overhead minimal, ce qui autorise son utilisation en production 24 h/24.
  • Réseautage : avec XDP, eBPF peut traiter ou rediriger des paquets très tôt dans la pile réseau, permettant une atténuation efficace des attaques DDoS.
  • Sécurité : il permet de surveiller tous les appels système et le trafic réseau pour créer des systèmes de détection d’intrusion sophistiqués.

3. Frameworks et outils

Programmer directement en bytecode eBPF étant complexe, la communauté utilise principalement deux frameworks développés sous le projet iovisor :

  • BCC (BPF Compiler Collection) : permet d’écrire le code eBPF en C et de l’orchestrer avec des scripts en Python, Lua ou C++.
  • bpftrace : propose un langage de haut niveau beaucoup plus simple pour créer des outils de diagnostic en une seule ligne de commande.

4. Limites et risques de sécurité

Bien que puissant, l’eBPF présente des défis importants :

  • Programmation restreinte : les programmes sont limités en taille (4096 instructions), en profondeur de pile (512 octets) et n’ont pas accès directement aux fonctions de synchronisation du noyau, ce qui peut créer des problèmes de TOCTTOU (Time-Of-Check To Time-Of-Use).
  • Défis pour la surveillance : contrairement aux idées reçues, l’eBPF n’a pas été conçu à l’origine pour la sécurité ; les sondes peuvent parfois échouer à se déclencher sans avertissement.
  • Vecteur de malware : des attaquants peuvent détourner l’eBPF pour créer des rootkits furtifs. Par exemple, la fonction bpf_probe_write_user permet de modifier la mémoire d’un processus utilisateur pendant un appel système, et bpf_override_return permet de bloquer des actions de sécurité indésirables.
  • Privilèges : l’installation de programmes eBPF nécessite généralement les pleins pouvoirs (root) ou des capacités spécifiques comme CAP_BPF. Pour des raisons de durcissement, de nombreuses distributions comme Ubuntu désactivent par défaut l’eBPF pour les utilisateurs non privilégiés afin d’éviter des attaques par escalade de privilèges.

Résumé

TIP

eBPF allows users to dynamically install code that can execute in kernel context but be orchestrated from user space.

Hyperviseurs

Définition — Hyperviseur

Un hyperviseur est une plateforme de virtualisation (un environnement d’exécution) permettant à plusieurs systèmes d’exploitation de fonctionner en parallèle sur une seule machine physique.

Typologie

On compte deux types d’hyperviseurs :

  1. Type 1, dits natifs, ou bare-metal Les hyperviseurs de type 1 s’exécutent directement sur le matériel hôte pour gérer les systèmes d’exploitation invités. En l’occurrence, KVM, Microsoft Hyper-V et VMware vSphere sont des hyperviseurs de type 1.

    Bon à savoir QubesOS est un système d'exploitation orienté sécurité basé sur l'hyperviseur bare-metal Xen.

  2. Type 2, dits hébergés Les hyperviseurs de type 2 s’exécutent sur un système d’exploitation en tant que couche logicielle. Les ressources des machines virtuelles sont ordonnancées sur un système d’exploitation hôte. On compte parmi les solutions implémentant ce modèle : QEMU, CrosVM, VirtualBox.

Usages et différenciation

Le type d’hyperviseur n’est pas seulement à considérer pour l’architecture induite, mais bien pour l’usage visé et les spécificités qui en sont inhérentes. De façon basique, on utilise des hyperviseurs hébergés sur un PC personnel, dans un cadre d’usage quotidien ou de test. À contrario, on utilise un hyperviseur bare-metal sur un serveur dédié, notamment pour de la production ou lorsque la performance est nécessaire.

FonctionnalitéType 2 (hébergé)Type 1 (bare-metal)Description
Live migration❌ Non✅ OuiDéplacer une VM en cours d’exécution vers un autre serveur physique sans coupure.
Haute disponibilité (HA)❌ Non✅ OuiRedémarrer automatiquement une VM sur un autre serveur si son hôte physique tombe en panne.
Clustering❌ Non✅ OuiRegrouper et gérer plusieurs serveurs physiques comme une seule ressource unifiée.
Passthrough matériel (GPU, réseau)⚠️ Émulé / Partiel✅ CompletDonner un accès direct (PCIe) à un composant physique (GPU, carte réseau) à une VM.
Performances et latence⚠️ Moyennes✅ Quasi-nativesVitesse d’exécution. Le Type 2 souffre d’un « overhead » car il doit passer par l’OS hôte.
Sécurité et isolation⚠️ Dépend de l’OS hôte✅ Très forteRéduction de la surface d’attaque : si l’OS hôte d’un Type 2 est compromis, les VM le sont aussi.
Overprovisioning (CPU/RAM)⚠️ Limité✅ AvancéCapacité à allouer virtuellement plus de ressources aux VM qu’il n’y en a physiquement.
Intégration stockage réseau❌ Basique✅ AvancéeConnexion directe aux baies de stockage d’entreprise (SAN, iSCSI, Fibre Channel).
Cas d’usage principalTests, développement, labo localProduction, datacenters, cloudL’environnement naturel pour lequel l’outil est conçu.
Exemples de logicielsVirtualBox, VMware WorkstationVMware ESXi, Proxmox (KVM), Hyper-VExemples concrets des solutions sur le marché.

Une précision sur le passthrough GPU

Sur un Type 2, on a souvent une accélération 3D matérielle, mais c’est le système hôte qui partage la carte graphique via des pilotes émulés. Sur un Type 1, on fait du vrai PCIe passthrough : la carte graphique est littéralement « détachée » du serveur physique et confiée exclusivement à la VM (très utilisé pour le cloud gaming ou l’IA).

gVisor

Container Security Platform. Improve container security, deliver security-imperative apps, increase security productivity, and enforce compliance.

gVisor est un noyau d’application open-source développé en Go par Google. Il implémente l’API Linux afin d’isoler (sandboxer) les conteneurs, et d’agir comme un kernel indépendant. Notamment intéressant lorsqu’il s’agit d’exécuter du code non-sûr, ou lors de SaaS traitant des données sensibles ; gVisor reste malgré tout coûteux en termes d’overhead. En effet, les systèmes générant beaucoup de syscalls, d’opérations réseau ou d’I/O disque verront leurs performances chuter de façon significative comparativement à un conteneur classique ou une VM.

Linux namespaces