স্মার্ট কন্ট্রাক্ট বাগ খুঁজতে কীভাবে স্লিদার (Slither) ব্যবহার করবেন
কীভাবে স্লিদার ব্যবহার করবেন
এই টিউটোরিয়ালের উদ্দেশ্য হলো স্মার্ট কন্ট্রাক্টে স্বয়ংক্রিয়ভাবে বাগ খুঁজতে কীভাবে স্লিদার ব্যবহার করতে হয় তা দেখানো।
- ইন্সটলেশন
- কমান্ড লাইন ব্যবহার
- স্ট্যাটিক অ্যানালাইসিসের পরিচিতি: স্ট্যাটিক অ্যানালাইসিসের সংক্ষিপ্ত পরিচিতি
- API: Python API-এর বিবরণ
ইন্সটলেশন
স্লিদার চালানোর জন্য Python >= 3.6 প্রয়োজন। এটি pip বা Docker ব্যবহার করে ইন্সটল করা যেতে পারে।
pip-এর মাধ্যমে স্লিদার:
pip3 install --user slither-analyzer
Docker-এর মাধ্যমে স্লিদার:
docker pull trailofbits/eth-security-toolbox
docker run -it -v "$PWD":/home/trufflecon trailofbits/eth-security-toolbox
শেষ কমান্ডটি একটি Docker-এ eth-security-toolbox চালায় যার আপনার বর্তমান ডিরেক্টরিতে অ্যাক্সেস রয়েছে। আপনি আপনার হোস্ট থেকে ফাইলগুলো পরিবর্তন করতে পারেন এবং Docker থেকে ফাইলগুলোর উপর টুলগুলো চালাতে পারেন
Docker-এর ভিতরে, রান করুন:
solc-select 0.5.11
cd /home/trufflecon/
স্ক্রিপ্ট রান করা
Python 3 দিয়ে একটি Python স্ক্রিপ্ট রান করতে:
python3 script.py
কমান্ড লাইন
কমান্ড লাইন বনাম ব্যবহারকারী-সংজ্ঞায়িত স্ক্রিপ্ট। স্লিদার কিছু পূর্বনির্ধারিত ডিটেক্টরের (detectors) সাথে আসে যা অনেক সাধারণ বাগ খুঁজে বের করে। কমান্ড লাইন থেকে স্লিদার কল করলে সমস্ত ডিটেক্টর রান হবে, এর জন্য স্ট্যাটিক অ্যানালাইসিসের বিস্তারিত জ্ঞানের প্রয়োজন নেই:
slither project_paths
ডিটেক্টরগুলো ছাড়াও, স্লিদারে এর প্রিন্টার (একটি নতুন ট্যাবে খোলে) এবং টুলগুলোর (একটি নতুন ট্যাবে খোলে) মাধ্যমে কোড রিভিউ করার ক্ষমতা রয়েছে।
প্রাইভেট ডিটেক্টর এবং GitHub ইন্টিগ্রেশনে অ্যাক্সেস পেতে crytic.io (একটি নতুন ট্যাবে খোলে) ব্যবহার করুন।
স্ট্যাটিক অ্যানালাইসিস
স্লিদার স্ট্যাটিক অ্যানালাইসিস ফ্রেমওয়ার্কের ক্ষমতা এবং ডিজাইন ব্লগ পোস্ট (1 (একটি নতুন ট্যাবে খোলে), 2 (একটি নতুন ট্যাবে খোলে)) এবং একটি একাডেমিক পেপারে (একটি নতুন ট্যাবে খোলে) বর্ণনা করা হয়েছে।
স্ট্যাটিক অ্যানালাইসিস বিভিন্ন ধরনের হয়ে থাকে। আপনি সম্ভবত বুঝতে পেরেছেন যে clang (একটি নতুন ট্যাবে খোলে) এবং gcc (একটি নতুন ট্যাবে খোলে)-এর মতো কম্পাইলারগুলো এই গবেষণার কৌশলগুলোর উপর নির্ভর করে, তবে এটি (Infer (একটি নতুন ট্যাবে খোলে), CodeClimate (একটি নতুন ট্যাবে খোলে), FindBugs (একটি নতুন ট্যাবে খোলে) এবং Frama-C (একটি নতুন ট্যাবে খোলে) ও Polyspace (একটি নতুন ট্যাবে খোলে)-এর মতো ফরমাল মেথডের উপর ভিত্তি করে তৈরি টুলগুলোরও ভিত্তি হিসেবে কাজ করে।
আমরা এখানে স্ট্যাটিক অ্যানালাইসিস কৌশল এবং গবেষকদের নিয়ে বিস্তারিত আলোচনা করব না। এর পরিবর্তে, স্লিদার কীভাবে কাজ করে তা বোঝার জন্য যা প্রয়োজন আমরা তার উপর ফোকাস করব, যাতে আপনি বাগ খুঁজে বের করতে এবং কোড বুঝতে এটি আরও কার্যকরভাবে ব্যবহার করতে পারেন।
কোড রিপ্রেজেন্টেশন
ডায়নামিক অ্যানালাইসিসের বিপরীতে, যা একটি একক এক্সিকিউশন পাথ নিয়ে কাজ করে, স্ট্যাটিক অ্যানালাইসিস একই সাথে সমস্ত পাথ নিয়ে কাজ করে। এটি করার জন্য, এটি একটি ভিন্ন কোড রিপ্রেজেন্টেশনের উপর নির্ভর করে। সবচেয়ে সাধারণ দুটি হলো অ্যাবস্ট্রাক্ট সিনট্যাক্স ট্রি (AST) এবং কন্ট্রোল ফ্লো গ্রাফ (CFG)।
অ্যাবস্ট্রাক্ট সিনট্যাক্স ট্রি (AST)
কম্পাইলার যখনই কোড পার্স করে তখনই AST ব্যবহার করা হয়। এটি সম্ভবত সবচেয়ে মৌলিক কাঠামো যার উপর স্ট্যাটিক অ্যানালাইসিস করা যেতে পারে।
সংক্ষেপে, একটি AST হলো একটি কাঠামোগত ট্রি যেখানে সাধারণত প্রতিটি লিফে (leaf) একটি ভেরিয়েবল বা কনস্ট্যান্ট থাকে এবং অভ্যন্তরীণ নোডগুলো হলো অপারেন্ড বা কন্ট্রোল ফ্লো অপারেশন। নিচের কোডটি বিবেচনা করুন:
function safeAdd(uint a, uint b) pure internal returns(uint){
if(a + b <= a){
revert();
}
return a + b;
}
এর সাথে সম্পর্কিত AST নিচে দেখানো হলো:
স্লিদার solc দ্বারা এক্সপোর্ট করা AST ব্যবহার করে।
তৈরি করা সহজ হলেও, AST হলো একটি নেস্টেড কাঠামো। অনেক সময়, এটি বিশ্লেষণ করা সবচেয়ে সহজ কাজ নয়। উদাহরণস্বরূপ, a + b <= a এক্সপ্রেশন দ্বারা ব্যবহৃত অপারেশনগুলো শনাক্ত করতে, আপনাকে প্রথমে <= এবং তারপর + বিশ্লেষণ করতে হবে। একটি সাধারণ পদ্ধতি হলো তথাকথিত ভিজিটর প্যাটার্ন (visitor pattern) ব্যবহার করা, যা ট্রির মধ্য দিয়ে রিকার্সিভভাবে নেভিগেট করে। স্লিদারে ExpressionVisitor (একটি নতুন ট্যাবে খোলে)-এ একটি জেনেরিক ভিজিটর রয়েছে।
নিচের কোডটি এক্সপ্রেশনে কোনো যোগ (addition) আছে কিনা তা শনাক্ত করতে ExpressionVisitor ব্যবহার করে:
from slither.visitors.expression.expression import ExpressionVisitor
from slither.core.expressions.binary_operation import BinaryOperationType
class HasAddition(ExpressionVisitor):
def result(self):
return self._result
def _post_binary_operation(self, expression):
if expression.type == BinaryOperationType.ADDITION:
self._result = True
visitor = HasAddition(expression) # expression হলো সেই এক্সপ্রেশন যা পরীক্ষা করা হবে
print(f'The expression {expression} has a addition: {visitor.result()}')
কন্ট্রোল ফ্লো গ্রাফ (CFG)
দ্বিতীয় সবচেয়ে সাধারণ কোড রিপ্রেজেন্টেশন হলো কন্ট্রোল ফ্লো গ্রাফ (CFG)। নাম থেকেই বোঝা যায়, এটি একটি গ্রাফ-ভিত্তিক রিপ্রেজেন্টেশন যা সমস্ত এক্সিকিউশন পাথ প্রকাশ করে। প্রতিটি নোডে এক বা একাধিক নির্দেশাবলী থাকে। গ্রাফের এজগুলো (edges) কন্ট্রোল ফ্লো অপারেশনগুলোকে (if/then/else, loop ইত্যাদি) উপস্থাপন করে। আমাদের আগের উদাহরণের CFG হলো:
CFG হলো সেই রিপ্রেজেন্টেশন যার উপর ভিত্তি করে বেশিরভাগ অ্যানালাইসিস তৈরি করা হয়।
আরও অনেক কোড রিপ্রেজেন্টেশন রয়েছে। আপনি যে অ্যানালাইসিস করতে চান তার উপর ভিত্তি করে প্রতিটি রিপ্রেজেন্টেশনের সুবিধা এবং অসুবিধা রয়েছে।
অ্যানালাইসিস
স্লিদার দিয়ে আপনি যে সবচেয়ে সহজ ধরনের অ্যানালাইসিস করতে পারেন তা হলো সিনট্যাকটিক অ্যানালাইসিস।
সিনট্যাক্স অ্যানালাইসিস
স্লিদার একটি প্যাটার্ন ম্যাচিং-এর মতো পদ্ধতি ব্যবহার করে অসঙ্গতি এবং ত্রুটিগুলো খুঁজে বের করতে কোডের বিভিন্ন উপাদান এবং তাদের রিপ্রেজেন্টেশনের মধ্য দিয়ে নেভিগেট করতে পারে।
উদাহরণস্বরূপ, নিচের ডিটেক্টরগুলো সিনট্যাক্স-সম্পর্কিত সমস্যাগুলো খোঁজে:
-
স্টেট ভেরিয়েবল শ্যাডোয়িং (একটি নতুন ট্যাবে খোলে): সমস্ত স্টেট ভেরিয়েবলের উপর ইটারেট করে এবং চেক করে যে কোনোটি ইনহেরিট করা কন্ট্রাক্ট থেকে কোনো ভেরিয়েবলকে শ্যাডো করে কিনা (state.py#L51-L62 (একটি নতুন ট্যাবে খোলে))
-
ভুল ERC-20 ইন্টারফেস (একটি নতুন ট্যাবে খোলে): ভুল ERC-20 ফাংশন স্বাক্ষর (signatures) খোঁজে (incorrect_erc20_interface.py#L34-L55 (একটি নতুন ট্যাবে খোলে))
সিমেন্টিক অ্যানালাইসিস
সিনট্যাক্স অ্যানালাইসিসের বিপরীতে, একটি সিমেন্টিক অ্যানালাইসিস আরও গভীরে যাবে এবং কোডের "অর্থ" বিশ্লেষণ করবে। এই পরিবারে কিছু বিস্তৃত ধরনের অ্যানালাইসিস অন্তর্ভুক্ত রয়েছে। এগুলো আরও শক্তিশালী এবং দরকারী ফলাফলের দিকে নিয়ে যায়, তবে এগুলো লেখাও বেশি জটিল।
সবচেয়ে উন্নত দুর্বলতা শনাক্তকরণের জন্য সিমেন্টিক অ্যানালাইসিস ব্যবহার করা হয়।
ডেটা ডিপেন্ডেন্সি অ্যানালাইসিস
একটি ভেরিয়েবল variable_a-কে variable_b-এর ডেটা-নির্ভর বলা হয় যদি এমন কোনো পাথ থাকে যার জন্য variable_a-এর মান variable_b দ্বারা প্রভাবিত হয়।
নিচের কোডে, variable_a হলো variable_b-এর উপর নির্ভরশীল:
// ...
variable_a = variable_b + 1;
স্লিদারে বিল্ট-ইন ডেটা ডিপেন্ডেন্সি (একটি নতুন ট্যাবে খোলে) ক্ষমতা রয়েছে, এর ইন্টারমিডিয়েট রিপ্রেজেন্টেশনের কারণে (পরবর্তী বিভাগে আলোচনা করা হয়েছে)।
ডেটা ডিপেন্ডেন্সি ব্যবহারের একটি উদাহরণ ডেঞ্জারাস স্ট্রিক্ট ইকুয়ালিটি ডিটেক্টরে (একটি নতুন ট্যাবে খোলে) পাওয়া যেতে পারে। এখানে স্লিদার একটি বিপজ্জনক মানের সাথে স্ট্রিক্ট ইকুয়ালিটি তুলনা খুঁজবে (incorrect_strict_equality.py#L86-L87 (একটি নতুন ট্যাবে খোলে)), এবং ব্যবহারকারীকে জানাবে যে কোনো আক্রমণকারীকে কন্ট্রাক্টটি ফাঁদে ফেলা থেকে বিরত রাখতে ==-এর পরিবর্তে >= বা <= ব্যবহার করা উচিত। অন্যান্য বিষয়ের মধ্যে, ডিটেক্টরটি balanceOf(address)-এ কলের রিটার্ন ভ্যালুকে বিপজ্জনক হিসেবে বিবেচনা করবে (incorrect_strict_equality.py#L63-L64 (একটি নতুন ট্যাবে খোলে)), এবং এর ব্যবহার ট্র্যাক করতে ডেটা ডিপেন্ডেন্সি ইঞ্জিন ব্যবহার করবে।
ফিক্সড-পয়েন্ট কম্পিউটেশন
যদি আপনার অ্যানালাইসিস CFG-এর মধ্য দিয়ে নেভিগেট করে এবং এজগুলো অনুসরণ করে, তবে আপনি সম্ভবত ইতিমধ্যে ভিজিট করা নোডগুলো দেখতে পাবেন। উদাহরণস্বরূপ, যদি নিচে দেখানো লুপটি উপস্থিত থাকে:
for(uint i; i < range; ++){
variable_a += 1
}
আপনার অ্যানালাইসিসকে জানতে হবে কখন থামতে হবে। এখানে দুটি প্রধান কৌশল রয়েছে: (1) প্রতিটি নোডে একটি নির্দিষ্ট সংখ্যক বার ইটারেট করা, (2) একটি তথাকথিত ফিক্সপয়েন্ট (fixpoint) গণনা করা। একটি ফিক্সপয়েন্ট বলতে মূলত বোঝায় যে এই নোডটি বিশ্লেষণ করা কোনো অর্থপূর্ণ তথ্য প্রদান করে না।
রিএন্ট্রান্সি ডিটেক্টরগুলোতে ফিক্সপয়েন্ট ব্যবহারের একটি উদাহরণ পাওয়া যেতে পারে: স্লিদার নোডগুলো এক্সপ্লোর করে এবং এক্সটার্নাল কল, স্টোরেজে রাইট এবং রিড খোঁজে। একবার এটি একটি ফিক্সপয়েন্টে পৌঁছালে (reentrancy.py#L125-L131 (একটি নতুন ট্যাবে খোলে)), এটি এক্সপ্লোরেশন থামিয়ে দেয় এবং বিভিন্ন রিএন্ট্রান্সি প্যাটার্নের মাধ্যমে (reentrancy_benign.py (একটি নতুন ট্যাবে খোলে), reentrancy_read_before_write.py (একটি নতুন ট্যাবে খোলে), reentrancy_eth.py (একটি নতুন ট্যাবে খোলে)) কোনো রিএন্ট্রান্সি উপস্থিত আছে কিনা তা দেখতে ফলাফলগুলো বিশ্লেষণ করে।
দক্ষ ফিক্সড পয়েন্ট কম্পিউটেশন ব্যবহার করে অ্যানালাইসিস লেখার জন্য অ্যানালাইসিসটি কীভাবে এর তথ্য প্রচার করে সে সম্পর্কে ভালো ধারণা থাকা প্রয়োজন।
ইন্টারমিডিয়েট রিপ্রেজেন্টেশন
একটি ইন্টারমিডিয়েট রিপ্রেজেন্টেশন (IR) হলো এমন একটি ভাষা যা মূল ভাষার চেয়ে স্ট্যাটিক অ্যানালাইসিসের জন্য বেশি উপযুক্ত। স্লিদার Solidity-কে এর নিজস্ব IR-এ অনুবাদ করে: SlithIR (একটি নতুন ট্যাবে খোলে)।
আপনি যদি শুধুমাত্র বেসিক চেক লিখতে চান তবে SlithIR বোঝার প্রয়োজন নেই। তবে, আপনি যদি উন্নত সিমেন্টিক অ্যানালাইসিস লেখার পরিকল্পনা করেন তবে এটি কাজে আসবে। SlithIR (একটি নতুন ট্যাবে খোলে) এবং SSA (একটি নতুন ট্যাবে খোলে) প্রিন্টারগুলো আপনাকে কোডটি কীভাবে অনুবাদ করা হয় তা বুঝতে সাহায্য করবে।
API-এর বেসিকস
স্লিদারে একটি API রয়েছে যা আপনাকে কন্ট্রাক্ট এবং এর ফাংশনগুলোর বেসিক অ্যাট্রিবিউটগুলো এক্সপ্লোর করতে দেয়।
একটি কোডবেস লোড করতে:
from slither import Slither
slither = Slither('/path/to/project')
কন্ট্রাক্ট এবং ফাংশন এক্সপ্লোর করা
একটি Slither অবজেক্টে থাকে:
contracts (list(Contract): কন্ট্রাক্টগুলোর তালিকাcontracts_derived (list(Contract): এমন কন্ট্রাক্টগুলোর তালিকা যা অন্য কোনো কন্ট্রাক্ট দ্বারা ইনহেরিট করা হয়নি (কন্ট্রাক্টগুলোর সাবসেট)get_contract_from_name (str): এর নাম থেকে একটি কন্ট্রাক্ট রিটার্ন করে
একটি Contract অবজেক্টে থাকে:
name (str): কন্ট্রাক্টের নামfunctions (list(Function)): ফাংশনগুলোর তালিকাmodifiers (list(Modifier)): ফাংশনগুলোর তালিকাall_functions_called (list(Function/Modifier)): কন্ট্রাক্ট দ্বারা পৌঁছানো যায় এমন সমস্ত ইন্টারনাল ফাংশনের তালিকাinheritance (list(Contract)): ইনহেরিট করা কন্ট্রাক্টগুলোর তালিকাget_function_from_signature (str): এর স্বাক্ষর (signature) থেকে একটি ফাংশন রিটার্ন করেget_modifier_from_signature (str): এর স্বাক্ষর থেকে একটি মডিফায়ার রিটার্ন করেget_state_variable_from_name (str): এর নাম থেকে একটি স্টেট ভেরিয়েবল রিটার্ন করে
একটি Function বা একটি Modifier অবজেক্টে থাকে:
name (str): ফাংশনের নামcontract (contract): যে কন্ট্রাক্টে ফাংশনটি ডিক্লেয়ার করা হয়েছেnodes (list(Node)): ফাংশন/মডিফায়ারের CFG গঠনকারী নোডগুলোর তালিকাentry_point (Node): CFG-এর এন্ট্রি পয়েন্টvariables_read (list(Variable)): রিড করা ভেরিয়েবলগুলোর তালিকাvariables_written (list(Variable)): রাইট করা ভেরিয়েবলগুলোর তালিকাstate_variables_read (list(StateVariable)): রিড করা স্টেট ভেরিয়েবলগুলোর তালিকা (variables`read-এর সাবসেট)state_variables_written (list(StateVariable)): রাইট করা স্টেট ভেরিয়েবলগুলোর তালিকা (variables`written-এর সাবসেট)

