1 pontos por GN⁺ 2023-10-30 | 1 comentários | Compartilhar no WhatsApp
  • A ISO mínima de instalação do NixOS foi reconstruída de forma independente e idêntica bit a bit à distribuição publicada pelo Hydra, mostrando que é possível verificar se os binários distribuídos correspondem ao código-fonte
  • Esta verificação reproduz não apenas os pacotes incluídos na ISO, mas também o próprio processo de geração da ISO, confirmando um escopo mais amplo do que a simples reprodutibilidade de pacotes
  • A reconstrução começou a partir do appliance VirtualBox do NixOS 20.03, usando a revisão 63678e9f3d3a do nixpkgs, com --option substitute false para desativar a dependência de cache binário
  • Se a OVA de 2020 ou o git baixado tivessem um backdoor sofisticado, isso ainda poderia servir como vetor de ataque; por isso, a verificação baseada em um sistema totalmente bootstrapado ainda segue pendente
  • A reconstrução da ISO mínima é um marco importante, mas os próximos passos incluem remover soluções temporárias, reproduzir mais mídias de instalação, criar infraestrutura para reconstruções independentes regulares e ferramentas de atestado de build

Reprodutibilidade verificada na ISO mínima

  • A build da ISO nixos-minimal publicada pelo Hydra foi reconstruída de forma independente, obtendo um resultado idêntico bit a bit
  • O escopo da reprodução se divide em dois eixos
    • Todos os pacotes incluídos na ISO
    • O próprio processo de build que cria a ISO
  • Pacotes necessários para a build da ISO, mas não incluídos dentro dela, também foram compilados, sem depender de binários em cache
  • Builds reproduzíveis oferecem um caminho de confiança para verificar se os binários distribuídos correspondem fielmente ao código-fonte e se não foram adulterados em pipelines de build como o Hydra

Procedimento de reconstrução e limitações

  • A reconstrução foi realizada iniciando um novo appliance VirtualBox com NixOS 20.03
    • CPU e memória foram alocados em quantidade suficiente, e o disco foi expandido para cerca de 65 GB
    • Após instalar o git, foi feito o clone de nixpkgs e o checkout da revisão 63678e9f3d3a
    • Com --option substitute false, os itens necessários foram compilados na máquina local em vez de serem buscados em cache binário
  • O procedimento inclui medidas temporárias para contornar problemas conhecidos
  • Ainda existem limitações do ponto de vista de confiança na cadeia de suprimentos

Diferença entre o anúncio de 2021 e o resultado atual

  • Em 2021, houve um anúncio de que a ISO mínima era 100% reproduzível, mas na época apenas os pacotes necessários para a build da ISO haviam sido reproduzidos individualmente, enquanto ainda restavam diferenças na reconstrução da ISO em si
    • A causa eram problemas remanescentes no cache do Hydra e na forma de geração da ISO
    • Depois que esses problemas foram corrigidos, surgiram regressões como um problema upstream no Python 3.10, e só nesta semana o estado necessário para verificar toda a cadeia voltou a ser alcançado
  • Os próximos passos incluem remover soluções temporárias, reproduzir mais pacotes e reproduzir outras mídias de instalação, como a ISO do Gnome
  • Também são necessárias infraestrutura para reconstruções independentes regulares e ferramentas para compartilhamento e consumo de atestados de build, como o trustix

1 comentários

 
GN⁺ 2023-10-30
Comentários do Hacker News
  • Recompilar a ISO mínima a partir do código-fonte é um marco impressionante na jornada rumo a um sistema que possa ser compilado de forma reproduzível com base no código-fonte
    O Guix também alcançou recentemente um resultado ortogonal, mas igualmente impressionante, nessa mesma jornada: fez o bootstrap de toda a cadeia de ferramentas do compilador a partir de um único binário reproduzível de 357 bytes, sem outros blobs binários de compiladores
    Talvez em breve os dois se unam e seja possível compilar de forma reproduzível uma distribuição inteira a partir do código-fonte
    https://guix.gnu.org/en/blog/2023/the-full-source-bootstrap-...

    • É incrível, e fico feliz em ver gente travando a boa luta mesmo diante de reações como “se houver um backdoor, no fim todo mundo não vai receber o mesmo backdoor?”
      O ponto central que muita gente não entende é que a ideia não é provar que o resultado é 100% confiável, e sim provar que ele é 100% fiel ao código-fonte
      Ou seja, se alguma coisa suspeita como um backdoor furtivo for descoberta, isso sempre poderá ser reproduzido de forma conclusiva
      Do lado do mal, isso significa não ter para onde correr nem onde se esconder
    • Ainda não chegou ao estágio do stage0 do Guix, mas houve uma apresentação interessante na NixCon sobre fazer o bootstrap do Nix a partir do TinyCC: https://media.ccc.de/v/nixcon-2023-34402-bootstrapping-nix-a...
    • O fato de o binário do compilador de bootstrap ter 357 bytes é realmente impressionante
    • Com 357 bytes, dá até para se perguntar se um binário reproduzível é mesmo necessário
      Parece que daria para documentar manualmente todos os 357 bytes de código de máquina e torná-los compreensíveis para humanos
  • Talvez seja uma pergunta boba, já que nunca fiz algo assim, mas fico me perguntando por que a reprodutibilidade não é o comportamento padrão
    Se você compilou duas cópias de um software a partir do mesmo código-fonte, não entendo muito bem o que impede que elas sejam completamente idênticas toda vez
    Sei que há muitas partes móveis, mas ainda não entendo bem como surgem as diferenças

    • Há muitas causas específicas, e provavelmente timestamps são o problema mais comum
      Uma lista de problemas frequentes pode ser vista aqui: https://reproducible-builds.org/docs/
      No geral, o ponto principal é que os desenvolvedores não testam se a compilação é reproduzível
      Quando isso entra nos testes de release, normalmente continua reproduzível
    • Existem muitas causas
      Exemplos clássicos que tornam algo explicitamente não reproduzível são timestamps e informações do autor
      Também há coisas que quebram a reprodutibilidade padrão de forma implícita; por exemplo, muitos runtimes não definem a ordem dos itens de um hashmap, e o compilador pode percorrer esse hashmap para gerar o binário
    • Pode ser por causa da paralelização
      Pode haver operações em que a ordem importa, e dependendo do estado da CPU podem sair binários um pouco diferentes, embora todos estejam corretos
    • A equipe do Go publicou recentemente um texto sobre o que fez para tornar a cadeia de ferramentas do Go totalmente reproduzível: https://go.dev/blog/rebuild
    • Às vezes é por causa de algoritmos aleatórios, às vezes por questões de desempenho, por exemplo quando não ordenar algo é mais rápido
      Às vezes também são metadados dependentes do tempo ou do ambiente, ou coisas como a ordem de execução das threads
  • Desculpem a ignorância, mas eu achava que um dos principais motivos para a existência do NixOS era a reprodutibilidade
    Achei que esses problemas já estivessem resolvidos
    Usei o NixOS por umas 2 horas e queria experimentar o Hyprland, mas como o Hyprland exige alguma configuração, imaginei que no NixOS seria mais fácil reaproveitar a configuração de outras pessoas do que em outras distribuições
    Só que foi difícil achar configurações, encontrei umas 3 gists aleatórias no GitHub, nenhuma funcionou, e acabei desistindo

    • O NixOS tem a vantagem de que tudo é compilado dentro do seu próprio sandbox, apenas com dependências explicitamente declaradas e com hash
      Como não depende do ambiente completo do sistema, como nas distribuições comuns, em muitos casos isso já faz com que o mesmo binário seja gerado todas as vezes
      Mas o próprio processo de build ainda pode ser não determinístico em vários pacotes, então isso por si só não produz reprodutibilidade completa de imediato
    • Reprodutibilidade pode significar duas coisas
      O sentido em que você está pensando é o de recompilar facilmente um pacote binário usando as mesmas versões de dependências, opções de build etc.
      Ou seja, não deveria haver espaço para aparecer um novo erro de compilação do tipo “na minha máquina funcionou”
      O sentido de que estão falando aqui é o de todos os artefatos de build serem binários idênticos byte a byte
      Eles não devem depender do nome da máquina, do horário da compilação, da ordem em que os arquivos terminam de compilar em um build paralelo etc., e isso é bem mais difícil
    • Nix é uma ferramenta difícil de aprender
      Se você não está acostumado, dificilmente é a ferramenta que escolheria numa situação de “quero algo funcionando agora”
      No NixOS, “reproduzível” costuma significar algo mais próximo de “o mesmo código Nix gera o mesmo comportamento do programa”
      É parecido com o que as pessoas esperam de um Dockerfile, e está num nível de resolver problemas como “na minha máquina funcionou” ou “da última vez funcionou”
      builds reproduzíveis têm como objetivo que os artefatos gerados em máquinas diferentes sejam idênticos bit a bit
      Isso permite verificar se o código foi compilado a partir de um determinado conjunto de fontes, criando uma camada extra de segurança
      Também fiquei curioso sobre quais termos de busca você usou para procurar configurações
      Se você procurar por “nixos configuration”, vai encontrar resultados como https://github.com/search?q=nixos%20configuration&type=repos..., e só olhando para Hyprland já aparece bastante coisa, como https://github.com/search?q=wayland.windowManager.hyprland&t...
  • Vale a pena ver https://github.com/donovanglover/nix-config
    É uma configuração baseada em Flake e inclui Hyprland e várias coisas interessantes
    No momento, o NixOS não é uma ferramenta adequada para pessoas mais frágeis ou com pouco tempo
    Espero que isso mude algum dia, mas, se você aguentar e passar por isso, pode obter benefícios

  • É preciso lembrar que a reprodutibilidade de Nix / NixOS / Nixpkgs é reprodutibilidade da fonte
    Se a fonte mudar, você recebe um aviso, mas isso é diferente da reprodutibilidade de binários, em que os binários podem variar a cada build
    A reprodutibilidade binária de Nix / NixOS / Nixpkgs, pelo menos de forma sistemática, não parece ser bem testada
    Guix, Arch Linux e Debian lidam melhor com reprodutibilidade binária do que Nix / NixOS / Nixpkgs
    Referências: https://r13y.com/ (Nix*) / https://tests.reproducible-builds.org/debian/reproducible.ht... (Debian) / https://tests.reproducible-builds.org/archlinux/archlinux.ht... (Arch Linux) / https://data.guix.gnu.org/repository/1/branch/master/latest-... (Guix, pode demorar para carregar, e uma cópia em cache está em https://archive.is/lTuPk)

    • Nesse contexto, “reprodutibilidade” tem duas definições
      Reprodutibilidade de entrada significa “invalidação perfeita de cache com base nas entradas”
      Nix e Guix fazem isso perfeitamente por projeto, e às vezes isso até causa rebuilds demais
      Debian e Arch Linux não tratam isso como preocupação principal e lidam com a questão de quais pacotes rebuildar quando um certo arquivo-fonte é atualizado por meio de abordagens ad hoc, como gatilhos manuais de rebuild
      Reprodutibilidade de saída significa “o processo de build é determinístico e sempre produz o mesmo binário”, e é esse o tema do post original
      O Nix ajuda nisso ao construir pacotes em sandbox, mas não é uma solução mágica
      Nesse ponto, o Nix está no mesmo barco que Debian e Arch Linux
      Na prática, as distribuições frequentemente enviam patches de reprodutibilidade para upstream, e outras distribuições também se beneficiam disso
      Nesse contexto, https://reproducible.nixos.org é o equivalente dos outros links apresentados; concordo que o relatório do Nix é menos detalhado, mas isso não significa que a reprodutibilidade binária do Nix seja pior
      Se isso for lido como “o Nix só é bom em reprodutibilidade de entrada e é ruim em reprodutibilidade binária”, então está errado
      É justamente esse marco que está sendo comemorado aqui
    • Acho que vale a pena ler o texto
      Ele fala em reproduzir bit a bit não só os binários, mas também a forma como eles são empacotados em ISO
      O r13y.com está desatualizado e, pelo que eu lembro, o menos de 1% que faltava era por causa de uma regressão upstream do Python
      A reprodutibilidade dos próprios binários, excluindo o empacotamento em ISO, já tinha sido alcançada há alguns anos
      Quando se vai para pacotes além da ISO principal, a comparação fica complicada
      A forma de lidar com pacotes é sutil, mas neste contexto importante e diferente; muitos pacotes que provavelmente estariam no AUR do Arch entram no Nix como pacotes normais, e a maioria dos pacotes upstream do tipo -bin simplesmente não é necessária no Nix
      Em geral, o Nix facilita a criação de builds reproduzíveis, mas isso nem sempre é possível independentemente do Nix, e muitas vezes exige patches
      Somando isso ao fato de que o repositório principal de pacotes do Nix tem mais de 80 mil pacotes, enquanto o Arch tem menos de 15 mil sem contar o AUR, comparações por porcentagem não são muito úteis
      Um mal-entendido muito comum é achar que o hash dos caminhos do Nix store é baseado na saída do build, quando na verdade ele se baseia em todas as fontes e entradas usadas para construir os binários no ambiente isolado, independentemente de serem binários ou não
      Por isso, o benefício de segurança que as pessoas esperam não surge exatamente como imaginam, mas, em compensação, isso permite usar até software que não é compilado de forma reproduzível em uma forma de distribuição razoavelmente reproduzível, com as mesmas funcionalidades, configurações de compilador, versões de dependências, usuário, configuração etc.
    • Acho que é exatamente disso que a primeira referência e o texto original estão tratando
      É verificar se os binários são iguais quando compilados a partir da mesma fonte em máquinas diferentes
      O ponto principal é que os binários não mudem a cada build
      O método de teste também diz que cada build é executado duas vezes, em momentos diferentes, com hardware diferente e kernels diferentes
    • Eu não sabia que o Arch Linux testava reprodutibilidade
      Pelo visto, aparece como 85,6% reproduzível: https://reproducible.archlinux.org
      Fico curioso sobre quanto trabalho seria necessário no NixOS, que tem mais de 80 mil pacotes nos repositórios oficiais
  • Considerando os objetivos ou a realidade do nixpkgs, isso não é verdade de forma alguma
    O post original fala sobre reproduzir uma ISO mínima binária que contém vários pacotes binários

  • É irônico e engraçado que o projeto OpenBSD esteja indo com força total na direção oposta
    O OpenBSD faz com que cada instalação tenha deslocamentos de endereço únicos e aleatorizados
    Entendo que os dois objetivos, builds reproduzíveis e instalações únicas, são ortogonais e podem ser alcançados ao mesmo tempo, mas essa dualidade ainda é engraçada

    • Ainda seria possível demonstrar a reprodutibilidade se os deslocamentos de endereço pudessem ser aleatorizados com uma semente fornecida
      Ou também daria para aleatorizar os deslocamentos na inicialização do programa, o que manteria a reprodutibilidade e ainda aumentaria a segurança
      Aí os deslocamentos mudariam a cada execução
    • O OpenBSD faz linkedição aleatorizada no momento do boot
      Os próprios pacotes ainda podem ser reproduzíveis
      Toda a aleatorização é feita localmente, depois de baixar o pacote e verificar o checksum
  • Agora eu só queria que, como quase todas as outras distribuições Linux fazem desde os anos 90, os mantenedores ao menos assinassem os pacotes
    Assim, todos teriam alguma noção de que o código que estão compilando é o mesmo código enviado e revisado por pessoas conhecidas
    Até que as assinaturas sejam padronizadas, é difícil imaginar usar Nix em produção para proteger algo de valor

    • Vejo os mantenedores de pacotes do Nix mais como provedores de uma interface útil que facilita combinar software
      Em geral, eles não dão nenhuma garantia sobre o conteúdo dos pacotes
      Esperar garantias significativas da assinatura deles me parece parecido com esperar suporte ao produto do entregador
      Também não é preciso confiar que o empacotamento não foi feito de forma maliciosa
      Como o Nix faz builds reproduzíveis, se você não quiser depender do cache binário, pode inspecionar a derivation e compilar você mesmo
      Se o conteúdo original é malicioso ou não, isso no fim é uma questão entre o desenvolvedor e o usuário
      Se outras distribuições te levaram a acreditar no contrário, eu diria que isso foi quase enganoso
      A única exceção que me vem à cabeça é o Tails, mas o Tails não é tão amplo quanto o Nix
    • Fico curioso sobre quais distribuições fazem isso
      Pelo que vejo, o Debian não faz mais isso e o sistema de build assina os builds; o Fedora também não; do Arch não tenho certeza, mas acho que também não
      O sistema de build do NixOS assina todos os artefatos de build com a própria chave e verifica as assinaturas no download
      Se você for mais paranoico, o Nix pelo menos facilita bastante compilar tudo direto do código-fonte
  • É um marco muito impressionante, e parabéns a todos que tornaram isso possível
    Pelo que foi dito, até ao realmente recompilar a ISO ainda apareciam diferenças, e a causa eram problemas remanescentes no cache do Hydra e a forma como a ISO era gerada
    Fico curioso se alguém pode explicar como corrigiram “a forma como a ISO era gerada”
    Já tentei fazer uma ISO reproduzível antes, mas não consegui fazer o sistema de arquivos gerar os extents de forma determinística

    • No caso do NixOS, isso está na seção “como foi reproduzido” do texto
      A etapa final daquele processo gera a ISO no diretório ./result/iso
      Acho que o que você procura é o comando chamado por esse build, mas não sei exatamente qual etapa você está tentando localizar
      Por exemplo, a chamada do xorriso está aqui: https://github.com/NixOS/nixpkgs/blob/master/nixos/lib/make-...
  • Para fazer isso, não seria preciso falsificar a hora do sistema?
    Horários costumam acabar entrando nos binários de várias formas

    • Na prática, timestamps provavelmente são a causa mais comum de não determinismo
      Isso é tão comum que muitos compiladores implementaram a variável de fato padrão SOURCE_DATE_EPOCH para falsificar timestamps: https://reproducible-builds.org/docs/source-date-epoch/
    • Fico curioso se você pode dar exemplos de como e por que isso acontece
    • Também dá para simplesmente não incluir timestamps em build nenhum, ou defini-los como 0, para que a data do build fique em 1970 em todo lugar
  • Isso não ajuda a resolver o problema descrito por Ken Thompson em “Reflections on Trusting Trust”?
    Se for possível fazer bootstrap completo do sistema inteiro a partir do código-fonte, parece que ficaria mais difícil inserir algo como um compilador com backdoor embutido

    • Na prática ajuda, mas não é uma “solução” completa
      Em teoria, ainda poderia haver um backdoor sofisticado no ambiente em que a ISO é construída
      Se você realmente quiser resolver esse problema, pode olhar o Diverse Double Compiling(https://dwheeler.com/trusting-trust/) ou bootstrap completo do ambiente(https://bootstrappable.org/)
      A seção “essa abordagem não tem um problema de bootstrap?” do texto também é relevante
      Ainda assim, só o fato de reproduzir o build já ajuda muito a tornar esse tipo de ataque cada vez menos plausível
  • Por causa de trabalho recente, andei vivendo dentro do ecossistema Red Hat
    Fico curioso sobre como isso se compara com Fedora Silverblue, Ansible ou Fedora Silverblue + Ansible

    • No ecossistema Fedora, o mais próximo do construtor de ISO do NixOS e da sua reprodutibilidade é o osbuild / imagebuilder: https://www.osbuild.org/guides/introduction.html
      O Imagebuilder diz ter reprodutibilidade, mas, pelo que sei, ele instala a maioria dos pacotes rpm como binários, não a partir do código-fonte
      Então, a menos que todos os pacotes de entrada também sejam reproduzíveis, isso não é reprodutibilidade estrita
      Se a explicação sobre compilar pacotes a partir do código-fonte, criar a imagem da distribuição e lidar com reprodutibilidade não fez muito sentido para você, provavelmente você não é o público-alvo principal
    • O Nix é um SO declarativo, em que você descreve como o sistema operacional deve ser
      Já o Ansible especifica os passos que o SO deve seguir

Silverblue e Nix são praticamente ortogonais entre si, além do fato de ambos serem distribuições Linux
O Silverblue é uma tentativa de mudar a forma de entrega de software usando apenas contêineres sobre um host imutável
Se você estiver procurando uma alternativa ao Ansible que imite o Nix até certo ponto usando Jsonnet e rastreamento de estado, vale a pena dar uma olhada no Etcha: https://etcha.dev

  • O Ansible aplica mudanças mutáveis ao SO em unidades de tarefa
    O Nix é imutável
    Novas mudanças são criadas de forma totalmente nova, e só depois que a build é bem-sucedida todos os pacotes são “linkados simbolicamente” ao sistema atual
    O Fedora Silverblue é baseado em ostree https://github.com/ostreedev/ostree
    Ele funciona de forma parecida com o git para a árvore raiz, mas para aplicar mudanças é preciso reinicializar o sistema inteiro
    O Nix faz isso por meio de links simbólicos para os pacotes, então não é necessário reinicializar o sistema
    Há uma explicação mais detalhada aqui: https://dataswamp.org/~solene/2023-07-12-intro-to-immutable-...