ပထမဆုံးအနေနဲ့ reentrancy attack ဆိုတာဘာလဲ၊ ဘယ်လိုကာကွယ်နိုင်သလဲဆိုတာကို ရိုးရှင်းလွယ်ကူတဲ့နည်းနဲ့ နားလည်အောင် ကူညီပေးပါမယ်။ ပြီးရင် အားနည်းချက်တွေ ဘယ်နေရာမှာရှိသလဲ၊ တိုက်ခိုက်သူရဲ့ကုဒ်က ဘယ်လိုဖြစ်မလဲဆိုတာ ပြသဖို့ ကုဒ်ဥပမာတွေကို ပိုမိုအသေးစိတ် လေ့လာပါမယ်။ အရေးအကြီးဆုံးအနေနဲ့ သင့်ပရောဂျက်မှာရှိတဲ့ smart contract တစ်ခုတည်းကိုသာမက အားလုံးကိုပါ ကာကွယ်ပေးနိုင်မယ့် နောက်ဆုံးပေါ် အတည်ပြုထားသော နည်းလမ်းတွေကို ပြသပေးပါမယ်။

ကြိုတင်အသိပေးချက်: nonReentrant() modifier အကြောင်း ကြားဖူးပြီးသားဆိုရင် ဆက်ဖတ်ပါ။ အောက်မှာ globalNonReentrant() modifier နဲ့ checks-effects-interactions ပုံစံကို မကြာခင် သိရှိရတော့မှာပါ။



အပေါ်ကပုံမှာ ContractA နဲ့ ContractB ကို တွေ့ရပါတယ်။ သိထားပြီးဖြစ်တဲ့အတိုင်း smart contract တစ်ခုဟာ အခြား smart contract တစ်ခုနဲ့ အပြန်အလှန်လုပ်ဆောင်နိုင်ပါတယ်။ ဒီဥပမာမှာ ContractA က ContractB ကို ခေါ်နိုင်ပါတယ်။ ဒါကြောင့် reentrancy ရဲ့ အခြေခံအယူအဆက ContractA က လုပ်ဆောင်နေဆဲအချိန်မှာ ContractB က ContractA ကို ပြန်ခေါ်နိုင်တာပါ။

ဒါဆို တိုက်ခိုက်သူက ဒါကို ဘယ်လို အသုံးချနိုင်မလဲ။

အပေါ်မှာ Ether 10 ခုရှိတဲ့ ContractA ကို တွေ့ရပြီး ContractB က ContractA ထဲမှာ Ether 1 ခု ထည့်သွင်းထားတာကို တွေ့နိုင်ပါတယ်။ ဒီအခြေအနေမှာ ContractB ဟာ ContractA ရဲ့ withdraw လုပ်ဆောင်ချက်ကို အသုံးပြုပြီး Ether ကို သူ့ဆီ ပြန်ပို့နိုင်ပါတယ်။ အကြောင်းကတော့ သူ့လက်ကျန်ငွေက 0 ထက်များတဲ့ စစ်ဆေးချက်ကို ကျော်ဖြတ်နိုင်လို့ပါ။ အဲဒီနောက် သူ့ရဲ့ စုစုပေါင်းလက်ကျန်ကို 0 အဖြစ် ပြောင်းလဲနိုင်ပါတယ်။



အခု ContractB က reentrancy ကို ဘယ်လိုသုံးပြီး withdraw လုပ်ဆောင်ချက်ကို အသုံးချကာ ContractA ထဲက Ether အားလုံးကို ခိုးယူနိုင်သလဲ ကြည့်ကြရအောင်။ အခြေခံအားဖြင့် တိုက်ခိုက်သူဟာ attack() နဲ့ fallback() ဆိုတဲ့ လုပ်ဆောင်ချက်နှစ်ခု လိုအပ်ပါမယ်။

Solidity မှာ fallback function ဆိုတာ အမည်၊ ပါရာမီတာ၊ ပြန်ပေးတန်ဖိုး မရှိတဲ့ ပြင်ပလုပ်ဆောင်ချက်တစ်ခုပါ။ ဘယ်သူမဆို အောက်ပါနည်းလမ်းတွေနဲ့ fallback function ကို ခေါ်နိုင်ပါတယ်- စာချုပ်ထဲမှာ မရှိတဲ့ လုပ်ဆောင်ချက်တစ်ခုကို ခေါ်ခြင်း၊ လိုအပ်တဲ့ အချက်အလက်တွေ မပေးဘဲ လုပ်ဆောင်ချက်ကို ခေါ်ခြင်း၊ အချက်အလက်မပါဘဲ စာချုပ်ဆီ Ether ပို့ခြင်း။

Reentrancy ဘယ်လိုအလုပ်လုပ်သလဲဆိုတာ (မြားတွေကို တစ်ဆင့်ချင်း လိုက်ကြည့်ကြရအောင်) ကတော့ တိုက်ခိုက်သူက attack() လုပ်ဆောင်ချက်ကို ခေါ်ပြီး၊ အဲဒီအထဲကနေ ContractA ရဲ့ withdraw() လုပ်ဆောင်ချက်ကို ခေါ်တာပါ။ လုပ်ဆောင်ချက်အတွင်း ContractB ရဲ့ လက်ကျန်ငွေက 0 ထက်များသလား စစ်ဆေးပြီး များတယ်ဆိုရင် ဆက်လက်လုပ်ဆောင်ပါမယ်။



ContractB ရဲ့ လက်ကျန်ငွေက 0 ထက်များတဲ့အတွက် Ether 1 ခုကို ပြန်ပို့ပြီး fallback လုပ်ဆောင်ချက်ကို အစပျိုးစေပါတယ်။ ဒီအချိန်မှာ ContractA မှာ Ether 9 ခုရှိပြီး ContractB မှာ Ether 1 ခု ရှိနေပြီးဖြစ်တာကို သတိပြုပါ။



နောက်တစ်ဆင့်အနေနဲ့ fallback လုပ်ဆောင်ချက်ကို လုပ်ဆောင်တဲ့အခါ ContractA ရဲ့ withdraw လုပ်ဆောင်ချက်ကို ထပ်မံခေါ်ပြီး ContractB ရဲ့ လက်ကျန်ငွေက 0 ထက်များသလား ထပ်စစ်ဆေးပါတယ်။ အပေါ်ကပုံကို ထပ်ကြည့်မယ်ဆိုရင် သူ့လက်ကျန်ငွေက Ether 1 ခုအဖြစ် ရှိနေသေးတာကို တွေ့ရပါမယ်။



ဆိုလိုတာက စစ်ဆေးချက်ကို ကျော်ဖြတ်ပြီး ContractB ဆီ Ether တစ်ခု ထပ်ပို့တာကြောင့် fallback လုပ်ဆောင်ချက်ကို အစပျိုးစေပါတယ်။ “balance=0” ပါတဲ့ စာကြောင်းကို ဘယ်တော့မှ လုပ်ဆောင်နိုင်မှာ မဟုတ်တာကို သတိပြုပါ။ ဒါကြောင့် ContractA ထဲက Ether အားလုံး ကုန်သွားတဲ့အထိ ဒီလုပ်ငန်းစဉ် ဆက်ဖြစ်နေပါမယ်။

___________

အခု Solidity ကုဒ်နဲ့ reentrancy ကို ခွဲခြားသိနိုင်တဲ့ smart contract တစ်ခုကို ကြည့်ကြရအောင်။



EtherStore စာချုပ်ထဲမှာ ပေးပို့သူရဲ့ လက်ကျန်ငွေကို သိမ်းဆည်းပြီး ပြင်ဆင်ပေးတဲ့ deposit() လုပ်ဆောင်ချက်နဲ့ သိမ်းဆည်းထားတဲ့ လက်ကျန်ငွေအားလုံးကို တစ်ကြိမ်တည်း ထုတ်ယူပေးမယ့် withdrawAll() လုပ်ဆောင်ချက် ရှိပါတယ်။ withdrawAll() ကို ဘယ်လိုရေးထားသလဲဆိုတာ သတိပြုပါ။ အရင်ဆုံး require နဲ့ လက်ကျန်ငွေက 0 ထက်များကြောင်း စစ်ဆေးပြီး ချက်ချင်း Ether ပို့ကာ နောက်ဆုံးမှ ပေးပို့သူရဲ့ လက်ကျန်ငွေကို 0 အဖြစ် ပြင်ဆင်ထားပါတယ်။



ဒီမှာ EtherStore စာချုပ်ထဲက Ether တွေကို ထုတ်ယူဖို့ reentrancy ကို အသုံးပြုမယ့် Attack စာချုပ်ရှိပါတယ်။ သူ့ရဲ့ကုဒ်ကို လေ့လာကြည့်ရအောင်။

  • constructor ထဲမှာ တိုက်ခိုက်သူက EtherStore ရဲ့ လိပ်စာကို ပေးပြီး instance တစ်ခု ဖန်တီးပါတယ်။ ဒါမှ သူ့ရဲ့ လုပ်ဆောင်ချက်တွေကို အသုံးပြုနိုင်မှာပါ။

  • ဒီမှာ EtherStore က ဒီစာချုပ်ဆီ Ether ပို့တဲ့အခါ ခေါ်ဆိုမယ့် fallback() လုပ်ဆောင်ချက်ကို တွေ့နိုင်ပါတယ်။ အဲဒီအထဲမှာ လက်ကျန်ငွေက 1 နဲ့ ညီမျှသည်ဖြစ်စေ၊ 1 ထက်များသည်ဖြစ်စေ EtherStore ရဲ့ withdraw ကို ခေါ်ပါမယ်။

  • ပြီးတော့ attack() လုပ်ဆောင်ချက်ထဲမှာ EtherStore ကို အသုံးချမယ့် လုပ်ဆောင်ချက်တွေ ရှိပါတယ်။ မြင်ရတဲ့အတိုင်း ပထမဆုံး Ether လုံလောက်မှုရှိကြောင်း သေချာစေပြီး တိုက်ခိုက်မှုကို စတင်ပါမယ်။ ပြီးရင် EtherStore ထဲမှာ လက်ကျန်ငွေ 0 ထက်များနေစေပြီး ငွေထုတ်ခြင်း မစခင် စစ်ဆေးချက်တွေကို ကျော်ဖြတ်နိုင်ဖို့ Ether 1 ခု ထည့်သွင်းပါမယ်။

အပေါ်မှာ ContractA နဲ့ ContractB ဥပမာကို အဆင့်ဆင့် ရှင်းပြခဲ့ပြီးဖြစ်တဲ့အတွက် အခု ဘယ်လိုဖြစ်ပျက်မလဲဆိုတာ အကျဉ်းချုပ်ကြည့်ရအောင်။ ပထမဆုံး တိုက်ခိုက်သူက attack() ကို ခေါ်ပါမယ်။ အဲဒီအထဲကနေ EtherStore ရဲ့ withdrawAll() ကို ခေါ်ပြီးနောက် Attack စာချုပ်ရဲ့ fallback လုပ်ဆောင်ချက်ဆီ Ether ပို့ပါမယ်။ အဲဒီမှာ reentrancy စတင်ပြီး EtherStore ရဲ့ လက်ကျန်ငွေကို ထုတ်ယူသွားပါမယ်။

ဒါဆို reentrancy တိုက်ခိုက်မှုတွေကနေ ကျွန်ုပ်တို့ရဲ့ စာချုပ်တွေကို ဘယ်လိုကာကွယ်နိုင်မလဲ။

စာချုပ်တွေကို အပြည့်အဝကာကွယ်နိုင်ဖို့ ကာကွယ်ရေးနည်းလမ်း သုံးခုကို ပြပါမယ်။ လုပ်ဆောင်ချက်တစ်ခုတည်းအတွင်း ဖြစ်တဲ့ reentrancy၊ လုပ်ဆောင်ချက်အချင်းချင်းကြား ဖြစ်တဲ့ reentrancy နဲ့ စာချုပ်အချင်းချင်းကြား ဖြစ်တဲ့ reentrancy တွေကို ဘယ်လိုတားဆီးရမလဲဆိုတာ ရှင်းပြပါမယ်။



လုပ်ဆောင်ချက်တစ်ခုတည်းကို ကာကွယ်ဖို့ ပထမဆုံးနည်းလမ်းက noReentrant လို့ခေါ်တဲ့ modifier ကို အသုံးပြုတာပါ။

modifier ဆိုတာ အခြားလုပ်ဆောင်ချက်တွေရဲ့ အပြုအမူကို ပြင်ဆင်ဖို့ အသုံးပြုတဲ့ အထူးလုပ်ဆောင်ချက်တစ်မျိုးပါ။ modifier တွေက လုပ်ဆောင်ချက်တစ်ခုလုံးကို ပြန်ရေးစရာမလိုဘဲ အဲဒီလုပ်ဆောင်ချက်ထဲကို ထပ်ဆောင်းသတ်မှတ်ချက်တွေ၊ လုပ်ဆောင်ချက်တွေ ထည့်သွင်းနိုင်စေပါတယ်။

ဒီမှာ လုပ်ဆောင်ချက် အလုပ်လုပ်နေချိန် စာချုပ်ကို လော့ခ်ချပါတယ်။ ဒီနည်းနဲ့ လုပ်ဆောင်ချက်ရဲ့ ကုဒ်ကို ဖြတ်သန်းပြီး require ထဲမှာ လုပ်ထားတဲ့ စစ်ဆေးချက်ကို ထပ်မံကျော်ဖြတ်နိုင်ဖို့ locked အခြေအနေကိန်းရှင်ကို false အဖြစ် ပြောင်းရမှာဖြစ်တဲ့အတွက် တစ်ခုတည်းသော လုပ်ဆောင်ချက်ထဲကို ပြန်ဝင်နိုင်မှာ မဟုတ်ပါဘူး။

___________



ဒုတိယနည်းလမ်းကတော့ စာချုပ်တွေကို cross-function reentrancy ကနေ ကာကွယ်ပေးမယ့် Checks-Effects-Interactions ပုံစံကို အသုံးပြုတာပါ။ အပေါ်က ပြင်ဆင်ထားတဲ့ EtherStore စာချုပ်မှာ ဘာတွေ ပြောင်းလဲထားသလဲဆိုတာ သတိထားမိလား။

Check-Effects-Interaction ပုံစံကို အသေးစိတ်လေ့လာရန် https://fravoll.github.io/solidity-patterns/checks_effects_interactions.html ကို ဖတ်ရှုဖို့ အကြံပြုပါတယ်။





အပေါ်မှာ ဘယ်ဘက်ပုံထဲက အားနည်းချက်ရှိတဲ့ကုဒ်နဲ့ နှိုင်းယှဉ်ပြထားပါတယ်။ အဲဒီကုဒ်မှာ Ether ပို့ပြီးမှ လက်ကျန်ငွေကို ပြင်ဆင်တာဖြစ်ပြီး အပေါ်မှာ တွေ့ရတဲ့အတိုင်း အဲဒီနေရာကို ဘယ်တော့မှ ရောက်မလာနိုင်ပါဘူး။ ညာဘက်မှာတော့ balances[msg.sender] = 0 (သက်ရောက်မှု) ကို require(bal > 0) (စစ်ဆေးချက်) ပြီးချင်း၊ Ether ပို့ခြင်း (အပြန်အလှန်လုပ်ဆောင်မှု) မလုပ်ခင် ရွှေ့ထားပါတယ်။

ဒီနည်းနဲ့ အခြားလုပ်ဆောင်ချက်တစ်ခုက withdrawAll() ကို ခေါ်နေတယ်ဆိုရင်တောင် Ether မပို့ခင် လက်ကျန်ငွေကို အမြဲပြင်ဆင်ထားမှာဖြစ်တဲ့အတွက် ဒီစာချုပ်ကို တိုက်ခိုက်သူထံမှ ကာကွယ်ထားနိုင်ကြောင်း သေချာစေပါတယ်။

ပုံစံကို https://twitter.com/GMX_IO မှ ဖန်တီးထားသည်

တတိယနည်းလမ်းအနေနဲ့ အခြားစာချုပ်များအကြား reentrancy ဖြစ်ခြင်းမှ ကာကွယ်ဖို့ GlobalReentrancyGuard စာချုပ်ကို ဖန်တီးပြပါမယ်။ ဒီနည်းလမ်းဟာ အချင်းချင်း အပြန်အလှန်လုပ်ဆောင်နေတဲ့ စာချုပ်များစွာပါဝင်တဲ့ ပရောဂျက်များအတွက် သင့်လျော်ကြောင်း နားလည်ထားဖို့ အရေးကြီးပါတယ်။

ဒီမှာရှိတဲ့ အယူအဆဟာ ပထမနည်းလမ်းမှာ ကျွန်ုပ်ရှင်းပြခဲ့တဲ့ noReentrant modifier နဲ့ အတူတူပါပဲ။ modifier ထဲကို ဝင်ပြီး စာချုပ်ကို လော့ခ်ချဖို့ ကိန်းရှင်တစ်ခုကို ပြင်ဆင်ကာ ကုဒ်အဆုံးသတ်တဲ့အထိ လော့ခ်ကို မဖြည်ပါဘူး။ အဓိကကွာခြားချက်ကတော့ လုပ်ဆောင်ချက်ထဲ ဝင်ထားခြင်း ရှိ၊ မရှိ စစ်ဆေးဖို့ သီးခြားစာချုပ်တစ်ခုထဲမှာ သိမ်းဆည်းထားတဲ့ ကိန်းရှင်ကို အသုံးပြုတာပါ။







အယူအဆကို နားလည်နိုင်ရန် ကိုးကားချက်အဖြစ် လုပ်ဆောင်ချက်အမည်များသာ အသုံးပြုပြီး တကယ့်ကုဒ်မပါသော ဥပမာတစ်ခုကို ဒီမှာ ဖန်တီးထားပါတယ်။ ကျွန်ုပ်၏အတွေ့အကြုံအရ စကားလုံးများဖြင့်သာ ရေးပြခြင်းထက် အခြေအနေကို ပိုမိုမြင်ယောင်နိုင်အောင် ကူညီပေးနိုင်ပါတယ်။

ဒီမှာ တိုက်ခိုက်သူက ScheduledTransfer စာချုပ်ထဲက လုပ်ဆောင်ချက်ကို ခေါ်ပါမယ်။ သတ်မှတ်ချက်တွေ ပြည့်မီပြီးနောက် အဲဒီစာချုပ်က သတ်မှတ်ထားတဲ့ Ether ကို AttackTransfer စာချုပ်ဆီ ပို့ပါမယ်။ ဒါကြောင့် fallback လုပ်ဆောင်ချက်ထဲ ဝင်သွားပြီး ScheduledTransfer စာချုပ်ရဲ့ ရှုထောင့်ကနေဆိုရင် ငွေလွှဲမှုကို “ပယ်ဖျက်” သွားပေမယ့် Ether ကိုတော့ လက်ခံရရှိပါမယ်။ ဒီနည်းနဲ့ ScheduledTransfer ထဲက Ether အားလုံး ကုန်သွားတဲ့အထိ လှည့်ပတ်နေပါမယ်။

ကောင်းပါပြီ၊ အပေါ်မှာ ဖော်ပြခဲ့တဲ့ GlobalReentrancyGuard ကို သုံးရင် ဒီလိုတိုက်ခိုက်မှုမျိုးကို တားဆီးနိုင်ပါတယ်။

__________________

Smart Contract၊ Web3 လုံခြုံရေး၊ Solidity၊ smart contract စစ်ဆေးခြင်းနဲ့ အခြားအကြောင်းအရာတွေအတွက် နေ့စဉ်နောက်ဆုံးရသတင်းတွေကို Twitter @TheBlockChainer မှာ ကြည့်ရှုနိုင်ပါတယ်။

__________________