Contêineres rootless do Podman e o exploit Copy Fail
(garrido.io)- CVE-2026-31431 Copy Fail permite que um usuário local sem privilégios obtenha um shell
roote, mesmo dentro de contêineres rootless do Podman, possibilita a elevação pararootdentro do contêiner - Os contêineres rootless do Podman combinam namespaces de usuário, separação de UID e Linux capabilities para mapear o
rootinterno do contêiner para um usuário sem privilégios no host e limitar os privilégios no host - Nos testes, o usuário
foode um contêiner rootless non-root conseguiu se tornarrootdentro do contêiner após executar o Copy Fail, mas os privilégios permaneceram limitados ao que o usuário sem privilégiosbarpodia fazer no host, e arquivos do host pertencentes aorootnão puderam ser lidos - Ao aplicar
--security-opt=no-new-privilegesou--cap-drop=all, mesmo após a execução do Copy Fail o shell permaneceu comofooe com capabilitiesnone, impedindo a obtenção imediata de um shellroote a elevação de capabilities - Os efeitos do Copy Fail podem persistir além do ciclo de vida do contêiner, exigindo patch do kernel e reinicialização, e também devem ser aplicadas medidas de defesa em profundidade como sistema de arquivos raiz somente leitura, limites de recursos com cgroups, imagens de runtime enxutas e firewall
Copy Fail e o escopo de exposição dos contêineres rootless do Podman
- CVE-2026-31431 foi divulgado em 29 de abril no copy.fail e, ao executar o script em Python publicado, um usuário local sem privilégios pode obter um shell
root - O Copy Fail também pode ser explorado dentro de contêineres Linux, e mesmo em contêineres rootless do Podman é possível obter um shell
rootdentro do contêiner - Nos testes, o
rootdo contêiner ficou limitado, no nível do host, ao escopo de privilégios do usuário sem privilégiosbar, que executou o contêiner - A implementação rootless do Podman combina namespaces de usuário, separação de UID e Linux capabilities para limitar os privilégios no host dos processos do contêiner
- O Copy Fail mostra que contêineres rootless não são imunes à vulnerabilidade, mas que a configuração do Podman pode reduzir o alcance do ataque após a invasão
Como funcionam os contêineres rootless
-
Exemplo básico: usuário sem privilégios
barexecuta um servidor HTTP- O ambiente de exemplo consiste em um usuário sem privilégios
bar, com UID1001, que compila uma imagem baseada emubuntu:latestcom o Podman e executapython3 -m http.server - No host, ao verificar com
ps, o processopython3aparece sendo executado sob o usuáriobar - Como o Podman usa um modelo de fork/exec, o processo do contêiner se torna descendente do processo
podman run, e a separação de UID normalmente isola o processo do contêiner dorootdo host e de outros usuários - Em uma configuração típica do Docker, mesmo que um usuário sem privilégios execute
docker run, o cliente Docker se comunica com um daemon com privilégios de root, e como o daemon é quem finalmente cria o processo do contêiner, no host esse processo pode aparecer comoroot
- O ambiente de exemplo consiste em um usuário sem privilégios
-
Rootless rootful
- Se a imagem do contêiner não tiver uma diretiva
USERexplícita nem a flag--user, o comando do contêiner normalmente será executado comorootdentro do contêiner - Na saída de
podman top, o processo do servidor HTTP é mapeado para o usuário1001no host, mas dentro do contêiner é executado comoroot - Essa configuração caracteriza um estado rootless rootful: sem privilégios no host, mas como
rootdentro do contêiner
- Se a imagem do contêiner não tiver uma diretiva
-
Namespaces de usuário
- Os contêineres rootless do Podman usam namespaces de usuário para mapear UID/GID de forma diferente dentro e fora do contêiner
- No exemplo, o
rootcom UID0dentro do contêiner é mapeado parabar, com UID1001, no host - A configuração
bar:165536:65536em/etc/subuiddefine o intervalo de UIDs que pode ser atribuído aos processos no namespace debar - No exemplo, além do UID
1001debar, os UIDs de165536até231072podem ser atribuídos aos processos debar - Se
sleepfor executado como o usuáriowww-datadentro do contêiner, ele aparece comowww-datainternamente, mas como165568no host - Ao entrar no namespace de usuário com
podman unshare, o diretório home que no host pertence abar:baraparece comoroot:rootdentro do namespace - O Docker também oferece suporte a namespaces de usuário, mas requer configuração separada e permite apenas um único namespace de usuário, enquanto o Podman executa os contêineres rootless de cada usuário UNIX dentro do namespace desse próprio usuário
-
Operações privilegiadas e Linux capabilities
- O Podman usa Linux capabilities para conceder privilégios de root granulares aos processos do contêiner
- Durante a compilação da imagem, operações como
apt installse tornam possíveis por meio de uma combinação de capabilities comochown,dac_override,fowner,setgid,setuid,net_bind_serviceesys_chroot - Se todas as capabilities forem removidas com
podman build --cap-drop=all, oaptfalha em operações comosetgroups,setegid,seteuidechown, e a compilação da imagem falha - Também é possível adicionar apenas as capabilities necessárias e, no exemplo, a instalação de pacotes é feita adicionando
CAP_SETUID,CAP_SETGID,CAP_CHOWN,CAP_DAC_OVERRIDE,CAP_FOWNER - No estado padrão de execução, o servidor HTTP roda como
rootdentro do contêiner e possui várias capabilities efetivas, comoCHOWN,DAC_OVERRIDE,FOWNER,FSETID,KILL,NET_BIND_SERVICE,SETFCAP,SETGID,SETPCAP,SETUID,SYS_CHROOT - Como o servidor HTTP não precisa desses privilégios, é possível remover todas as capabilities com
podman run --cap-drop=all; nesse caso,podman topmostra as capabilities efetivas comonone
-
Rootless non-root
- Para executar o servidor HTTP como um usuário sem privilégios também dentro do contêiner, pode-se usar um usuário já existente em
/etc/passwd, comowww-data, ou criar um usuário dedicado durante a compilação da imagem - No exemplo, são criados o usuário e grupo
foo, com UID1002, são concedidas permissões de leitura em/var/www/html, e então é definidoUSER foo:foo - Ao executar essa imagem com
--cap-drop=all, o processo passa a ficar comofoodentro do contêiner, UID166537no host e capabilities efetivasnone - Os processos do contêiner devem ser executados com o menor privilégio necessário; por exemplo, se
fooprecisar fazer bind na porta privilegiada80, deve-se adicionar--cap-add=CAP_NET_BIND_SERVICE - As formas de execução de contêiner podem ser divididas em quatro categorias
- usuário
rootno host +rootno contêiner: root rootful - usuário
rootno host + usuário sem privilégios no contêiner: root non-root - usuário sem privilégios no host +
rootno contêiner: rootless rootful - usuário sem privilégios no host + usuário sem privilégios no contêiner: rootless non-root
- usuário
- O Podman facilita a execução de contêineres rootless rootful e, se for possível executar os processos do contêiner como usuários sem privilégios, também é relativamente fácil montar uma configuração rootless non-root
- Para executar o servidor HTTP como um usuário sem privilégios também dentro do contêiner, pode-se usar um usuário já existente em
Montagens bind e isolamento de UID
- Ao montar um diretório do host no contêiner, a possibilidade de acessar arquivos pertencentes ao
rootdo host, aobardo host e aofoodo namespace varia de acordo com o mapeamento de UID - No exemplo, são criados no diretório
/var/lib/bar/testos arquivosroot.txt, pertencente aorootdo host, ebar.txt, pertencente aobardo host, e o diretório é montado no contêiner como/testcom leitura e escrita - Ao executar o contêiner como
foo, o arquivo pertencente aobardo host aparece comoroot:rootdentro do contêiner, enquanto o arquivo pertencente aorootdo host aparece comonobody:nogrouppor não estar mapeado para o namespace - O
foodentro do contêiner não consegue lerbar.txtnemroot.txt, e um rootless non-root oferece isolamento adicional em relação ao rootless rootful - O
foo.txtcriado porfoono diretório montado aparece no host como pertencente ao UID166537, e o usuáriobardo host não consegue ler o conteúdo desse arquivo - Ao executar o contêiner como
rootinterno, orootdo namespace consegue ler o arquivo pertencente aobardo host e o arquivo pertencente aofoo, mas não consegue ler o arquivoroot.txtpertencente aorootdo host - Ao executar como
rootinterno com--cap-drop=all, ele também não consegue ler o arquivo defooe só consegue ler o arquivo pertencente aobardo host
Teste do Copy Fail
-
Condições de teste
- No teste de Copy Fail, foi usada a versão de exploit do commit originalmente publicado
8e918b5 - A imagem de contêiner de exemplo adiciona
curlà imagem existente do servidor HTTP para permitir baixar o script de exploit de dentro do contêiner - A imagem é construída com o nome
copyfail - O kernel de teste é o
6.12.74+deb13+1-amd64do Debian, e, no contexto do Debian, versões recentes abaixo de6.12.85ainda podem ser consideradas kernels sem patch - Em geral, quando o usuário sem privilégios
foochamasu, é exigida a senha deroot - Em cada teste, o usuário do contêiner baixa o script do Copy Fail em
/tmp, executa-o e, ao obter um shell deroot, chamasleep - Como o Copy Fail persiste além do ciclo de vida do contêiner, a VM é reiniciada antes de cada teste
- No teste de Copy Fail, foi usada a versão de exploit do commit originalmente publicado
-
Resultado em rootless rootful
- Ao executar o contêiner com
--user=root, os processos dentro do contêiner já estão comoroot - Nesse estado, ao executar o script do Copy Fail e chamar
su, obtém-se um shelluid=0(root), mas como o usuáriorootjá pode abrir outro shell root comsusem senha, o Copy Fail não acrescenta nada de prático - No
podman top,/bin/bash,python3 copy_fail_exp.py,suesleepaparecem todos comorootdentro do contêiner e usuário1001no host - O mesmo conjunto de capabilities é mantido, e aparecem
CHOWN,DAC_OVERRIDE,FOWNER,FSETID,KILL,NET_BIND_SERVICE,SETFCAP,SETGID,SETPCAP,SETUID,SYS_CHROOT - O
rootinterno consegue lerbar.txtefoo.txtem/test, mas não consegue lerroot.txt, pertencente aorootdo host
- Ao executar o contêiner com
-
Resultado em rootless non-root
- Após executar o contêiner como
foo, ao rodar o script do Copy Fail e chamarsu, ocorre escalonamento de privilégios pararootdentro do contêiner - O
iddo shell resultante aparece comouid=0(root) gid=1002(foo) groups=1002(foo) - No
podman top, o/bin/bashinicial, o processo que executa o exploit e a chamada desuaparecem com UID166537no host, usuáriofoono contêiner e capabilitiesnone - Após a elevação de privilégios,
[sh]esleepaparecem como usuário1001no host e usuáriorootno contêiner, obtendo o mesmo conjunto de capabilities do rootless rootful - O
rootelevado dentro do contêiner ainda não consegue lerroot.txt, pertencente aorootdo host - Nessa situação, o contêiner foi comprometido, mas o alcance do ataque fica limitado ao contêiner e ao que o usuário sem privilégios
barno host conseguiria fazer
- Após executar o contêiner como
-
Resultado com
no-new-privileges- O Podman permite usar
--security-opt=no-new-privilegespara impedir que os processos do contêiner obtenham mais privilégios do que tinham no momento da inicialização - Ao aplicar essa opção a um contêiner rootless non-root e executar o Copy Fail, o shell é aberto, mas continua no estado
uid=1002(foo) - No
podman top, todos os processos também permanecem com UID166537no host, usuáriofoono contêiner e capabilitiesnone - Mesmo em
/testmontado,foosó consegue ler o próprio arquivo e não consegue lerbar.txtnemroot.txt - O contêiner foi comprometido, mas permanece limitado ao usuário interno sem privilégios
fooe a um estado sem capabilities
- O Podman permite usar
-
Resultado com
--cap-drop=all- Mesmo ao executar um contêiner rootless non-root com
--cap-drop=all, ofoooriginalmente não possui capabilities - Nesse estado, ao executar o Copy Fail e chamar
su, o shell aberto permanece comouid=1002(foo) - No
podman top,/bin/bash, a execução do exploit,su, o shell esleeppermanecem todos comofooe com capabilitiesnone - O exploit falha em obter um shell de
root, efoosó consegue ler o próprio arquivo em/test - Esse resultado é semelhante ao teste com
no-new-privileges, e as duas medidas podem ser usadas em conjunto para reduzir de forma eficaz a exposição a capabilities
- Mesmo ao executar um contêiner rootless non-root com
-
Persistência do exploit
- Embora a obtenção imediata de um shell de
roote de capabilities tenha podido ser bloqueada comno-new-privilegesou--cap-drop=all, o efeito do próprio exploit permanece - Se depois disso um novo contêiner for executado sem restrições de capabilities, o usuário sem privilégios
foono contêiner poderá se tornarrootno contêiner apenas chamandosu - Portanto, o patch do kernel e a reinicialização ainda são necessários
- Embora a obtenção imediata de um shell de
Estratégias de defesa em profundidade
-
Imagens somente leitura
- Adicionar
--read-onlyaopodman runfaz com que o sistema de arquivos raiz do contêiner seja montado como somente leitura - O Podman monta por padrão alguns diretórios como graváveis, como
/tmp,/rune/var/tmp, portanto, para torná-lo completamente somente leitura, também é preciso adicionar--read-only-tmpfs=false - Se um contêiner somente leitura for comprometido, gravações no sistema não serão permitidas, o que pode limitar alguns ataques após o exploit
- Ainda assim, como é possível enviar a saída de
curlpor pipe parapython3, a configuração somente leitura por si só não impede a execução do exploit - O servidor HTTP
python3do exemplo não precisa gravar no sistema de arquivos, então essa opção pode ser usada com segurança - Muitas imagens pré-compiladas presumem acesso de gravação a determinados diretórios e podem não funcionar corretamente com um sistema de arquivos raiz somente leitura
- Um sistema de arquivos raiz somente leitura é independente dos volumes graváveis anexados ao contêiner, e em caso de comprometimento ainda será possível gravar nesses diretórios montados
- Adicionar
-
Limites de recursos
- Docker e Podman podem usar cgroups para limitar os recursos fornecidos ao contêiner
- O contêiner não precisa de memória, CPU ou PIDs ilimitados
- Após verificar o uso de recursos do contêiner com
podman stats, é possível aplicar limites adequados
-
Restrição de binários disponíveis
- O exemplo usa a imagem
ubuntupor simplicidade, mas a imagemubuntuinclui muitos binários que um invasor pode usar em caso de comprometimento - A execução do servidor HTTP não precisa da maioria desses binários
- É melhor manter a imagem de runtime o mais enxuta possível
- Você pode usar builds multiestágio para separar o ambiente de build do ambiente de runtime
- Também é possível usar imagens voltadas a um propósito específico, como python3, variantes
-slimdo Debian ou distribuições menores comoalpine - Se for compatível com o processo do contêiner, é possível usar distroless images ou
scratchpara criar um runtime sem shell, gerenciador de pacotes ou utilitários de sistema
- O exemplo usa a imagem
-
Firewall
- É possível usar
iptablesounftablespara restringir processos de contêiner com firewall - Devem ser permitidas apenas as conexões de entrada e saída estritamente necessárias para o processo do contêiner
- No exemplo do servidor HTTP, não há necessidade de DNS nem de conexões com servidores locais ou remotos, então é possível restringir para permitir apenas pacotes
tcpvindos de conexões de entrada já estabelecidas
- É possível usar
Implicações operacionais
- Contêineres rootless padrão do Podman oferecem, por padrão, um isolamento melhor do que configurações padrão de contêineres Docker
- O Docker também permite execução rootless e uso de namespaces de usuário não privilegiados, mas isso exige mais esforço de configuração do que no Podman, e diferenças de arquitetura também influenciam
- O Docker ainda é amplamente usado, e ferramentas de self-hosting como Dokku, Kamal, Coolify e Dokploy também usam Docker por padrão
- Se imagens do Docker Hub forem executadas sem revisão adequada ou sem aplicação de medidas de contenção, os serviços podem operar com uma superfície de ataque maior do que o necessário
- É preciso entender os detalhes de implementação das imagens de contêiner
- Você deve saber qual usuário ou quais usuários executam o processo do contêiner
- Você deve saber de quais diretórios do sistema de arquivos raiz o processo do contêiner depende
- É preciso distinguir entre as capacidades Linux necessárias e as que não são necessárias
- Ao combinar os vários mecanismos oferecidos pelo Podman e pelos contêineres, é possível reforçar o contêiner e reduzir o raio de impacto em caso de comprometimento
- Dependendo da carga de trabalho, não se deve confiar no contêiner como única fronteira de segurança
- Usar contêineres em conjunto com máquinas físicas ou virtuais separadas pode fornecer isolamento de forma eficaz
- O Podman oferece uma forma de isolar cargas de trabalho executando cada uma, mesmo no mesmo host, com um usuário não privilegiado separado e seu próprio namespace de usuário
1 comentários
Opiniões do Lobste.rs
É preciso focar no comportamento primitivo que a vulnerabilidade torna possível, mais do que no exploit divulgado
Essa vulnerabilidade permite gravar no cache de páginas independentemente de ser somente leitura ou não, então um contêiner malicioso pode alterar páginas pertencentes a arquivos da imagem base do overlayfs, e, dependendo da forma como os contêineres são implantados, o impacto pode se propagar para outros contêineres também
Numa configuração rootless como esta, o alvo seriam outros contêineres executados com o mesmo usuário no sistema host
Outra forma de explorar seria iniciar ou localizar um contêiner baseado numa imagem base que já se saiba estar em uso, alterar o cache de páginas dentro desse contêiner e então fazer com que outro contêiner que compartilhe o mesmo runtime e os mesmos dados do overlayfs execute esse código
Rootless e namespaces de usuário são importantes, mas aqui não ajudam muito e, como diz o site copy.fail, vale considerar bloquear em contêineres a chamada de sistema seccomp
socket(AF_ALG, ...)Seria bom explicar com mais detalhe o que exatamente significa “dependendo da forma como os contêineres são implantados”
Uma vantagem do Podman rootless é que, dependendo da carga de trabalho, não é necessário executar contêineres no host com o mesmo usuário
Se você está falando do caso de rodar vários contêineres rootless com o usuário principal da workstation, concordo, mas em servidores é possível separar cada um em usuários distintos, e até a mesma imagem de contêiner pode ser executada por usuários diferentes sem privilégios
Isso é bem diferente do padrão do Docker, que executa a maior parte como
root, mas eu também escrevi no fim do texto que isso não é a fronteira de segurança definitiva, e se faz sentido ou não distribuir contêineres rootless entre vários usuários sem privilégios depende do caso de usoAlgumas cargas de trabalho eu isolo em VMs
Fiquei na dúvida se dizer que rootless e namespaces de usuário não ajudam aqui significa impedir o exploit
Ainda não usei políticas explícitas de seccomp em contêineres, então não tratei disso, mas é um bom incentivo para olhar melhor
Gosto de Podman e de contêineres rootless, mas ao ver o CopyFail cheguei à mesma conclusão do comentário irmão
Mesmo com a vantagem adicional de controle de acesso de
podman+rootless, isso acaba reafirmando o conselho clássico de que contêiner não é fronteira de segurança, e um único exploit de kernel pode furar tudoSou apenas um administrador de sistemas por hobby, mas como novidade nessa área vi o backend libkrun para crun com podman
A promessa é continuar lidando com a maior parte das cargas de trabalho conteinerizadas como estão, mas por baixo executando-as em uma MicroVM com um kernel convidado separado; só não sei o nível de maturidade, validação em produção e auditoria de segurança, e parte disso parece bem de ponta
Como MicroVM está sendo adotada ativamente em ferramentas de codificação com LLM, esse estado talvez não dure muito
podman machinetambém parecia promissor, mas infelizmente foi pensado só para workstations de desenvolvedor e com um modelo de apenas uma VM de execução de contêineres por sistema hostAinda assim, acho que dizer “contêiner não é fronteira de segurança” simplifica demais. Contêiner claramente é uma fronteira de segurança, só que não é tão forte quanto gostaríamos de acreditar
Em implantações locais, essa linha fica um pouco mais borrada
Do ponto de vista de hardware, VM não é inerentemente mais segura que processos, mas a fronteira é mais defensável por três motivos
Escape de VM é menos comum que chamada de sistema, então há mais espaço para aplicar mitigação de canal lateral sem perda de desempenho
A interface de host de uma VM é muito mais simples. Um dispositivo de bloco tem uma interface de leitura e escrita em blocos, e um dispositivo de rede envia e recebe quadros
A chamada
setsockoptque Linux ou *BSD oferecem em sockets representa uma superfície de ataque muito maior do que a maioria dos drivers emulados ou paravirtualizados, e mesmo isso é apenas uma parte muito pequena da superfície total de ataque do kernelA interface de VM também tende a ter bem menos estado. Há transações em andamento em um anel no modelo de requisição-resposta, mas fora isso quase nada
Credenciais, UID, GID, tabelas de descritores de arquivo e afins adicionam complexidade baseada em estado ao kernel, e um processo pode abusar disso se houver bugs
A dificuldade da variação para workstation é que ela reintroduz essa complexidade
Por exemplo, a camada base do contêiner pode ser exposta como um dispositivo de bloco contendo um sistema de arquivos imutável, mas volumes e pastas compartilhadas provavelmente seriam montados com 9pfs ou VirtIO-FS, isto é, 9p ou FUSE sobre VirtIO
Aí a superfície de ataque volta a crescer
Com sorte, seria necessária uma cadeia de exploits
Tenho mais familiaridade com o lado do FreeBSD, onde normalmente o componente que fornece dispositivos paravirtualizados ou emulados é colocado em sandbox com Capsicum, então primeiro é preciso comprometer o processo do host e depois ainda furar o kernel para acessar algo ao qual a VM não tinha permissão
Mas, sem esse sandboxing adicional, o escape de contêiner volta ao mundo em que pode fazer tudo o que o usuário conseguiria fazer, o que num desktop não é muito melhor do que comprometer o root
Pessoalmente prefiro mais o gVisor. Não é um runtime baseado em VMM, mas já existe há anos, é usado por empresas como a Tencent, e se encaixa bem no meu ambiente, onde todos os contêineres já rodam dentro de uma VM do Proxmox
Outra coisa que estou testando é o syd-oci, que parece ter recebido um pouco menos de atenção do que as recomendações padrão de MicroVM ou gVisor
Obrigado pela referência ao libkrun; parece uma possibilidade promissora
Também parece haver uma boa chance de isso levar a auditorias de segurança