1 pontos por GN⁺ 5 시간 전 | 1 comentários | Compartilhar no WhatsApp
  • O -- do Git não é um terminador de opções comum: ele separa revisões de especificações de caminho. Por isso, para passar revisões não confiáveis com segurança, é necessário usar --end-of-options, compatível desde o Git 2.24.0
  • Em git log --end-of-options "$rev" -- "$path", o primeiro marcador separa opções e revisões, enquanto o -- final separa revisões e caminhos; eles não são intercambiáveis
  • Mesmo executando diretamente um array argv sem shell, se uma entrada que começa com hífen for interpretada como opções como --upload-pack, core.sshCommand ou ProxyCommand, pode ocorrer injeção de argumentos CWE-88
  • Entre 19 gerenciadores de pacotes analisados, 17 executavam o binário do Git como modo padrão ou único, mas a única ferramenta que usava --end-of-options era o cmd/go do Go
  • Mitigações fundamentais têm custo de compatibilidade: dependendo do subcomando, é preciso elevar a versão mínima do Git para 2.24.0, 2.30.0 ou 2.43.1; já bibliotecas Git eliminam a fronteira de injeção de argumentos, mas precisam acompanhar diretamente as correções de segurança de checkout do upstream

Diferença entre -- e --end-of-options

  • Em ferramentas Unix comuns, -- marca o fim da interpretação de opções; assim, rm -- -f trata -f como nome de arquivo, não como opção de remoção forçada
  • Desde cedo, o Git usa -- como separador entre revisões e especificações de caminho (pathspec)
    • git log foo é ambíguo: pode significar um branch chamado foo ou um arquivo chamado foo
    • git log main -- README.md significa commits em main que tocaram em README.md
  • Por causa desse design, não havia marcador de fim de opções na posição de revisão; em git log "$rev", se $rev começa com hífen, o Git o interpreta como opção
  • O commit que introduziu --end-of-options explica que, como o -- existente já separava revisões de especificações de caminho, era necessário um marcador separado para distinguir opções de revisões
  • --end-of-options foi documentado em gitcli(7) e adicionado em novembro de 2019, com o lançamento do Git 2.24.0

Uso correto por comando

  • git clone -- "$url" segue a convenção POSIX no clone, então o -- antes da URL encerra a interpretação de opções
  • O -- final em git checkout "$ref" -- marca $ref como revisão, não como nome de arquivo, mas não impede que $ref seja interpretado primeiro como opção
  • Para passar com segurança uma revisão e um caminho não confiáveis juntos, é preciso usar os dois marcadores, como em git log --end-of-options "$rev" -- "$path"
    • --end-of-options separa opções e revisões
    • -- separa revisões e caminhos
  • Tratar os dois marcadores como intercambiáveis não bloqueia entradas que começam com hífen

Suporte varia conforme o subcomando

  • O suporte a --end-of-options não foi aplicado a todos os comandos Git de uma só vez; ele foi adicionado por subcomando
  • git rev-parse usa seu próprio analisador de argumentos e só passou a ter suporte no Git 2.30.0, um ano depois da introdução inicial
  • git checkout e git reset interpretam -- por conta própria, e a implementação inicial deixava --end-of-options na lista de argumentos, fazendo com que fosse rejeitado
    • Esse problema foi resolvido em fevereiro de 2024, com o lançamento do Git 2.43.1

Injeção de argumentos mesmo sem shell

  • Git, Mercurial e SSH têm, como recursos oficiais, opções que executam comandos especificados pelo chamador
    • git clone --upload-pack=<cmd> especifica o binário do lado do servidor
    • -c core.sshCommand=<cmd> em qualquer chamada do Git altera o comando de conexão
    • --config=alias.<subcmd>=!<shell> no Mercurial redefine um subcomando a executar como um script shell arbitrário
    • -oProxyCommand=<cmd> no SSH especifica um comando de proxy
  • Se um programa wrapper colocar strings não confiáveis na lista de argumentos, esses recursos podem se transformar em meios de ataque
  • Esse tipo de falha corresponde à CWE-88, injeção de argumentos (argument injection), e é diferente de injeção de comandos shell
    • Ela ocorre mesmo que o programa use um array argv e exec em vez de system()
    • O array é passado intacto ao Git, mas o Git interpreta argumentos que começam com hífen como opções
  • O docker build CVE-2019-13139 é um caso que usava os/exec do Go e array argv, sem passar por shell
    • O fragmento #ref:dir da URL de contexto Git era passado a git fetch origin <ref>, e <ref> era interpretado como --upload-pack=<cmd>

Vulnerabilidades repetidas em vários sistemas de controle de versão

  • Em agosto de 2017, no mesmo dia, o mesmo padrão foi divulgado em quatro sistemas de controle de versão
  • Todos os quatro sistemas passavam o nome do host da URL como argumento do SSH, e um nome de host começando com -oProxyCommand= era tratado como opção do SSH
  • Segundo a análise post-mortem do Phabricator, entre as três ferramentas mantidas ativamente na época, apenas o Subversion adicionou -- antes do nome do host
    • Git e Mercurial validaram o formato do nome do host, em parte porque nem todas as implementações de SSH dão suporte a --
  • Código sem -- parece e funciona normalmente até que um argumento comece com hífen; portanto, esse mecanismo é inseguro por padrão

Caminhos de exposição em gerenciadores de pacotes

  • Gerenciadores de pacotes recebem URLs Git ou refs de manifestos, arquivos de lock e metadados de dependências transitivas, e os repassam a subprocessos
    • gem 'foo', git: '...' no Gemfile
    • github:user/repo#ref em package.json
    • Configurações equivalentes em pyproject.toml, Cargo.toml, mix.exs, Package.swift, pubspec.yaml, conanfile.py e go.mod
  • Entre 19 gerenciadores de pacotes analisados no HEAD de julho de 2026, 17 executavam o binário do Git como caminho padrão ou único
  • Os outros dois usam bibliotecas por padrão
  • O Nix usa libgit2 ao ler repositórios locais, mas executa um processo Git para fetch porque libgit2 não oferece suporte a auxiliares git-credential
  • Os alvos analisados foram Bundler, Cargo, CocoaPods, Composer, Conan, Go, Helm, Homebrew, Mix, Nix, npm, pip, pnpm, Poetry, Pub, SwiftPM, uv, vcpkg e Yarn

CVEs confirmadas em gerenciadores de pacotes

Estado real das defesas e a correção do Go

  • Entre os 17 gerenciadores de pacotes que executam processos Git, a única ferramenta que usava --end-of-options era o cmd/go do Go
  • Em junho de 2019, o Go adicionou -- antes de URLs de repositório como medida geral de endurecimento
  • Em janeiro de 2026, ficou claro que apenas -- não bastava; como correção para CVE-2025-68119, --end-of-options foi adicionado de forma ampla
  • A mesma correção também incluiu HGPLAIN=+strictflags
    • Essa configuração limita a interpretação inicial de opções do Mercurial desde 2017, quando o Mercurial 4.4.2 foi lançado
  • O commit de correção do Go afirma que talvez seja necessária uma mudança mais estrutural para dificultar a reintrodução do mesmo problema, mas resolve o problema atual por ora

A maioria das defesas foi adicionada após divulgações de vulnerabilidades

  • Os demais gerenciadores de pacotes, quando protegem a lista de argumentos, usam principalmente -- ou a rejeição de hífen inicial na entrada
  • O -- antes da URL em git clone no Bundler foi adicionado pelo patch da CVE-2021-43809
  • A rejeição de hífen inicial no cocoapods-downloader foi aplicada em três commits ao longo de dez dias em março de 2022, coincidindo com a divulgação da CVE-2022-21223
  • A defesa do Poetry foi adicionada em setembro de 2021, recebeu uma CVE um ano depois e migrou para dulwich seis meses depois
  • Como exceção, o vcpkg usa -- desde o primeiro dia em que o suporte a registros Git foi escrito

Restrições de compatibilidade criadas pela versão mínima do Git

  • O aviso da CVE-2022-24828 do Composer aponta --end-of-options como a correção correta, mas, como precisava dar suporte a versões mais antigas do Git, optou por rejeitar nomes de branch que começam com hífen
  • A integração Git do vcpkg declara Git 2.7.4 como versão mínima, e HOMEBREW_MINIMUM_GIT_VERSION do Homebrew para Linux é 2.7.0, definido em 2018
  • Com o fim da vida do Amazon Linux 2, que fornecia Git 2.14.3, em junho de 2026, as distribuições que esses limites mínimos acompanhavam só agora estão saindo do escopo de suporte
  • O estado do suporte de longo prazo do Ubuntu também dificulta uma migração em massa
    • Ubuntu 18.04 fornece Git 2.17.0 e tem suporte estendido até 2028
    • Ubuntu 20.04 fornece Git 2.25.1 e tem suporte estendido até 2030
    • Git 2.25.1 aceita --end-of-options em git fetch, mas o rejeita em git rev-parse
  • Para depender de --end-of-options, a maioria dos subcomandos precisa exigir Git 2.24.0 como mínimo; rev-parse precisa de 2.30.0; e checkout e reset, de 2.43.1
  • Elevar a versão mínima impede dar suporte a usuários que usam versões antigas do Git incluídas nas distribuições

Usar bibliotecas Git em vez de executar processos

  • libgit2, gitoxide, go-git, JGit e dulwich implementam, dentro do processo, os protocolos de transporte Git necessários para clone e fetch
  • Como não há uma fronteira argv separada, não existe alvo onde injetar a lista de argumentos
  • Jujutsu usa gitoxide para integração com Git e não tem CVEs públicas do tipo injeção de argumentos
    • Os dois avisos até agora são de path traversal e ausência herdada de uma biblioteca na verificação de colisão SHA-1
  • A CVE-2025-21613 do go-git se limita ao transporte file://
    • Esse caminho é o único trecho de código no go-git que executa o binário do Git
  • Incluir uma implementação própria do Git exige acompanhar todas as correções de segurança de checkout publicadas pelo Git upstream, e tanto libgit2 quanto JGit já tiveram correções recorrentes relacionadas a isso
  • Esse custo é real, mas a questão deixa de ser lembrar permanentemente de validar argumentos em cada ponto de chamada e passa a ser aplicar um fluxo concreto de patches upstream

Escopo das mudanças propostas ao Homebrew

  • O PR do Homebrew eleva a versão mínima do Git para 2.30.0 e adiciona --end-of-options nos seguintes pontos
    • Antes da URL em clone, remote set-url e ls-remote
    • Antes do ref em rev-parse
  • Chamadas a checkout e reset não foram alteradas
    • Proteger também esses dois comandos exigiria o Git 2.43.1, lançado em fevereiro de 2024
    • Essa versão é mais recente do que o Git fornecido por várias distribuições atualmente suportadas

1 comentários

 
GN⁺ 5 시간 전
Opiniões no Lobste.rs
  • Ultimamente estou cada vez mais viciado em jj. Comparado a essa bagunça do Git, chega a dar uma paz, e embora eu ainda esteja aprendendo, ele reflete bem a intenção e também deixa claro o fluxo de trabalho desejado
    Em especial, o conhecimento aprendido em um comando jj se aplica naturalmente a outros comandos. A documentação do git log mistura uma explosão de flags por opção de saída, a “notação especial” da sintaxe de intervalo de commits e ainda flags extras de filtro, e pouco desse conhecimento se transfere para outros comandos do Git
    Já a documentação do jj log é tão concisa que sobra espaço na página. Ela substitui a complexidade improvisada do Git por três coisas: conjuntos de revisões, conjuntos de arquivos e uma DSL de templates de saída, usadas de forma consistente em todo o jj, o que torna tudo muito mais simples, combinável e intuitivo. É preciso considerar que o Git acumulou penduricalhos por 20 anos, mas o jj parece estar em posição muito melhor para evitar isso
  • Eu sei que normalmente é preciso usar -- antes de argumentos fornecidos pelo usuário em comandos, mas exigir ainda por cima regras próprias de transformação aqui vira uma armadilha perigosa demais
  • É o resultado honesto de ter tornado a ferramenta complexa demais. O Git parece uma nova BASAHO
  • Fico curioso sobre por que originalmente escolheram -- para resolver a ambiguidade dos argumentos de arquivo. Em vez da abordagem simples de receber arquivos como argumentos explícitos, será que houve algum trade-off de projeto não tão visível por trás dessa escolha?
    • Usar uma convenção já estabelecida de parsing de opções no UNIX para um propósito completamente diferente foi, como esperado, uma ideia idiota, e uma escolha extremamente típica do Git
  • A filosofia UNIX de que tudo é texto causa problema de novo. A linha de comando é um exemplo perfeito de dados estruturados, mas parece que o custo de coordenação coletiva para sair desse buraco é alto demais
    Relembrando o debate entre Tcl e Scheme nos anos 1990, talvez neste caso “quanto pior, melhor” estivesse certo. Enquanto famílias de S-expressions, incluindo JSON e XML, quase não se firmaram no ecossistema UNIX, o Tcl tinha uma abordagem relativamente mais principista de encaixar sublinguagens dentro de strings