FlawOpen

flawopen.com/Teardowns/cve-2024-21626-runc-container-breakout-leaky-vessels

● CVE-2024-21626 · CVSS 8.6[1], [5] · Alta
Pesquisa · FlawOpen

CVE-2024-21626: como o runc corrigiu o escape Leaky Vessels

A CVE-2024-21626 combinou um vazamento interno de descritor no runc com a ausência de uma verificação do limite após chdir. Conforme o caminho de inicialização, o contêiner podia começar com um diretório de trabalho no namespace do host ou alcançar arquivos do host por um caminho relacionado a exec.[1], [2], [3]

💡 Explicação em Linguagem Simples (ELI5)

O runc prepara um cômodo isolado para o contêiner. Um handle interno vazado podia fazer o diretório de trabalho escolhido apontar para fora desse cômodo; a checagem final do limite e o fechamento dos handles internos impedem essa passagem.[1], [2]

Conceitos Centrais e Termos

runc
O runtime OCI que cria e inicia contêineres.[1]
Descritor de arquivo interno
Um handle do sistema operacional para um arquivo ou diretório aberto. Um descritor vazado pode manter acesso entre processos se continuar aberto durante a execução de um filho.[1], [3]
cwd
O diretório de trabalho do processo. No Linux, Getwd pode retornar ENOENT quando ele está fora da raiz do namespace de montagem.[1], [2]
Escape de contêiner
Um caminho pelo qual um processo do contêiner alcança recursos do host fora do limite de isolamento.[1]

Mapeamentos de frameworks

  • Referência CWE para a fraqueza descrita neste guia: CWE-403

Análise de Causa Raiz

Antes do runc 1.1.12, descritores internos podiam vazar para o runc init. Eles eram internos ao runc init, não simplesmente herdados pelo processo do contêiner. Nos ataques 1 e 2, o diretório de trabalho configurado ou escolhido podia resolver por um handle do namespace de montagem do host; o runc não verificava após chdir se o diretório final permanecia dentro da raiz do contêiner. Variantes separadas usavam o tratamento de caminhos de execve para sobrescrever um binário do host. A versão 1.1.12 adicionou quatro camadas: validar o diretório com o comportamento Getwd/ENOENT do Linux, fechar descritores internos imediatamente antes de execve, corrigir vazamentos identificados e marcar descritores que não sejam de stdio como close-on-exec antes de iniciar runc init.[1], [2], [3], [4]

CVE-2024-21626: variantes de ataque e proteções do runc 1.1.12

Variante do avisoCondições e limite necessáriosCorreção ou medida temporária relevante
Ataque 1 — imagem maliciosa via runc runUm usuário privilegiado precisa ser induzido a iniciar uma imagem não confiável. O diretório de trabalho configurado pode resolver fora do namespace de montagem do contêiner.A verificação unix.Getwd() após chdir falha quando o cwd está fora da raiz do namespace. Como medida temporária, permita apenas / como cwd para imagens não confiáveis.
Ataque 2 — processo no contêiner com runc execUm processo já dentro do contêiner precisa antecipar a escolha de um cwd diferente de / feita pelo administrador com runc exec --cwd e preparar o caminho relevante antes da execução.A mesma verificação de cwd após chdir bloqueia a execução fora do contêiner. Até aplicar a correção, restrinja --cwd a /.
Ataque 3a — sobrescrita de binário do host ao iniciar imagemÉ uma variação do ataque 1 e mantém a exigência de um usuário privilegiado iniciar a imagem maliciosa; a variante cruza o limite de execve até um binário do host.Feche os descritores internos do runc imediatamente antes de execve, corrija os vazamentos identificados e defina O_CLOEXEC. Atualize para runc 1.1.12 ou versão corrigida pelo fornecedor.
Ataque 3b — sobrescrita de binário do host via runc execEsta variação adapta o ataque 2 e parte de um processo malicioso em um contêiner em execução que o invasor pode afetar; ainda é necessário que um administrador privilegiado faça uma operação normal de runc exec nesse contêiner. O aviso diz que isso exige menos interação do usuário que o caminho por imagem.As proteções de fechamento antes de execve e close-on-exec tratam esse caminho distinto. Os mantenedores não apontam uma alternativa prática; atualize.

Fluxo de Ataque Passo a Paso

Passo 1

Vazamento de handle interno

Durante a configuração, um descritor interno do runc que referenciava o namespace de montagem do host podia permanecer acessível ao runc init.[1], [3]

Passo 2

Caminho de diretório alcançável

Uma imagem maliciosa ou um fluxo runc exec com condições específicas podia fazer o diretório solicitado resolver fora da raiz do contêiner.[1], [2]

Passo 3

Falha de limite

O código anterior não verificava o diretório final após chdir; variantes relacionadas abusavam do tratamento de caminhos em execve para sobrescrever um binário do host.[2], [3]

Passo 4

Impacto e condições

O aviso documenta acesso ao sistema de arquivos e controle do host em quatro variantes. Ataques por imagem exigem que um usuário privilegiado execute uma imagem não confiável. O ataque 3b ainda exige que um administrador faça uma operação normal de runc exec em um contêiner que o invasor possa afetar.[1]

Código-Fonte: Vulnerável vs. Seguro

IMPLEMENTAÇÃO VULNERÁVEL
// Comportamento pré-correção simplificado; não é trecho literal do runc
if err := os.Chdir(config.Cwd); err != nil {
    return err
}
// Faltava verificar se o cwd final está dentro da raiz do namespace
PATCH SEGURO E ROBUSTO
// Verificação de limite do runc 1.1.12 (trecho upstream)
if wd, err := unix.Getwd(); errors.Is(err, unix.ENOENT) {
    return errors.New("cwd está fora da raiz do namespace de montagem do contêiner")
} else if err != nil {
    return fmt.Errorf("não foi possível validar a segurança do cwd: %w", err)
} else if !filepath.IsAbs(wd) {
    return fmt.Errorf("cwd não é absoluto: %q", wd)
}

Lista de Verificação de Segurança para Engenharia

Fontes