1 pontos por GN⁺ 3 시간 전 | 1 comentários | Compartilhar no WhatsApp
  • O Nix, um gerenciador de pacotes baseado em store, foi projetado para colocar pacotes em um prefixo fixo como /nix/store, o que cria fortes limitações em ambientes de Nix sem root que querem usar o store em outro local sem uma instalação prévia do Nix nem privilégios de root
  • Ao usar --store /tmp/... junto com chroot e namespace de montagem, é possível manter o mesmo hash de builds feitos em /nix/store, continuando a aproveitar caches binários como cache.nixos.org
  • Se o prefixo do store for alterado com local?store=/tmp/... sem namespace, o hash muda, e até um build simples de hello pode invalidar toda a árvore de dependências e levar à recompilação do GCC
  • O núcleo da proposta é usar caminhos relativos baseados em $ORIGIN, suportados pelo linker dinâmico do Linux, no RUNPATH do ELF em vez de caminhos absolutos, para que a mudança de local do store não se propague em forma de hashes diferentes e recompilações
  • O principal gargalo que impede a relocação real é que o kernel não oferece suporte a $ORIGIN no PT_INTERP de ELF nem em shebangs de scripts; como caminho de solução, são propostos patch no kernel, wrappers estáticos, caminhos relativos específicos por linguagem e metadados relocatable = true;

O conflito entre prefixo fixo do store e Nix sem root

  • Sistemas baseados em store como Nix e Guix armazenam todos os pacotes sob um prefixo definido
    • Nix usa /nix/store
    • Guix usa /gnu/store
  • Essa estrutura facilita reescrever caminhos de binários e bibliotecas
    • Por exemplo, /bin/bash pode ser trocado por um caminho completo do store como /nix/store/gik3rh1vz2jlgnifb9dh6vc6sxwwz9jj-bash-5.3p9/bin/bash
  • Há casos em que se quer colocar o store em outro local
    • Ambientes em que o Nix ainda não está instalado
    • Ambientes sem as permissões necessárias
    • Nesses casos surge o problema do “Nix sem root”
  • Hoje o Nix já permite definir outro caminho de store, mas a preservação do hash depende da forma usada
    • nix build nixpkgs#hello instala em /nix/store/zi2bj2hlavv8q743li2s9diqbcpmrf9b-hello-2.12.3/
    • nix build --store /tmp/fzakaria/store nixpkgs#hello usa chroot e namespace de montagem para instalar em /tmp/fzakaria/store/nix/store/zi2bj2hlavv8q743li2s9diqbcpmrf9b-hello-2.12.3/
    • Nos dois casos, o hash zi2bj2hlavv8q743li2s9diqbcpmrf9b permanece igual
  • Quando o hash é o mesmo, é possível usar derivations pré-calculadas de substituidores binários como https://cache.nixos.org

O custo de mudar o store sem namespace

  • Ferramentas como Bazel e Buck2 podem já usar namespaces para seu próprio sandboxing
    • Ao tentar integrar o Nix a esse ecossistema, restrições de namespaces de usuário aninhados e de montagem podem prejudicar bastante a viabilidade prática
  • Também é possível definir um prefixo alternativo de store sem chroot nem namespace de montagem, mas isso traz o problema da mudança de hash
    • O comando de exemplo usa --store 'local?store=/tmp/fzakaria/store&state=/tmp/fzakaria/state&log=/tmp/fzakaria/log'
    • O caminho resultante para hello vira /tmp/fzakaria/store/qv3fhi1j9gh27fyds5n5b16yia8i6zn5-hello-2.12.3
    • O hash deixa de ser zi2... e passa a ser qv3fhi1j9gh27fyds5n5b16yia8i6zn5
  • A simples troca da string do prefixo do store invalida em cascata toda a árvore de dependências
    • Só para imprimir “Hello World” a partir de outra pasta, pode-se acabar compilando o GCC por 4 horas
    • Nesse cenário, não é possível aproveitar caches públicos
  • Essa limitação já está documentada no manual do Nix

O que o $ORIGIN resolve e o limite que ainda fica no kernel

  • A origem do problema é que o prefixo do store faz parte da própria derivation, influenciando o cálculo do hash
  • Se, em vez de usar o prefixo completo do store em toda parte, forem usados caminhos relativos, a mudança de hash pode ser evitada
  • O RUNPATH de binários ELF é um dos pontos em que isso pode ser aplicado
    • Um exemplo atual de RUNPATH em hello é /nix/store/57iz36553175g3178pvxjij8z5rcsd4n-glibc-2.42-61/lib
    • O loader do Linux suporta $ORIGIN, que representa o diretório do executável
    • Assim, o RUNPATH pode ser escrito como $ORIGIN/../../57iz36553175g3178pvxjij8z5rcsd4n-glibc-2.42-61/lib
    • Com isso, o local do store pode mudar sem alterar o hash nem exigir recompilação
  • Porém, antes de o linker dinâmico ler o RUNPATH, o kernel do Linux precisa primeiro carregar o próprio linker dinâmico
    • Esse caminho fica armazenado no cabeçalho PT_INTERP do ELF
    • O exemplo é /nix/store/57iz36553175g3178pvxjij8z5rcsd4n-glibc-2.42-61/lib/ld-linux-x86-64.so.2
    • Hoje o kernel do Linux não oferece suporte a $ORIGIN em PT_INTERP
  • Shebangs de scripts sofrem da mesma limitação
    • Um exemplo é #!/nix/store/gik3rh1vz2jlgnifb9dh6vc6sxwwz9jj-bash-5.3p9/bin/bash
    • Ao fazer o parse de #!, o kernel espera um caminho absoluto
    • Atualmente também não há suporte a $ORIGIN em shebangs
  • Caminhos relativos com base no diretório de trabalho atual até podem ser usados, mas se o script for executado de outro lugar tudo quebra, então não são confiáveis

Propostas para chegar a binários relocáveis

  • Para criar binários realmente relocáveis, é preciso contornar ou alterar as restrições do kernel
  • Três abordagens são propostas
    • Fazer patch no kernel Linux para suportar $ORIGIN em PT_INTERP e em shebangs
    • Envolver todos os binários em um pequeno binário estático, que calcula sua própria localização e depois executa o linker dinâmico
    • Passar a usar também recursos de caminhos relativos específicos de cada linguagem
      • Em Python, por exemplo, __file__ permite acessar arquivos com base na localização do próprio arquivo
  • A abordagem apontada como mais adequada é estender o suporte no kernel Linux
    • Em máquinas NixOS, seria possível aplicar esse patch no kernel via Nix
  • Além disso, propõe-se adicionar a cada derivation um metadado relocatable = true; para indicar se ela é relocável

1 comentários

 
GN⁺ 3 시간 전
Comentários no Lobste.rs
  • Seria bom se o kernel Linux passasse a oferecer suporte a $ORIGIN em PT_INTERP. Já tentei isso antes com um binário wrapper estático, e vi várias outras tentativas (bom exemplo); todas são hacks excelentes e elegantes, mas no fim continuam sendo hacks
    Ainda não entendi direito as implicações de segurança, então seria útil ter uma explicação bem organizada
    Parece que o Solaris oferece suporte a isso, então talvez exista uma forma segura de fazer. É difícil encontrar referências, mas no ENOEXEC do manual de execve(2) diz que falha se o cabeçalho de programa PT_INTERP do arquivo de imagem de processo setuid/setgid tiver um caminho relativo ou usar o token $ORIGIN

  • Muito software tem caminhos embutidos no momento do build ou constantes, então no fim não seria preciso recompilar de qualquer forma para funcionar direito?

    • Isso já acontece bastante, principalmente nas linhas shebang. O Nix inclui muitos auxiliares para substituir esses valores no momento do build
    • Sim, mas esse problema já existia independentemente de um repositório local. Provavelmente as derivações de nível inferior vão consumir outPath via {foo}, mas se uma das dependências superiores for trocada por um repositório local, vai ser preciso reconstruir
  • O patch dcrt1 para musl (feito por rcombs) resolve esse problema em espaço de usuário

  • Não daria para criar um sistema de arquivos montado em /origin para que ele fosse interpretado como $ORIGIN? Aí isso funcionaria tanto em shebang quanto em ELF, sem sintaxe adicional

    • Se você consegue criar e montar /origin, então não poderia simplesmente criar /nix e executar o nix-daemon?
    • Acho que a meta é funcionar também fora do NixOS, sem precisar instalar ou configurar sistemas de arquivos extras ou um daemon
  • Não seria um risco de segurança se o binário pudesse apontar para um loader próprio usando um caminho relativo, provavelmente inseguro?

    • Nesse modelo de ameaça, o que está sendo confiado e o que não está? Se você já vai executar esse binário de qualquer forma, a questão é apenas executá-lo com um loader verificado?
    • Por que isso deveria ser considerado menos seguro do que variáveis de ambiente que definem o caminho de busca de outras bibliotecas de linkedição dinâmica, como libc.so.6?