1 points par GN⁺ 3 시간 전 | 1 commentaires | Partager sur WhatsApp
  • 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/... avec chroot et 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 comme cache.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 de hello peut invalider tout le graphe de dépendances et mener à une recompilation de GCC
  • Le cœur de la proposition consiste à utiliser dans le RUNPATH ELF 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 $ORIGIN dans PT_INTERP des 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-attribut relocatable = 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
  • Cette structure facilite la réécriture des chemins des binaires et des bibliothèques
    • Par exemple, /bin/bash peut être remplacé par un chemin complet du store comme /nix/store/gik3rh1vz2jlgnifb9dh6vc6sxwwz9jj-bash-5.3p9/bin/bash
  • 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#hello installe dans /nix/store/zi2bj2hlavv8q743li2s9diqbcpmrf9b-hello-2.12.3/
    • nix build --store /tmp/fzakaria/store nixpkgs#hello installe dans /tmp/fzakaria/store/nix/store/zi2bj2hlavv8q743li2s9diqbcpmrf9b-hello-2.12.3/ en utilisant chroot et un espace de noms de montage
    • dans les deux cas, le hachage zi2bj2hlavv8q743li2s9diqbcpmrf9b est 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 chroot ni 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 hello devient /tmp/fzakaria/store/qv3fhi1j9gh27fyds5n5b16yia8i6zn5-hello-2.12.3
    • le hachage n’est plus zi2..., mais qv3fhi1j9gh27fyds5n5b16yia8i6zn5
  • 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 RUNPATH des binaires ELF est l’un des points où cela peut s’appliquer
    • l’exemple actuel de RUNPATH pour hello est /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 RUNPATH sous 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
  • 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_INTERP de 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 $ORIGIN dans PT_INTERP
  • 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, $ORIGIN n’est actuellement pas pris en charge
  • 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 $ORIGIN dans PT_INTERP et 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
  • 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

 
GN⁺ 3 시간 전
Avis sur Lobste.rs
  • Ce serait bien que le noyau Linux ajoute la prise en charge de $ORIGIN dans PT_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 hacks
    Je 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 programme PT_INTERP du fichier image d’un processus setuid/setgid contient un chemin relatif ou utilise le jeton $ORIGIN

  • Beaucoup 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 ?

    • C’est déjà souvent le cas, surtout avec les lignes shebang. Nix inclut de nombreux outils qui remplacent ces valeurs au moment du build
    • Oui, mais ce problème existait déjà indépendamment d’un dépôt local. Les derivations de niveau inférieur consommeront probablement outPath via {foo} ; si l’une des dépendances de niveau supérieur est remplacée par un dépôt local, il faut recompiler
  • Le 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 /origin pour qu’il soit résolu comme $ORIGIN ? Cela fonctionnerait alors à la fois pour les shebangs et pour l’ELF, sans syntaxe supplémentaire

    • Si l’on peut créer et monter /origin, ne pourrait-on pas simplement créer /nix et lancer nix-daemon ?
    • À mon avis, l’objectif est probablement d’être compatible en dehors de NixOS, sans installation ni configuration d’un système de fichiers ou d’un démon supplémentaires
  • 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é ?

    • Dans ce modèle de menace, qu’est-ce qui est considéré comme fiable ou non ? Si l’on va exécuter ce binaire de toute façon, est-ce simplement l’idée de dire qu’il faut au moins l’exécuter avec un loader vérifié ?
    • Pourquoi faudrait-il considérer cela comme moins sûr qu’une variable d’environnement qui définit le chemin de recherche d’autres bibliothèques liées dynamiquement, comme libc.so.6 ?