8.9 KiB
PR-004: Runtime VFS Path Traversal Hardening
Briefing
Hoje o VirtualFS normaliza barras, mas nao canonicaliza nem rejeita segmentos ... Em seguida, o backend de host concatena o caminho recebido com a raiz montada. Isso abre espaco para escapar da raiz virtual com caminhos como /user/../../outside.txt.
Este PR endurece a fronteira entre o filesystem virtual e o filesystem do host. O objetivo e garantir que toda operacao de read/write/delete/list/exists permaneça estritamente dentro da raiz montada.
Problema
- O
VirtualFSaceita caminhos relativos e absolutos sem validacao estrutural suficiente. - O backend
HostDirBackendfazroot.join(path)sem bloquear traversal. - O problema afeta confidencialidade, integridade e isolamento do runtime.
Escopo
- Endurecer a normalizacao de caminhos no
VirtualFS. - Endurecer a resolucao no
HostDirBackend. - Garantir comportamento consistente para
read_file,write_file,delete,list_direexists. - Cobrir casos de traversal em testes unitarios.
Fora de Escopo
- Suporte a links simbolicos com politicas avancadas.
- Politicas de permissao por namespace (
/system,/user,/apps, etc). - Refactor completo da API de filesystem.
Abordagem
- Introduzir uma regra unica de validacao de caminho virtual:
- converter
\para/; - exigir caminho absoluto virtual;
- colapsar
.quando aparecer; - rejeitar qualquer segmento vazio ambiguo ou
..; - retornar erro explicito em vez de tentar "corrigir" traversal.
- converter
- Fazer o
VirtualFSoperar apenas sobre caminhos validados. - Endurecer o
HostDirBackendpara nunca confiar apenas na normalizacao acima:- resolver o caminho relativo a partir da raiz;
- rejeitar novamente qualquer tentativa de escapar;
- manter defesa em profundidade mesmo se outro backend ou chamador evoluir errado.
- Garantir que operacoes booleanas como
existsnao silenciem traversal como se fosse "arquivo inexistente" sem distinguir erro estrutural quando isso for relevante para a API.
Algoritmo
Normalizacao de caminho virtual
Entrada: path: &str
Saida: caminho virtual sanitizado ou erro.
Passos:
- Substituir
\por/. - Se o caminho nao comecar com
/, prefixar/. - Separar por
/. - Ignorar segmentos vazios e
.. - Se algum segmento for
.., falhar comFsError. - Reconstruir o caminho como
/<seg1>/<seg2>/.... - Preservar
/como raiz quando nao houver segmentos.
Resolucao no backend do host
Entrada: caminho virtual sanitizado.
Saida: PathBuf dentro de root ou erro.
Passos:
- Remover o
/inicial do caminho virtual. - Concatenar cada segmento validado manualmente em um
PathBufiniciado emroot. - Nunca aceitar segmentos
..,.ou componentes de prefixo/plataforma. - Antes de retornar, garantir que o caminho construido continua sob
root.
Plano de Execucao
Gate arquitetural
Antes de iniciar a implementacao, esta PR deve congelar a seguinte decisao:
existspermanece com retornoboolnesta PR.- Consequencia: caminho invalido ou tentativa de traversal em
existsdeve resultar emfalse, sem acesso ao host. - Justificativa: mudar
existsparaResult<bool, FsError>propaga alteracao de contrato paraFsBackend,VirtualFS, runtime e syscall, o que expande o escopo desta PR.
Se essa decisao nao for aceita, a PR deve voltar para discussao arquitetural antes de qualquer alteracao de codigo.
Fase 1 - Endurecer contrato de erro
Arquivos alvo:
crates/console/prometeu-system/src/services/fs/fs_error.rs
Passos:
- Introduzir um erro explicito para caminho invalido, por exemplo
FsError::InvalidPath(String)ou equivalente. - Garantir mensagem clara para os casos:
- caminho com
..; - componente de plataforma/prefixo inesperado;
- caminho vazio estruturalmente invalido, se a implementacao optar por rejeita-lo.
- caminho com
Objetivo:
- impedir que traversal seja reportado genericamente como
IOErrorouOther.
Fase 2 - Endurecer o VirtualFS
Arquivos alvo:
crates/console/prometeu-system/src/services/fs/virtual_fs.rs
Passos:
- Trocar
normalize_path(&self, path: &str) -> Stringpor uma funcao fallible:normalize_path(path: &str) -> Result<String, FsError>.
- Implementar a normalizacao unica da camada virtual:
- converter
\para/; - garantir raiz virtual absoluta;
- colapsar
.e segmentos vazios internos irrelevantes; - rejeitar
...
- converter
- Fazer
list_dir,read_file,write_fileedeletefalharem antes de tocar no backend quando o caminho for invalido. - Manter
existscomo fronteira booleana:- se a normalizacao falhar, retornar
false; - nao chamar o backend nesse caso.
- se a normalizacao falhar, retornar
Objetivo:
- estabelecer uma unica regra de caminho virtual para toda a API publica do VFS.
Fase 3 - Endurecer o HostDirBackend
Arquivos alvo:
crates/host/prometeu-host-desktop-winit/src/fs_backend.rs
Passos:
- Trocar
resolve(&self, path: &str) -> PathBufpor uma resolucao fallible. - Iterar manualmente pelos componentes do caminho e rejeitar:
ParentDir;CurDir;RootDir;Prefix(_)em plataformas que exponham prefixos.
- Construir o
PathBufa partir derootapenas com segmentos normais. - Validar ao final que o caminho produzido continua estritamente sob
root. - Fazer
list_dir,read_file,write_fileedeleteretornarem erro estrutural em vez de acessar o host. - Fazer
existsretornarfalsepara caminhos invalidos, sem side effect.
Objetivo:
- manter defesa em profundidade mesmo que outra camada no futuro normalize errado.
Fase 4 - Cobertura de testes no prometeu-system
Arquivos alvo:
crates/console/prometeu-system/src/services/fs/virtual_fs.rs
Passos:
- Adicionar testes unitarios de normalizacao/rejeicao para:
../x/../x/user/../../x\\user\\..\\..\\x
- Adicionar um teste que prove que
exists("../x") == false. - Adicionar um mock backend com contadores ou flags para comprovar que caminhos invalidos:
- nao chegam em
read_file; - nao chegam em
write_file; - nao chegam em
delete; - nao chegam em
list_dir; - nao chegam em
exists.
- nao chegam em
Objetivo:
- provar que a barreira virtual bloqueia traversal antes de qualquer backend.
Fase 5 - Cobertura de testes no host
Arquivos alvo:
crates/host/prometeu-host-desktop-winit/src/fs_backend.rs
Passos:
- Adicionar teste positivo de round-trip:
- criar
/user/test.txt; - ler;
- verificar
exists; - apagar.
- criar
- Adicionar teste de traversal em leitura e escrita usando diretorio temporario:
- garantir erro para
/user/../../outside.txt; - garantir que nenhum arquivo e criado fora de
root.
- garantir erro para
- Adicionar teste para
existselist_dircom traversal:existsretornafalse;list_dirretorna erro;- nenhum acesso fora da raiz e observado.
Objetivo:
- provar que a defesa em profundidade do backend funciona mesmo isoladamente.
Fase 6 - Validacao final
Comandos:
cargo test -p prometeu-systemcargo test -p prometeu-host-desktop-winit
Checklist de saida:
- nenhuma operacao acessa caminho fora da raiz montada;
- caminhos validos continuam funcionais;
- traversal falha de forma deterministica e explicita;
existspreserva contrato booleano sem vazar acesso ao host.
Sequencia recomendada
- Implementar
FsErrorprimeiro. - Endurecer
VirtualFSe fechar os testes unitarios da camada virtual. - Endurecer
HostDirBackendcom defesa em profundidade. - Adicionar os testes de host.
- Rodar os testes dos dois crates.
Riscos de execucao
- O comportamento atual aceita caminhos permissivos como
user/file.txt; apos esta PR eles continuarao aceitos apenas se normalizarem para uma forma absoluta segura. - A maior fonte de expansao de escopo e tentar mudar o contrato de
exists; esta PR nao deve fazer isso. - Se surgir necessidade de suportar links simbolicos ou canonicalizacao real de host, isso caracteriza outra PR.
Criterios de Aceite
- Qualquer tentativa de traversal com
..e rejeitada emread_file. - Qualquer tentativa de traversal com
..e rejeitada emwrite_file. - Qualquer tentativa de traversal com
..e rejeitada emdelete. existselist_dirnao acessam caminhos fora da raiz montada.- Caminhos normais como
/user/save.datcontinuam funcionando. - O backend de host continua criando subdiretorios validos dentro da raiz.
Tests
- Teste unitario no
VirtualFSpara rejeitar:../x/../x/user/../../x\\user\\..\\..\\x
- Teste unitario no backend do host validando que um caminho de traversal nao resulta em acesso fora da raiz temporaria.
- Teste positivo para operacoes validas:
- criar arquivo em
/user/test.txt; - ler o mesmo arquivo;
- confirmar
exists; - apagar o arquivo.
- criar arquivo em
- Rodar:
cargo test -p prometeu-systemcargo test -p prometeu-host-desktop-winit
Risco
Baixo para a arquitetura e medio para compatibilidade, porque caminhos hoje aceitos de forma permissiva podem passar a falhar explicitamente. Esse endurecimento e desejado.