Czego podatność kontenerów Cloudflare nauczyła nas o bezpieczeństwie wielodostępowego backendu gier
W skrócie
Dowiedz się, jak podatność kontenerów Cloudflare ujawniła ryzyko ekspozycji danych w wielodostępowych backendach gier i jak je wyeliminować.
Twój konteneryzowany backend gier uruchamia nową maszynę wirtualną dla każdego meczu, każdego lobby, każdej partii analityki. Gdy kontener się wyłącza, dane znikają — prawda?
Niekoniecznie. We wrześniu 2026 roku badacz bezpieczeństwa Oren Yomtov z Accomplish odkrył, że Cloudflare Containers miał podatność na ekspozycję danych między dzierżawcami (cross-tenant). Pozostałe bloki dyskowe ze zniszczonych kontenerów były odczytywalne przez niezwiązane obciążenia na tym samym hoście. Odzyskane dane obejmowały struktury katalogów, strony baz danych i kompletne strukturalnie bazy SQLite.
Jeśli uruchamiasz backendy gier na dowolnej wielodostępowej platformie kontenerowej, musisz zrozumieć pierwotną przyczynę tej podatności — konfigurację magazynu Linux o nazwie skip_block_zeroing w podsystemie dm-thin. Ten post wyjaśnia, co poszło nie tak, zawiera praktyczny runbook wykrywania i naprawy, który możesz zastosować już dziś, oraz omawia zasady architektoniczne, które zapobiegają dotarciu tej klasy podatności do danych twoich graczy.
Jak thin provisioning tworzy ekspozycję danych między dzierżawcami
Cloudflare Containers uruchamia obciążenia wewnątrz mikroVM Firecracker na sprzęcie wielodostępowym. Każdy kontener otrzymuje zapisywalny dysk root, wspierany przez cienkie provisionowanie Linux device-mapper (dm-thin). Firecracker udostępnia ten dysk maszynie gościa jako /dev/vdc.
Thin provisioning działa przez odroczenie alokacji fizycznego magazynu. Gdy tworzysz wirtualny dysk o rozmiarze 10 GiB, zużywa on prawie żadnej fizycznej przestrzeni. Bloki są alokowane na żądanie ze wspólnej puli, gdy gość zapisuje do wcześniej niezmapowanego regionu. Dotknięte pule Cloudflare używały cienkiego bloku o rozmiarze 64 KiB.
Tutaj znajduje się sedno podatności. Gdy kontener jest niszczony, mapowania jego cienkich wolumenów są usuwane, a fizyczne bloki wracają do wspólnej puli do ponownego użycia. Dotknięta pula została skonfigurowana z:
skip_block_zeroing
Z tą flagą dm-thin pomija zerowanie nowo alokowanych bloków przed udostępnieniem ich kolejnemu dzierżawcy. Pełny zapis 64 KiB nadpisuje cały zrecyklowany blok. Ale częściowy zapis — na przykład 4 KiB — zastępuje tylko ten region. Pozostałe 60 KiB może zachować dane poprzedniego właściciela bloku.
To nie jest teoretyczny przypadek graniczny. Badacz odzyskał struktury katalogów, strony baz danych i kompletne bazy SQLite z pozostałych bloków w 18 z 24 produkcyjnych instancji i 20 z 22 leżących u ich podstaw węzłów na czterech kontynentach.
Dlaczego częściowe zapisy są kluczowym wektorem
Odczyt niezmapowanego regionu nowego cienkiego dysku zwraca zera — dm-thin serwuje zera bez alokowania fizycznego bloku. To bezpieczne. Exploit działa, ponieważ mały zapis wyzwala alokację zrecyklowanego bloku 64 KiB bez jego wcześniejszego wyzerowania.
Wzorzec ataku:
- Utwórz kontener na koncie Workers Paid.
- Otwórz
/dev/vdc(zapisywalny dysk root). - Zidentyfikuj regiony wyrównane do 64 KiB odpowiadające wolnemu miejscu ext4.
- Zapisz jeden wyrównany blok 4 KiB w każdym docelowym regionie.
- Odczytaj pełne bloki 64 KiB.
- Przeanalizuj tylko te 60 KiB, których atakujący nie nadpisał.
Krok 4 to krytyczny moment. Zapis 4 KiB powoduje, że dm-thin alokuje fizyczny blok ze wspólnej puli. Ponieważ zerowanie jest wyłączone, pozostałe 60 KiB może zawierać szczątkowe dane od poprzedniego właściciela tego bloku.
Co dokładnie zweryfikował badacz
Badacze użyli sum kontrolnych bloków katalogów ext4 (funkcja metadata_csum), aby odróżnić bloki własnego systemu plików testowego od bloków obcych. W sześciu instancjach produkcyjnych:
- 5 614 zbadanych bloków katalogów podlegających testowaniu
- 0 bloków przypisanych do własnego systemu plików badaczy
- 2 700 odrębnych obcych i-węzłów katalogów zidentyfikowanych przez analizę sum kontrolnych
Odzyskane typy plików nie były śmieciami. Obejmowały strukturalnie znaczące bazy SQLite — rodzaj danych, które w kontekście backendu gier mogą zawierać stany zapisu graczy, tokeny sesji lub rekordy ekwipunku.
Playbook wykrywania: Jak wykryć zbieranie szczątkowych danych w twojej infrastrukturze
Jeśli prowadzisz konteneryzowane backendy gier na infrastrukturze wielodostępowej, potrzebujesz dwóch warstw wykrywania: audytu konfiguracji i analizy anomalii wejścia/wyjścia (I/O) w czasie rzeczywistym.
Krok 1: Zaudytuj swoją konfigurację dm-thin
Uruchom ten skrypt na każdym hoście, na którym działają twoje kontenery:
#!/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
Jeśli skip_block_zeroing pojawi się gdziekolwiek w konfiguracji twojej puli, masz tę samą klasę podatności, którą Cloudflare załatał. Napraw to natychmiast — nie czekaj na zaplanowane okno konserwacyjne.
Krok 2: Monitoruj charakterystyczny sygnał I/O
Exploit wytwarza wykrywalny sygnał: małe zapisy, po których następują nieproporcjonalnie duże odczyty. Zapis 4 KiB alokuje blok 64 KiB; późniejszy odczyt odzyskuje pełne 64 KiB. Telemetria kontenerów, w której wolumen odczytów przekracza wolumen zapisów o >10x, jest podejrzana.
#!/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)
W incydencie Cloudflare badacze wytworzyli dokładnie ten sygnał w ramach proof-of-concept. Zespół bezpieczeństwa Cloudflare zbudował sygnatury detekcyjne na podstawie PoC, zastosował je do historycznej telemetrii dyskowych operacji I/O i potwierdził, że jedyną pasującą aktywnością były działania badaczy oraz własnych inżynierów Cloudflare podczas autoryzowanej walidacji. Nie znaleziono żadnej eksploatacji przez strony trzecie.
Lekcja: jeśli zbierasz telemetrię I/O kontenerów, możesz przeprowadzić audyt retrospektywnie. Jeśli jej nie zbierasz, działasz po omacku.
Runbook naprawczy
Jeśli twój audyt ujawni skip_block_zeroing lub podobną ekspozycję, oto sekwencja naprawcza, którą wykonał Cloudflare — a kolejność ma znaczenie.
Krok 1: Ponownie włącz zerowanie bloków we wszystkich pulach
Usuń skip_block_zeroing z konfiguracji puli dm-thin. To zmiana w czasie działania na urządzeniu thin-pool, ale chroni tylko nowe alokacje. Bloki już zmapowane do działających kontenerów i zbuforowanych snapshotów obrazów pozostają podatne.
# 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 wdrożył tę poprawkę w ciągu około 6 godzin od pierwszego zgłoszenia. Badacze niezależnie potwierdzili, że PoC przestał działać po tej zmianie.
Krok 2: Wycofaj wszystkie działające dyski kontenerów
Bloki już zmapowane do aktywnych urządzeń cienkich zachowują swoją (niezerowaną) zawartość. Jedynym sposobem na wyeliminowanie szczątkowych danych jest zniszczenie i ponowne utworzenie każdego dysku kontenera.
Cloudflare opróżniał hosty w godzinach poza szczytem i restartował maszyny wirtualne na każdym hoście. To operacja krocząca — w przypadku backendu gier zaplanuj ją w oknie najniższego ruchu. Spodziewaj się krótkiej przerwy na każdym hoście. Jeśli twoja warstwa matchmakingu wspiera płynne przekazanie, gracze na opróżnianych hostach zostaną przeniesieni zamiast rozłączonych.
Krok 3: Wyczyść zbuforowane snapshoty obrazów
Ten krok łatwo przeoczyć. Warstwy orkiestracji kontenerów zazwyczaj buforują warstwy obrazów OCI jako snapshoty dm-thin w celu szybkiego uruchamiania kontenerów. Te zbuforowane snapshoty zostały utworzone przed poprawką i mogą zawierać szczątkowe dane w nieużywanych regionach (w tym wolnym miejscu ext4). Nowy kontener, który dziedziczy zbuforowaną warstwę, może odczytać szczątkowe bajty z /dev/vdc bez alokowania nowego bloku.
Cloudflare usunął wszystkie zbuforowane snapshoty sprzed mitigacji w całej dotkniętej flocie, kończąc sprzątanie 15 dni po pierwszym zgłoszeniu. Zbuforowane warstwy zostały odtworzone przy użyciu wyzerowanych alokacji.
Kolejność odtwarzania ma znaczenie. Jeśli wyczyścisz cache przed włączeniem zerowania, właśnie wepchniesz niezerowane bloki z powrotem do świeżych snapshotów.
Krok 4: Zweryfikuj, że poprawka działa
Po naprawie uruchom skrypt detekcyjny na nowej telemetrii I/O przez co najmniej 48 godzin. Porównaj proporcje zapisów/odczytów z linią bazową sprzed poprawki. Charakterystyczny wzorzec wysokiego stosunku powinien całkowicie zniknąć.
Powinieneś również zweryfikować tabelę dm-thin na każdym hoście po restarcie:
# 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"
Zasady architektoniczne zapobiegające tej klasie podatności
Incydent Cloudflare to jeden z przykładów szerszej klasy awarii izolacji magazynu w środowiskach wielodostępowych. Oto zasady, które chronią twój backend gier — niezależnie od tego, czy zarządzasz własnymi kontenerami, czy polegasz na platformie.
Zasada 1: Nigdy nie wyłączaj zerowania bloków w pulach wielodostępowych
Flaga skip_block_zeroing istnieje dla wydajności — zerowanie bloków 64 KiB przy każdej alokacji dodaje narzut I/O. Na sprzęcie jednodostępowym, gdzie działa tylko twój kod, ten kompromis może być do zaakceptowania. Na wspólnej infrastrukturze, gdzie kontenery są recyklingowane między dzierżawcami, to defekt bezpieczeństwa.
Zasada: Każda pula dm-thin, która alokuje bloki dla więcej niż jednej tożsamości dzierżawcy, musi mieć włączone zerowanie bloków. Wymuszaj to na warstwie infrastructure-as-code, aby żaden host nie mógł być provisionowany bez tego.
Zasada 2: Traktuj dyski kontenerów jako efemeryczne i niebezpieczne
Twój backend gier nigdy nie powinien zakładać, że dysk kontenera jest czysty przy starcie. Nawet przy włączonym zerowaniu bloków istnieją przypadki brzegowe związane z warstwami buforującymi, dziedziczeniem snapshotów i błędami jądra.
Napisz entrypoint kontenera tak, aby formatował lub czyścił zapisywalny wolumen przy starcie:
#!/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
To dodaje kilka sekund do startu kontenera. W przypadku meczu gry trwającego 5–45 minut to do przyjęcia. Dla wymagań startu w czasie poniżej sekundy użyj szybszego podejścia: blkdiscard (które wyzwala TRIM i może zerować bloki w zależności od backendu magazynu) w połączeniu z mkfs.ext4 -F.
Zasada 3: Monitoruj proporcje I/O jako sygnał bezpieczeństwa
Większość monitorowania kontenerów skupia się na CPU, pamięci i sieci. Dodaj dyskowe I/O do swojej telemetrii bezpieczeństwa. Stosunek bajtów odczytanych do bajtów zapisanych to silny sygnał ataków zbierania szczątkowych danych — legalne obciążenia gier zapisują i odczytują w zrównoważonych wzorcach. Kontener, który zapisuje fragmenty 4 KiB, a następnie odczytuje 60+ KiB na region, jest anomalią.
Jeśli twój backend już loguje zdarzenia cyklu życia kontenerów, możesz korelować anomalie I/O z tożsamością dzierżawcy i znacznikami czasu utworzenia, aby zawęzić promień rażenia potencjalnego incydentu. Dla deweloperów gier, którzy chcą wbudowanych raportów awarii i logów użytkowników bez zarządzania tą infrastrukturą telemetryczną we własnym zakresie, platformy takie jak horizOn zapewniają te możliwości od ręki — co oznacza, że spędzasz mniej czasu na budowaniu instalacji, a więcej na logice gry.
Zasada 4: Wdróż obronę w głąb (defense in depth) dla danych graczy
Nawet jeśli dysk twojego kontenera przecieka, szkody są ograniczone, jeśli dane na nim są zaszyfrowane lub bez klucza są bez znaczenia. Przechowuj stany zapisu graczy, tokeny sesji i dane ekwipunku zaszyfrowane w spoczynku. Klucz szyfrowania powinien znajdować się w menedżerze sekretów, a nie na dysku kontenera.
To ta sama zasada, którą omówiliśmy w naszej analizie naruszenia danych Star Citizen i architektury backendów gier odpornych na kompromitacje — obrona w głąb oznacza założenie, że każda pojedyncza warstwa prędzej czy później zawiedzie.
Lista kontrolna najlepszych praktyk
Zaudytuj każdą konfigurację puli dm-thin przed wdrożeniem. Dodaj kontrolę przed startem do swojego potoku orkiestracji kontenerów, która kończy się niepowodzeniem, jeśli
skip_block_zeroingjest obecny. To pięciolinijkowa kontrola CI, która zapobiega całej klasie podatności.Zbieraj telemetrię dyskowego I/O kontenerów z atrybucją do dzierżawcy. Nie możesz wykryć eksploatacji, której nie obserwujesz. Loguj wolumeny zapisów/odczytów na kontener, skorelowane z ID dzierżawcy i ID hosta. Przechowuj co najmniej 30 dni w celu retrospektywnego dochodzenia.
Zeruj lub czyść zapisywalne wolumeny kontenerów przy starcie. Nie polegaj wyłącznie na warstwie magazynu. Świeży
mkfs.ext4przy starcie dodaje 2–5 GiB narzutu zapisu, ale gwarantuje, że żadne szczątkowe dane nie przetrwają recyklingu kontenera.Opróżniaj i recykluj kontenery podczas poprawek bezpieczeństwa, nie tylko po nich. Włączenie zerowania bloków chroni tylko nowe alokacje. Istniejące mapowania w działających kontenerach i zbuforowanych snapshotach obrazów muszą być jawnie wyczyszczone. Zawsze po poprawce wykonaj recykling całej floty.
Szyfruj wrażliwe dane graczy w spoczynku z zewnętrznym zarządzaniem kluczami. Jeśli szczątkowy blok wycieknie, szyfrogram bez klucza jest bezużyteczny. Stany zapisu graczy zarządzane przez horizOn — Cloud Save powiązany z kontem — używają zapisów świadomych rewizji i rozwiązywania konfliktów po stronie klienta, ale izolacja dysku nadal pozostaje twoją odpowiedzialnością, jeśli sam hostujesz kontenery.
Harmonogram: Jak zareagował Cloudflare
Dla porównania, oto jak szybko może działać dobrze wyposażony zespół przy krytycznej podatności infrastruktury:
| Czas (UTC) | Działanie |
|---|---|
| Sep 4, 15:26 | Badacz zgłasza przez HackerOne |
| Sep 4, 18:45 | Otwarto incydent bezpieczeństwa, potwierdzono konfigurację produkcyjną |
| Sep 4, 21:27 | Wdrożono poprawkę w czasie działania z testem ponownego użycia |
| Sep 4, 23:15 | Rozpoczyna się wdrożenie |
| Sep 7, 06:13 | Wdrożenie zakończone, rozpoczyna się sprzątanie |
| Sep 14, 10:50 | Badacz potwierdza, że PoC już nie działa |
| Sep 19, 15:03 | Wyczyszczono wszystkie zbuforowane snapshoty sprzed mitigacji |
To poniżej 3 godzin od zgłoszenia do potwierdzenia pierwotnej przyczyny i poniżej 8 godzin do wdrożonej poprawki. Pełne sprzątanie floty zajęło 15 dni — normalne dla operacji kroczących w globalnej infrastrukturze.
Szybkość ma znaczenie. Każda godzina między zgłoszeniem podatności a poprawką to godzina, w której możliwa jest eksploatacja. Jeśli prowadzisz własną infrastrukturę kontenerową, zbuduj runbook zanim go będziesz potrzebować.
Podsumowanie
Podatność kontenerów Cloudflare nie była zero-dayem w sensie kryptograficznym. Był to znany kompromis w konfiguracji magazynu — wydajność ponad bezpieczeństwo — niewłaściwy dla obciążeń wielodostępowych. Poprawka to pojedyncza zmiana konfiguracji plus recykling floty.
Jeśli uruchamiasz backendy gier na współdzielonej infrastrukturze kontenerowej, zaudytuj swoje pule dm-thin już dziś. Uruchom skrypt detekcyjny na swojej telemetrii I/O. A jeśli wolisz nie zarządzać samodzielnie izolacją dysków kontenerów, recyklingiem floty i potokami telemetrii I/O, horizOn zajmuje się uwierzytelnianiem, raportami awarii, cloud save, tablicami wyników i innymi usługami backendowymi, których potrzebuje twoja gra — dzięki czemu możesz skupić się na wydaniu gry, zamiast debugować bezpieczeństwo warstwy magazynu na produkcji o 3 nad ranem.
Źródło: Jak Cloudflare rozwiązał podatność na ekspozycję danych między dzierżawcami w Containers