Ce que la vulnérabilité des conteneurs Cloudflare nous a appris sur la sécurité des backends de jeux multi-tenant
En bref
Découvrez comment la vulnérabilité des conteneurs Cloudflare expose les risques de sécurité des backends de jeux multi-tenant et comment les prévenir.
Votre backend de jeu conteneurisé démarre une nouvelle VM pour chaque match, chaque lobby, chaque lot d'analyses. Quand ce conteneur s'arrête, les données disparaissent, non ?
Pas nécessairement. En septembre 2026, le chercheur en sécurité Oren Yomtov d'Accomplish a découvert que les conteneurs Cloudflare présentaient une vulnérabilité d'exposition de données inter-tenants. Les blocs disque résiduels des conteneurs détruits étaient lisibles par des workloads sans lien sur le même hôte. Les données récupérées comprenaient des structures de répertoires, des pages de base de données et des bases de données SQLite structurellement complètes.
Si vous faites tourner des backends de jeu sur une plateforme de conteneurs multi-tenant, la cause racine de cette vulnérabilité — une configuration de stockage Linux appelée skip_block_zeroing dans le sous-système dm-thin — est quelque chose que vous devez comprendre. Cet article détaille ce qui a mal tourné, fournit un runbook de détection et de remédiation applicable dès aujourd'hui, et couvre les principes d'architecture qui empêchent cette classe de vulnérabilité d'atteindre les données de vos joueurs.
Comment le thin provisioning crée une exposition de données inter-tenants
Les conteneurs Cloudflare exécutent des workloads dans des micro-VM Firecracker sur du matériel multi-tenant. Chaque conteneur reçoit un disque racine inscriptible adossé au thin provisioning de device-mapper Linux (dm-thin). Firecracker expose ce disque à la VM invitée sous la forme de /dev/vdc.
Le thin provisioning fonctionne en différant l'allocation de stockage physique. Lorsque vous créez un disque virtuel de 10 Gio, il ne consomme presque aucun espace physique. Les blocs sont alloués à la demande depuis un pool partagé lorsque l'invité écrit dans une région précédemment non mappée. Les pools affectés de Cloudflare utilisaient une taille de bloc thin de 64 Kio.
C'est ici que se trouve la vulnérabilité. Lorsqu'un conteneur est détruit, ses mappings de volume thin sont supprimés et les blocs physiques retournent dans le pool partagé pour être réutilisés. Le pool affecté était configuré avec :
skip_block_zeroing
Avec ce drapeau, dm-thin ne met pas à zéro les blocs nouvellement alloués avant de les rendre accessibles au locataire suivant. Une écriture complète de 64 Kio écrase l'intégralité du bloc recyclé. Mais une écriture partielle — disons 4 Kio — ne remplace que cette région. Les 60 Kio restants peuvent conserver des données du propriétaire précédent du bloc.
Ce n'est pas un cas limite théorique. Le chercheur a récupéré des structures de répertoires, des pages de base de données et des bases de données SQLite complètes à partir de blocs résiduels sur 18 des 24 placements en production et 20 des 22 nœuds sous-jacents répartis sur quatre continents.
Pourquoi les écritures partielles sont le vecteur clé
Lire une région non mappée d'un nouveau disque thin renvoie des zéros — dm-thin sert des zéros sans allouer de bloc physique. C'est sûr. L'exploit fonctionne parce qu'une petite écriture déclenche l'allocation d'un bloc recyclé de 64 Kio sans le mettre à zéro au préalable.
Le schéma d'attaque :
- Créez un conteneur sur un compte Workers Paid.
- Ouvrez
/dev/vdc(le disque racine inscriptible). - Identifiez les régions alignées sur 64 Kio correspondant à l'espace libre ext4.
- Écrivez un bloc aligné de 4 Kio dans chaque région cible.
- Relisez les blocs complets de 64 Kio.
- Examinez uniquement les 60 Kio que l'attaquant n'a pas écrasés.
L'étape 4 est le moment critique. L'écriture de 4 Kio amène dm-thin à allouer un bloc physique depuis le pool partagé. Comme la mise à zéro est désactivée, les 60 Kio restants peuvent contenir des données résiduelles de celui qui possédait ce bloc auparavant.
Ce que le chercheur a réellement validé
Les chercheurs ont utilisé les sommes de contrôle des blocs de répertoires ext4 (la fonctionnalité metadata_csum) pour distinguer leurs propres blocs de système de fichiers de test des blocs étrangers. Sur six placements en production :
- 5 614 blocs de répertoires testables examinés
- 0 bloc attribué au propre système de fichiers des chercheurs
- 2 700 inodes de répertoires étrangers distincts identifiés via l'analyse des sommes de contrôle
Les types de fichiers récupérés n'étaient pas des déchets. Ils comprenaient des bases de données SQLite structurellement significatives — le genre de données qui, dans un contexte de backend de jeu, pourrait contenir des états de sauvegarde de joueurs, des jetons de session ou des enregistrements d'inventaire.
Playbook de détection : repérer la collecte de données résiduelles dans votre infrastructure
Si vous exploitez des backends de jeu conteneurisés sur une infrastructure multi-tenant, vous avez besoin de deux niveaux de détection : l'audit de configuration et l'analyse des anomalies d'E/S en runtime.
Étape 1 : Auditez votre configuration dm-thin
Exécutez ce script sur chaque hôte qui fait tourner vos conteneurs :
#!/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
Si skip_block_zeroing apparaît n'importe où dans votre configuration de pool, vous avez la même classe de vulnérabilité que Cloudflare a corrigée. Corrigez-la immédiatement — n'attendez pas une fenêtre de maintenance planifiée.
Étape 2 : Surveillez la signature d'E/S caractéristique
L'exploit produit une signature détectable : de petites écritures suivies de lectures disproportionnellement grandes. Une écriture de 4 Kio alloue un bloc de 64 Kio ; une lecture ultérieure récupère les 64 Kio complets. Une télémétrie de conteneur où le volume de lecture dépasse le volume d'écriture de >10x est suspecte.
#!/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)
Dans l'incident Cloudflare, la preuve de concept des chercheurs a produit exactement cette signature. L'équipe de sécurité de Cloudflare a construit des signatures de détection à partir de la PoC, les a appliquées à la télémétrie historique des E/S disque, et a confirmé que la seule activité correspondante provenait des chercheurs et des propres ingénieurs de Cloudflare lors d'une validation autorisée. Aucune exploitation par un tiers n'a été trouvée.
La leçon : si vous collectez la télémétrie d'E/S des conteneurs, vous pouvez auditer rétroactivement. Si vous ne la collectez pas, vous volez à l'aveugle.
Runbook de remédiation
Si votre audit révèle skip_block_zeroing ou une exposition similaire, voici la séquence de remédiation que Cloudflare a exécutée — et l'ordre compte.
Étape 1 : Réactivez la mise à zéro des blocs sur tous les pools
Retirez skip_block_zeroing de votre configuration de pool dm-thin. C'est un changement runtime sur le périphérique thin-pool, mais il ne protège que les nouvelles allocations. Les blocs déjà mappés dans les conteneurs en cours d'exécution et les snapshots d'images en cache restent vulnérables.
# 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>
Cloudflare a fusionné ce correctif environ 6 heures après le rapport initial. Les chercheurs ont indépendamment confirmé que la PoC ne fonctionnait plus après ce changement.
Étape 2 : Retirez tous les disques de conteneurs en cours d'exécution
Les blocs déjà mappés dans les périphériques thin actifs conservent leur contenu (non mis à zéro). La seule façon d'éliminer les données résiduelles est de détruire et recréer chaque disque de conteneur.
Cloudflare a vidé les hôtes pendant les heures creuses et redémarré les VM sur chaque hôte. C'est une opération progressive — pour un backend de jeu, planifiez-la pendant votre fenêtre de trafic la plus faible. Attendez-vous à une brève interruption par hôte. Si votre couche de matchmaking prend en charge le transfert gracieux, les joueurs sur les hôtes en cours de vidage sont migrés plutôt que déconnectés.
Étape 3 : Supprimez les snapshots d'images en cache
Cette étape est facile à manquer. Les couches d'orchestration de conteneurs mettent généralement en cache les couches d'images OCI sous forme de snapshots dm-thin pour un démarrage rapide des conteneurs. Ces snapshots en cache ont été créés avant le correctif et peuvent contenir des données résiduelles dans des régions inutilisées (y compris l'espace libre ext4). Un nouveau conteneur qui hérite d'une couche en cache peut lire des octets résiduels depuis /dev/vdc sans jamais allouer un nouveau bloc.
Cloudflare a supprimé tous les snapshots en cache antérieurs à la mitigation sur la flotte affectée, achevant le nettoyage 15 jours après le rapport initial. Les couches en cache ont été recréées avec des allocations mises à zéro.
L'ordre de recréation compte. Si vous videz les caches avant d'activer la mise à zéro, vous venez de repousser des blocs non mis à zéro dans de nouveaux snapshots.
Étape 4 : Validez que le correctif tient
Après la remédiation, exécutez le script de détection sur la nouvelle télémétrie d'E/S pendant au moins 48 heures. Comparez les ratios écriture/lecture à votre référence pré-correctif. Le motif caractéristique à ratio élevé devrait disparaître entièrement.
Vous devez également vérifier la table dm-thin sur chaque hôte après redémarrage :
# 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"
Principes d'architecture pour prévenir cette classe de vulnérabilité
L'incident Cloudflare est un exemple d'une classe plus large de défaillances d'isolation de stockage multi-tenant. Voici les principes qui protègent votre backend de jeu — que vous gériez vos propres conteneurs ou que vous vous appuyiez sur une plateforme.
Principe 1 : Ne désactivez jamais la mise à zéro des blocs dans les pools multi-tenant
Le drapeau skip_block_zeroing existe pour la performance — mettre à zéro des blocs de 64 Kio à chaque allocation ajoute une surcharge d'E/S. Sur du matériel mono-tenant où seul votre code s'exécute, le compromis peut être acceptable. Sur une infrastructure partagée où les conteneurs sont recyclés entre locataires, c'est un défaut de sécurité.
Règle : Tout pool dm-thin qui alloue des blocs à plus d'une identité de locataire doit avoir la mise à zéro des blocs activée. Appliquez cela au niveau de l'infrastructure-as-code afin qu'aucun hôte ne puisse être provisionné sans elle.
Principe 2 : Traitez les disques de conteneurs comme éphémères et dangereux
Votre backend de jeu ne devrait jamais supposer que le disque d'un conteneur est propre au démarrage. Même avec la mise à zéro des blocs activée, il existe des cas limites avec les couches de cache, l'héritage de snapshots et les bugs du noyau.
Écrivez votre point d'entrée de conteneur pour formater ou effacer le volume inscriptible au démarrage :
#!/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
Cela ajoute quelques secondes au démarrage du conteneur. Pour un match de jeu qui dure 5 à 45 minutes, c'est acceptable. Pour des exigences de démarrage à froid en moins d'une seconde, utilisez l'approche plus rapide : blkdiscard (qui déclenche TRIM et peut mettre à zéro les blocs selon le backend de stockage) combiné à mkfs.ext4 -F.
Principe 3 : Surveillez les ratios d'E/S comme signal de sécurité
La plupart des surveillances de conteneurs se concentrent sur le CPU, la mémoire et le réseau. Ajoutez les E/S disque à votre télémétrie de sécurité. Le ratio des octets lus sur les octets écrits est un signal fort pour les attaques de collecte de données résiduelles — les workloads de jeu légitimes écrivent et lisent selon des schémas équilibrés. Un conteneur qui écrit des morceaux de 4 Kio puis relit 60+ Kio par région est anormal.
Si votre backend journalise déjà les événements du cycle de vie des conteneurs, vous pouvez corréler les anomalies d'E/S avec l'identité du locataire et les horodatages de création pour réduire le rayon d'explosion d'un incident potentiel. Pour les développeurs de jeux qui veulent des rapports de crash et des journaux utilisateur intégrés sans gérer eux-mêmes cette infrastructure de télémétrie, des plateformes comme horizOn offrent ces capacités prêtes à l'emploi — ce qui signifie que vous passez moins de temps à construire de la plomberie et plus de temps sur la logique du jeu.
Principe 4 : Implémentez la défense en profondeur pour les données des joueurs
Même si votre disque de conteneur fuit, les dégâts sont limités si les données qu'il contient sont chiffrées ou dénuées de sens sans clé. Stockez les états de sauvegarde des joueurs, les jetons de session et les données d'inventaire chiffrés au repos. La clé de chiffrement doit vivre dans un gestionnaire de secrets, pas sur le disque du conteneur.
C'est le même principe que nous avons abordé dans notre analyse de la fuite de données Star Citizen et comment architecturer des backends de jeu pour survivre aux compromissions — la défense en profondeur signifie supposer qu'une seule couche finira par échouer.
Liste de contrôle des bonnes pratiques
Auditez chaque configuration de pool dm-thin avant le déploiement. Ajoutez une vérification pré-vol à votre pipeline d'orchestration de conteneurs qui échoue si
skip_block_zeroingest présent. C'est une vérification CI de cinq lignes qui prévient toute une classe de vulnérabilités.Collectez la télémétrie d'E/S disque des conteneurs avec attribution au locataire. Vous ne pouvez pas détecter une exploitation que vous n'observez pas. Journalisez les volumes d'écriture/lecture par conteneur, corrélés avec l'ID du locataire et l'ID de l'hôte. Conservez au moins 30 jours pour une enquête rétroactive.
Mettez à zéro ou effacez les volumes inscriptibles des conteneurs au démarrage. Ne vous fiez pas uniquement à la couche de stockage. Un
mkfs.ext4frais au démarrage ajoute 2 à 5 Gio de surcharge d'écriture mais garantit qu'aucune donnée résiduelle ne survit au recyclage du conteneur.Videz et recyclez les conteneurs pendant les correctifs de sécurité, pas seulement après. Activer la mise à zéro des blocs ne protège que les nouvelles allocations. Les mappings existants dans les conteneurs en cours d'exécution et les snapshots d'images en cache doivent être explicitement supprimés. Suivez toujours le correctif d'un recyclage à l'échelle de la flotte.
Chiffrez les données sensibles des joueurs au repos avec une gestion de clés externe. Si un bloc résiduel fuit, le texte chiffré sans la clé est inutile. Les états de sauvegarde des joueurs gérés via le Cloud Save lié au compte de horizOn utilisent des écritures conscientes des révisions et une résolution de conflits côté client — mais l'isolation disque sous-jacente reste votre responsabilité si vous auto-hébergez des conteneurs.
Chronologie : comment Cloudflare a réagi
Pour référence, voici à quelle vitesse une équipe bien dotée peut agir sur une vulnérabilité critique d'infrastructure :
| Time (UTC) | Action |
|---|---|
| Sep 4, 15:26 | Le chercheur signale via HackerOne |
| Sep 4, 18:45 | Incident de sécurité ouvert, configuration de production confirmée |
| Sep 4, 21:27 | Correctif runtime fusionné avec test de réutilisation |
| Sep 4, 23:15 | Déploiement commence |
| Sep 7, 06:13 | Déploiement terminé, nettoyage commence |
| Sep 14, 10:50 | Le chercheur confirme que la PoC ne fonctionne plus |
| Sep 19, 15:03 | Tous les snapshots en cache antérieurs à la mitigation supprimés |
C'est moins de 3 heures entre le rapport et la cause racine confirmée, et moins de 8 heures pour un correctif fusionné. Le nettoyage complet de la flotte a pris 15 jours — normal pour des opérations progressives sur une infrastructure mondiale.
La vitesse compte. Chaque heure entre un rapport de vulnérabilité et un correctif est une heure où l'exploitation est possible. Si vous exploitez votre propre infrastructure de conteneurs, construisez le runbook avant d'en avoir besoin.
L'essentiel
La vulnérabilité des conteneurs Cloudflare n'était pas un zero-day au sens cryptographique. C'était un compromis de configuration de stockage connu — la performance plutôt que la sécurité — inapproprié pour des workloads multi-tenant. Le correctif était un simple changement de configuration plus un recyclage de la flotte.
Si vous faites tourner des backends de jeu sur une infrastructure de conteneurs partagée, auditez vos pools dm-thin dès aujourd'hui. Exécutez le script de détection sur votre télémétrie d'E/S. Et si vous préférez ne pas gérer vous-même l'isolation des disques de conteneurs, le recyclage de flotte et les pipelines de télémétrie d'E/S, horizOn gère l'authentification, les rapports de crash, le cloud save, les classements et les autres services backend dont votre jeu a besoin — afin que vous puissiez vous concentrer sur la livraison de votre jeu, pas sur le débogage de la sécurité de la couche de stockage en production à 3 heures du matin.