David Madore's WebLog: De la difficulté de soumettre du code à Linux

[Index of all entries / Index de toutes les entréesLatest entries / Dernières entréesXML (RSS 1.0) • Recent comments / Commentaires récents]

↓Entry #2234 [older| permalink|newer] / ↓Entrée #2234 [précédente| permalien|suivante] ↓

(jeudi)

De la difficulté de soumettre du code à Linux

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 AF_INET et de type SOCK_STREAM) 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 socket. Notamment, deux processus Unix peuvent communiquer entre eux par une socket de domaine Unix (AF_UNIX), qui apparaît alors comme un fichier spécial sur la machine (représentant le point de communication), ou encore par une socket de domaine INET (AF_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.

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.

↑Entry #2234 [older| permalink|newer] / ↑Entrée #2234 [précédente| permalien|suivante] ↑

[Index of all entries / Index de toutes les entréesLatest entries / Dernières entréesXML (RSS 1.0) • Recent comments / Commentaires récents]