Bugs que o Rust não consegue detectar
(corrode.dev)- A segurança de memória melhora bastante, mas mesmo em código Rust de produção os problemas de tratamento nas fronteiras do sistema continuam existindo e podem levar a vulnerabilidades
- Fluxos que reinterpretam o mesmo caminho em várias syscalls, mudam permissões depois da criação e fazem comparação de caminhos baseada em string tendem a criar problemas como TOCTOU e exposição indevida de permissões
- Em Unix, caminhos, variáveis de ambiente e dados de stream circulam como bytes brutos, então um tratamento centrado em
Stringou o uso defrom_utf8_lossy,unwrapeexpectpode causar corrupção de dados ou DoS - Se erros forem descartados, falhas podem parecer sucesso, e diferenças de comportamento em relação ao GNU coreutils também podem virar problemas de segurança imediatamente em shell scripts e ferramentas privilegiadas
- Nesta auditoria, não apareceram bugs da família de segurança de memória, como buffer overflow, use-after-free e double-free; os principais riscos restantes estavam concentrados menos dentro do Rust e mais nas fronteiras com o mundo externo
Os limites do Rust expostos pela auditoria
- Os 44 CVEs divulgados pela Canonical no uutils mostram que, mesmo em código Rust de produção, ainda podem restar vulnerabilidades que borrow checker, clippy e cargo audit não conseguem detectar
- O centro do problema estava menos na segurança de memória e mais no tratamento das fronteiras do sistema
- Havia diferença de tempo entre caminho e syscall
- Havia desencontro entre dados em bytes do Unix e strings UTF-8
- Havia diferenças de comportamento em relação à ferramenta original
- Havia omissões no tratamento de erros e encerramentos com
panic!
- Essa lista de CVEs mostra de forma condensada onde a segurança termina em código de sistema escrito em Rust
Reinterpretar o caminho duas vezes cria TOCTOU
- Se o mesmo caminho é verificado em uma syscall e depois usado novamente em outra, isso facilmente leva a uma vulnerabilidade de TOCTOU
- Entre as duas chamadas, um atacante com permissão de escrita no diretório pai pode trocar componentes do caminho por um link simbólico
- Na segunda chamada, o kernel reinterpreta o caminho desde o início, fazendo a operação privilegiada apontar para um alvo escolhido pelo atacante
- A API
std::fsdo Rust usa por padrão a reinterpretação baseada em&Path, o que facilita esse tipo de errofs::metadata,File::create,fs::remove_fileefs::set_permissionsreinterpretam o caminho a cada chamada- Em ferramentas privilegiadas que precisam bloquear atacantes locais, esse caminho padrão se torna perigoso
- Em
CVE-2026-35355, foi explorado um fluxo que apaga um arquivo e depois cria um novo no mesmo caminho- Em
src/uu/install/src/install.rs, haviafs::remove_file(to)?seguido deFile::create(to)? - Se entre a remoção e a criação
tofor trocado por um link simbólico apontando para um alvo como/etc/shadow, um processo privilegiado pode sobrescrever esse arquivo
- Em
- A correção passou a usar
OpenOptions::create_new(true)para criar apenas arquivos novos- Segundo a documentação,
create_newnão permite nem arquivos existentes nem symlinks quebrados no local de destino
- Segundo a documentação,
- Se for necessário operar duas vezes sobre o mesmo caminho, o mais seguro é fixar-se em um descritor de arquivo
- Fora do caso de criação de arquivo novo, o ideal é abrir o diretório pai uma vez e trabalhar com caminhos relativos a partir desse handle
- Se você opera duas vezes sobre o mesmo caminho, deve assumir TOCTOU até prova em contrário
Permissões não devem ser alteradas depois da criação
- Criar um diretório ou arquivo com permissões padrão e só depois aplicar
chmodtambém cria uma pequena janela de exposição- Se você escreve algo como
fs::create_dir(&path)?e depoisfs::set_permissions(&path, Permissions::from_mode(0o700))?, nesse intervalopathexiste com as permissões padrão - Outro usuário pode fazer
open()durante essa janela, e mesmo que ochmodvenha depois, descritores de arquivo já obtidos não são revogados
- Se você escreve algo como
- As permissões devem ser definidas no momento da criação
- Use
OpenOptions::mode()eDirBuilderExt::mode()para que o objeto já nasça com as permissões desejadas - O kernel ainda aplica
umask, então, se esse impacto for importante,umasktambém precisa ser tratado explicitamente
- Use
Comparar strings de caminho não é o mesmo que identidade no sistema de arquivos
- A checagem inicial de
--preserve-rootemchmodfazia apenas comparação de stringrecursive && preserve_root && file == Path::new("/")- Entradas que na prática apontavam para a raiz, mas cuja string não era
/, como/../,/./,/usr/..ou um link simbólico para/, contornavam a checagem
- A correção passou a usar
fs::canonicalizepara resolver o caminho para o caminho absoluto real antes da comparação- PR da correção
canonicalizeresolve..,.e links simbólicos, retornando o caminho real
- No caso de
--preserve-root, esse método funciona porque/não tem diretório pai - Para comparar em geral se dois caminhos arbitrários apontam para o mesmo objeto no sistema de arquivos, deve-se comparar não a string, mas
(dev, inode)- O GNU coreutils também usa essa abordagem
- Em
CVE-2026-35363,rmrejeitava.e.., mas aceitava./e.///, permitindo apagar o diretório atual- Quando a diferença de entrada é tratada só no nível da string, a checagem é facilmente contornada
Nas fronteiras Unix, bytes vêm antes de strings
Stringe&strem Rust são sempre UTF-8, mas caminhos, variáveis de ambiente, argumentos e dados de stream no Unix vivem no mundo dos bytes brutos- Escolhas erradas ao atravessar essa fronteira levam a duas categorias de bug
- Conversões com perda, como
from_utf8_lossy, trocam bytes inválidos porU+FFFDe corrompem dados silenciosamente - Conversões rígidas, como
unwrapou?, podem rejeitar a entrada ou encerrar o processo
- Conversões com perda, como
- O
CVE-2026-35346docommfoi um caso em que a conversão com perda estragava a saída- Em
src/uu/comm/src/comm.rs, os bytes de entradaraerberam convertidos comString::from_utf8_lossye impressos comprint! - O GNU
commpreserva os bytes mesmo em arquivos binários, mas o uutils trocava UTF-8 inválido porU+FFFD, corrompendo a saída - A correção foi usar
BufWriterewrite_allpara escrever os bytes brutos diretamente emstdout
- Em
print!passa porDisplaye força uma ida e volta em UTF-8, enquantoWrite::write_allnão faz isso- Em código de sistema para Unix, é preciso usar o tipo adequado para cada caso
- Passar por
Stringpor conveniência de formatação facilita a entrada silenciosa de corrupção de dados
Todo panic pode virar negação de serviço
- Em CLIs,
unwrap,expect, indexação de slices, aritmética sem verificação efrom_utf8podem se tornar pontos de DoS quando o atacante controla a entradapanic!faz unwind da stack e interrompe o processo- Se estiver rodando em um cron job, pipeline de CI ou shell script, a tarefa inteira pode parar
- Em ambientes de execução repetida, isso pode até causar crash loops e paralisar o sistema
- Em
CVE-2026-35348desort --files0-from, o processo parava ao encontrar nomes de arquivo fora de UTF-8 em uma lista separada por NUL- O parser chamava
std::str::from_utf8(bytes).expect(...)para cada nome - O GNU
sorttrata nomes de arquivo como bytes brutos, como o kernel, mas o uutils forçava UTF-8 e abortava o processo no primeiro caminho não UTF-8
- O parser chamava
- Em código que lida com entrada não confiável,
unwrap,expect, indexação e cast comasdevem ser vistos como CVEs em potencial- O ideal é usar
?,get,checked_*etry_from, propagando o erro real ao chamador
- O ideal é usar
- Também foram sugeridas regras de clippy para pegar isso em CI
unwrap_usedexpect_usedpanicindexing_slicingarithmetic_side_effects
- Em código de teste, esses alertas podem ser exagerados, então faz sentido restringi-los ao escopo
cfg(test)
Descartar erros faz falha parecer sucesso
- Alguns CVEs surgiram de erros ignorados ou de fluxos em que a informação de erro se perdia
chmod -Rechown -Rretornavam apenas o código de saída do último arquivo processado- Mesmo que vários arquivos anteriores tivessem falhado, se o último desse certo o comando podia terminar com
0 - Scripts poderiam interpretar isso como uma execução totalmente bem-sucedida
- Mesmo que vários arquivos anteriores tivessem falhado, se o último desse certo o comando podia terminar com
- Para imitar o comportamento do GNU em
/dev/null,ddchamavaset_len()e entãoResult::ok()no resultado- A intenção era descartar erros apenas em uma situação limitada, mas o mesmo código acabou valendo também para arquivos comuns
- Mesmo com o disco cheio, um arquivo de destino parcialmente gravado podia ficar para trás sem aviso
- Quando se descarta
Resultcom.ok(),.unwrap_or_default()oulet _ =, perde-se a causa de falhas importantes - Mesmo que o programa não interrompa na primeira falha, ele deve lembrar o código de erro mais grave e encerrar com ele
- Se for realmente necessário descartar um
Result, o código deve registrar por que essa falha pode ser ignorada com segurança
Compatibilidade exata com a ferramenta original também é um recurso de segurança
- Vários CVEs não surgiram porque o código executava operações perigosas, e sim porque ele se comportava de forma diferente do GNU
- Shell scripts reais dependem do comportamento do GNU, então diferenças de semântica acabam virando problemas de segurança
CVE-2026-35369emkill -1é o exemplo mais claro- O GNU interpreta
-1como signal 1 e exige um PID - O uutils interpretava isso como enviar o sinal padrão ao PID -1
- No Linux, PID -1 significa todos os processos visíveis, então um simples erro de digitação podia virar uma finalização em massa do sistema
- O GNU interpreta
- Em ferramentas reimplementadas, a compatibilidade bug-for-bug vira uma proteção que inclui código de saída, mensagens de erro, edge cases e semântica das opções
- Cada ponto em que o comportamento diverge do GNU aumenta a chance de shell scripts tomarem decisões erradas
- O uutils agora também roda em CI a suite de testes upstream do GNU coreutils
- Isso parece um nível adequado de defesa contra esse tipo de diferença
É preciso resolver antes de cruzar a fronteira de confiança
CVE-2026-35368foi uma execução local de código como root emchroot- O padrão do problema estava em fazer
chroot(new_root)?e só então resolver o nome do usuário dentro da nova raiz controlada pelo atacanteget_user_by_name(name)?acabava lendo bibliotecas compartilhadas do novo sistema de arquivos raiz para resolver o nome do usuário- Se o atacante colocasse arquivos dentro do chroot, isso podia levar à execução de código como uid 0
- O GNU
chrootresolve o usuário antes dochroot- A correção passou a seguir a mesma ordem
- Depois que a fronteira de confiança é cruzada, cada chamada de biblioteca pode virar uma forma de executar código do atacante
- Nem mesmo linkagem estática impede esse problema
get_user_by_namepassa por NSS e pode fazerdlopende móduloslibnss_*em tempo de execução
Bugs que o Rust realmente impediu
- Também fica claro quais tipos de bug não apareceram nesta auditoria
- Não houve buffer overflow
- Não houve use-after-free
- Não houve double-free
- Não houve data race em estado mutável compartilhado
- Não houve null-pointer dereference
- Não houve leitura de memória não inicializada
- Mesmo quando havia bugs nas ferramentas, a auditoria não encontrou casos exploráveis como leitura arbitrária de memória
- O GNU coreutils vem acumulando nos últimos anos vários CVEs da família de segurança de memória
pwddeep path buffer overflownumfmtout-of-bounds readunexpand --tabsheap buffer overflowod --strings -Nescrita de NUL fora do heap buffersortleitura de 1 byte antes do heap buffersplit --line-bytesheap overwrite em CVE-2024-0684b2sum --checkleitura de memória não alocada em entrada malformadatail -fstack buffer overrun
- Na comparação do mesmo período, a reimplementação em Rust manteve zero ocorrências nessa categoria de bug
- Ainda assim, a auditoria não prova a ausência de bugs de segurança de memória; apenas não encontrou nenhum
- Os problemas que restam surgem principalmente não dentro do Rust, mas nas fronteiras com o mundo externo
- caminhos
- bytes e strings
- syscall
- diferenças de tempo e mudanças de estado no sistema de arquivos
Rust correto também é Rust idiomático
- Rust idiomático não é só código que passa no borrow checker e deixa o
clippyem silêncio - A correção também precisa fazer parte da idiomaticidade
- Isso porque os formatos de código que sobrevivem na prática se consolidam a partir da experiência da comunidade
- Sistemas robustos não devem esconder a bagunça do mundo real, e sim refleti-la como ela é
- descritores de arquivo em vez de caminhos
OsStrem vez deString?em vez deunwrap- compatibilidade bug-for-bug com o original em vez de uma semântica “mais limpa” só na aparência
- O sistema de tipos consegue expressar muita coisa, mas não condições fora de seu controle, como o tempo que passa entre duas syscalls
- Rust idiomático exige que tipos, nomes e fluxo de controle do código revelem a verdade do ambiente de execução
- Mesmo que fique menos bonito do que um código de quadro branco, é preciso um formato mais honesto
Referências
- An update on rust-coreutils: divulgação dos resultados da auditoria
- Patterns for Defensive Programming in Rust: padrões de Rust defensivo para ler junto
- Pitfalls of Safe Rust: erros comuns que podem surgir até em safe Rust
- Sharp Edges In The Rust Standard Library: comportamentos surpreendentes do
std - uutils/coreutils on GitHub: reimplementação do GNU coreutils em Rust
1 comentários
Comentários do Hacker News
Como mantenedor do GNU Coreutils, li o texto com interesse, mas no pouco que usei Rust achei fácil demais criar uma race TOCTOU com
std::fsEspero que uma API parecida com
openatentre na biblioteca padrão em algum momentoE não concordo com a regra de resolver o caminho antes de comparar
Em geral, é melhor chamar
fstate compararst_devest_ino, e o texto até mencionava um pouco issoUm efeito colateral considerado com menos frequência é o custo de desempenho
Num exemplo real, em um caminho de diretório muito profundo,
cplevou 0,010 s, enquantouu_cplevou 12,857 sCriar caminhos assim de propósito no mundo real é raro, mas o software GNU se esforça muito para evitar limites arbitrários
https://www.gnu.org/prep/standards/standards.html#Semantics
E o texto diz que a reescrita em Rust teve 0 bugs de segurança de memória no mesmo período, mas isso não é verdade :)
https://github.com/advisories/GHSA-w9vv-q986-vj7x
Sim,
std::fssofre do problema do lowest common denominatorEra preciso colocar alguma coisa no Rust 1.0 e, infelizmente, esse estado acabou se cristalizando por muito tempo
Acho que o
uutilsé um bom lugar para tentar projetar uma API substituta de std::fs em que seja mais difícil errarObrigado por explicar essa visão do outro lado de forma tão concisa
Queria perguntar o que deveríamos aprender aqui
Para um post de internet, vou perguntar de propósito de forma bem agressiva, porque com contraste fica mais fácil enxergar diferenças e erros
Claro que você não tem obrigação nenhuma de gastar seu tempo ou energia mental com isso
Fico curioso por que velocidade, desempenho, race condition e
st_inovivem aparecendo juntosLatência, gravação em armazenamento real, atomicidade, ACID e a velocidade finita de propagação da informação parecem acabar convergindo para uma essência parecida
Sistemas confiáveis, como os de contabilidade, parecem no fim precisar de ACID, e sistemas pouco confiáveis talvez sejam esquecidos tão rápido que dá a impressão de que as diferenças entre computadores nem importam tanto
Também me pergunto se, em aplicações do dia a dia, throughput realmente é mais importante do que latency
E entendo o foco em números de inode por causa da história de C, dos Unix-like e do GNU coreutils,
mas fico pensando em um exemplo bem básico como fazer um pendrive simplesmente funcionar direito para armazenar arquivos
sem evitar complexidades como buffering de I/O da
libc,fflush, buffering do kernel, múltiplos núcleos, time-sharing e vários aplicativos rodando ao mesmo tempoSou totalmente iniciante, mas fiquei curioso por que foi necessário um loop
whileem vez de simplesmente darcddireto com$(yes a/ | head -n $((32 * 1024)) | tr -d '\n')Edit: entendi. Era por causa de
-bash: cd: a/a/a/....../a/a/: File name too longNão sei se você viu, mas existe uma demo de conversão automática de um utilitário GNU como
wgetpara um subset seguro de C++https://duneroadrunner.github.io/scpp_articles/PoC_autotranslation_of_wget
A ideia é trocar quase em 1:1 elementos perigosos de C por elementos seguros de C++ com comportamento correspondente, então parece menos provável introduzir bugs novos e diferenças novas de comportamento do que numa reescrita
Com uma pequena limpeza no código-fonte original, a conversão pode ser totalmente automatizada, então no estágio de build dá para gerar um executável seguro em memória, um pouco mais lento, a partir do código C original
Talvez seja uma pergunta meio boba, mas fico curioso se o pessoal do GNU Coreutils está avaliando ou planejando uma reescrita própria em Rust
Talvez soubessem usar Rust, mas não conheciam o suficiente a API Unix e sua semântica e armadilhas
A maior parte desses erros entra na categoria de coisas bem iniciantes para quem vem de décadas de GNU coreutils ou desenvolvimento baseado em BSD e Solaris
Muitos desses problemas já vieram à tona e foram organizados há décadas, e embora ainda exista uma longa cauda de correções no código legado, hoje em dia em geral continua entrando só um volume pequeno
Li aquela thread da Canonical e fiquei de queixo caído
A ideia era mais ou menos: “Rust é mais seguro, segurança é prioridade máxima, então distribuir uma reescrita completa do coreutils é urgente. Se alguma coisa quebrar, tudo bem, consertamos depois”
Eu não quero rodar na minha máquina código feito por gente que pensa assim
Eu também sou a favor de Rust, mas Rust ser mais seguro só vale mantidas as demais condições iguais
Aqui as demais condições estão longe de ser iguais
Reescritas inevitavelmente vão ter muito mais bugs e vulnerabilidades do que código mantido por décadas, então o argumento de segurança faz sentido como estratégia de transição de longo prazo, mas não justifica um rollout apressado
Minimizar o impacto nos usuários depois da distribuição, ou dizer “é assim que os bugs aparecem” e “o coreutils antigo também não tinha testes adequados”, é irresponsável demais
Usuários não são cobaias
Acho que mantenedores têm uma responsabilidade moral de não prejudicar a confiabilidade dos sistemas dos usuários
Mais fundamentalmente, parece que a biblioteca padrão do Rust empurra o desenvolvedor para uma API limpa no nível errado de abstração
Por exemplo, para operações baseadas em caminho em vez de operações de arquivo baseadas em handle
Espero estar enganado
Acho que a proposta do Rust é fazer com que as armadilhas maiores e mais comuns simplesmente não precisem de tanta atenção
E o ponto central deste texto parece ser justamente que as APIs de sistema de arquivos deveriam cumprir esse papel
Alguém já cunhou a expressão disassembler rage para algo parecido
A ideia é que, se você olhar de perto o bastante, qualquer erro parece amador
A expressão também vem da postura de quem fica só no disassembler xingando o programador de alto nível por ter usado
ifem vez deswitchnuma função 100 frames abaixo na call stackAqui estamos vendo só alguns pontos em que eles erraram, e quase nada das milhares de linhas de código escrito corretamente ao redor
Ter
panicnesse tipo de utilitário é um erro bem amador, mesmo pelos padrões de RustSe fosse algo como erro irrecuperável de alocação, tudo bem, mas
expecteunwrapsão difíceis de justificar a menos que a invariante que impede aquele caminho de código seja garantida de forma realmente rígidaUma das dificuldades ao reescrever código é que o código original foi sendo moldado gradualmente para responder a problemas que só apareciam em ambientes reais de operação
As lições aprendidas nesse processo ficam silenciosamente embutidas no código e, se não estiverem documentadas, há uma quantidade enorme de trabalho oculto até alcançar um nível equivalente
O texto original mostra muito bem exatamente esse tipo de lista
Ainda assim, antes de sair chamando de amador, também é preciso reconhecer que esse é um dos fenômenos mais próprios do software
A menos que houvesse ótima documentação técnica no coreutils e testes cobrindo esses casos que foram simplesmente ignorados, isso era quase inevitável
Um bom exemplo citado no texto é o CVE de chroot + NSS
A regra de que o NSS é dinâmico e vai fazer
dlopende bibliotecas dentro dochrootnão está escrita de forma visível em lugar nenhumIsso é mais o tipo de coisa que administradores de sistemas aprenderam na prática por mais de 25 anos, e uma reescrita clean-room costuma reaprender isso como um novo CVE
Mesmo que você porte o mesmo código com um LLM, a situação é parecida
Dá para ler as assinaturas de função, mas o que realmente importa são as marcas e cicatrizes que ficaram naquele código
Fica ainda mais difícil se fizerem esse trabalho evitando a GPL e sem nem ler o código-fonte original
Na minha opinião, se o
uutilsfosse GPL e pudesse se inspirar diretamente no código-fonte original do coreutils, teria sido muito melhorTambém é preciso deixar claro que não documentar essas lições, ou pelo menos os bugs e vulnerabilidades que se tentou evitar, também é uma prática ruim
Claro, é difícil documentar todos os bugs evitados implicitamente só por escrever o código direito,
mas para quem vier depois é importante deixar algo como “o motivo de usar
fooaqui em vez debaré que, sob a condição ABC, usarbarcriaria umbazperigoso por causa de XYZ”Mesmo que pareça um pequeno desperdício de tempo e espaço de documentação, ainda assim é melhor
Sinto que várias das coisas apontadas neste texto, especialmente comparando com o código-fonte do GNU coreutils, deveriam ter sido pegas por testes unitários decentes ou por revisão manual
Reescrever o coreutils parece uma ideia terrível
https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/
e parece ter sido conduzido da forma errada, sem absorver adequadamente o conhecimento acumulado pelo software anterior
Para reescrever, você precisa entender e aprender completamente com a obra anterior
Senão vai repetir os mesmos erros, e francamente isso é bem constrangedor
Para deixar claro, gosto de Rust, uso em vários projetos e acho excelente
Só que Rust não salva engenharia ruim
Curiosamente, o
uutilsusa a suite de testes do GNU coreutilsE, além disso, eles deixam explícito que não aceitam contribuições escritas a partir da leitura do código GPL
Se foi o pessoal que fez
unity,upstartesnap, isso já entra no campo do esperadoParece até que deveríamos dar esta mensagem de boas-vindas a novos programadores de sistemas:
Unix é quebrado, e no fim você vai ter de escrever gambiarras feias e nada didáticas com as próprias mãos, além de fazer testes empíricos
É assim que software confiável e boa engenharia de software realmente funcionam
Fico curioso por que differential fuzzing não pegou esses bugs
https://github.com/uutils/coreutils/tree/main/fuzz/uufuzz
O padrão de verificar um caminho com uma syscall e depois fazer outra syscall no mesmo caminho para executar a operação sempre traz o mesmo tipo de problema
Um atacante com permissão de escrita no diretório pai pode trocar um componente do caminho por um link simbólico nesse intervalo, e o kernel vai resolver o caminho de novo do zero na segunda chamada, fazendo a operação privilegiada ir para o alvo escolhido pelo atacante
Um atacante com permissão de escrita no diretório pai também pode aprontar com hardlinks
Mesmo que só possa mexer em arquivos regulares, na prática quase não há mitigação decente
Veja o exemplo em https://michael.orlitzky.com/articles/posix_hardlink_heartache.xhtml
A causa raiz de alguns bugs parece ser que a API Unix é opaca demais
Por exemplo, o fato de
get_user_by_namecarregar bibliotecas compartilhadas dentro do novo sistema de arquivos raiz para resolver nomes de usuário e, por causa disso, permitir que um atacante que pode plantar arquivos dentro dochrootexecute código como uid 0, parece quase uma armadilha explosivaUma função que obtém dados de usuário de repente também carregar bibliotecas compartilhadas parece um design que mistura responsabilidades
Eu acharia melhor separar, no nível de função, a consulta de dados de usuário do carregamento de bibliotecas, ou pelo menos deixar esse comportamento evidente já no nome
Em parte até pode ser, mas se decidiram reescrever o coreutils do zero, então entender a API POSIX é literalmente parte central do trabalho
Além disso, se o código que verifica se o caminho aponta para a raiz do sistema de arquivos era
file == Path::new("/"), isso não é problema de APIQuem escreveu isso quase não parece qualificado para participar desse projeto
Acho até que usar uma linguagem segura funcional pode levar a pessoa a imaginar que os dados com que ela lida também não têm estado
Mas no sistema operacional muita coisa muda o tempo todo
Até surgirem sistemas de arquivos que ofereçam snapshots, é preciso verificar tudo repetidamente
No fim, o que se precisa é de uma API que, dado um input, devolva ou um resultado de sucesso ou uma falha
não uma API que devolva uma entre três coisas: sucesso, falha ou erro
Sim, o
musl libcremove justamente uma parte desse tipo de comportamentoAcho que a causa raiz não é tanto a opacidade da API Unix, mas o fato de não terem pensado direito no caso em que root faz chroot para um diretório que não controla
Qualquer coisa para a qual se fez
chrootfica sob controle de quem montou aquelechroot, e se a pessoa não entende isso, não tem qualificação para usarchroot()get_user_by_namepode até parecer uma pegadinha, mas na prática há pouca diferença real entre usarnewroot/etc/passwde usarnewroot/usr/lib/x86_64-linux-gnu/libnss_compat.so,newroot/bin/she afinsPor isso acho que
/usr/sbin/chrootnem deveria ter motivo para consultar IDs de usuárioO
toybox chrootnão faz issoNo fim, o bug não foi a forma errada de fazer algo, e sim o fato de terem feito isso para começo de conversa
Unix e POSIX são um fractal de armadilhas em qualquer nível de zoom
Mesmo supondo que o pessoal de Rust tenha reescrito o coreutils sem experiência com Linux, o que eu entendo menos ainda é como o Ubuntu aceitou isso no mainline
Parece até que o Ubuntu tem uma política de trocar, quase a cada release, um componente fundamental do sistema por um experimento capenga e inacabado
O ponto principal aqui não é “nossa, tinha bug em código Rust”, e sim justamente isso
O original é sob licença GPL, e a reescrita é sob licença MIT
Se for verdade que “esses bugs vieram de código Rust realmente distribuído, e os autores sabiam o que estavam fazendo”,
fico me perguntando se isso significa que os utilitários originais não tinham test harness e que a reescrita também não começou por aí
Mesmo com muitos edge cases, parece que daria para abstrair razoavelmente o SO e o FS e verificar coisas como se
rm .//realmente não apaga o diretório atual quando deveria se comportar de um certo jeitoIsso me parece menos um problema de código bagunçado ou uma crítica à linguagem e mais, de novo, aquela velha atitude de que programação de sistemas não testa
Por outro lado, se os utilitários originais tinham testes e ainda assim havia tantas lacunas, então talvez a própria suite de testes original fosse bastante insuficiente
Acho que sim
Mas não tenho tanta certeza assim de que dê para abstrair o SO e o FS o bastante para validar isso
porque as pessoas tentam esse tipo de coisa desde antes de eu nascer e, pelo jeito, ainda não conseguiram
Por exemplo, já é difícil até decidir quantas barras
/você deve concatenar num testeIndo além, suponha que
rmse recusasse a apagar um arquivo se os 9 primeiros bytes fossemimportantsem saber essa string de antemão, já é complicado imaginar como criar um teste que descubra esse comportamento
E se essa palavra mágica nem estiver no dicionário, fica ainda pior
Quase nunca vi alguém dizer seriamente que “programação de sistemas não testa”
O que eu ouço com frequência é que testes nem sempre cumprem o papel que as pessoas esperam deles
Pelo que entendi, no processo de desenvolvimento do
uutilshouve testes extensivos de comparação de comportamento com os utilitários originais, e até tentativas de preservar os bugsEste é um dos motivos pelos quais o Windows desativa symlink por padrão
Não é uma solução por abstração, e sim praticamente pela remoção da funcionalidade
Nos Unix-like isso não dá para fazer, porque existe software demais dependendo de symlinks há décadas
O MacOS também tem uma resposta parecida
Por exemplo, o bug de
chroot()em geral não vira um problema real na configuração padrão, porque o MacOS bloqueiachroot()por padrãoPara usar, é preciso desativar o system integrity protection
O problema fundamental está nas arestas cortantes da API POSIX, e a solução fica mais perto de eliminar isso do que de abstrair
Acho normal que as pessoas experimentem e tentem, mesmo de forma desajeitada
É assim que se aprende e cresce
O que realmente me deixa curioso é como a cadeia de decisão do Ubuntu se quebrou a ponto de isso chegar em produção