🎯 Contexte

J’ai réalisé une exploration approfondie de ce qui se passe réellement lors d’une connexion SSH - de l’arrivée du premier paquet réseau jusqu’à l’apparition du prompt bash dans mon terminal. Cette investigation vise à comprendre concrètement l’interaction entre le matériel (carte réseau, CPU, RAM) et le logiciel (Kernel, processus, threads), mais surtout à approfondir le fonctionnement interne de Linux.

Objectif : documenter la chaîne complète de visibilité d’une connexion SSH en utilisant les outils de diagnostic d’un Site Reliability Engineer (SRE). Chaque étape est observable, traçable et compréhensible.

Note : ce lab a été réalisé dans un environnement de test. Les IPs, instances EC2 et clés d’authentification ont été supprimées après publication.


Table des matières

  1. Stack du lab
  2. Vue d’ensemble : les trois niveaux d’observation
  3. Niveau 1 : côté client,la négociation SSH
  4. Niveau 2 : côté serveur, l’arrivée des paquets
  5. Niveau 3 : côté serveur, la naissance du Shell
  6. Exploration approfondie : /proc et les File Descriptors
  7. Synthèse
  8. Conclusion

Stack du lab

  • cmd windows et conteneur EC2 ubuntu t3.micro par défaut (image : ubuntu-noble-24.04-amd64-server)
  • Connection sans mot de passe avec échange de clé RSA.

AWS instance EC2

windows terminal

Vue d’ensemble : trois niveaux d’observation

Pour observer la chaîne complète du matériel au shell, nous allons définir trois niveaux d’observation. En SRE, on appelle cela le traçage de la pile. Nous analyserons :

  • la négociation SSH au niveau applicatif (côté client)
  • les paquets TCP reçus au niveau carte réseau et kernel (côté serveur)
  • la création des processus au niveau kernel (côté serveur)

Voici les outils et fichiers que nous allons explorer :

ÉtapeOutil / SourceNiveau d’Analyse
Arrivée réseautcpdumpCarte Réseau (NIC) → Kernel
Négociationssh -vApplication (Windows) → Processus (sshd)
Auth & créationstraceKernel Syscalls (clone, execve)
GénéalogiepstreeHiérarchie des processus (Parent/Enfant)
Contrôle/proc/limitsGouvernance du Kernel sur le Matériel
Communication/proc/fdTuyauterie physique (Sockets / TTY)
Qualité de lienssStatistiques TCP du Kernel

Niveau 1 : côté client, la négociation SSH

Commande de base

Pour voir l’échange complet entre le client et le serveur, j’utilise le mode verbeux de SSH :

ssh -v ubuntu@ec2-13-40-190-84.eu-west-2.compute.amazonaws.com

Lien SRE : observation de la couche application (protocole SSH).

SSH Remote Connection 1 SSH Remote Connection 2 SSH Remote Connection 3 SSH Remote Connection 4

Phase 1 : initialisation logicielle et handshake réseau (L4)

OpenSSH_for_Windows_9.5p2, LibreSSL 3.8.2
debug1: Reading configuration data...
debug1: Connecting to ... [64:ff9b::d28:be54] port 22.
debug1: Connection established.

Les lignes de debug :

  • OpenSSH_for_Windows_9.5p2, LibreSSL 3.8.2 : Le processus client s’identifie. Il utilise la bibliothèque LibreSSL pour les fonctions cryptographiques.
  • debug1: Reading configuration data… : Le processus fait un appel système pour lire ton fichier de config local afin d’appliquer tes réglages (User, Port, Proxy).
  • debug1: Connecting to … [64:ff9b::d28:be54] port 22. : Le client demande au Kernel d’ouvrir un socket TCP vers cette adresse IPv6.
  • debug1: Connection established. : Le Kernel a terminé le “Three-way handshake” TCP. Le tuyau réseau est ouvert.

Ce qui se passe :

  • Le processus client s’identifie et utilise la bibliothèque LibreSSL pour les fonctions cryptographiques
  • Le client fait un appel système pour lire le fichier de configuration local
  • Le client demande au Kernel d’ouvrir un socket TCP vers l’adresse IPv6
  • Le Kernel termine le “Three-way handshake” TCP - le tuyau réseau est ouvert

Niveau matériel :

  • Le CPU exécute les instructions du binaire ssh.exe
  • La carte réseau (NIC) envoie des paquets électriques/optiques (SYN) sur le réseau

SRE : si cette phase bloque sur “Connecting”, le problème est en amont du serveur (Security Group AWS, Firewall local, ou table de routage du Kernel).

Phase 2 : inventaire des clés d’identité

debug1: identity file .../id_rsa type 0
debug1: identity file .../id_rsa-cert type -1
debug1: identity file .../id_ecdsa type -1
debug1: identity file .../id_ed25519 type 3

Les lignes de debug :

  • debug1: identity file …/id_rsa type 0 : Trouvé. type 0 signifie un algorithme RSA.
  • debug1: identity file …/id_rsa-cert type -1 : type -1 signifie “Fichier non trouvé”. Pas de certificat RSA.
  • debug1: identity file …/id_ecdsa type -1 : Pas de clé ECDSA trouvée. (Idem pour les lignes suivantes avec -1 : le client scanne les formats ecdsa_sk, ed25519_sk, xmss, dsa).
  • debug1: identity file …/id_ed25519 type 3 : Trouvé. type 3 correspond à l’algorithme moderne ED25519.

Signification des types :

  • type 0 = Clé RSA trouvée
  • type 3 = Clé ED25519 trouvée
  • type -1 = Fichier non trouvé

Ce qui se passe : le client scanne ses propres fichiers de clés sur le disque dur et prépare la liste à proposer au serveur.

Niveau matériel :

  • Accès aux entrées/sorties (I/O Disque) pour lire les fichiers .ssh/id_rsa, etc.
  • La RAM stocke temporairement ces empreintes de clés

SRE : surveille les versions d’OpenSSH. Un décalage trop important peut faire échouer la négociation à cause d’algorithmes dépréciés.

Phase 3 : négociation du protocole (Banner Exchange)

debug1: Local version string SSH-2.0-OpenSSH_for_Windows_9.5
debug1: Local version string SSH-2.0-OpenSSH_for_Windows_9.5
debug1: Remote protocol version 2.0, remote software version OpenSSH_9.6p1 Ubuntu-3ubuntu13.14
debug1: Authenticating to ... as 'ubuntu'

Les lignes de debug :

  • debug1: Local version string SSH-2.0-OpenSSH_for_Windows_9.5 : Ton PC envoie sa version au serveur.
  • debug1: Remote protocol version 2.0, remote software version OpenSSH_9.6p1 Ubuntu-3ubuntu13.14 : Le serveur sshd répond. On voit qu’il tourne sur Ubuntu.
  • debug1: compat_banner: match… : Le client ajuste son comportement selon la version du serveur pour éviter les bugs de compatibilité.
  • debug1: Authenticating to … as ‘ubuntu’ : Le client déclare l’utilisateur de session choisi.

Ce qui se passe : les deux machines s’échangent leurs versions et le client ajuste son comportement selon la version du serveur pour éviter les bugs de compatibilité.

Phase 4 : échange de clés (KEX - Key Exchange)

debug1: load_hostkeys: fopen ... No such file or directory
debug1: SSH2_MSG_KEXINIT sent / received
debug1: kex: algorithm: curve25519-sha256
debug1: kex: host key algorithm: ssh-ed25519
debug1: kex: server->client cipher: chacha20-poly1305
debug1: kex: client->server cipher: chacha20-poly1305
debug1: expecting SSH2_MSG_KEX_ECDH_REPLY / received

Les lignes de debug :

  • **debug1: load_hostkeys: fopen … No such file or directory : Le client cherche tes fichiers known_hosts sur Windows pour vérifier l’identité du serveur.
  • debug1: SSH2_MSG_KEXINIT sent / received : Les deux machines s’échangent leurs listes d’algorithmes préférés.
  • debug1: kex: algorithm: curve25519-sha256 : Accord sur l’algorithme pour générer la clé de session.
  • debug1: kex: host key algorithm: ssh-ed25519 : Accord sur la méthode de signature du serveur.
  • debug1: kex: server->client cipher: chacha20-poly1305… : Choix du chiffrement pour les données reçues.
  • debug1: kex: client->server cipher: chacha20-poly1305… : Choix du chiffrement pour les données envoyées.
  • debug1: expecting SSH2_MSG_KEX_ECDH_REPLY / received : Échange Diffie-Hellman réussi. Un secret partagé est créé sans avoir transité sur le réseau.

Ce qui se passe : utilisation du protocole Diffie-Hellman (ECDH). Les deux machines calculent un secret commun sans se le transmettre directement sur le réseau.

Niveau matériel :

  • Forte sollicitation du CPU : calculs mathématiques intensifs sur les courbes elliptiques
  • Utilisation possible d’instructions matérielles dédiées au chiffrement (comme AES-NI)

SRE : en cas de forte charge CPU sur un serveur gérant des milliers de connexions SSH simultanées, c’est cette phase qui consomme le plus de ressources matérielles.

Phase 5 : authentification du serveur (Host Key)

debug1: Server host key: ssh-ed25519 SHA256:HtEBZ...
debug1: Host ... is known and matches the ED25519 host key.
debug1: Found key in C:\...\known_hosts:60

Les lignes de debug :

  • debug1: Server host key: ssh-ed25519 SHA256:HtEBZ… : Le serveur présente son empreinte digitale.
  • debug1: Host … is known and matches the ED25519 host key. : Succès. Ton PC reconnaît ce serveur.
  • debug1: Found key in C:\…\known_hosts:60 : Le client confirme avoir lu la preuve à la ligne 60 de ton fichier local.

Ce qui se passe : le serveur présente son empreinte digitale. Le client vérifie que le serveur est bien celui qu’il prétend être en comparant avec le fichier known_hosts.

Niveau matériel :

  • I/O Disque : lecture du fichier known_hosts et chargement de la ligne 60 en RAM
  • CPU : exécution d’une fonction de hachage (SHA256)

SRE : en cas d’erreur “Host key verification failed”, c’est soit une alerte de sécurité critique (attaque Man-in-the-Middle), soit le signe que l’IP a été réutilisée par une nouvelle instance.

Phase 6 : activation du chiffrement (New Keys)

debug1: ssh_packet_send2_wrapped: resetting send seqnr 3
debug1: rekey out after 134217728 blocks
debug1: SSH2_MSG_NEWKEYS sent / received

Les lignes de debug :

  • debug1: ssh_packet_send2_wrapped: resetting send seqnr 3 : Le compteur de paquets redémarre ; le chiffrement est activé.
  • debug1: rekey out after 134217728 blocks : Le Kernel et SSH prévoient de renouveler la clé après ~1 Go de données.
  • debug1: SSH2_MSG_NEWKEYS sent / received : Confirmation mutuelle que tout le trafic est désormais crypté.

Ce qui se passe : point de non-retour. Les deux machines envoient NEWKEYS pour activer le chiffrement avec le secret partagé. Le compteur de paquets est réinitialisé.

Niveau matériel :

  • Le CPU bascule en mode “chiffrement systématique” avec optimisations matérielles
  • À partir de maintenant, capturer le trafic avec Wireshark ne montrera que des données aléatoires

SRE : le rekey après ~1 Go de données est crucial pour la sécurité. Surveille la charge CPU lors de gros transferts (scp, rsync).

Phase 7 : authentification de l’utilisateur

debug1: get_agent_identities
debug1: Will attempt key
debug1: SSH2_MSG_EXT_INFO received
debug1: kex_input_ext_info: server-sig-algs=...
debug1: SSH2_MSG_SERVICE_ACCEPT received
debug1: Authentications that can continue: publickey
debug1: Next authentication method: publickey
debug1: Offering public key: .../id_rsa
debug1: Server accepts key: .../id_rsa
Authenticated to ... using "publickey".

Les lignes de debug :

  • debug1: get_agent_identities: … No such file or directory : Le client n’a pas trouvé d’Agent SSH en mémoire (RAM).
  • debug1: Will attempt key: …/id_rsa : Le client va essayer ta première clé trouvée.
  • debug1: SSH2_MSG_EXT_INFO received : Le serveur envoie des options supplémentaires de sécurité.
  • debug1: kex_input_ext_info: server-sig-algs=… : Le serveur liste les signatures qu’il accepte (ex: rsa-sha2-512).
  • debug1: SSH2_MSG_SERVICE_ACCEPT received : Le processus sshd distant est prêt pour l’auth.
  • debug1: Authentications that can continue: publickey : Le serveur dit : “Je ne veux QUE des clés, pas de mot de passe”.
  • debug1: Next authentication method: publickey : Le client s’exécute.
  • debug1: Offering public key: …/id_rsa : Envoi de ta clé publique.
  • debug1: Server accepts key: …/id_rsa : Le serveur a lu ton fichier authorized_keys et a validé la clé.
  • Authenticated to … using “publickey”. : Porte ouverte.

Ce qui se passe : le serveur annonce qu’il n’accepte que publickey. Tu présentes ta clé, le processus sshd vérifie dans authorized_keys et donne l’accès.

Niveau matériel (Serveur) :

  • Kernel Linux : appel système open() pour lire /home/ubuntu/.ssh/authorized_keys
  • Vérification des permissions : si les droits sont trop ouverts, sshd ignorera la clé par sécurité

SRE : Authentications that can continue: publickey est le standard de sécurité en Cloud. Toujours désactiver l’authentification par mot de passe dans /etc/ssh/sshd_config.

Phase 8 : ouverture de la session Shell

debug1: channel 0: new session [client-session]
debug1: Requesting no-more-sessions...
debug1: Entering interactive session.
debug1: pledge: filesystem
debug1: ENABLE_VIRTUAL_TERMINAL...
debug1: client_input_global_request...
debug1: client_input_hostkeys: searching...
debug1: Remote: /home/ubuntu/.ssh/authorized_keys:2: key options...
debug1: pledge: fork
Welcome to Ubuntu ... (GNU/Linux 6.14.0-1018-aws x86_64)

Les lignes de debug :

  • debug1: channel 0: new session [client-session] : Création d’un canal logique pour ton terminal.
  • debug1: Requesting no-more-sessions… : Optimisation du protocole.
  • debug1: Entering interactive session. : Le tunnel est prêt pour tes entrées clavier.
  • debug1: pledge: filesystem : Limitation des droits du processus ssh.exe sur ton Windows (Sécurité).
  • debug1: ENABLE_VIRTUAL_TERMINAL… : Ton terminal Windows active le support des codes couleurs Linux.
  • debug1: client_input_global_request… : Le serveur envoie des paramètres de session globaux.
  • debug1: client_input_hostkeys: searching… : Le serveur propose ses autres clés publiques pour ton futur usage.
  • debug1: Remote: /home/ubuntu/.ssh/authorized_keys:2: key options… : Le serveur confirme les options de ta clé (PTY, forwarding).
  • debug1: pledge: fork : Le client Windows s’interdit désormais de créer de nouveaux processus.
  • Welcome to Ubuntu … (GNU/Linux 6.14.0-1018-aws x86_64) : Ton shell est né. Le Kernel Linux te souhaite la bienvenue.

Ce qui se passe : le processus sshd demande au Kernel de créer un nouveau processus (fork) pour lancer le shell (/bin/bash).

Niveau matériel :

  • Le Kernel alloue une nouvelle zone de mémoire isolée
  • Attribution d’un identifiant de processus (PID)
  • Création d’un terminal virtuel (TTY)

Diagram complet du processus de connection SSH côté client

SSH connection diagram


Niveau 2 : côté serveur, l’arrivée des paquets

Observer les paquets réseau avec tcpdump

Pour voir la négociation TCP (le paquet qui frappe à la porte du Kernel), j’utilise tcpdump :

sudo tcpdump -i any port 22 -n

tcpdump command any

Ce que l’on voit sur ma capture : Sur ce dump, la session est déjà ouverte. On n’observe donc plus le handshake (flags SYN), mais le transfert de données interactives :

  • Flags [P.] (Push) : Le serveur ou le client demande au Kernel de transmettre immédiatement les données à l’application sans attendre que le buffer soit plein. C’est ce qui rend le terminal réactif.
  • Flags [.] (ACK) : L’accusé de réception. Le Kernel confirme qu’il a bien reçu les données précédentes.
  • Length 36 : La taille des paquets. En SSH interactif, les paquets sont petits car ils correspondent souvent à quelques caractères tapés ou affichés.

Lien SRE : Ici, on observe la couche transport (L4). C’est à ce niveau que l’on diagnostique les problèmes de performance réseau : si les temps entre un paquet In et sa réponse Out augmentent, on identifie immédiatement une latence entre le Kernel et l’application sshd.

Si rien ne s’affiche, c’est qu’aucun paquet ne circule physiquement sur le port. tcpdump ne lit pas des fichiers de logs. En réalité, il intercepte les paquets au moment précis où ils touchent la carte réseau (NIC) et c’est pourquoi il peut d’ailleurs ralentir les opérations.

💡 Pourquoi cette observation peut ralentir le système ?

L’acte d’observer un système peut modifier son comportement (effet Heisenbug). Voici pourquoi tcpdump est une opération coûteuse :

  • Mode Promiscuous : le Kernel demande à la carte réseau d’accepter tous les paquets du segment, augmentant les interruptions CPU.
  • Context Switching : à chaque paquet, le Kernel doit mettre en pause les autres processus (comme ton shell ou sshd) pour traiter l’interception.
  • Copie Mémoire : les données doivent être copiées depuis l’espace privilégié du Kernel (Kernel Space) vers l’espace utilisateur (User Space) où tourne l’outil, consommant des cycles CPU et de la bande passante RAM.

Note : toujours utiliser l’option -n pour éviter que tcpdump ne sature le réseau avec des requêtes DNS inutiles pour chaque IP croisée.

Filtrage précis : ne voir que les SYN

sudo tcpdump -i any "tcp[tcpflags] & (tcp-syn) != 0" -n

tcpdump command filtered

Pourquoi ce filtre ?

  • Filtrage précis : au lieu de regarder tout le port 22, on ne montre que les paquets avec le Flag SYN activé
  • Économie de ressources : sur un serveur en production pour ne pas saturer le CPU

Analyse du handshake TCP au niveau kernel

Pour capturer et analyser :

sudo tcpdump -i any port 22 -n > dump.txt
grep -A 10 "\[S\]" dump.txt

tcpdump with redirection

Avant que les processus ne s’échangent des données, le Kernel Linux gère l’ouverture du canal de communication. Voici les trois premières lignes extraites du dump réseau :

13:49:00.016816 ens5 In  IP 89.85.120.155.1558 > 172.31.40.53.22: Flags [S], seq 2674647759, win 65535, length 0
13:49:00.016850 ens5 Out IP 172.31.40.53.22 > 89.85.120.155.1558: Flags [S.], seq 4207677883, ack 2674647760, win 62727, length 0
13:49:00.046566 ens5 In  IP 89.85.120.155.1558 > 172.31.40.53.22: Flags [.], ack 1, win 255, length 0

Ce que l’on observe dans les logs :

  1. SYN (Flags [S]) : le PC Windows (89.85.120.155) envoie une demande de synchronisation au serveur sur le port 22
  2. SYN-ACK (Flags [S.]) : le Kernel Linux (172.31.40.53) répond : “Reçu, je suis prêt”
  3. ACK (Flags [.]) : le PC confirme - la connexion est établie au niveau réseau (L4)

La négociation applicative est visible dans le dump :

immédiatement après le handshake TCP, on peut voir les processus s’échanger leurs versions en clair :

13:30:36.505703 ... Out ... SSH: SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.14
13:30:36.536145 ... In ... SSH: SSH-2.0-OpenSSH_for_Windows_9.5
  1. Le serveur sshd envoie sa version au client
  2. Le client Windows répond avec sa version

Voir les logs d’authentification

Pour observer les événements au niveau du service SSH :

sudo journalctl -u ssh -f

journalctl sshd logs

Ce que l’on voit : les tentatives de connexion, les acceptations de clés, les erreurs d’authentification en temps réel.


Niveau 3 : côté serveur, la naissance du Shell

Comprendre la hiérarchie des processus

Pour voir comment sshd demande au Kernel de créer le processus bash, j’utilise strace — mais d’abord, il faut comprendre la structure des processus.

Lister les processus sshd :

ps aux | grep sshd

processus list

Ce que l’on observe :

On voit plusieurs processus sshd mais à quoi correspondent-ils ? Comment sont-ils liés entre eux ? C’est ce que l’on va déterminer en visualisant la généalogie des process.

Visualiser la généalogie complète :

pstree -p -s $$

processus tree

Rôle de chaque maillon :

  1. systemd(1) : Le patron du système (PID 1), il a lancé le service SSH au démarrage.
  2. sshd(888) : Le démon “Master” (Listener), il attend sur le port 22.
  3. sshd(1022) : Processus “Garde du corps” (root).
  • Créé dès que tu frappes à la porte.
  • Gère les étapes sensibles nécessitant les droits root (lecture de authorized_keys).
  • Correspond aux lignes sshd: ubuntu [priv].
  1. sshd(1077) : Processus de session ubuntu.
  • Une fois ton identité vérifiée, le garde du corps (1022) crée ce fils.
  • N’a plus les droits root, seulement tes droits utilisateur ubuntu.
  • C’est le principe de Privilege Separation.
  1. bash(1078) : Ton terminal, fils de ta session SSH.

Pourquoi c’est important ?

Cette structure en deux étapes (sshd [priv]sshd ubuntu) est une sécurité fondamentale. Si un pirate exploite une faille dans ton shell (1078) ou ta session (1077), il reste coincé avec les droits restreints de l’utilisateur ubuntu et ne peut pas remonter vers le processus root (1022) sans une autre faille majeure.

Note : pourquoi le PID du bash n’apparaît pas dans un simple ps aux | grep sshd ? Parce qu’après l’appel système execve, le processus fils s’est métamorphosé en bash. Pour voir l’ensemble de la chaîne, il faut utiliser une expression régulière.

ps aux | grep -E 'sshd|bash'

processus tree

On voit maintenant l’ensemble de la généalogie dans une seule commande, incluant le tunnel SSH et le shell associé.

Identifier le processus id et id parent du Shell en cours :

ps -o pid,ppid,user,args -p $$

les PIDs sont différents car issus d’une autre session.

processus id and id parent

Explication :

  • $$ = variable spéciale du shell contenant le PID du processus actuel (ton bash)
  • ppid = parent PID, le numéro du processus qui a “enfanté” ton shell

Observer la création des processus avec strace

Pour observer la naissance d’une session, je lance une connexion SSH depuis un premier terminal. Depuis un second terminal, j’utilise strace pour écouter le processus Master. Notez que le PID du Master (888) reste fixe, mais chaque nouvelle connexion générera de nouveaux PIDs éphémères.

Lancement de l’écoute sur le master :

# -f : suit les fils (forks)
# -p : s'attache au PID du Master
# -e : filtre uniquement les appels système de création et d'exécution
sudo strace -f -p 888 -e trace=execve,clone

Cette méthode permet de visualiser en temps réel la gestion de la mémoire et la création de threads par le Kernel (le passage du binaire passif à un processus actif). Le screen ci-bas indique les processus sur la machine qui écoute (ce n’est pas la généalogie des process qu’on va étudier et qui est la suivante: 888 –> 1022 –> 1077 –> 1078 pour rappel voir la capture dans comprendre la hiérarchie des processus).

SSH strace listening

1. La naissance du processus privilégié

Dès que la connexion réseau est acceptée, le master se duplique :

SSH strace root PID

  • Analyse : le Master (888) crée le PID 1022. Ce processus “Garde du corps” possède les droits root pour vérifier tes clés et lire /etc/shadow.

2. MOTD (Message Of The Day)

Avant même de pouvoir taper une commande, le système prépare l’accueil. On voit des dizaines de clone et execve :

[pid  1025] execve("/usr/bin/run-parts", ["run-parts", "--lsbsysinit", "/etc/update-motd.d"], ...) = 0
[pid  1027] execve("/usr/bin/uname", ["uname", "-o"], ...) = 0
[pid  1033] execve("/usr/bin/grep", ["grep", "-c", "^processor", "/proc/cpuinfo"], ...) = 0
[pid  1040] execve("/usr/bin/landscape-sysinfo", ["/usr/bin/landscape-sysinfo"], ...) = 0

On voit ici des captures du milieu du strace montrant les appels uname, grep ou landscape-sysinfo qui sont utilisées pour construire le MOTD affiché à la connection ssh :

SSH strace motd 1 SSH strace motd 2 SSH strace motd 3 SSH strace motd 4

  • Analyse : Chaque information affichée à la connexion (version du Kernel, charge CPU, mises à jour disponibles) est le résultat d’un nouveau processus éphémère.

  • Le mécanisme du $PATH : Sur les captures, on remarque de nombreuses erreurs ENOENT (No such file or directory). C’est tout à fait normal : pour chaque commande comme who ou run-parts, le Kernel teste successivement les répertoires listés dans la variable $PATH(ex:/usr/local/sbin , /usr/local/bin , /usr/sbin ) avant de trouver le bon exécutable dans /usr/bin/\.

Le Kernel teste donc successivement chaque répertoire du $PATH :

  1. /usr/local/sbin → ENOENT
  2. /usr/local/bin → ENOENT
  3. /usr/sbin → ENOENT
  4. /usr/binTrouvé !

3. Le passage au Shell utilisateur

Une fois l’authentification réussie, le processus root crée la session finale :

SSH strace bash exec

  • Analyse : le process 1022 (droit root) crée le process 1077 (process sans droit root) qui lui même crée le PID 1078 qui subit la transformation finale. L’appel execve remplace le code de SSH par celui de Bash. C’est à cet instant que le process 1078 devient le terminal interactif.

4. Initialisation de l’environnement Bash

Une fois Bash lancé, configuration de l’expérience utilisateur :

[pid  1079] execve("/usr/bin/locale-check", ["/usr/bin/locale-check", "C.UTF-8"], ...) = 0
[pid  1082] execve("/usr/bin/lesspipe", ["lesspipe"], ...) = 0
[pid  1086] execve("/usr/bin/dircolors", ["dircolors", "-b"], ...) = 0
  • Analyse : ces appels configurent la langue, les couleurs de la commande ls et les raccourcis de lecture.

Exploration approfondie : /proc et les File Descriptors

Je relance une nouvelle connection ssh et observe la filiation des processus. J’obtiens une nouvelle chaine :

SSH strace proc pstree output

Le fichier /proc/[PID]/status

Pour comprendre l’état de santé et les propriétés d’exécution d’un processus, il faut lire le fichier status. Voici les données relevées pour le processus SSH utilisateur (PID 7745) :

cat /proc/7745/status

SSH strace proc status 7745 SSH strace proc status 7745

Analyse des indicateurs prioritaires :

  • State : S (sleeping). c’est l’état normal pour les processus SSH (7629 et 7745). Ils “dorment” en attendant l’arrivée de données sur la socket réseau ou une action utilisateur.
  • VmRSS : mémoire physique réelle (RAM) consommée. Le processus utilisateur 7745 occupe ici environ 7164 kB. Une explosion de ce chiffre est le premier signe d’une fuite mémoire.
  • Threads : nombre d’unités d’exécution. On en compte 1 pour Bash, mais ce chiffre peut atteindre des milliers pour des applications Java ou Go.
  • CapEff (Capabilities) : il s’agit des droits spéciaux accordés sans être totalement root, ici il n’y en a pas.

Regardons le processus 7629 qui possède des capacités étendues pour gérer l’authentification CapEff :

SSH strace proc status 7629 SSH strace proc status 7269

On peut observer que CapEff n’est pas à 0 et qu’il y a donc des Capabilities qui sont donnés à ce compte (des droits spécifiques)

Variables d’environnement : /proc/[PID]/environ

Pour voir ce que le Kernel a injecté dans le processus au moment de son execve, on inspecte le fichier environ. Par défaut, les variables sont séparées par des caractères nuls (invisibles), rendant le fichier illisible avec un simple cat. Il faut soit utiliser strings (disponible dans le package binutils), soit utiliser la commande manuelle.

Utilisation de strings pour la lecture :

sudo apt install binutils
sudo strings /proc/7745/environ
sudo strings /proc/7629/environ
sudo strings /proc/888/environ
sudo strings /proc/7746/environ

Alternative sans binutils :

sudo cat /proc/[PID]/environ | tr '\0' '\n'

SSH strace proc environ

On observe que nous n’avons pas accès aux fichiers pour 7745 (sshd user), 7629(sshd root) et 888 (sshd master listening), mais en revanche on a accès au fichier d’environnement de 7746 (bash).

Note cloud AWS : comme illustré, même en utilisant sudo, l’accès au fichier environ des processus parents (comme le PID 888 ou 7629) est strictement bloqué. Sur les instances EC2, même sudo ne peut pas lire /proc/888/environ. AWS implémente des politiques de sécurité renforcées via LSM (Linux Security Modules) et AppArmor qui restreignent l’accès aux processus système, même pour root. C’est une couche de défense supplémentaire.

Ce que l’on y trouve :

  • SSH_CLIENT : affiche l’adresse IP source et le port de la connexion Windows. C’est la preuve que le Kernel porte cette information durant toute la session.
  • PATH : les dossiers où le Kernel cherche les binaires (expliquant les erreurs ENOENT observées lors du strace).
  • LANG / LC_ALL : les configurations de langue et de région.

File Descriptors : /proc/self/fd

Procédons à l’examen des File Descriptors (FD) :

1. Le processus Bash (PID 7746)

ls -l /proc/7746/fd

SSH proc fd bash

Chaque processus naît avec trois “tuyaux” standards, ici reliés au terminal virtuel (TTY) /dev/pts/0 :

  • 0 (stdin) : entrée standard qui attend les saisies clavier.
  • 1 (stdout) : sortie standard qui renvoie les caractères à l’écran.
  • 2 (stderr) : sortie d’erreur pour les messages d’alerte.
  • 255 : descripteur spécifique à Bash, agissant comme une copie de secours du terminal pour restaurer l’affichage en cas d’erreur d’une commande.

2. Le processus SSH utilisateur (PID 7745)

Pour observer la fonctionnement réseau de la session ssh, il faut regarder le file descriptor du parent direct du Bash :

sudo ls -l /proc/7745/fd

SSH proc fd user

Ici, la structure est différente et nous allons voir qu’elle partage des éléments avec le processus sshd root 7629 :

  • 0, 1, 2 ➔ /dev/null : SSH étant un démon, ses propres entrées/sorties sont redirigées vers “la poubelle”.
  • Sockets (3, 4, 5, 6) : représentent la connexion réseau. L’un de ces descripteurs reçoit les paquets chiffrés venant de Windows. –> C’est aussi ici qu’elle partage des éléments avec le processus sshd root (comme la socket extérieure fichier 4 - on va voir ça juste en bas.)
  • ptmx (7, 10, 11) : Le Pseudo-Terminal Multiplexer est le composant du Kernel qui fait le pont entre le SSH déchiffré et le terminal /dev/pts/0.

Regardons maintenant le file descriptor du processus sshd root :

SSH proc fd root

Comment savoir quelle est la socket extérieure ? En vérifiant avec sudo ss -ntp, celle qui est relié à l’IP windows est une socket extérieure.

SSH proc fd ss

On peut observer fd=4 qui signifie file descriptor 4, et si on regarde dans le fichier file descriptor juste en haut, on verra que le fichier 4 est relié à la socket 30695 (4 -> ‘socket:[30695]’) pour les deux processus 7629 et 7745 (respectivement root et utilisateur).

Bien que les deux processus gèrent la même session, on remarque des inodes de sockets différentes (ex: [31124] sur l’un, [31294] sur l’autre). Le processus root (7629) détient la socket “maîtresse” connectée au réseau extérieur mais la partage avec l’utilisateur 7745.

Le processus utilisateur (7745) possède ses propres sockets de communication interne. SSH sépare strictement les privilèges : le processus root gère le chiffrement lourd et le réseau, puis transmet les données déchiffrées au processus utilisateur via une paire de sockets locales (socketpair). C’est pour cela que les inodes diffèrent : il s’agit d’un relais interne sécurisé.

Schéma de la communication SSH

┌─────────────────┐
│  Windows Client │
│       IP        │
└────────┬────────┘
         │ Socket [30695]
         ▼
┌─────────────────┐
│ sshd [priv]     │  ← FD 4 (root, chiffrement)
│ PID 7629        │
└────────┬────────┘
         │ Relay interne (socketpair)
         ▼
┌─────────────────┐
│ sshd user       │  ← FD 4 (utilisateur, déchiffré)
│ PID 7745        │
└────────┬────────┘
         │ ptmx (Pseudo-Terminal)
         ▼
┌─────────────────┐
│ bash            │  ← /dev/pts/0
│ PID 7746        │
└─────────────────┘

Flux de données :

  1. Paquets chiffrés arrivent sur la socket [30695]
  2. Le processus root (7629) déchiffre avec ses Capabilities
  3. Les données sont transmises au processus utilisateur (7745) via un relay interne sécurisé
  4. Le ptmx crée le terminal virtuel pour bash (7746)

Je remets ici une capture d’écran complète pour bien tout observer en même temps et mieux comprendre :

SSH proc fd global

Corrélation Socket et IP réseau

Comme vu précédemment, pour prouver que la socket listée dans /proc/7745/fd est bien le tunnel vers l’adresse IP Windows, on utilise l’outil ss (Socket Statistics) :

sudo ss -ntp | grep 7745
sudo ss -ntp | grep 7629

SSH proc fd ss

Le système affiche alors une ligne confirmant la connexion entre l’IP Windows (89.85.120.170) et l’instance sur le port 22. Le numéro d’inode de cette socket correspondra exactement à celui trouvé dans le répertoire /proc/7745/fd. La boucle entre le matériel (réseau), le Kernel et le processus utilisateur est bouclée.

Les limites du système : /proc/self/limits

cat /proc/self/limits

proc/self/limit

Pourquoi c’est vital ?

Le Kernel Linux n’accorde jamais de ressources illimitées. Pour éviter qu’un seul processus ne sature tout le matériel, il impose des quotas.

Lignes critiques :

  • Max open files (Nofile) :

    • Signification : nombre maximum de “fichiers” qu’un processus peut manipuler simultanément
    • Piège : une connexion réseau SSH = un fichier (socket). Si limite trop basse (ex: 1024), un serveur web ne peut pas gérer plus de 1000 visiteurs
  • Max processes (Nproc) :

    • Signification : nombre total de processus que l’utilisateur peut lancer
    • Lien avec strace : si limite atteinte, clone() échouera avec l’erreur EAGAIN
  • Max resident set (RSS) :

    • Signification : limite de RAM physique autorisée pour ce processus
    • Lien matériel : si dépassement, le Kernel “swapper” (écrire sur disque au lieu de RAM) = chute des performances

Soft Limit vs Hard Limit :

  • Soft Limit : limite actuelle, le processus peut la modifier lui-même
  • Hard Limit : plafond absolu, seul root peut augmenter cette valeur

Récap outils et étapes

Mapping d’une session ssh

  1. Matériel : carte réseau reçoit les paquets
  2. Kernel : handshake TCP se fait
  3. Processus parent : le master sshd (celui qui écoute l’extérieur) détecte l’appel
  4. Syscalls : sshd appelle clone() et execve() pour créer une session
  5. Utilisateur : le bash est prêt

Vision SRE

EtapesOutils / sourcesNiveau d’analyse
Arrivée réseautcpdumpCarte Réseau (NIC) → Kernel
Négociationssh -vApplication (Windows) → Processus (sshd)
Auth & créationstraceKernel Syscalls (clone, execve)
GénéalogiepstreeHiérarchie des processus (Parent/Enfant)
Contrôle/proc/limitsGouvernance du Kernel sur le matériel
Communication/proc/fdTuyauterie physique (Sockets / TTY)
Qualité de lienss -iStatistiques TCP du Kernel

Fichiers clés à connaître

FichierCe qu’il représenteComposant matériel
/proc/[PID]/statusÉtat dynamique (RAM, Threads, PID)RAM & CPU Scheduler
/proc/[PID]/environContexte de lancement (Variables)Mémoire du Processus
/proc/[PID]/fd/Fichiers et Sockets réseau ouvertsDisque & Carte Réseau
/proc/[PID]/limitsQuotas de ressourcesGouvernance Kernel

Conclusion

Chaque action logicielle a un impact matériel mesurable (CPU, RAM, NIC, Disque)

Savoir identifier à quelle couche se situe un problème (Réseau ? Kernel ? Application ? Permissions ?) est essentiel, mais pour cela il faut comprendre comment ça fonctionne.

Prochaines étapes

  • Test de chaos engineering (pousser les limites pour voir quand et comment ça bug)
  • Approfondir la compréhension des cgroups / namespaces et de systemd
  • Explorer eBPF pour le tracing kernel
  • Explorer les métriques de performance
  • Étudier le fonctionnement du scheduler Linux
  • Comprendre la gestion de la mémoire (MMU, Page Tables)