Torna al Blog

Cosa ci ha insegnato la vulnerabilità dei container di Cloudflare sulla sicurezza dei backend di gioco multi-tenant

Pubblicato il 25 settembre 2026
Cosa ci ha insegnato la vulnerabilità dei container di Cloudflare sulla sicurezza dei backend di gioco multi-tenant Generata con l'aiuto dell'IA

In breve

Scopri come la vulnerabilità dei container Cloudflare ha esposto dati cross-tenant e come proteggere la sicurezza dei backend di gioco multi-tenant.

Il tuo backend di gioco containerizzato avvia una nuova VM per ogni partita, ogni lobby, ogni batch di analisi. Quando il container viene spento, i dati spariscono, giusto?

Non necessariamente. A settembre 2026, il ricercatore di sicurezza Oren Yomtov di Accomplish ha scoperto che i container Cloudflare presentavano una vulnerabilità di esposizione dei dati cross-tenant. I blocchi disco residui dei container distrutti erano leggibili da carichi di lavoro non correlati sullo stesso host. I dati recuperati includevano strutture di directory, pagine di database e database SQLite strutturalmente completi.

Se esegui backend di gioco su qualsiasi piattaforma container multi-tenant, la causa principale di questa vulnerabilità — una configurazione di storage Linux chiamata skip_block_zeroing nel sottosistema dm-thin — è qualcosa che devi capire. Questo post spiega cosa è andato storto, fornisce un runbook di rilevamento e remediation che puoi applicare oggi, e copre i principi architetturali che impediscono a questa classe di vulnerabilità di raggiungere i dati dei tuoi giocatori.

Come il thin provisioning crea esposizione dei dati cross-tenant

I container Cloudflare eseguono carichi di lavoro all'interno di micro-VM Firecracker su hardware multi-tenant. Ogni container riceve un disco root scrivibile basato sul thin provisioning di Linux device-mapper (dm-thin). Firecracker espone questo disco alla VM guest come /dev/vdc.

Il thin provisioning funziona rimandando l'allocazione fisica dello storage. Quando crei un disco virtuale da 10 GiB, consuma quasi nessuno spazio fisico. I blocchi vengono allocati on-demand da un pool condiviso quando il guest scrive in una regione precedentemente non mappata. I pool interessati di Cloudflare usavano una dimensione del blocco thin di 64 KiB.

È qui che vive la vulnerabilità. Quando un container viene distrutto, le sue mappature thin-volume vengono eliminate e i blocchi fisici tornano nel pool condiviso per essere riutilizzati. Il pool interessato era configurato con:

skip_block_zeroing

Con questo flag, dm-thin salta lo zeroing dei blocchi appena allocati prima di renderli accessibili al tenant successivo. Una scrittura completa di 64 KiB sovrascrive l'intero blocco riciclato. Ma una scrittura parziale — diciamo, 4 KiB — sostituisce solo quella regione. I restanti 60 KiB possono conservare dati del precedente proprietario del blocco.

Non è un caso limite teorico. Il ricercatore ha recuperato strutture di directory, pagine di database e database SQLite completi da blocchi residui in 18 dei 24 placement di produzione e 20 dei 22 nodi sottostanti, distribuiti su quattro continenti.

Perché le scritture parziali sono il vettore chiave

Leggere una regione non mappata di un nuovo disco thin restituisce zeri — dm-thin serve zeri senza allocare un blocco fisico. Questo è sicuro. L'exploit funziona perché una piccola scrittura innesca l'allocazione di un blocco riciclato da 64 KiB senza azzerarlo prima.

Il pattern dell'attacco:

  1. Crea un container su un account Workers a pagamento.
  2. Apri /dev/vdc (il disco root scrivibile).
  3. Identifica le regioni allineate a 64 KiB corrispondenti allo spazio libero ext4.
  4. Scrivi un blocco allineato da 4 KiB in ogni regione target.
  5. Rileggi i blocchi completi da 64 KiB.
  6. Esamina solo i 60 KiB che l'attaccante non ha sovrascritto.

Il passo 4 è il momento critico. La scrittura da 4 KiB fa sì che dm-thin allochi un blocco fisico dal pool condiviso. Poiché lo zeroing è disabilitato, i restanti 60 KiB possono contenere dati residui di chiunque abbia posseduto quel blocco in precedenza.

Cosa ha realmente validato il ricercatore

I ricercatori hanno usato i checksum dei blocchi di directory ext4 (la funzionalità metadata_csum) per distinguere i blocchi del proprio filesystem di test dai blocchi estranei. In sei placement di produzione:

  • 5.614 blocchi di directory testabili esaminati
  • 0 blocchi attribuiti al filesystem dei ricercatori
  • 2.700 inode di directory estranei distinti identificati tramite analisi dei checksum

I tipi di file recuperati non erano spazzatura. Includevano database SQLite strutturalmente significativi — il tipo di dati che, in un contesto di backend di gioco, potrebbe contenere salvataggi dei giocatori, token di sessione o record di inventario.

Playbook di rilevamento: come individuare l'harvesting di dati residui nella tua infrastruttura

Se gestisci backend di gioco containerizzati su infrastruttura multi-tenant, hai bisogno di due livelli di rilevamento: audit della configurazione e analisi delle anomalie I/O a runtime.

Passo 1: Verifica la tua configurazione dm-thin

Esegui questo script su ogni host che esegue i tuoi container:

#!/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 appare in qualsiasi punto della configurazione del tuo pool, hai la stessa classe di vulnerabilità che Cloudflare ha corretto. Risolvila immediatamente — non aspettare una finestra di manutenzione programmata.

Passo 2: Monitora la firma I/O caratteristica

L'exploit produce una firma rilevabile: piccole scritture seguite da letture sproporzionatamente grandi. Una scrittura da 4 KiB alloca un blocco da 64 KiB; una lettura successiva recupera l'intero 64 KiB. La telemetria dei container in cui il volume di lettura supera quello di scrittura di >10x è sospetta.

#!/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)

Nell'incidente Cloudflare, la proof-of-concept dei ricercatori ha prodotto esattamente questa firma. Il team di sicurezza di Cloudflare ha creato firme di rilevamento dalla PoC, le ha applicate alla telemetria storica dell'I/O su disco e ha confermato che l'unica attività corrispondente proveniva dai ricercatori e dagli stessi ingegneri Cloudflare durante la validazione autorizzata. Non è stato trovato alcuno sfruttamento da parte di terzi.

La lezione: se raccogli la telemetria I/O dei container, puoi fare audit retroattivamente. Se non la raccogli, stai volando alla cieca.

Runbook di remediation

Se il tuo audit rivela skip_block_zeroing o un'esposizione simile, ecco la sequenza di remediation che Cloudflare ha eseguito — e l'ordine è importante.

Passo 1: Riattiva lo zeroing dei blocchi su tutti i pool

Rimuovi skip_block_zeroing dalla configurazione del tuo pool dm-thin. Questa è una modifica runtime sul dispositivo thin-pool, ma protegge solo le nuove allocazioni. I blocchi già mappati nei container in esecuzione e negli snapshot delle immagini in cache rimangono vulnerabili.

# 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 ha integrato questa correzione in circa 6 ore dalla segnalazione iniziale. I ricercatori hanno confermato in modo indipendente che la PoC ha smesso di funzionare dopo questa modifica.

Passo 2: Ritira tutti i dischi dei container in esecuzione

I blocchi già mappati nei thin device attivi mantengono il loro contenuto (non azzerato). L'unico modo per eliminare i dati residui è distruggere e ricreare ogni disco dei container.

Cloudflare ha drenato gli host nelle ore non di punta e ha riavviato le VM su ciascun host. È un'operazione rolling — per un backend di gioco, programmala nella finestra di traffico più basso. Aspettati una breve interruzione per host. Se il tuo layer di matchmaking supporta il graceful handoff, i giocatori sugli host in drenaggio vengono migrati invece di essere disconnessi.

Passo 3: Cancella gli snapshot delle immagini in cache

Questo passo è facile da trascurare. I layer di orchestrazione dei container in genere mettono in cache i layer delle immagini OCI come snapshot dm-thin per un avvio rapido dei container. Questi snapshot in cache sono stati creati prima della correzione e possono contenere dati residui in regioni inutilizzate (incluso lo spazio libero ext4). Un nuovo container che eredita un layer in cache può leggere byte residui da /dev/vdc senza mai allocare un nuovo blocco.

Cloudflare ha rimosso tutti gli snapshot in cache pre-mitigazione dall'intera flotta interessata, completando la pulizia 15 giorni dopo la segnalazione iniziale. I layer in cache sono stati ricreati usando allocazioni azzerate.

L'ordine di ricreazione è importante. Se cancelli le cache prima di abilitare lo zeroing, hai appena rimesso i blocchi non azzerati direttamente in nuovi snapshot.

Passo 4: Verifica che la correzione regga

Dopo la remediation, esegui lo script di rilevamento sulla nuova telemetria I/O per almeno 48 ore. Confronta i rapporti scrittura/lettura con la tua baseline pre-patch. Il pattern caratteristico ad alto rapporto dovrebbe scomparire del tutto.

Dovresti anche verificare la tabella dm-thin su ogni host dopo il riavvio:

# 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"

Principi architetturali per prevenire questa classe di vulnerabilità

L'incidente Cloudflare è un esempio di una classe più ampia di fallimenti di isolamento dello storage multi-tenant. Ecco i principi che proteggono il tuo backend di gioco — che tu gestisca i tuoi container o ti affidi a una piattaforma.

Principio 1: Non disabilitare mai lo zeroing dei blocchi nei pool multi-tenant

Il flag skip_block_zeroing esiste per le prestazioni — azzerare blocchi da 64 KiB a ogni allocazione aggiunge overhead I/O. Su hardware single-tenant dove gira solo il tuo codice, il compromesso può essere accettabile. Su infrastruttura condivisa dove i container vengono riciclati tra tenant, è un difetto di sicurezza.

Regola: qualsiasi pool dm-thin che alloca blocchi a più di un'identità tenant deve avere lo zeroing dei blocchi abilitato. Imponilo a livello di infrastructure-as-code, così nessun host può essere provisionato senza.

Principio 2: Tratta i dischi dei container come effimeri e pericolosi

Il tuo backend di gioco non dovrebbe mai dare per scontato che il disco di un container sia pulito all'avvio. Anche con lo zeroing dei blocchi abilitato, ci sono casi limite con i layer di cache, l'ereditarietà degli snapshot e i bug del kernel.

Scrivi il tuo entrypoint del container per formattare o cancellare il volume scrivibile all'avvio:

#!/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

Questo aggiunge qualche secondo all'avvio del container. Per una partita che dura 5–45 minuti, è accettabile. Per requisiti di cold-start sub-secondo, usa l'approccio più veloce: blkdiscard (che attiva il TRIM e può azzerare i blocchi a seconda del backend di storage) combinato con mkfs.ext4 -F.

Principio 3: Monitora i rapporti I/O come segnale di sicurezza

La maggior parte del monitoraggio dei container si concentra su CPU, memoria e rete. Aggiungi l'I/O su disco alla tua telemetria di sicurezza. Il rapporto tra byte letti e byte scritti è un forte segnale per gli attacchi di harvesting di dati residui — i carichi di lavoro di gioco legittimi scrivono e leggono in pattern bilanciati. Un container che scrive chunk da 4 KiB e poi rilegge 60+ KiB per regione è anomalo.

Se il tuo backend registra già gli eventi del ciclo di vita dei container, puoi correlare le anomalie I/O con l'identità del tenant e i timestamp di creazione per ridurre il raggio d'esplosione di un potenziale incidente. Per gli sviluppatori di giochi che vogliono crash report e log utente integrati senza gestire da soli questa infrastruttura di telemetria, piattaforme come horizOn offrono queste funzionalità out of the box — il che significa meno tempo a costruire plumbing e più tempo per la logica di gioco.

Principio 4: Implementa la difesa in profondità per i dati dei giocatori

Anche se il disco del tuo container perde dati, il danno è limitato se i dati su di esso sono crittografati o privi di significato senza una chiave. Archivia i salvataggi dei giocatori, i token di sessione e i dati di inventario crittografati a riposo. La chiave di crittografia dovrebbe vivere in un secrets manager, non sul disco del container.

È lo stesso principio che abbiamo trattato nella nostra analisi della violazione dei dati di Star Citizen e di come architettare backend di gioco per sopravvivere alle compromissioni — la difesa in profondità significa assumere che ogni singolo layer prima o poi fallirà.

Checklist delle best practice

  1. Verifica ogni configurazione dei pool dm-thin prima del deployment. Aggiungi un controllo pre-flight alla tua pipeline di orchestrazione dei container che fallisca se skip_block_zeroing è presente. È un controllo CI di cinque righe che previene un'intera classe di vulnerabilità.

  2. Raccogli la telemetria I/O del disco dei container con attribuzione al tenant. Non puoi rilevare uno sfruttamento che non osservi. Registra i volumi di scrittura/lettura per container, correlati con tenant ID e host ID. Conserva almeno 30 giorni per indagini retroattive.

  3. Azera o cancella i volumi scrivibili dei container all'avvio. Non affidarti solo al layer di storage. Un mkfs.ext4 fresco all'avvio aggiunge 2–5 GiB di overhead di scrittura ma garantisce che nessun dato residuo sopravviva al riciclo dei container.

  4. Drena e ricicla i container durante le patch di sicurezza, non solo dopo. Abilitare lo zeroing dei blocchi protegge solo le nuove allocazioni. Le mappature esistenti nei container in esecuzione e negli snapshot delle immagini in cache devono essere cancellate esplicitamente. Segui sempre la correzione con un riciclo dell'intera flotta.

  5. Crittografa i dati sensibili dei giocatori a riposo con gestione esterna delle chiavi. Se un blocco residuo fuoriesce, il testo cifrato senza chiave è inutile. I salvataggi dei giocatori gestiti tramite il Cloud Save legato all'account di horizOn usano scritture consapevoli delle revisioni e risoluzione dei conflitti lato client — ma l'isolamento del disco sottostante rimane una tua responsabilità se gestisci i container in autonomia.

Timeline: come ha risposto Cloudflare

Per riferimento, ecco quanto velocemente un team con buone risorse può muoversi su una vulnerabilità critica dell'infrastruttura:

Ora (UTC) Azione
4 set, 15:26 Il ricercatore segnala tramite HackerOne
4 set, 18:45 Aperto incidente di sicurezza, confermata la configurazione di produzione
4 set, 21:27 Correzione runtime integrata con test di riutilizzo
4 set, 23:15 Inizia il rollout
7 set, 06:13 Rollout completato, inizia la pulizia
14 set, 10:50 Il ricercatore conferma che la PoC non funziona più
19 set, 15:03 Cancellati tutti gli snapshot in cache pre-mitigazione

Sono meno di 3 ore dalla segnalazione alla conferma della causa principale, e meno di 8 ore per una correzione integrata. La pulizia completa della flotta ha richiesto 15 giorni — normale per operazioni rolling su un'infrastruttura globale.

La velocità è importante. Ogni ora tra una segnalazione di vulnerabilità e una correzione è un'ora in cui lo sfruttamento è possibile. Se gestisci la tua infrastruttura container, costruisci il runbook prima di averne bisogno.

In sintesi

La vulnerabilità dei container di Cloudflare non era uno zero-day in senso crittografico. Era un compromesso noto di configurazione dello storage — prestazioni invece della sicurezza — inappropriato per carichi di lavoro multi-tenant. La correzione è stata una singola modifica di configurazione più un riciclo della flotta.

Se esegui backend di gioco su infrastruttura container condivisa, verifica oggi i tuoi pool dm-thin. Esegui lo script di rilevamento sulla tua telemetria I/O. E se preferisci non gestire da solo l'isolamento del disco dei container, il riciclo della flotta e le pipeline di telemetria I/O, horizOn gestisce autenticazione, crash report, cloud save, classifiche e gli altri servizi backend di cui il tuo gioco ha bisogno — così puoi concentrarti sul pubblicare il gioco, non sul debuggare la sicurezza del layer di storage in produzione alle 3 di notte.


Fonte: Come Cloudflare ha affrontato una vulnerabilità di esposizione dei dati cross-tenant nei Containers