Echidna naudojimas išmaniajai sutarties bibliotekai išbandyti

Echidna naudojimas išmaniajai sutarties bibliotekai išbandyti

Šiame įraše parodysime, kaip išbandyti savo išmaniąsias sutartis su Echidna fuzzer. Tiksliau, pamatysite, kaip:

  • Raskite klaidą, kurią aptikome tikrindami protokolą „Set Protocol“, naudodami skirtingą suliejimą ir
  • Nustatykite ir valdykite naudingas savo išmaniųjų sutarčių bibliotekų funkcijas.

Parodysime, kaip visa tai padaryti naudojant crytic.io, kuris suteikia GitHub integraciją ir papildomus saugos valdiklius.

Bibliotekos gali importuoti riziką

Labai svarbu rasti atskirų išmaniųjų sutarčių gedimus: sutartis, tiek žetonų, tiek eterio forma, gali valdyti didelius ekonominius išteklius, o nuostoliai dėl pažeidžiamumo gali siekti milijonus dolerių. Vis dėlto Ethereum blokų grandinėje yra vienas kodas, kuris yra dar svarbesnis už bet kokią individualią sutartį: bibliotekos kodas.

Bibliotekomis gali dalytis: daugelis didelės vertės sutartys, taigi, tarkime, subtili nežinoma klaida, SafeMathužpuolikas ne tik daugelis ypatingos svarbos sutartys Šio tipo infrastruktūros kodo svarba yra gerai suprantama ne blokų grandinės kontekste – dažniausiai naudojamų bibliotekų, pvz., TLS ar sqlite, klaidos yra užkrečiamos ir paveikia bet kokį kodą, kuris priklauso nuo galimai pažeidžiamos bibliotekos.

Bibliotekos testavimas dažniausiai skirtas atminties pažeidžiamumui aptikti. Tačiau blokų grandinėje nuo kamino suskaidymo arba memcpy iš regiono, kuriame yra privatūs raktai; Mums labiausiai rūpi bibliotekos kodo semantinis tikslumas. Išmaniosios sutartys veikia finansų pasaulyje, kuriame „kodas yra įstatymas“, ir jei biblioteka kai kuriais atvejais apskaičiuoja neteisingus rezultatus, ši „teisinė spraga“ gali išplisti į skambinimo sutartį ir priversti užpuoliką netinkamai elgtis pagal sutartį.

Tokios spragos gali turėti kitų pasekmių, nei tai, kad biblioteka duoda neteisingus rezultatus; Jei užpuolikas gali priversti bibliotekos kodą netikėtai sugrįžti, jis turi raktą į galimą atsisakymo teikti paslaugas ataką. Jei užpuolikas gali įtraukti bibliotekos funkciją į nuotėkio kilpą, jis gali sujungti atsisakymą teikti paslaugą su brangiu dujų suvartojimu.

Tai yra „Bit Trace“ klaidos, aptiktos senesnėje bibliotekos versijoje, skirtoje adresų masyvams tvarkyti, esmė, kaip aprašyta šiame Set Protocol kodo patikrinime.

Sugedęs kodas atrodo taip:

/*** Returns whether or not there's a duplicate. Runs in O(n^2).* @param A Array to search* @return Returns true if duplicate, false otherwise*/function hasDuplicate(address[] memory A) returns (bool)   {     for (uint256 i = 0; i < A.length - 1; i++) {       for (uint256 j = i + 1; j < A.length; j++) {         if (A[i] == A[j]) {            return true;         }       }   }   return false;}

Problema ta, kad jei A.length yra yra 0 (A tuščias), tada A.length - 1 perpildymas ir išorė (i) ciklas kartojasi per visą rinkinį uint256 vertybes. vidinis (j) ciklas šiuo atveju neveiks, todėl (iš esmės) turime įtemptą kilpą, kuri nieko nedaro amžinai. Žinoma, šiame procese visada baigsis dujos ir procesas, kuris tai atlieka hasDuplicate paieška nepavyksta. Jei užpuolikas tinkamoje vietoje gali sugeneruoti tuščią masyvą, tada (pavyzdžiui) sutartis, kuri adresų masyve įgyvendina tam tikrą literalą. hasDuplicate gali būti išjungtas – galbūt visam laikui.

biblioteka

Norėdami gauti daugiau informacijos, žiūrėkite mūsų pavyzdžio kodą ir peržiūrėkite šią Echidna naudojimo pamoką.

Aukšto lygio biblioteka suteikia patogią adresų rinkinio valdymo funkciją. Įprastas naudojimo atvejis apima prieigos valdymą naudojant baltąjį adresų sąrašą. AddressArrayUtils.sol gali išbandyti 19 funkcijų:

function indexOf(address[] memory A, address a)function contains(address[] memory A, address a)function indexOfFromEnd(address[] A, address a)function extend(address[] memory A, address[] memory B)function append(address[] memory A, address a)function sExtend(address[] storage A, address[] storage B)function intersect(address[] memory A, address[] memory B)function union(address[] memory A, address[] memory B)function unionB(address[] memory A, address[] memory B)function difference(address[] memory A, address[] memory B)function sReverse(address[] storage A)function pop(address[] memory A, uint256 index)function remove(address[] memory A, address a)function sPop(address[] storage A, uint256 index)function sPopCheap(address[] storage A, uint256 index)function sRemoveCheap(address[] storage A, address a)function hasDuplicate(address[] memory A)function isEqual(address[] memory A, address[] memory B)function argGet(address[] memory A, uint256[] memory indexArray)

Atrodo daug, bet dauguma funkcijų yra panašios, nes AddressArrayUtils pateikia ir funkcines versijas (kurios veikia pagal atminties masyvo parametrus), ir mutuojančias versijas (kurioms reikia saugojimo masyvų). extend, reverse, popir remove. Kai pamatysite, kaip rašėme testą. popParašykite testą už sPop Tikriausiai nebus per sunku.

Funkcijomis pagrįstas neryškus 101

Mūsų darbas yra atlikti mus dominančias funkcijas – čia, visas – ir:

  • Tada supraskite, ką daro kiekviena funkcija
  • Parašykite testą, kuris privers funkciją tai padaryti!

Žinoma, vienas iš būdų tai padaryti yra parašyti daug vienetų testų, tačiau tai yra problematiška. jei norime kruopščiai Išbandykite biblioteką, tai bus daug darbo ir atvirai kalbant, tikriausiai blogai dirbsime. Ar tikrai galime apsvarstyti kiekvieną kampinį atvejį? Net jei bandytume aprėpti visą šaltinio kodą, trūksta šaltinio kodokaip hasDuplicate Klaidą galima lengvai nepastebėti.

norime naudoti nuosavybe pagrįstas testavimas parodyti bendrą elgesį visi galimi įrašaitada sukurkite kelis įrašus. Parašyti bendrą elgesio aprašymą yra sunkiau nei parašyti bet kokį konkretų „davus X įvestį, funkcija turi atlikti/grąžinti Y“ testą. Bet rašymas visi Reikalingi betono bandymai būtų nepaprastai dideli. Svarbiausia, kad net puikiai atlikti rankiniai vienetų testai nesugeba rasti keistų kraštinių klaidų, kurių ieško užpuolikai.

Echidna testavimo diržai: hasDuplicate

Akivaizdžiausias dalykas, susijęs su kodu, kuriuo galima išbandyti biblioteką, yra tai, kad jis didesnis nei pati biblioteka! Tai nėra neįprasta tokioje situacijoje. Neleiskite, kad tai jūsų atgrasytų; Skirtingai nei biblioteka, bandomoji juosta, kuri laikoma nebaigta ir palaipsniui tobulinama bei plečiama, veikia puikiai. Bandymo kūrimas yra laipsniškas, o jei turite įrankį, pvz., Echidna, kad padidintumėte savo investicijas, net ir nedidelės pastangos gali duoti didelės naudos.

Norėdami gauti konkretų pavyzdį, pažvelkime į tai: hasDuplicate klaida. Mes norime tai patikrinti:

  • Jei yra kopija, hasDuplicate ataskaitos ir
  • Jei kopijos nėra, hasDuplicate praneša, kad jo nėra.

Galime tik pakartotinai kreiptis hasDuplicate bet apskritai tai nelabai padeda (čia gali leisti rasti klaidą). Jei turėtume kitą, nepriklausomai sukurtą, aukštos kokybės adresų masyvo paslaugų biblioteką, su kuria galėtume palyginti, metodą, vadinamą diferencialiniu testavimu. Deja, dažniausiai tokios informacinės bibliotekos neturime.

Mūsų požiūris yra įdiegti silpnesnę diferencialinio testo versiją, ieškant kitos funkcijos, kuri gali aptikti pasikartojančius duomenis neieškodama bibliotekoje. hasDuplicate. Tam naudosime indexOf ir indexOfFromEnd norėdami patikrinti, ar elemento indeksas (pradedant nuo 0) yra toks pat, kaip ir tada, kai atliekama paieška iš masyvo pabaigos:

    for (uint i = 0; i < addrs1.length; i++) {      (i1, b) = AddressArrayUtils.indexOf(addrs1, addrs1[i]);      (i2, b) = AddressArrayUtils.indexOfFromEnd(addrs1, addrs1[i]);      if (i1 != (i2-1)) { // -1 because fromEnd return is off by one	hasDup = true;      }    }    return hasDup == AddressArrayUtils.hasDuplicate(addrs1);  }

Visą pavyzdinį kodą žiūrėkite mūsų addressarrayutils demonstracijoje

Šis kodas kartojasi per addrs1 ir suranda kiekvieno elemento pirmojo rodinio rodyklę. Žinoma, jei nėra dublikatų, tai visada įvyks. pagal mane pats. Tada kodas suranda paskutinio elemento rodinio rodyklę (t.y. nuo galo). Jei šie du indeksai skiriasi, yra dublikatas. Echidnoje ypatybės yra tiesiog Būlio solidumo funkcijos, kurios paprastai grąžinamos teisingos, kai ypatybė įvykdoma (išimtį matysime toliau), o jei grąžinama arba klaidinga, jos nepavyksta. dabar mūsų hasDuplicate testuoja abu hasDuplicate ir dvi indexOf funkcijos. Jei jie nesutiks, Echidna mums pasakys.

Dabar galime pridėti keletą suliejimo funkcijų ir nustatyti addrs1.

Leiskite, kad ši funkcija veiktų Crytic:

„Crytic“ nepavyksta išbandyti „hasDuplicate“.

Pirma, crytic_hasDuplicate nesėkmingai:

crytic_hasDuplicate: failed!  Call sequence:    set_addr(0x0)

Trigerio veiksmų seka yra labai paprasta: addrs1paskambink vėliau hasDuplicate aukščiau. Viskas – dėl nutekėjimo ciklo išeikvosite jūsų dujų biudžetą, o Crytic/Echidna praneš, kad nuosavybė sugedo. Tai 0x0 Echidna sprendžia pasekmes, kai sumažina nesėkmę iki paprasčiausios įmanomos tvarkos.

Kitos mūsų funkcijos (crytic_revert_remove ir crytic_remove) praeina, tai gerai. Jei ištaisysime klaidą hasDuplicate tada visi mūsų testai bus sėkmingi:

Visi trys funkcijų testai dabar praeina „Crytic“.

Tai crytic_hasDuplicate: fuzzing (2928/10000) mums sako, kad tai brangu hasDuplicate nuosavybė sugenda iš karto, tik 3 000 iš 10 000 maksimalaus kiekvienos nuosavybės bandymų buvo atlikti, kol pasiekėme penkių minučių skirtąjį laiką.

Echidna bandymo diržai: likusi bibliotekos dalis

Dabar, kai pamatėme bandomąjį pavyzdį, pateikiame keletą pagrindinių pasiūlymų, kaip sukurti likusius testus (kaip mes padarėme su addressarrayutils_demo saugykla):

  • Išbandykite skirtingus būdus, kaip apskaičiuoti tą patį dalyką. Kuo daugiau „diferencinių“ funkcijos versijų turite, tuo didesnė tikimybė, kad išsiaiškinsite, ar viena iš jų neteisinga. Pavyzdžiui, pažiūrėkite į visus metodus, kuriuos kryžmiškai patikrinome. indexOf, containsir indexOfFromEnd.
  • Testas dėl atšaukti. jei pridėsite priešdėlį _revert_ prieš jūsų nuosavybės pavadinimą, kaip mes čia, Nuosavybė persijungia tik tuo atveju, jei visi skambučiai į ją atšaukiami. Tai užtikrina, kad kodas suges tada, kai turėjo nepavykti.
  • Nepamirškite patikrinti, ar nėra akivaizdžių paprastų raidžių, pavyzdžiui, masyvo skirtumas su pačiu savimi visada tuščias (ourEqual(AddressArrayUtils.difference(addrs1, addrs1), empty)).
  • Nekintamieji patikrinimai ir išankstinės sąlygos kituose bandymuose gali veikti kaip kryžminis tikrinamų funkcijų patikrinimas. atkreipkite dėmesį į tai hasDuplicate raginama atlikti daugybę testų, kurių tikrinti neketinama hasDuplicate nė vienas; vien žinant, kad masyvas neturi pasikartojančių masyvų, gali generuoti papildomus daugelio kitų elgsenų pažodinius žodžius, pavyzdžiui, bet kurioje vietoje pašalinus adresą X, masyve nebeliks X.

Darbo su Crytic pradžia

Echidna testus galite atlikti patys, atsisiųsdami ir įdiegdami įrankį arba naudodami mūsų „Docker“ versiją, tačiau naudojant „Crytic“ platformą integruojamas „Echidna“ savybėmis pagrįstas testavimas, „Slither“ statinė analizė (įskaitant naujus analizatorius, kurių nėra viešoje „Slither“ versijoje), galima atnaujinti. valdikliai ir jūsų įrenginio valdikliai vientisoje aplinkoje, susietoje su jūsų versija, kontroliuoja jūsų testus. Be to, addressarrayutils_demo saugykla rodo viską, ko reikia funkcijomis pagrįstiems bandymams: tai gali būti taip paprasta, kaip sukurti minimalų Truffle diegimą, pridėti crytic.sol failą su Echidna funkcijomis ir įjungti funkcijomis pagrįstus testus saugyklos konfigūracijoje Kritikas. .

Prisiregistruokite prie Crytic šiandien ir prisijunkite arba sekite mūsų Slack kanalą (#crytic), jei turite klausimų @CryticCI iš Twitter.

*** Tai saugos tinklaraštininkų tinklo sindikacijos tinklaraštis iš „Trail of Bits Blog“, kurį sukūrė Alexas Groce. Skaitykite originalų įrašą adresu https://blog.trailofbits.com/2020/08/17/using-echidna-to-test-a-smart-contract-library/

Leave a Comment

Your email address will not be published. Required fields are marked *