O que a vulnerabilidade de contêineres da Cloudflare nos ensinou sobre segurança de backend de jogos multi-tenant
Em resumo
Aprenda como a vulnerabilidade de contêineres da Cloudflare expôs dados entre tenants e como proteger seu backend de jogos multi-tenant hoje.
Seu backend de jogos conteinerizado cria uma VM nova para cada partida, cada lobby, cada lote de análise. Quando esse contêiner é encerrado, os dados desaparecem — certo?
Não necessariamente. Em setembro de 2026, o pesquisador de segurança Oren Yomtov, da Accomplish, descobriu que o Cloudflare Containers tinha uma vulnerabilidade de exposição de dados entre tenants. Blocos de disco residuais de contêineres destruídos podiam ser lidos por workloads não relacionados no mesmo host. Os dados recuperados incluíam estruturas de diretórios, páginas de banco de dados e bancos de dados SQLite estruturalmente completos.
Se você executa backends de jogos em qualquer plataforma de contêineres multi-tenant, a causa raiz dessa vulnerabilidade — uma configuração de armazenamento Linux chamada skip_block_zeroing no subsistema dm-thin — é algo que você precisa entender. Este post detalha o que deu errado, fornece um runbook de detecção e remediação que você pode aplicar hoje e cobre os princípios arquiteturais que impedem que essa classe de vulnerabilidade alcance os dados dos seus jogadores.
Como o Thin Provisioning Cria Exposição de Dados Entre Tenants
O Cloudflare Containers executa workloads dentro de micro-VMs Firecracker em hardware multi-tenant. Cada contêiner recebe um disco raiz gravável com suporte do thin provisioning do Linux device-mapper (dm-thin). O Firecracker expõe esse disco para a VM convidada como /dev/vdc.
O thin provisioning funciona adiando a alocação de armazenamento físico. Quando você cria um disco virtual de 10 GiB, ele consome quase nenhum espaço físico. Os blocos são alocados sob demanda a partir de um pool compartilhado quando o convidado grava em uma região anteriormente não mapeada. Os pools afetados da Cloudflare usavam um tamanho de bloco thin de 64 KiB.
É aqui que a vulnerabilidade mora. Quando um contêiner é destruído, os mapeamentos de volume thin são excluídos e os blocos físicos retornam ao pool compartilhado para reutilização. O pool afetado foi configurado com:
skip_block_zeroing
Com essa flag, o dm-thin pula a zeragem de blocos recém-alocados antes de disponibilizá-los ao próximo tenant. Uma gravação completa de 64 KiB sobrescreve todo o bloco reciclado. Mas uma gravação parcial — digamos, 4 KiB — substitui apenas aquela região. Os 60 KiB restantes podem reter dados do dono anterior do bloco.
Isso não é um caso extremo teórico. O pesquisador recuperou estruturas de diretórios, páginas de banco de dados e bancos de dados SQLite completos de blocos residuais em 18 de 24 posicionamentos de produção e 20 de 22 nós subjacentes em quatro continentes.
Por Que Gravações Parciais São o Vetor Principal
Ler uma região não mapeada de um novo disco thin retorna zeros — o dm-thin serve zeros sem alocar um bloco físico. Isso é seguro. O exploit funciona porque uma pequena gravação dispara a alocação de um bloco reciclado de 64 KiB sem zerá-lo antes.
O padrão de ataque:
- Criar um contêiner em uma conta Workers Paid.
- Abrir
/dev/vdc(o disco raiz gravável). - Identificar regiões alinhadas a 64 KiB correspondentes ao espaço livre do ext4.
- Gravar um bloco alinhado de 4 KiB em cada região alvo.
- Ler de volta os blocos completos de 64 KiB.
- Examinar apenas os 60 KiB que o atacante não sobrescreveu.
O passo 4 é o momento crítico. A gravação de 4 KiB faz o dm-thin alocar um bloco físico do pool compartilhado. Como a zeragem está desabilitada, os 60 KiB restantes podem conter dados residuais de quem quer que tenha sido o dono anterior daquele bloco.
O Que o Pesquisador Realmente Validou
Os pesquisadores usaram checksums de blocos de diretório do ext4 (o recurso metadata_csum) para distinguir seus próprios blocos de filesystem de teste de blocos estrangeiros. Em seis posicionamentos de produção:
- 5.614 blocos de diretório testáveis examinados
- 0 blocos atribuídos ao filesystem dos próprios pesquisadores
- 2.700 inodes de diretório estrangeiros distintos identificados por análise de checksum
Os tipos de arquivo recuperados não eram lixo. Incluíam bancos de dados SQLite estruturalmente significativos — o tipo de dado que, em um contexto de backend de jogos, pode conter estados de save de jogadores, tokens de sessão ou registros de inventário.
Runbook de Detecção: Encontrando Coleta de Dados Residuais na Sua Infraestrutura
Se você opera backends de jogos conteinerizados em infraestrutura multi-tenant, você precisa de duas camadas de detecção: auditoria de configuração e análise de anomalias de I/O em tempo de execução.
Passo 1: Audite Sua Configuração dm-thin
Execute este script em cada host que executa seus contêineres:
#!/bin/bash
set -euo pipefail
echo "=== dm-thin Pool Configuration Audit ==="
echo ""
# Find all thin pool devices on this host
for pool in $(dmsetup status --target thin-pool 2>/dev/null | awk '{print $1}'); do
echo "Pool: $pool"
TABLE=$(dmsetup table "$pool")
echo " Full table: $TABLE"
if echo "$TABLE" | grep -q "skip_block_zeroing"; then
echo " ⚠️ WARNING: skip_block_zeroing is ENABLED"
echo " Recycled blocks may retain previous tenant data."
echo " Action: Remove skip_block_zeroing from pool table."
else
echo " ✅ Block zeroing is active (default dm-thin behavior)"
fi
echo ""
done
echo "=== Thin-block size check ==="
for pool in $(dmsetup status --target thin-pool 2>/dev/null | awk '{print $1}'); do
BLOCK_SECTORS=$(dmsetup table "$pool" | grep -oP '\d+ \d+ thin-pool' | awk '{print $1}')
BLOCK_KB=$((BLOCK_SECTORS * 512 / 1024))
echo " Pool $pool: block size = ${BLOCK_KB} KiB"
if [ "$BLOCK_KB" -le 64 ]; then
echo " ⚠️ Small block size amplifies residual data exposure."
echo " Larger block sizes reduce the ratio of leftover bytes per partial write."
fi
done
Se skip_block_zeroing aparecer em qualquer lugar na configuração do seu pool, você tem a mesma classe de vulnerabilidade que a Cloudflare corrigiu. Corrija imediatamente — não espere por uma janela de manutenção programada.
Passo 2: Monitore a Assinatura de I/O Característica
O exploit produz uma assinatura detectável: pequenas gravações seguidas por leituras desproporcionalmente grandes. Uma gravação de 4 KiB aloca um bloco de 64 KiB; uma leitura subsequente recupera os 64 KiB completos. Telemetria de contêineres em que o volume de leitura excede o volume de gravação em >10x é suspeita.
#!/usr/bin/env python3
"""
Detect anomalous write-then-read patterns characteristic of
cross-tenant residual data harvesting in thin-provisioned containers.
Usage: python detect_residual_harvesting.py io_events.jsonl
Input: JSONL file with one event per line:
{"container_id": "abc123", "operation": "write", "size_bytes": 4096}
"""
import json
import sys
from collections import defaultdict
ANOMALY_THRESHOLD = 10.0 # read_bytes : write_bytes ratio
def analyze_io_events(events_file):
with open(events_file) as f:
events = [json.loads(line) for line in f]
containers = defaultdict(
lambda: {"writes": 0, "reads": 0, "write_bytes": 0, "read_bytes": 0}
)
for event in events:
cid = event.get("container_id", "unknown")
op = event.get("operation")
size = event.get("size_bytes", 0)
if op == "write":
containers[cid]["writes"] += 1
containers[cid]["write_bytes"] += size
elif op == "read":
containers[cid]["reads"] += 1
containers[cid]["read_bytes"] += size
print("=" * 60)
print("CONTAINERS WITH ANOMALOUS READ/WRITE RATIOS")
print("=" * 60)
flagged = 0
for cid, stats in sorted(containers.items()):
if stats["write_bytes"] == 0:
continue
ratio = stats["read_bytes"] / stats["write_bytes"]
if ratio > ANOMALY_THRESHOLD:
flagged += 1
print(f"\n🚩 Container: {cid}")
print(f" Writes: {stats['writes']:,} ops | "
f"{stats['write_bytes']:,} bytes")
print(f" Reads: {stats['reads']:,} ops | "
f"{stats['read_bytes']:,} bytes")
print(f" Ratio: {ratio:.1f}x — investigate")
if flagged == 0:
print("\n✅ No anomalous containers detected.")
print(f"\nScanned {len(containers)} containers. Flagged: {flagged}")
return flagged
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python detect_residual_harvesting.py <io_events.jsonl>")
sys.exit(1)
sys.exit(1 if analyze_io_events(sys.argv[1]) > 0 else 0)
No incidente da Cloudflare, a prova de conceito dos pesquisadores produziu exatamente essa assinatura. O time de segurança da Cloudflare criou assinaturas de detecção a partir do PoC, aplicou-as à telemetria histórica de I/O de disco e confirmou que a única atividade correspondente veio dos pesquisadores e dos próprios engenheiros da Cloudflare durante a validação autorizada. Nenhuma exploração por terceiros foi encontrada.
A lição: se você está coletando telemetria de I/O de contêineres, você pode auditar retroativamente. Se você não está coletando, está voando às cegas.
Runbook de Remediação
Se sua auditoria revelar skip_block_zeroing ou exposição semelhante, aqui está a sequência de remediação que a Cloudflare executou — e a ordem importa.
Passo 1: Reative a Zeragem de Blocos em Todos os Pools
Remova skip_block_zeroing da configuração do seu pool dm-thin. Esta é uma alteração em tempo de execução no dispositivo thin-pool, mas protege apenas novas alocações. Blocos já mapeados em contêineres em execução e snapshots de imagem em cache permanecem vulneráveis.
# This is a simplified representation — actual dm-thin
# pool recreation commands vary by your orchestration layer.
# The core principle: restart the pool WITHOUT skip_block_zeroing.
dmsetup reload <pool-name> --table "0 <size> thin-pool <meta_dev> <data_dev> <data_block_size> 0"
dmsetup suspend <pool-name>
dmsetup resume <pool-name>
A Cloudflare aplicou essa correção em aproximadamente 6 horas após o relato inicial. Os pesquisadores confirmaram de forma independente que o PoC parou de funcionar após essa mudança.
Passo 2: Aposente Todos os Discos de Contêineres em Execução
Blocos já mapeados em thin devices ativos retêm seu conteúdo (não zerado). A única maneira de eliminar dados residuais é destruir e recriar todos os discos de contêineres.
A Cloudflare drenou hosts durante horários de menor movimento e reiniciou as VMs em cada host. Esta é uma operação contínua — para um backend de jogos, agende-a durante a janela de menor tráfego. Espere uma breve interrupção por host. Se sua camada de matchmaking suporta transição suave, os jogadores nos hosts em drenagem são migrados em vez de desconectados.
Passo 3: Limpe os Snapshots de Imagem em Cache
Este passo é fácil de esquecer. Camadas de orquestração de contêineres normalmente armazenam em cache as camadas de imagem OCI como snapshots dm-thin para inicialização rápida de contêineres. Esses snapshots em cache foram criados antes da correção e podem conter dados residuais em regiões não utilizadas (incluindo espaço livre do ext4). Um novo contêiner que herda uma camada em cache pode ler bytes residuais de /dev/vdc sem nunca alocar um novo bloco.
A Cloudflare removeu todos os snapshots em cache pré-mitigação em toda a frota afetada, concluindo a limpeza 15 dias após o relato inicial. As camadas em cache foram recriadas usando alocações zeradas.
A ordem de recriação importa. Se você limpar os caches antes de ativar a zeragem, você acabou de empurrar blocos não zerados de volta para snapshots novos.
Passo 4: Valide que a Correção se Sustenta
Após a remediação, execute o script de detecção contra a nova telemetria de I/O por pelo menos 48 horas. Compare as proporções de gravação/leitura com sua linha de base anterior ao patch. O padrão característico de alta proporção deve desaparecer completamente.
Você também deve verificar a tabela dm-thin em cada host após o reboot:
# Confirm no host still has skip_block_zeroing
dmsetup table 2>/dev/null | grep -q "skip_block_zeroing" && \
echo "CRITICAL: Host $(hostname) still has skip_block_zeroing" || \
echo "OK: $(hostname) — block zeroing active"
Princípios Arquiteturais para Prevenir Essa Classe de Vulnerabilidade
O incidente da Cloudflare é um exemplo de uma classe mais ampla de falhas de isolamento de armazenamento multi-tenant. Aqui estão os princípios que protegem seu backend de jogos — seja você gerenciando seus próprios contêineres ou contando com uma plataforma.
Princípio 1: Nunca Desabilite a Zeragem de Blocos em Pools Multi-Tenant
A flag skip_block_zeroing existe por performance — zerar blocos de 64 KiB a cada alocação adiciona overhead de I/O. Em hardware single-tenant onde apenas seu código roda, o tradeoff pode ser aceitável. Em infraestrutura compartilhada onde contêineres são reciclados entre tenants, é um defeito de segurança.
Regra: Qualquer pool dm-thin que aloque blocos para mais de uma identidade de tenant deve ter a zeragem de blocos habilitada. Aplique isso na camada de infraestrutura como código para que nenhum host possa ser provisionado sem ela.
Princípio 2: Trate os Discos de Contêineres como Efêmeros e Perigosos
Seu backend de jogos nunca deve presumir que o disco de um contêiner está limpo na inicialização. Mesmo com a zeragem de blocos habilitada, existem casos extremos com camadas de cache, herança de snapshots e bugs de kernel.
Escreva seu entrypoint de contêiner para formatar ou limpar o volume gravável na inicialização:
#!/bin/bash
# Ensure a clean writable disk on container start
if [ -b /dev/vdc ]; then
# Overwrite with zeros (slow but thorough)
# For a game backend, the writable disk is typically small (1-4 GiB)
dd if=/dev/zero of=/dev/vdc bs=1M count=4096 status=progress
# Create a fresh filesystem
mkfs.ext4 -F /dev/vdc
mount /dev/vdc /workspace
fi
Isso adiciona alguns segundos à inicialização do contêiner. Para uma partida de jogo que dura de 5 a 45 minutos, isso é aceitável. Para requisitos de cold-start abaixo de um segundo, use a abordagem mais rápida: blkdiscard (que dispara TRIM e pode zerar blocos dependendo do backend de armazenamento) combinado com mkfs.ext4 -F.
Princípio 3: Monitore as Proporções de I/O como Sinal de Segurança
A maioria do monitoramento de contêineres foca em CPU, memória e rede. Adicione I/O de disco à sua telemetria de segurança. A proporção de bytes lidos por bytes gravados é um sinal forte para ataques de coleta de dados residuais — workloads legítimos de jogos gravam e leem em padrões equilibrados. Um contêiner que grava blocos de 4 KiB e depois lê de volta 60+ KiB por região é anômalo.
Se seu backend já registra eventos de ciclo de vida de contêineres, você pode correlacionar anomalias de I/O com identidade de tenant e timestamps de criação para reduzir o raio de impacto de um possível incidente. Para desenvolvedores de jogos que querem relatórios de crash e logs de usuário embutidos sem gerenciar essa infraestrutura de telemetria, plataformas como horizOn oferecem esses recursos prontos — ou seja, você gasta menos tempo construindo encanamento e mais tempo na lógica do jogo.
Princípio 4: Implemente Defesa em Profundidade para os Dados dos Jogadores
Mesmo que o disco do seu contêiner vaze, o dano é limitado se os dados nele estiverem criptografados ou forem inúteis sem uma chave. Armazene estados de save de jogadores, tokens de sessão e dados de inventário criptografados em repouso. A chave de criptografia deve viver em um gerenciador de segredos, não no disco do contêiner.
Este é o mesmo princípio que abordamos em nossa análise sobre o vazamento de dados do Star Citizen e como arquitetar backends de jogos para sobreviver a comprometimentos — defesa em profundidade significa assumir que qualquer camada individual eventualmente falhará.
Checklist de Melhores Práticas
Audite toda configuração de pool dm-thin antes do deploy. Adicione uma verificação pre-flight ao seu pipeline de orquestração de contêineres que falhe se
skip_block_zeroingestiver presente. É uma verificação de CI de cinco linhas que previne uma classe inteira de vulnerabilidades.Colete telemetria de I/O de disco de contêineres com atribuição de tenant. Você não pode detectar exploração que não observa. Registre volumes de gravação/leitura por contêiner, correlacionados com ID de tenant e ID de host. Retenha por pelo menos 30 dias para investigação retroativa.
Zere ou limpe os volumes graváveis dos contêineres na inicialização. Não dependa apenas da camada de armazenamento. Um
mkfs.ext4novo na inicialização adiciona 2–5 GiB de overhead de gravação, mas garante que nenhum dado residual sobreviva à reciclagem de contêineres.Drene e recicle contêineres durante patches de segurança, não apenas depois. Ativar a zeragem de blocos protege apenas novas alocações. Mapeamentos existentes em contêineres em execução e snapshots de imagem em cache devem ser explicitamente limpos. Sempre siga a correção com uma reciclagem em toda a frota.
Criptografe dados sensíveis dos jogadores em repouso com gerenciamento externo de chaves. Se um bloco residual vazar, o texto cifrado sem a chave é inútil. Estados de save de jogadores gerenciados pelo Cloud Save vinculado à conta do horizOn usam gravações cientes de revisão e resolução de conflitos no lado do cliente — mas o isolamento de disco subjacente ainda é sua responsabilidade se você auto-hospedar contêineres.
Linha do Tempo: Como a Cloudflare Respondeu
Para referência, veja a rapidez com que um time bem equipado pode agir em uma vulnerabilidade crítica de infraestrutura:
| Hora (UTC) | Ação |
|---|---|
| 4 de set, 15:26 | Pesquisador reporta via HackerOne |
| 4 de set, 18:45 | Incidente de segurança aberto, setup de produção confirmado |
| 4 de set, 21:27 | Correção em runtime mesclada com teste de reutilização |
| 4 de set, 23:15 | Rollout começa |
| 7 de set, 06:13 | Rollout concluído, limpeza começa |
| 14 de set, 10:50 | Pesquisador confirma que o PoC não funciona mais |
| 19 de set, 15:03 | Todos os snapshots em cache pré-mitigação limpos |
Isso dá menos de 3 horas do relato até a causa raiz confirmada, e menos de 8 horas até uma correção mesclada. A limpeza completa da frota levou 15 dias — normal para operações contínuas em uma infraestrutura global.
A velocidade importa. Cada hora entre um relato de vulnerabilidade e uma correção é uma hora em que a exploração é possível. Se você opera sua própria infraestrutura de contêineres, construa o runbook antes de precisar dele.
Conclusão
A vulnerabilidade de contêineres da Cloudflare não foi um zero-day no sentido criptográfico. Foi um tradeoff conhecido de configuração de armazenamento — performance em vez de segurança — que era inadequado para workloads multi-tenant. A correção foi uma única mudança de configuração mais uma reciclagem da frota.
Se você executa backends de jogos em infraestrutura de contêineres compartilhada, audite seus pools dm-thin hoje. Execute o script de detecção contra sua telemetria de I/O. E se você preferir não gerenciar isolamento de disco de contêineres, reciclagem de frota e pipelines de telemetria de I/O por conta própria, o horizOn cuida de autenticação, relatórios de crash, cloud save, leaderboards e outros serviços de backend que seu jogo precisa — para que você possa focar em lançar seu jogo, em vez de depurar segurança na camada de armazenamento em produção às 3 da manhã.
Fonte: Como a Cloudflare resolveu uma vulnerabilidade de exposição de dados entre tenants em Containers