Il y a des problèmes informatiques qui ne seront jamais résolus parce qu'ils sont intrinsèquement difficiles (par exemple, pour une raison algorithmique) : on est bien obligé de le comprendre. Il y en a d'autres qui posent des difficultés non pas pour des raisons intrinsèques mais pour des raisons historiques : on peut aussi comprendre que ce soit difficile de se battre avec des bizarreries historiques qui se sont profondément enracineés (le changement du protocole Internet d'IPv4 à la version IPv6 représent un bon exemple de cette nature). Mais il y en a aussi, et là c'est vraiment désolant, qui ne seront jamais résolus parce que les gens qui pourraient les résoudre ont des opinions dogmatiques sur la façon dont les choses devraient être faites, et que ces opinions empêchent toute résolution possible du problème. Je voudrais donner un exemple très concret.
Je situe d'abord un peu les choses pour ceux qui ne connaissent pas
le monde Unix. Le principal protocole qui permet à Internet de
fonctionner est appelé TCP/IP : plus
exactement, IP est le protocole qui donne un (ou
plusieurs) numéro(s) à chaque ordinateur, et leur permet d'échanger
des « paquets » d'information, et TCP vient là-dessus
fournir une notion de connexion fiable. Dans le monde Unix,
les deux bouts de cette connexion s'appellent des sockets
(traduire ça comme des prises
et pas comme
des chaussettes
; mais en pratique, même quand on parle en
français, on dit une socket
) : une
socket TCP/IP (ou, dans le jargon plus
unixien, une socket de domaine
) est l'abstraction par laquelle un
processus (=programme) sur une machine peut parler à un autre, a
priori situé sur une machine distante, via le réseau. Maintenant,
il arrive aussi qu'un processus veuille communiquer avec un processus
sur la même machine : Unix offre un véritable labyrinthe de façons
différentes de faire ça ; d'aucunes sont très différentes, mais
certaines utilisent la même abstraction de AF_INET
et de
type SOCK_STREAM
socket
. Notamment,
deux processus Unix peuvent communiquer entre eux par une socket de
domaine Unix (
, qui apparaît alors comme un
fichier spécial sur la machine (représentant le point de
communication), ou encore par une socket AF_UNIX
)de domaine INET
(
, c'est-à-dire en faisant exactement comme
une connexion réseau Internet mais qui se trouve simplement relier la
machine à elle-même. Il peut y avoir plein de raisons, liées aux
idiosyncrasies d'Unix, de préférer, ou de devoir choisir, une socket
Unix ou au contraire une socket INET.AF_INET
)
Maintenant, quand deux processus Unix communiquent par une socket
de domaine Unix, chacun des deux a la capacité d'identifier l'autre,
c'est-à-dire de demander au système qui est en train de me parler
par cette socket ?
(la réponse donne le numéro du processus et
l'identitié de l'utilisateur qui en est propriétaire ; cette réponse
est fiable, parce qu'apportée par le noyau Unix lui-même ;
techniquement, sous Linux, cette information s'obtient
avec getsockopt(socket, SOL_SOCKET,
SO_PEERCRED,,)
). Quand on a affaire à une socket de domaine
INET, en général, vu que la communication peut venir de n'importe où
sur Internet, on ne peut pas identifier complètement le processus en
face, on peut simplement demander l'identité de la machine en
face (son numéro IP ; techniquement, ceci s'obtient
avec getpeername(socket,,)
). Mais si la
socket INET est reliée à la machine elle-même, c'est-à-dire, si deux
processus sur la même machine, sont en train de parler entre eux par
une connexion TCP/IP, le système pourrait
très bien fournir la même information (quel est le processus en
face ?
) qu'il accepte de le faire pour les sockets de domaine
Unix : seulement, il ne le fait pas. Ou plus exactement, ni Linux ni
Unix BSD ne le font (Solaris, en revanche, accepte de le
faire par l'appel système getpeerucred()
).
Or ceci est un manque grave, que je voudrais bien voir corrigé.
Pourquoi ? Le problème est que pour toutes sortes de raisons, on peut
ne pas avoir le choix du protocole qu'on parle : si on est obligé
d'utiliser une socket de domaine INET, le fait de ne pas pouvoir
obtenir plus d'information que l'autre bout est sur la même
machine
empêche de mettre des contrôles d'accès offrant une
sécurité minimale dans certaines situations. Je donne un exemple.
Considérons le programme ssh
, qui sert à mettre en
place une connexion cryptographiquement sécurisée entre deux
ordinateurs : on l'exécute sur la machine A en lui
demandant de se connecter à la machine B, il effectue une
négociation cryptographique entre les deux et permet ensuite
d'exécuter des commandes sur B de façon protégée. Une
fonction auxiliaire de ssh
est qu'on peut s'en servir
pour faire du forward de ports : c'est-à-dire qu'on demande
à ssh
d'accepter des
connexions TCP/IP sur (un certain
port TCP de) la machine A, de les transmettre
à travers le canal cryptographique, et de les réémettre à partir de la
machine B vers une tierce machine C (qui
pourrait, d'ailleurs, être égale à B). Ceci sert pour
toutes sortes de raisons, par exemple si je travaille sur A
mais que je veux faire croire à C que je viens
de B (parce que C n'autorise que B à
se connecter, ou pour je ne sais quelle autre raison). Je me sers par
exemple de ce mécanisme pour lire des articles de maths depuis chez
moi : je me connecte par ssh
depuis ma
machine A chez moi vers ma machine B au bureau,
je demande à ssh
de transmettre les connexions vers le
serveur Web C de l'éditeur de l'article, qui reconnaît ma
machine professionnelle B comme ayant accès aux abonnements
à telle ou telle revue scientifique, alors que ma machine
privée A serait refusée.
Maintenant, ce « forward de ports » est a priori destiné à un usage
privé : je veux que mes propres processus sur la machine A
puissent faire semblant de se connecter à C
depuis B, pour ça je leur demande de se connecter
au ssh
qui tourne sur la machine A, et je
voudrais bien que ce ssh
n'accepte que les connexions
venant de moi-même. Or dans toute cette histoire, toutes les
connexions passent par des sockets de domaine INET : il n'y a guère le
choix, parce que ce sont des connexions et des protocoles qui doivent
pouvoir transiter sur Internet. À cause de la limitation dont j'ai
parlé plus haut, ssh
ne peut pas vérifier que les
processus qui se connectent à lui (pour demander à bénéficier du
forward de ports) sont bien du même utilisateur : tout au plus, il
peut vérifier qu'ils tournent bien sur la même machine. Si la
machine A est multi-utilisateurs, ceci permet à n'importe
quel utilisateur de A de bénéficier du forward de
ports ssh
mis en place par n'importe quel
utilisateur.
C'est un problème assez catastrophique, et qui existe depuis bien des années. (Certes, beaucoup de machines sont, en fait, des machines personnelles et mono-utilisateur, auquel cas le problème est beaucoup plus mineur, mais on ne peut pas sérieusement faire cette hypothèse de façon générale.)
Et le plus ironique, c'est qu'il serait passablement facile à
corriger. Il faudrait modifier deux choses : (1) le noyau Linux, pour
lui permettre, comme sait le faire Solaris, d'identifier les
connexions locales même dans le domaine INET (et pas seulement le
domaine Unix), et (2) le programme ssh
(OpenSSH), pour utiliser cette identification et
n'accepter (optionnellement) que les connexions venant de la même
machine et du même utilisateur (c'est la partie en italique
qui serait nouvelle). Ces deux modifications devraient être certes
pas triviales mais pas immensément difficiles à apporter. (Le (1) est
le plus difficile, mais je sais que le noyau a l'information
nécessaire, puisqu'il l'expose dans le
pseudo-fichier /proc/net/tcp
— et d'ailleurs un programme
comme identd
s'en sert.) Le code du noyau Linux et de
OpenSSH sont libres, donc on peut y faire des
changements : quelle est, alors, la difficulté ?
La difficulté, ce sont les Gardiens du Code (je me permets de nouveau une référence à cette nouvelle de Kafka, Devant la Loi).
Parce que certes la licence de Linux et de OpenSSH m'autorise à faire des modifications dessus, mais si ce sont des modifications personnelles et qu'elles ne sont pas intégrées au code officiel, cela signifie qu'à chaque nouvelle version de l'un ou de l'autre il faudra fusionner les modifications que j'aurais faites et celles apportées par le code officiel : c'est un boulot dément (surtout s'agissant de Linux, qui bouge extrêmement vite). En pratique, donc, une modification faite à Linux qui n'est pas acceptée dans le code officiel est morte (ou alors il faut vraiment quelqu'un à plein temps pour s'en occuper). Donc pour corriger le problème, il faut faire accepter la modification par les Gardiens du Code officiel.
Mais ceux-ci (A) sont débordés débordés débordés, donc ils
regardent avec méfiance tout ce qui leur arrive (et on espère qu'ils
sont méfiants, parce que tout morceau de code intégré dans Linux
affecte directement la sécurité de millions d'ordinateurs), et
(B) ont, comme ont tendance à l'avoir les geeks, des
idées extrêmement arrêtées sur la façon dont les choses
doivent être faites. Or voilà ce qui est complètement prévisible sur
un problème dont la solution demande d'apporter un correctif à la fois
à Linux et à OpenSSH : les Gardiens de Linux vont
dire ce problème ne nous intéresse pas, c'est un problème dans
OpenSSH, pas chez nous
, et les Gardiens de
OpenSSH vont dire ce problème ne nous intéresse pas si
on ne peut y apporter une réponse que sous Linux et pas sous les
autres Unix, ce n'est pas une vraie réponse
. Voire, les Gardiens
de Linux vont dire cette fonctionalité (permettre d'authentifier
les connexions locales de domaine INET) n'a pas d'intérêt, personne ne
s'en servira(it), commencez par faire que OpenSSH s'en
serve et alors on verra
, et les Gardiens de OpenSSH
vont dire cette fonctionalité n'existe pas encore, on verra quand
elle existera
. Pour couronner le tout, les Gardiens des deux
côtés auront plein d'idées (complètement théoriques et absolument
inapplicables en pratique) sur la façon dont le problème de départ
(que personne ne conteste : ssh
ouvre ses connexions à
tous les vents) devrait être corrigé (du style : ah, il faut faire des
sockets de domaine Unix pour ça ; bon, mais en pratique ça
nécessiterait de réécrire des tonnes de programmes et de protocoles
Internet pour autoriser des connexions de domaine Unix, par exemple
pour le HTTP, ce n'est pas sérieusement envisageable en
pratique).
Et encore, même si on arrive à avoir un non
d'un Gardien,
c'est déjà quelque chose. Le plus souvent, on se heurtera simplement
à une absence complète de réponse. La manière dont je verrais les
choses, pour soumettre du code à Linux, on commencerait par exposer ce
qu'on veut faire, un mainteneur apporterait une réponse de principe
(oui, a priori je veux bien intégrer quelque chose comme ça
dans Linux
ou non, c'est hors de question
), et si la
réponse a priori est oui, on propose du code et on discute de
son intégration réelle. En pratique, ce qui se passe est que si on ne
propose pas de code, on ne reçoit jamais de réponse, et si on en
propose, il est fréquent de ne jamais recevoir de réponse non plus,
sauf si on est déjà un contributeur reconnu (c'est-à-dire,
essentiellement, travaillant à temps plein pour écrire du code pour
Linux). (Voici un
exemple parmi d'autres.)
Bref, j'ai posé une
question de principe pour le sujet évoqué ci-dessus sur la liste
de diffusion du noyau Linux et, comme je m'y attenais, je n'ai reçu
qu'une réponse de quelqu'un qui a mal lu et mal compris ce que je
proposais et qui m'a essentiellement traité de con (hors de
question : c'est trop dangereux
, avec un argument pas absurde sur
le fond, mais totalement exagéré : les processus privilégiés ne
s'attendent pas à ce que leurs privilèges puissent être examinés dans
leur dos). À vrai dire, je ne sais pas si celui qui m'a répondu est
véritablement un Gardien (son nom apparaît bien dans quelques fichiers
du noyau, mais il n'est pas un mainteneur officiel), mais en tout cas
il est certain que la seule réaction que j'ai attirée est une réaction
du type ce n'est pas comme ça qu'il faut faire
(je m'en doutais
complètement). Pas la moindre réaction du type tiens, c'est une
bonne idée
(même de la part d'un passant).
Du coup, je me demande bien si ça vaut la peine que j'essaie d'écrire le code pour Linux, sachant que j'estime pifométriquement à environ 5% la probabilité que le code soit accepté, et que même dans ce cas, il resterait encore à voir si on peut convaincre OpenSSH d'en faire usage, ce qui est probablement aussi peu gagné.
Quoi qu'il en soit, si quelqu'un trouve que je devrais écrire ce code, je vous encourage à voter avec vos pieds : postez sur la mailing-list en réponse dans le fil pour dire que ça vous paraît une bonne idée (et pourquoi), et peut-être que si le fil grossit il attirera l'attention de quelqu'un en position de Gardien qui pourrait consentir à un oui de principe.
Mais en toute honnêteté, je pense que ce problème ne sera jamais corrigé. Et c'est vraiment désolant, vu que ça ne devrait franchement pas être monstrueusement difficile.