Back to Blog

[Kelebek Etkisi] Bir Hata Düzeltmesinin Neden Olduğu Bileşik Güvenlik Olayı

Code Auditing
October 10, 2021
7 min read

BlockSec Ekibi (@BlockSecTeam) tarafından

Geçen hafta, Compound protokolü kullanıcılara yanlışlıkla çok sayıda COMP token gönderen bir hata içeriyordu. Bu hatanın (bu blogda hata 2) nedeni, daha önce keşfedilen başka bir hatanın (bu blogda Hata 1) yanlış düzeltilmesinden kaynaklanmaktadır.

Bu blogda, birinci hatanın temel nedenini ve birinci hataya yapılan düzeltmenin neden ikinci hataya yol açtığını ayrıntılı olarak açıklayacağız.

Arka Plan

Compound protokolü, Compound Teknik Belgesi'ne dayanmaktadır. cToken sözleşmeleri aracılığıyla, blok zincirindeki hesaplar cToken almak için sermaye (Ether veya ERC-20 token'ları) sağlar ya da protokolden (diğer varlıkları teminat olarak tutarak) varlık ödünç alır. Compound cToken sözleşmeleri bu bakiyeleri takip eder ve ödünç alanlar için faiz oranlarını algoritmik olarak belirler.

Kullanıcıları teşvik etmek amacıyla, Compound'a likidite sağlayan (sermaye temin eden) kullanıcılar faiz alabilir. Özellikle, kullanıcılar Compound'a varlık (örn. Ether veya diğer ERC20 token'ları) sağlar ve karşılığında ilgili cToken'ları alır. cToken Compound'a iade edildiğinde, kullanıcının Compound'da herhangi bir borcu yoksa, temel varlıklar (Ether veya ERC20 token'ları) ve faizler kullanıcıya iade edilir. Örneğin, bir kullanıcının 1000 Ether'i varsa, cToken elde etmek için cEth.mint(1000) işlemi aracılığıyla varlığını Compound'a yatırabilir.

cToken, Compound'da kilitlenmiş temel varlıkları temsil eder. Kullanıcı, cToken'ı diğer varlıkları ödünç almak için teminat olarak kullanabilir. Örneğin, bir kullanıcı ceth.mint(1000) ile 1000 Ether yatırabilir ve ardından elde ettiği cToken'ları cDai.borrow(x) aracılığıyla 75 Ether değerinde x Dai ödünç almak için kullanabilir (aşırı teminatlandırma -- bu sayı teminat faktörüne bağlıdır).

Temel mantık, Comptroller sözleşmesinde uygulanmaktadır. Bu sözleşme, bir kullanıcının durumlarını yönetir; örneğin, kullanıcı tarafından Compound'a ne kadar token yatırıldığı, kullanıcı tarafından ne kadar token ödünç alındığı ve kullanıcının daha fazla token ödünç alıp alamayacağı. Bu süreçte çağrılan işlevler arasında getHypotheticalAccountLiquidityInternal(), borrowAllowed(), mintAllowed() ve benzerleri yer almaktadır.

Compound'un ayrıca COMP adlı bir yönetim token'ı bulunmaktadır. COMP token'ı, önerilere oy vermek için kullanılabilir. Bunun yanı sıra, COMP token'ı borsalarda işlem görebilir. Şu anda COMP'ın fiyatı yaklaşık 300 dolar civarındadır.

Hata 1

31 Eylül 2021'de, Compound DAO'da Comptroller'daki bir hatayı düzeltmeyi amaçlayan yeni bir öneri (Öneri 62) yayımlandı.

Hata, her blokta kullanıcılara dağıtılabilecek COMP token sayısını temsil eden CompSpeed ile ilgilidir.

mint Fonksiyonunun Akışı

Aşağıda, bu hatanın nedenini açıklamak için mint fonksiyonunu kullanacağız. mint fonksiyonunun çağrı zinciri şu şekildedir: mintmintInternalmintFresh.

mintFresh fonksiyonunda, mintAllowed çağrılır ve ardından kullanıcının cToken bakiyesi güncellenir.

mintAllowed fonksiyonunda, önce updateCompSupplyIndex çağrılır, ardından distributeSupplierComp çağrılarak 1) piyasanın compSupplyState'i güncellenir ve 2) COMP token'ları kullanıcılara dağıtılır.

updateCompSupplyIndex

updateCompSupplyIndex fonksiyonu, her piyasanın durumunu, özellikle compSupplyState[cToken]'ı güncelleyecektir.

CompMarketState yapısında, bu güncellemenin blok numarası (block) ve kullanıcılara (cToken tutan) dağıtılması gereken COMP token sayısını etkileyecek bonus indeksi (index) kaydedilir.

Her token için bonus indeksi (index) nedir? Bu, zaman içinde birikmiş değerdir (aşağıdaki formülde gösterilmektedir).

Bu, kullanıcılara dağıtılması gereken COMP miktarını gösterir (kullanıcının her sahip olduğu cToken için).

distributeSupplierComp

Diğer fonksiyon olan distributeSupplierComp, kullanıcıya (tedarikçiye) dağıtılması gereken COMP token sayısını compAccrued[supplier]'da kaydetmekten sorumludur.

Özellikle, compSupplyState'deki global bonus indeksini (updateCompSupplyIndex fonksiyonunda) günceller. Ardından distributeSupplierComp fonksiyonunda, supplyIndex mevcut bonus indeksini kaydeder ve supplierIndex kullanıcı (tedarikçi) için son bonus indeksini gösterir. (supplyIndex - supplierIndex) * kullanıcının cToken bakiyesi delta değeri, kullanıcıya dağıtılması gereken COMP token sayısını gösterir.

Hata 1'in Nedeni

Piyasanın supplySpeed'ini (compSpeeds[address[cToken]]) ayarlamak için başka bir setCompSpeed fonksiyonu bulunmaktadır.

Bunun nedeni, bir piyasa için CompSpeed'i sıfıra ayarlarsak, bu piyasadaki kullanıcılara COMP token dağıtılmayacağı anlamına gelir. Bu nedenle, bir piyasa için COMP dağıtımını önce devre dışı bırakmak ve ardından yeniden etkinleştirmek istiyorsak, şu adımları izleyebiliriz:

  • Adım I: COMP token dağıtımını devre dışı bırakmak için CompSpeed[cToken]'ı sıfır olarak ayarlayın.
  • Adım II: CompSpeed[cToken]'ı sıfır olmayan bir değere ayarlamak için setCompSpeed fonksiyonunu çağırın.

Adım I: Adım I'de COMP token dağıtımı devre dışı bırakılmış piyasalar için (supplySpeed == 0), blok sıfır değildir; zira blok, updateCompSupplyIndex'te sürekli olarak güncellenmektedir (else if (deltaBlocks > 0)).

Adım II: Adım II'deki işlem gerçekleştirilirken, setCompSpeedInternal fonksiyonu else if (compSpeed != 0) ifadesinden (satır 1083) geçecektir. Ardından, 1088-1093. satırlarda, yeni bir piyasa için index ve block'u başlatmak amacıyla if (compSupplyState[address(cToken)].index == 0 && compSupplyState[address(cToken)].block == 0) kontrolü bulunmaktadır. Ancak, mevcut bir piyasada COMP token dağıtımını yeniden etkinleştirdiğimizden (yeni bir piyasa olmadığından), compSupplyState[address(cToken)].block sıfır olmadığı için 1090 ve 1091. satırlardaki index ve block'u başlatmak için gerekli ifadeler çalıştırılmayacaktır.

Özetle, mevcut durumda devre dışı bırakılmış bir piyasa için index sıfırdır. Ancak block sıfır değildir. Bu, devre dışı bırakılmış piyasayı CompSpeed[cToken]'ı sıfır olmayan bir değere ayarlayarak setCompSpeed çağrısıyla yeniden etkinleştirdiğimizde, indeks değerinin CompInitialIndex'e (1e36) yeniden başlatılmayacağı anlamına gelir (1090 ve 1091. satırlar çalıştırılmaz).

Hata 1'in Etkisi

COMP token'larını dağıtmaktan sorumlu olan distributeSupplierComp fonksiyonunu daha ayrıntılı inceliyoruz.

supplierIndex, compInitialIndex değerindedir. Ancak supplyIndex, hata nedeniyle hâlâ sıfırdır; bu durum Double memory deltaIndex = sub_(supplyIndex=0, supplierIndex=1e36) için bir alt taşmaya yol açacaktır.

Hata 2: Hata 1'in Düzeltmesiyle Ortaya Çıkan Hata

Hatayı gidermek için proje sahibi kod mantığını değiştirir. Özellikle, yeni bir piyasa başlatılırken index'i hemen compInitialIndex'e başlatır.

Global bonus indeksi (index), compInitialIndex'e başlatıldığından, kullanıcı bonus indeksi de bu değere başlatılmalıdır. distributeSupplierComp fonksiyonuna bir göz atalım.

  1. satırdaki if koşulu, supplyIndex değeri compInitialIndex'e (1e36) eşit olduğundan (büyük olmadığından) supplierIndex == 0 olsa bile karşılanamaz. Bu durum, supplierIndex'in compInitialIndex'e düzgün biçimde başlatılamamasına yol açar (değeri 0 olarak kalır). Ardından deltaIndex (supplyIndex - supplierIndex) sıfır yerine compInitialIndex olacaktır. Kullanıcının cToken bakiyesi sıfır değilse supplierTokens büyük bir değer haline gelecektir.

Özetle, bir kullanıcı hata 1'in düzeltilmesinden önce mint işlemini gerçekleştirmişse, cToken'larına sahip olur ve supplierIndex sıfır olur (COMP token dağıtıldığından). Ardından hata 1'in düzeltilmesinin ardından (hata 2'yi ortaya çıkaran düzeltme), kullanıcı tekrar mint fonksiyonunu çağırdığında çok sayıda COMP token alabilir (1e36*ctoken.balanceOf(user)).

Gerçek Dünya

Etkilenen piyasaları aşağıda gösteriyoruz:

0xF5DCe57282A584D2746FaF1593d3121Fcac444dC: cSAI
0x12392F67bdf24faE0AF363c24aC620a2f67DAd86: cTUSD
0x95b4eF2869eBD94BEb4eEE400a99824BF5DC325b: cMKR
0x4B0181102A0112A2ef11AbEE5563bb4a3176c9d7: cSUSHI
0xe65cdB6479BaC1e22340E4E755fAE7E509EcD06c: cAAVE
0x80a2AE356fc9ef4305676f7a3E2Ed04e12C33946: cYFI

Kullanıcı (0xa7b95d2a2d10028cc4450e453151181cbcac74fc), bu işlemde (0x6416ed016c39ffa23694a70d8a386c613f005be18aa0048ded8094f6165e7308) 4.466,542459954989867175 COMP token almıştır.

İşlemin daha ayrıntılı incelenmesi, hata 2 nedeniyle deltaIndex'in 1e36 olduğunu ve kullanıcının o sırada cToken'a sahip olduğunu göstermektedir.

Hata 2'nin Düzeltmesi

Hata 2'nin düzeltmesi basittir. distributeSupplierComp fonksiyonundaki if koşulunu değiştirir.

Dersler

  • Bu, başka bir hatanın düzeltilmesinden kaynaklanan bir hatadır. Yüksek profilli projeler için kod değişikliklerinin kapsamlı biçimde incelenmesi hâlâ açık bir sorudur.
  • DAO, merkezileşme riskini ortadan kaldırabilir. Ancak güvenlik olaylarına verilen yanıtı da yavaşlatan bir süreç haline getirir.
  • Yüksek profilli DeFi projeleri, geleneksel programlardaki iyi güvenlik uygulamalarını benimseyebilir; örneğin, sürekli test süreciyle verimli bir bulanık test sistemi kurabilirler.

BlockSec Hakkında

BlockSec, 2021 yılında küresel çapta tanınmış bir grup güvenlik uzmanı tarafından kurulmuş öncü bir blok zinciri güvenlik şirketidir. Şirket, kitlesel benimsemeyi kolaylaştırmak amacıyla gelişmekte olan Web3 dünyasının güvenliğini ve kullanılabilirliğini artırmayı taahhüt etmektedir. Bu doğrultuda BlockSec; akıllı sözleşme ve EVM zinciri güvenlik denetimi hizmetleri, güvenlik geliştirme ve tehditleri proaktif olarak engelleme için Phalcon platformu, fon takibi ve araştırma için MetaSleuth platformu ve kripto dünyasında verimli gezinen web3 geliştiricileri için MetaSuites uzantısı sunmaktadır.

Bugüne kadar şirket, MetaMask, Uniswap Foundation, Compound, Forta ve PancakeSwap gibi 300'den fazla saygın müşteriye hizmet vermiş ve Matrix Partners, Vitalbridge Capital ve Fenbushi Capital gibi önde gelen yatırımcıların katılımıyla gerçekleştirilen iki finansman turunda on milyonlarca ABD doları almıştır.

Resmi web sitesi: https://blocksec.com/

Resmi Twitter hesabı: https://twitter.com/BlockSecTeam

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit