🎯 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
- Stack du lab
- Vue d’ensemble : les trois niveaux d’observation
- Niveau 1 : côté client,la négociation SSH
- Niveau 2 : côté serveur, l’arrivée des paquets
- Niveau 3 : côté serveur, la naissance du Shell
- Exploration approfondie : /proc et les File Descriptors
- Synthèse
- 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.
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 :
| Étape | Outil / Source | Niveau d’Analyse |
|---|---|---|
| Arrivée réseau | tcpdump | Carte Réseau (NIC) → Kernel |
| Négociation | ssh -v | Application (Windows) → Processus (sshd) |
| Auth & création | strace | Kernel Syscalls (clone, execve) |
| Généalogie | pstree | Hiérarchie des processus (Parent/Enfant) |
| Contrôle | /proc/limits | Gouvernance du Kernel sur le Matériel |
| Communication | /proc/fd | Tuyauterie physique (Sockets / TTY) |
| Qualité de lien | ss | Statistiques 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).
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éetype 3= Clé ED25519 trouvéetype -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_hostset 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,
sshdignorera 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
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
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.
tcpdumpne 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
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
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 :
- SYN (Flags [S]) : le PC Windows (89.85.120.155) envoie une demande de synchronisation au serveur sur le port 22
- SYN-ACK (Flags [S.]) : le Kernel Linux (172.31.40.53) répond : “Reçu, je suis prêt”
- 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
- Le serveur
sshdenvoie sa version au client - 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
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
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 $$
Rôle de chaque maillon :
- systemd(1) : Le patron du système (PID 1), il a lancé le service SSH au démarrage.
- sshd(888) : Le démon “Master” (Listener), il attend sur le port 22.
- 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].
- 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.
- 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èmeexecve, le processus fils s’est métamorphosé enbash. Pour voir l’ensemble de la chaîne, il faut utiliser une expression régulière.
ps aux | grep -E 'sshd|bash'
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.
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
stracepour é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).
1. La naissance du processus privilégié
Dès que la connexion réseau est acceptée, le master se duplique :
- 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 :
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 commewhoourun-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 :
/usr/local/sbin→ ENOENT/usr/local/bin→ ENOENT/usr/sbin→ ENOENT/usr/bin→ Trouvé !
3. Le passage au Shell utilisateur
Une fois l’authentification réussie, le processus root crée la session finale :
- 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
execveremplace 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
lset 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 :
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
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 :
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'
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 fichierenvirondes processus parents (comme le PID 888 ou 7629) est strictement bloqué. Sur les instances EC2, mêmesudone 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
ENOENTobservé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
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
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 Multiplexerest 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 :
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.
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 :
- Paquets chiffrés arrivent sur la socket [30695]
- Le processus root (7629) déchiffre avec ses Capabilities
- Les données sont transmises au processus utilisateur (7745) via un relay interne sécurisé
- 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 :
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
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
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’erreurEAGAIN
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
- Matériel : carte réseau reçoit les paquets
- Kernel : handshake TCP se fait
- Processus parent : le master sshd (celui qui écoute l’extérieur) détecte l’appel
- Syscalls : sshd appelle
clone()etexecve()pour créer une session - Utilisateur : le bash est prêt
Vision SRE
| Etapes | Outils / sources | Niveau d’analyse |
|---|---|---|
| Arrivée réseau | tcpdump | Carte Réseau (NIC) → Kernel |
| Négociation | ssh -v | Application (Windows) → Processus (sshd) |
| Auth & création | strace | Kernel Syscalls (clone, execve) |
| Généalogie | pstree | Hiérarchie des processus (Parent/Enfant) |
| Contrôle | /proc/limits | Gouvernance du Kernel sur le matériel |
| Communication | /proc/fd | Tuyauterie physique (Sockets / TTY) |
| Qualité de lien | ss -i | Statistiques TCP du Kernel |
Fichiers clés à connaître
| Fichier | Ce qu’il représente | Composant matériel |
|---|---|---|
/proc/[PID]/status | État dynamique (RAM, Threads, PID) | RAM & CPU Scheduler |
/proc/[PID]/environ | Contexte de lancement (Variables) | Mémoire du Processus |
/proc/[PID]/fd/ | Fichiers et Sockets réseau ouverts | Disque & Carte Réseau |
/proc/[PID]/limits | Quotas de ressources | Gouvernance 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)

































