
Problema com tough, versões anteriores a 0.20.0 (Múltiplas CVEs)
Em março de 2025, múltiplas vulnerabilidades de segurança foram divulgadas na biblioteca Tough da AWS Labs (uma implementação cliente em Rust do TUF – The Update Framework). Esses problemas são rastreados sob CVE-2025-2885, CVE-2025-2886, CVE-2025-2887 e CVE-2025-2888, e foram corrigidos na versão 0.20.0 do Tough. Engenheiros de segurança podem usar este documento para entender os detalhes técnicos de cada vulnerabilidade, a causa raiz no código e os patches que as resolvem. Todos os usuários do Tough < 0.20.0 são fortemente aconselhados a atualizar para v0.20.0 ou posterior.
O Tough não validava a versão do Root metadata em sequência durante a atualização. Um atacante que controlasse um repositório (ou com capacidade de man-in-the-middle) poderia fornecer um arquivo de metadados root com um número de versão inesperado, fazendo com que o cliente buscasse e confiasse em uma versão errada. Em essência, o Tough não garantia que a versão dos novos metadados root fosse exatamente uma unidade maior do que a versão anteriormente confiável, violando o requisito do TUF de uma cadeia contínua de confiança. Isso poderia levar a ataques de rollback ou mix-and-match, em que um root antigo (mas devidamente assinado) é aceito como se fosse o mais recente, potencialmente reintroduzindo chaves de assinatura aposentadas ou confiança expirada.
tough/src/lib.rs. A implementação vulnerável apenas verificava se a versão do novo root não era menor que a anterior, em vez de exigir que fosse a próxima versão sequencial. Essa omissão viola a regra da especificação do TUF de que os clientes devem baixar as versões intermediárias do root em ordem (sem pular versões).Implementação Vulnerável: Na versão 0.19.x e anteriores, após baixar um novo root.json, o Tough verificava as assinaturas, mas não aplicava estritamente a continuidade da versão. Ele permitia que a versão do novo root fosse qualquer valor >= à versão confiável. Por exemplo, se o root confiável atual fosse a versão N, o Tough aceitaria um novo root com versão N+2 ou superior (desde que as assinaturas fossem válidas), pulando a N+1. O trecho de código abaixo ilustra a verificação anterior à correção:```rust
// (Prior to fix) Allow new root version to be >= old version – too permissive
ensure!(root.signed.version <= new_root.signed.version, error::OlderMetadataSnafu {
role: RoleType::Root,
current_version: root.signed.version,
new_version: new_root.signed.version
});
Um atacante poderia explorar isso apresentando <b>metadados de root de versão mais alta que na verdade são um conjunto de chaves mais antigo</b>. O Tough aceitaria, pensando que é uma atualização, e [confiaria em conteúdo assinado com chaves desatualizadas](https://github.com/advisories/GHSA-5vmp-m5v2-hx47#:~:text=The%20tough%20client%20will%20trust,with%20a%20previous%20root%20role).
<b>Implementação Corrigida:</b> O código corrigido (no Tough 0.20.0) exige explicitamente que a versão do novo root seja exatamente uma unidade maior que a versão antiga, veja o [commit 0eeb60a](https://github.com/awslabs/tough/commit/0eeb60aefe27f00b65730634b788a1aafb8bf3c6). Ele também garante que a nova versão seja mais alta (evita igualdade ou decremento) e impede qualquer salto maior que +1:```rust
// (Fixed in v0.20.0) Enforce sequential root version update (new_version == old_version + 1)
ensure!(
root.signed.version < new_root.signed.version &&
root.signed.version.get() + 1 == new_root.signed.version.get(),
error::OlderMetadataSnafu {
role: RoleType::Root,
current_version: root.signed.version,
new_version: new_root.signed.version
}
);
Esta alteração garante que o cliente baixe e aplique os metadados raiz em ordem (N, N+1, N+2, ...) sem pular. Se um arquivo de metadados raiz for encontrado com uma versão que não seja exatamente old_version+1, o Tough agora trata isso como um possível ataque e aborta a atualização.
O Tough tratou incorretamente os papéis de alvos delegados “terminantes” conforme definidos pelo TUF. Em um repositório TUF, uma delegação pode ser marcada como terminante, o que significa que, se uma busca por alvo alcançar essa delegação e o alvo não for encontrado nela, a busca deve parar (e não continuar para outras delegações de prioridade mais baixa). (veja aqui) Devido a um erro de lógica, o Tough falhou em encerrar a busca nesse caso – ele continuava buscando em delegações subsequentes mesmo quando deveria ter parado. Isso poderia permitir que um invasor que controla uma delegação de prioridade mais baixa servisse conteúdo para alvos que não deveria controlar, contornando os limites de confiança pretendidos.
tough/src/editor/targets.rs (e o código relacionado de resolução de delegações). O código vulnerável não tratava corretamente a flag terminating nas delegações. Ao iterar pelas delegações em busca de um alvo, o Tough prosseguia para a próxima delegação mesmo se a atual estivesse marcada como terminante e não tivesse correspondência, contrariando a especificação do TUF.Implementação vulnerável: No Tough <0.20.0, a lógica de find_target() simplesmente fazia recursão ou percorria todos os papéis delegados possíveis até que um alvo fosse encontrado ou todos fossem esgotados. Ela não definia nenhuma flag nem interrompia o loop ao encontrar um papel terminante. Pseudocódigo do comportamento antigo:```rust
for role in delegation_chain {
if role.has_target(target) {
return target_metadata;
}
// Missing: if role is terminating and target not found, should break.
// Tough erroneously continues to next delegation.
}
This means a lower-priority delegate (which should be ignored after a terminating delegation above it) could still be consulted and supply a malicious target file