Hlavní navigace

Osudná chyba, která nezabila Bitcoin. Co se stalo a jak se z toho poučit?

Autor: Depositphotos
Karel Wolf

Závažná chyba v produkčních verzích Bitcoin Core nodů, odhalená 17. září, umožňovala úspěšný DoS útok na klienty, a co hůř, také provádět dvojitou útratu.

Doba čtení: 11 minut

Vzhledem k 95% dominanci Bitcoin Core klientů v bitcoinové síti a reálnému riziku dvojité útraty (double spendu) šlo pravděpodobně o nejzávažnější krizi v kódu od doby, kdy se v srpnu 2010 podařilo v bloku 74638 potvrdit transakci, které se povedlo poslat 184 467 440 737 bitcoinů. Jak se s ní bitcoinová komunita vypořádala?

Chyba CVE-2018–17144 (označení v obecné databance zranitelností) se v produkčním kódu nacházela rok a striktně vzato ještě mnohem déle. Po jejím nahlášení byla chyba velice rychle opravena a uživatelé i těžaři byli vyzváni k urychlenému upgradu klientů (na verzi 0.16.3) s oficiálním odůvodněním, že na straších klientech hrozí úspěšné DoS útoky vyřazující nody z provozu.

Právě ne zcela transparentní přístup Core vývojářů vzbudil jisté rozpaky. Chyba ale byla ve skutečnosti závažnější, umožňovala totiž za splnění konkrétních podmínek provést dvojitou útratu neutracené transakce (UTXO), o čemž urgentní výzva k upgrade taktně pomlčela. Chyba naštěstí ani v jedné z variant nebyla nikdy zneužita a ke dnešku na postižených nodech běží jen naprosto nevýznamný zbytek sítě. Na vyšší verzi prakticky okamžitě přešla většina burz a nadpoloviční většina minerů. Jistá pachuť ale zůstává, co za ní vězí?

V první řadě je zde nejistota, zda se v kódu nemůže nacházet podobný problém, který teprve čeká na své odhalení, nebo splnění vhodných podmínek pro svou aktivaci. V druhé je zde starost, jak systémově vyloučit, aby se podobná situace v budoucnu nemohla opakovat.

Bitcoin Core byl dlouhá léta chápán jako referenční bitcoinový klient, pracuje na něm několik stovek vývojářů a přidávaný kód prochází poměrně náročným schvalovacím procesem, takže se mělo dlouhá léta za to, že se něco podobného Bitcoinu Core prakticky stát nemůže (jak dokládá například tweet nejznámějšího popularizátora Bitcoinu níže).

Za třetí: na trhu zůstává několik set altcoinů, které nejsou ničím jiným než kopií bitcoinového kódu s více či méně upravenými parametry, a ty samozřejmě často zůstávají zranitelné i nadále, zejména ty menší. A za čtvrté budí rozpaky již výše zmíněný přístup jádra vývojářů Bitcoin Core, kteří sice o skutečné závažnosti chyby věděli, ale taktně o ní pomlčeli a odkazovali se jen na méně závažnou DoS zranitelnost.

„Problém byl 17. září reportován několika prominentním vývojářům (Pieter Wuille, Greg Maxwell a Wladimir Van Der Laan) pracujícím na Bitcoin Core jako Denial of Service bug. Rychle jsme ale určili, že jde zároveň o zranitelnost umožňující nafukovat množství vytěžených bitcoinů. Abychom podpořili rychlý upgrade, bylo učiněno rozhodnutí okamžitě opravit méně závažnou zranitelnost (Denial of Service) a souběžně oslovit minery a navázaný byznys a odložit publikování plného rozsahu události na později. Účelem bylo poskytnout systémům dostatek času na upgrade,“ můžeme se dočíst v oficiálním shrnutí zranitelnosti na stránkách Bitcoin Core.

Velmi lehký úvod do problematiky UTXO: Bitcoinový blockchain nepoužívá účty, na kterých někdo hlídá účetní zůstatek, ale pracuje s takzvaným modelem UTXO (Unspent Transaction Output – neutracených transakčních výstupů). Jednou z možných analogií, jak si představit udržování přehledu o stavu na jednotlivých adresách, je systém klasické peněženky s bankovkami. Kolik držíme hotovosti, zjistíme jednoduše tím, že se podíváme, kolik bankovek (a jaké hodnoty) v peněžence držíme. V případě Bitcoinu pak, kolik UTXO (jaké hodnoty) je asociováno s naší veřejnou adresou nebo bitcoinovou peněženkou. Když chceme provést útratu, použijeme jednu nebo více bankovek (UTXO transakcí asociovaných s naší peněženkou), které předáme protistraně a možná dostaneme menší bankovku nebo mince zpátky (nové UTXO). Důležité je, že každé UTXO (podobně jako fyzickou bankovku) lze z naší strany utratit pouze jednou. Potud alespoň teorie. Tento systém má více skrytých výhod. Mezi dvě nejzajímavější patří snadná škálovatelnost řešení a snazší ochrana soukromí.

Velmi nepříjemný je také samozřejmě samotný fakt, že se chyba v kódu nacházela téměř dva roky (rok v release verzi). Kontrolní mechanismus, zdali dané UTXO (neutracená transakce) jsou utráceny pouze jednou, byl z kódu odstraněn v listopadu 2016, a to kvůli optimalizaci rychlosti (úspoře okolo 600 mikrosekund). Zákeřný miner tak mohl uspořádat transakce tak, že utratil jednu transakci dvakrát, aniž by si toho nody všimly.

Méně závažným důsledkem je DoS útok, neboť bitcoind či Bitcoin-Qt klienti, které přijmou duplikovanou UTXO, se zhroutí. Zranitelné jsou verze Bitcoin Core 0.14.x před 0.14.3, 0.15.x před 0.15.2 a 0.16.x před 0.16.3. Stejnou zranitelnost obsahuje také například alternativní implementace Bitcoin Knots (všechny verze 0.14.x a 0.16.x před 0.16.3) a Bitcoin ABC (Bitcoin Cash).

Chyba samotná má dva projevy – ve verzích klienta  0.14.x se projeví „pouze“ Denial of Service zranitelností, kdy software nodu pozná, že je něco špatně, a přestane pracovat (čímž se značně omezí šíření chybné transakce dále do sítě), ve verzích od 0.15.x do 0.16.2 se již objevuje také „inflation bug“ umožňující za specifických podmínek provést dvojitou útratu a tím pádem umělé nafukování finálního množství bitcoinů.

Anatomie DoS útoku

Historie problému se začíná psát vlastně již v lednu 2009, kdy se v kódu Bitcoin 0.1 objevuje kontrolní mechanismus ve funkci ConnectInputs, který ověřuje, zda některý ze vstupů v bloku nebyl v minulosti již utracen nebo není utrácen v rámci téže nové transakce několikrát. Jinými slovy zabraňuje tomu, aby šlo utratit v jedné transakci stejné UTXO vícekrát, nebo naopak aby více transakcí utrácelo totéž UTXO. Pokud je některá z daných podmínek splněna, dojde k chybě, takže se žádné transakce s duplicitními vstupy do bloků nedostaly.

V roce 2011 byl do kódu Bitcoin Core zahrnut pull request 433, který řešil podobnou situaci v mempoolu. Kód, který se nacházel ve funkci CheckTransaction, tedy dělal zdánlivě totéž jako předchozí kontrolní mechanismus, jen s tím rozdílem, že na jiném místě (funkce CheckTransaction), a pokrýval další dva možné vektory útoku. V důsledku to pak mohlo vypadat, že první mechanismus je v kódu trochu navíc.

V roce 2013 byl zahrnut do kódu pull request 2224, který měl pomoci rozlišovat mezi chybami v mechanismu vynucování konsensu a systémovými chybami. V této době došlo také k několika změnám ve funkci ConnectInputs (později přejmenovaná na ConnectBlock). Nás ale budou zajímat jen dvě věci. Zaprvé, že právě tato funkce se stala kontrolním mechanismem dvojité útraty, a za druhé, že to, co bylo dříve interpretováno jako chyba, se nově stalo asercí (což v C++, ve kterém je Bitcoin napsán, vede v případě splnění podmínky ke kompletnímu zastavení programu).

Jak již víme, v systému se tak nalézaly dva mechanismy, kontrolující zdánlivě to samé, jeden se nacházel ve funkci „CheckBlock“, která volal již zmiňovanou „CheckTransaction“, a druhý v „UpdateCoins“. Protože se jednalo o druhou, a tudíž redundantní kontrolu, jediný možný způsob, jak by mohlo dojít k detekci chyby v rámci UpdateCoins, byla datová nekonzistence (například chyba paměti). PR 2224 proto (správně) usoudil, že chyby detekované v UpdateCoins musí být ve skutečnosti pouze systémové chyby, a nikoli chyby konsensu. Jako prevenci před dalším poškozením dat proto způsobil zastavení programu (viz zmiňovaná aserce). Funkce, která měla sloužit jako kontrolní mechanismus konsensu, se tak stala pouhou systémovou kontrolou datové konsistence. 

Nyní se konečně dostáváme do roku 2017, kdy se objevuje PR 9049 jako součást verze softwaru 0.14.0. V roce 2017 došlo ve světě Bitcoinu k jedné významné události – konsensuální shodě o aktivaci SegWit. Ta sebou přinesla o něco větší velikost bloků, a tak v rámci kódu klientů proběhla celá řada změn, které měly za úkol validaci bloků urychlit. V rámci těchto změn se z funkce UpdateCoins stala opět funkce zodpovědná za kontrolu toho, aby jedna transakce neutrácela stejné UTXO vícekrát. Protože detekovaný pokus o dvojitou útratu dál zůstával asercí, způsoboval konflikt pád klienta. V důsledku sice nebyl ohrožený konsensus (klienti, kteří narazili na chybu, prostě spadli, a ta se tak nešířila dále do sítě), ale vnikla zranitelnost umožňující provést DoS útok na nody.

Útočník přitom musel splnit tři podmínky: provést dvojitou útratu UTXO v jedné transakci, zahrnout takový pokus do bloku a odvysílat svůj blok do sítě s nody verze 0.14. Protože ostatní mineři pokus rychle odhalí a transakci zahodí, musel si blok sám vytěžit (jinými slovy vynaložit patřičný proof of work). To z tohoto vektoru útoku dělá z hlediska nákladů poměrně nevděčnou záležitost. Odměna za vytěžený blok je dnes 12,5 BTC (asi 81 tisíc dolarů) a průměrné vynaložené náklady (hardware a energie) na hash power potřebnou k jeho vytěžení aktuálně ve velké části světa se buď přibližuje dolarovému ekvivalentu této částky, nebo jej dokonce převyšuje (existují výjimky, jako třeba Venezuela nebo regiony s extrémně levnou hydroelektřinou).

S náklady, které se více méně rovnají hodnotě 12,5 BTC (asi 81 tisíc dolarů) tak bylo možné shodit určité procento full nodů Bitcoin Core se softwarem 0.14.x. Ty pak budou restartovat tak dlouho, dokud se v síti neobjeví delší verze blockchainu bez špatného bloku, ke které by se mohli připojit. Jinými slovy, žádná zase tak strašná tragedie se nekoná. Konsensus není ohrožen a downtime síti také nehrozí.

Lehký rozbor útoku s nafukováním bitcoinové zásoby

Věci ale začínají být zajímavé v době, kdy je uvolněna verze Bitcoin Core klienta 0.15.0. Ta totiž obsahuje další optimalizaci rychlosti práce s UTXO. Aniž budeme znovu zacházet do technických podrobností, optimalizace jejich ukládání má za následek, že na místo pádu po odhalení bloku s transakcí, která se pokouší o dvojitou útratu, vidí program blok jako validní. Jde o nám již známé scénáře, kdy se jediná transakce pokouší dvakrát utratit tentýž výstup nebo kdy se více transakcí pokouší utratit totéž UTXO. Protože nody vidí chybné bloky jako validní, lze vytvářet nové bitcoiny z ničeho.

I zde je ale nutné splnit ještě něco málo dalších technických podmínek. Útok opět může provést pouze miner a musí se jednat o tzv. „špinavé UTXO“. Jednotlivé výstupy se dělí na čerstvé (nejčerstvější UTXO nalézající se zatím jen v paměti) a tzv. špinavé, které již byly uloženy na disk (děje se po každém novém bloku). Zatímco pokus o dvojitou útratu čerstvých UTXO povede jen k pádu nodu (viz aserce ve funkci UpdateCoins), skrze špinavé je již možné „tisknout“ neexistující bitcoiny. Byl by to ale opravdu takový problém?

Bitcoin Core (dříve Bitcoin-Qt), za jehož autorstvím stojí Wladimir J. van der Laan, je nejpopulárnější bitcoinový klient současnosti (tvoří 95 % sítě), historicky jde o třetí bitcoinový klient, který vychází přímo z referenčního kódu Satoshiho Nakamota. Od verze 0.5 je jeho součástí také program bitcoind (vůbec první bitcoinový klient), ke kterému tvoří Bitcoin Core grafické uživatelské rozhraní.

Pokud bych si například z jedné své adresy se 1000 BTC na druhou poslal dvakrát po 1000 BTC (pro zjednodušení s nulovým transakčním poplatkem) a vytěžený blok poslal do sítě, stane se následující: Nody 0.14.x spadnou a neplatný blok neověří, nody 0.15.0 až 0.16.2 blok akceptují a v části sítě se tak pohybují neexistující bitcoiny, starší, nebo naopak nejnovější nody a alternativní klienti blok odmítnou. V závislosti na tom, jaký software používá většina minerů, by pak teoreticky mohlo dojít k dočasnému rozdělení blockchainu na dvě sítě.

Protože by se síť začala chovat nestandardně, bylo by možné příčinu pravděpodobně celkem rychle identifikovat. Následně by proběhla diskuze o tom, který chain je ten pravý a podobně, jako v roce 2010 by ji s pravděpodobností hraničící s jistotou vyhrál řetězec s nenafouknutým množstvím bitcoinů, následoval by rollback a návrat do normálních kolejí. Rollback je ve světě kryptoměn za normálních okolností sprosté slovo, ale toto by byla krizová situace, navíc s historickým precedentem, další kroky lze tak poměrně snadno odhadnout.

Dlouhodobou existenci druhého chainu sice v otevřené decentralizované síti nelze zcela vyloučit (viz třeba situace s ETH a ETC po DAO hacku), ale pravděpodobná v tomto scénáři příliš není, a to jednoduše z ekonomických důvodů. Minerům se asi nebude dlouho vyplácet těžit chain, na kterém lze tisknout bitcoiny z ničeho, protože trh brzy pošle hodnotu takového tokenu tam, kam patří, a oni tak propálí energii, aniž za to dostanou adekvátní protihodnotu. Samotné náklady na útok by byly podobné jako v případě DoS útoku.

Možný zisk je vzhledem k velmi pravděpodobnému rychlému rollbacku jediný, a to dočasná ztráta důvěry v Bitcoin, která by mohla významně zpomalit jeho další adopci. Čistě ekonomicky ale smysl nedává, i to může být důvod, proč chyba nikdy nebyla zneužita. Teoreticky by si mohl miner (ideálně přes nějakou třetí stranu kvůli KYC/AML na burzách) před útokem otevřít na Bitcoin obří nahý short, je ovšem bez záruky, že trh na situaci adekvátně zareaguje, obzvlášť pokud bude krize rychle a hladce vyřešena, tak jak se to ostatně stalo i nyní.

Závěr

Co říci závěrem? Chyba bezesporu závažná byla a v rámci veřejné decentralizované sítě, kde nikdo nemá povinnost provozovat tu či onu verzi klienta, mohla nakrátko způsobit v síti pěkný chaos. Do fatálního selhání, které mohlo způsobit konec Bitcoinu, jak chybu vykreslovala některá mainstreamová média i některé „kryptoweby“, má ale rozhodně celkem daleko. Žádný software pravděpodobně nikdy nebyl a nebude 100% bez chyb a to platí i pro bitcoinové klienty.

Poučení? Možná je na čase se méně spoléhat na vývojářské celebrity a namísto toho více kontrolovat kód. Kdokoli z komunity mohl změny na úrovni vynucování konsensu zkontrolovat. Core vývojáři by mohli pro změnu do procesu schvalování zahrnout více testů chování při patologických scénářích. Je také otázka, zdali spoléhat se na jednu jedinou implementaci bitcoinového protokolu (Bitcoin Core) je ta nejlepší možná cesta.

To už se ale dostáváme hlouběji do problematiky Bitcoin governance modelu, což je už téma na úplně jiný článek.

Našli jste v článku chybu?