- Segurança de memória e sandboxing são conceitos de segurança independentes, e é preciso ter ambos para formar uma defesa robusta
- O Fil-C é uma implementação com segurança de memória para C/C++, garantindo segurança até o nível de chamadas de sistema do Linux e podendo ser usado até em componentes do sistema como o OpenSSH
- No processo de portar o sandbox baseado em seccomp-BPF do OpenSSH para o Fil-C, as principais questões foram a limitação da criação de threads e o ajuste dos filtros seccomp
- Para o gerenciamento de threads em segundo plano do runtime do Fil-C, foi adicionada a API
zlock_runtime_threads(), que controla o funcionamento das threads dentro do sandbox
- O Fil-C implementa a aplicação sincronizada de chamadas
prctl em todas as threads do runtime, para que no_new_privs e os filtros seccomp sejam aplicados de forma consistente a todo o processo
Relação entre segurança de memória e sandboxing
- Segurança de memória e sandboxing são camadas de segurança diferentes, e apenas uma delas não oferece proteção completa
- Exemplo de algo com segurança de memória, mas sem sandbox: um programa Java que pode sobrescrever arquivos por meio de entrada do usuário
- Exemplo de algo em sandbox, mas sem segurança de memória: um programa escrito em assembly com privilégios restritos
- Na prática, sandboxes reais têm brechas estruturais, como a permissão de comunicação com um processo broker
- Por isso, combinar segurança de memória e sandboxing é a melhor forma de defesa
Integração entre Fil-C e o sandbox do OpenSSH
- O Fil-C é uma implementação com segurança de memória para C/C++, mantendo essa segurança no nível das chamadas de sistema do Linux
- O runtime do Fil-C pode funcionar até em componentes de sistema de baixo nível, como
init e udevd
- O OpenSSH funciona normalmente no Fil-C e utiliza um sandbox seccomp-BPF
- No Linux, o OpenSSH constrói o sandbox com as seguintes ferramentas
chroot para restringir o acesso ao sistema de arquivos
- execução como usuário/grupo
sshd
setrlimit para limitar abertura de arquivos e criação de processos
- filtro seccomp-BPF para permitir apenas chamadas de sistema autorizadas
- O Fil-C oferece suporte básico a
chroot e troca de usuário, mas setrlimit e seccomp-BPF podem entrar em conflito com o funcionamento do runtime, exigindo ajustes adicionais
Controle de threads no runtime do Fil-C
- O runtime do Fil-C usa threads em segundo plano para coleta de lixo, interrompendo e reiniciando essas threads automaticamente quando necessário
- O sandbox
setrlimit do OpenSSH tem como objetivo proibir a criação de novos processos, então a criação de threads pelo runtime pode violar essa política
- Para resolver isso, foi adicionada a API
zlock_runtime_threads() em <stdfil.h>
- O runtime cria imediatamente as threads de que precisa e depois desativa o encerramento automático delas
- A chamada é feita na função
ssh_sandbox_child do OpenSSH antes de setrlimit ou seccomp-BPF
Ajustes no filtro seccomp do OpenSSH
- Depois da aplicação de
zlock_runtime_threads(), a maior parte das funções do sandbox continua funcionando normalmente
- As seguintes mudanças foram feitas no filtro seccomp
- Em caso de violação, foi configurado
SECCOMP_RET_KILL_PROCESS, para que as threads em segundo plano do Fil-C também sejam encerradas
MAP_NORESERVE foi adicionado à lista de permissões, dando suporte ao uso do alocador de memória do Fil-C
- A chamada
sched_yield foi permitida, pois é usada na implementação de locks do Fil-C
- As chamadas
futex usadas para sincronização no Fil-C já eram permitidas, então não foi necessária nenhuma mudança adicional
Como o Fil-C implementa prctl
- O OpenSSH usa duas chamadas
prctl ao instalar o filtro seccomp
PR_SET_NO_NEW_PRIVS para bloquear a obtenção de privilégios adicionais
PR_SET_SECCOMP, SECCOMP_MODE_FILTER para ativar o filtro
- O problema é que
prctl se aplica apenas à thread que faz a chamada, o que cria o risco de outras threads do runtime do Fil-C ficarem sem filtro
- Para evitar isso, o Fil-C usa a API
filc_runtime_threads_handshake() para fazer a aplicação sincronizada em todas as threads do runtime
- Garante que cada thread execute a mesma chamada
prctl
- Se houver várias threads de usuário, gera um erro de segurança do Fil-C, reforçando a proteção
Conclusão
- Combinar segurança de memória e sandboxing é a composição de segurança mais forte
- O Fil-C integra completamente o sandbox baseado em seccomp do OpenSSH, mantendo a segurança de memória sem reduzir o nível de proteção
- Em ambientes Linux, usar o Fil-C permite obter ao mesmo tempo segurança em nível de sistema e segurança em nível de linguagem
1 comentários
Comentários do Hacker News
Fico me perguntando por que não houve nenhuma menção ao landlock
Existe uma abordagem híbrida de compilação que segue C → WASM → C
Isso permite controlar completamente a interação com o SO e fazer sandbox do acesso à memória como no WASM, enquanto tecnicamente continua sendo código C
Mais informações podem ser vistas em RLBox
Ele pode corromper a memória, mas o alcance disso fica restrito ao sandbox
Sistemas como SECCOMP são burocráticos porque exigem definir minuciosamente todas as políticas de interação
Já o Fil-C adota uma abordagem em que a própria linguagem e o runtime tentam garantir o funcionamento correto do programa
Binários do Fil-C são executáveis comuns, então também podem ser usados junto com sandboxes como o SECCOMP
Se o Linux levou 20 anos para criar a interface
prctl, parece que vamos ter de esperar uns 10 anos para ver algo parecido no WASIMesmo dentro do sandbox, ainda é possível criar fluxos de execução estranhos
O autor do Fil-C faz invenções tecnicamente interessantes muito bem, mas há preocupação sobre se a implementação foi validada o suficiente
Foi dito que dá para compilar programas
setuidcom Fil-C, mas a parte modificada dold.sopode ser arriscadaAplicativos
setuidprecisam ser escritos de forma muito defensiva em relação a variáveis de ambiente, descritores de arquivo,rlimit, sinais etc.Essas partes ainda parecem inacabadas, então há risco em usar isso numa infraestrutura real
Ainda assim, testar uma base de código com Fil-C pode revelar bugs interessantes
A modificação no
ld.soé pequena, basicamente para ensinar o layout da libcO bug de
getenvrelacionado asetuidjá foi corrigido parasecure_getenvNo que foi apontado, há um pouco de verdade misturado com um pouco de FUD
No Fil-C, em situações de corrida de dados, ponteiros e capabilities podem ficar inconsistentes
Isso pode causar violação de segurança de memória
O autor nega isso, mas comparar com Java é inadequado
A tecnologia é excelente, mas a postura do autor reduz a confiança
WASM é ao mesmo tempo sandbox e ambiente de execução, e dependendo de como for usado pode oferecer algum grau de segurança de memória
Se você compilar C para WASM, os bugs continuam existindo, mas o alcance do impacto fica limitado
Portanto, faz sentido classificar o WASM como tecnologia de sandbox, mas como ambiente de execução ele oferece mais possibilidades
Um bug no módulo B pode permitir ler dados do módulo A
Ou seja, WASM não passa de um substituto para sandbox leve de processos
Porque também dá para dizer que C é “seguro dependendo de como é usado”
O WASM impede escapar do runtime, mas não impede escapar da memória do programa interno
Foi pedido um comparativo entre Fil-C e Rust
Fil-C é adequado para reforçar programas C existentes com foco em compatibilidade e segurança
Rust é melhor para criar novas bases de código com segurança estática e desempenho
Fil-C tem uma pequena perda de desempenho, mas é útil para código C existente (
ffmpeg,nginx,sudoetc.)Rust se destaca em multithreading e no sistema de tipos
O objetivo é garantir segurança de memória, mais do que melhorar o design da linguagem
Os concorrentes estão mais próximos de D, Nim e Go do que de Rust
Rust previne isso em tempo de compilação
As duas abordagens são ortogonais, e seria possível adicionar verificações em runtime no estilo Fil-C ao Rust
MicroVMs estão ficando cada vez mais populares
Fico curioso para saber como o Fil-C poderia aproveitar isso
Espero que esse projeto receba mais atenção
Seria ótimo ver ferramentas centrais como sudo e polkit distribuídas com segurança de memória
Seria bom ver mais uso de sandboxing mesmo em linguagens com segurança de memória
É uma pena que até em Rust ou Go quase não se use sandbox no nível do SO
É difícil configurá-lo no nível de biblioteca, e ele é sensível à versão do kernel e à implementação da libc
Também há a limitação de não conseguir filtrar entradas por trás de ponteiros, como caminhos de arquivos
Por isso, normalmente ele precisa ser configurado manualmente no nível da aplicação
Já o Go tem um runtime grande, o que dificulta torná-lo seguro como no Fil-C
Fiquei curioso sobre a diferença entre Fil-C e o Address Sanitizer (ASan) do clang
Se for só uma questão de gerar pânico em runtime, fica difícil chamar isso de “segurança de memória”, não?
Alguns bugs não são detectados
Ele funciona colocando “red zones” ao redor da memória, então a detecção depende um pouco de sorte
Segurança de memória não significa “não trava”, e sim “acessos incorretos não conseguem produzir efeito”
Foi perguntado por que não usar uma VM completa como sandbox
Um processo analisa entradas sem privilégios, enquanto outro roda com privilégios elevados
Os dois processos se comunicam por IPC
Usar VM aumenta a segurança, mas traz muito overhead, e recursos como GPU ou acesso a arquivos ficam mais complexos
Por isso, em geral, o sandbox no nível do SO acaba sendo mais limpo
É preciso atribuir a GPU de forma dedicada, e o Qubes também só se conecta via encaminhamento de X11, então não há aceleração