Nix a besoin de binaires relogeables
(fzakaria.com)- Nix, un gestionnaire de paquets basé sur un store, est conçu pour placer les paquets sous un préfixe fixe comme
/nix/store, ce qui impose de fortes contraintes dans les environnements Nix sans privilèges root où l’on veut utiliser un store ailleurs sans installation Nix existante ni droits root - En utilisant
--store /tmp/...avecchrootet un espace de noms de montage, il est possible de conserver le même hachage que pour une compilation classique dans/nix/store, et donc de continuer à exploiter des caches binaires commecache.nixos.org - Si l’on change le préfixe du store sans espace de noms via
local?store=/tmp/..., le hachage change, et même une simple compilation dehellopeut invalider tout le graphe de dépendances et mener à une recompilation de GCC - Le cœur de la proposition consiste à utiliser dans le
RUNPATHELF des chemins relatifs basés sur$ORIGIN, pris en charge par l’éditeur de liens dynamique Linux, au lieu de chemins absolus, afin d’éviter qu’un changement d’emplacement du store ne se propage au hachage et aux recompilations - Le principal blocage à une véritable relogeabilité est que le noyau ne prend pas en charge
$ORIGINdansPT_INTERPdes ELF ni dans les shebangs de scripts ; comme pistes, l’article propose un patch du noyau, des wrappers statiques, des chemins relatifs selon les langages et un méta-attributrelocatable = true;
Conflit entre préfixe de store fixe et Nix sans root
- Les systèmes basés sur un store comme Nix et Guix stockent tous les paquets sous un préfixe déterminé
- Nix :
/nix/store - Guix :
/gnu/store
- Nix :
- Cette structure facilite la réécriture des chemins des binaires et des bibliothèques
- Par exemple,
/bin/bashpeut être remplacé par un chemin complet du store comme/nix/store/gik3rh1vz2jlgnifb9dh6vc6sxwwz9jj-bash-5.3p9/bin/bash
- Par exemple,
- Il existe aussi des situations où l’on veut placer le store ailleurs
- environnements où Nix n’est pas déjà installé
- environnements où les droits nécessaires ne sont pas disponibles
- c’est ce qui mène au problème du « Nix sans root »
- Nix permet déjà de spécifier un autre chemin de store, mais selon la méthode, le hachage est conservé ou non
nix build nixpkgs#helloinstalle dans/nix/store/zi2bj2hlavv8q743li2s9diqbcpmrf9b-hello-2.12.3/nix build --store /tmp/fzakaria/store nixpkgs#helloinstalle dans/tmp/fzakaria/store/nix/store/zi2bj2hlavv8q743li2s9diqbcpmrf9b-hello-2.12.3/en utilisantchrootet un espace de noms de montage- dans les deux cas, le hachage
zi2bj2hlavv8q743li2s9diqbcpmrf9best identique
- Quand le hachage est identique, on peut réutiliser des dérivations pré-calculées depuis un substituteur binaire comme https://cache.nixos.org
Le coût d’un changement de store sans espace de noms
- Des outils comme Bazel ou Buck2 utilisent déjà potentiellement des espaces de noms pour leur propre sandboxing
- intégrer Nix dans cet écosystème devient peu pratique à cause des limitations sur l’imbrication des espaces de noms utilisateur et de montage
- Il est possible de définir un préfixe de store alternatif sans
chrootni espace de noms de montage, mais cela présente un défaut : le hachage change- la commande d’exemple utilise
--store 'local?store=/tmp/fzakaria/store&state=/tmp/fzakaria/state&log=/tmp/fzakaria/log' - le chemin résultant pour
hellodevient/tmp/fzakaria/store/qv3fhi1j9gh27fyds5n5b16yia8i6zn5-hello-2.12.3 - le hachage n’est plus
zi2..., maisqv3fhi1j9gh27fyds5n5b16yia8i6zn5
- la commande d’exemple utilise
- Un simple changement de chaîne dans le préfixe du store peut invalider en cascade l’ensemble du graphe de dépendances
- compiler GCC pendant quatre heures juste pour afficher « Hello World » depuis un autre dossier devient possible
- dans ce cas, les caches publics ne peuvent pas être utilisés
- Cette limite est actuellement documentée dans la documentation Nix
Ce que résout $ORIGIN, et les limites restantes du noyau
- Le problème vient du fait que le préfixe du store fait partie de la dérivation elle-même, et influence donc le calcul du hachage
- Si l’on n’utilise pas le préfixe complet du store partout, mais des chemins relatifs, on peut éviter les changements de hachage
- Le
RUNPATHdes binaires ELF est l’un des points où cela peut s’appliquer- l’exemple actuel de
RUNPATHpourhelloest/nix/store/57iz36553175g3178pvxjij8z5rcsd4n-glibc-2.42-61/lib - le chargeur Linux prend en charge
$ORIGIN, qui désigne le répertoire contenant l’exécutable - on pourrait donc écrire le
RUNPATHsous la forme$ORIGIN/../../57iz36553175g3178pvxjij8z5rcsd4n-glibc-2.42-61/lib - ainsi, déplacer le store ne changerait plus le hachage et ne nécessiterait plus de recompilation
- l’exemple actuel de
- Mais avant que l’éditeur de liens dynamique ne lise le
RUNPATH, le noyau Linux doit d’abord charger l’éditeur de liens lui-même- ce chemin est stocké dans l’en-tête
PT_INTERPde l’ELF - l’exemple donné est
/nix/store/57iz36553175g3178pvxjij8z5rcsd4n-glibc-2.42-61/lib/ld-linux-x86-64.so.2 - le noyau Linux ne prend actuellement pas en charge
$ORIGINdansPT_INTERP
- ce chemin est stocké dans l’en-tête
- Les shebangs de scripts ont la même contrainte
- exemple :
#!/nix/store/gik3rh1vz2jlgnifb9dh6vc6sxwwz9jj-bash-5.3p9/bin/bash - le noyau attend un chemin absolu lorsqu’il analyse
#! - là aussi,
$ORIGINn’est actuellement pas pris en charge
- exemple :
- Il est possible d’utiliser des chemins relatifs par rapport au répertoire de travail courant, mais cela casse dès qu’on exécute le script depuis un autre emplacement, donc ce n’est pas fiable
Vers des binaires relogeables
- Pour créer de véritables binaires relogeables, il faut contourner ou modifier les contraintes du noyau
- Trois approches sont proposées
- patcher le noyau Linux pour qu’il prenne en charge
$ORIGINdansPT_INTERPet les shebangs - envelopper tous les binaires dans un petit binaire statique qui calcule son propre emplacement puis lance l’éditeur de liens dynamique
- exploiter des mécanismes de chemins relatifs propres à chaque langage pour les fichiers
- en Python,
__file__permet d’accéder à des fichiers relativement à l’emplacement du script
- en Python,
- patcher le noyau Linux pour qu’il prenne en charge
- L’approche jugée la plus adaptée est l’extension de la prise en charge côté noyau Linux
- sur les machines NixOS, Nix peut servir à patcher le noyau pour ajouter ce support
- En complément, l’article propose d’ajouter à chaque dérivation un méta-attribut
relocatable = true;indiquant si elle peut être relogée
1 commentaires
Avis sur Lobste.rs
Ce serait bien que le noyau Linux ajoute la prise en charge de
$ORIGINdansPT_INTERP. J’avais déjà essayé auparavant avec un binaire wrapper statique, et j’ai vu plusieurs autres tentatives (bon exemple) ; tout cela constitue d’excellents hacks très élégants, mais ça reste des hacksJe n’ai pas encore bien compris les implications en matière de sécurité, donc une explication claire et structurée serait utile
Solaris semble le prendre en charge, donc il existe peut-être une manière sûre de le faire. C’est difficile de trouver des sources, mais dans l’ENOEXEC du manuel
execve(2), il est indiqué que l’exécution échoue si l’en-tête de programmePT_INTERPdu fichier image d’un processus setuid/setgid contient un chemin relatif ou utilise le jeton$ORIGINBeaucoup de logiciels contiennent des chemins intégrés au moment de la compilation ou des constantes ; ne faut-il pas de toute façon les recompiler pour qu’ils fonctionnent correctement ?
outPathvia{foo}; si l’une des dépendances de niveau supérieur est remplacée par un dépôt local, il faut recompilerLe patch dcrt1 pour musl (écrit par rcombs) résout ce problème en espace utilisateur
Ne pourrait-on pas créer un système de fichiers monté sur
/originpour qu’il soit résolu comme$ORIGIN? Cela fonctionnerait alors à la fois pour les shebangs et pour l’ELF, sans syntaxe supplémentaire/origin, ne pourrait-on pas simplement créer/nixet lancernix-daemon?Si un binaire peut spécifier un loader relatif, probablement non sûr et fourni par lui-même, n’y a-t-il pas un risque de sécurité ?
libc.so.6?