Як використовувати Slither для пошуку помилок у смартконтрактах
Як використовувати Slither
Мета цього посібника — показати, як використовувати Slither для автоматичного пошуку помилок у смарт-контрактах.
- Встановлення
- Використання командного рядка
- Вступ до статичного аналізу: Короткий вступ до статичного аналізу
- API: Опис Python API
Встановлення
Slither вимагає Python >= 3.6. Його можна інсталювати за допомогою pip або за допомогою Docker.
Slither через pip:
pip3 install --user slither-analyzerSlither через docker:
docker pull trailofbits/eth-security-toolboxdocker run -it -v "$PWD":/home/trufflecon trailofbits/eth-security-toolboxОстання команда запускає eth-security-toolbox у контейнері Docker, який має доступ до вашого поточного каталогу. Ви можете змінювати файли з вашого хоста та запускати інструменти для файлів із контейнера Docker
Усередині контейнера Docker запустіть:
solc-select 0.5.11cd /home/trufflecon/Запуск скрипту
Щоб запустити скрипт Python за допомогою Python 3:
python3 script.pyКомандний рядок
Командний рядок проти користувацьких скриптів. Slither постачається з набором попередньо визначених детекторів, які знаходять багато поширених помилок. Виклик Slither з командного рядка запустить усі детектори, для цього не потрібні глибокі знання статичного аналізу:
slither project_pathsОкрім детекторів, Slither має можливості перевірки коду за допомогою своїх принтерів (opens in a new tab) та інструментів (opens in a new tab).
Використовуйте crytic.io (opens in a new tab), щоб отримати доступ до приватних детекторів та інтеграції з GitHub.
Статичний аналіз
Можливості та дизайн фреймворку статичного аналізу Slither були описані в публікаціях у блозі (1 (opens in a new tab), 2 (opens in a new tab)) та в науковій статті (opens in a new tab).
Статичний аналіз існує в різних формах. Ви, найімовірніше, розумієте, що компілятори, як-от clang (opens in a new tab) та gcc (opens in a new tab), залежать від цих методів дослідження, але вони також лежать в основі (Infer (opens in a new tab), CodeClimate (opens in a new tab), FindBugs (opens in a new tab) та інструментів, що базуються на формальних методах, як-от Frama-C (opens in a new tab) і Polyspace (opens in a new tab)).
Тут ми не будемо вичерпно розглядати методи статичного аналізу та дослідників. Натомість ми зосередимося на тому, що необхідно для розуміння роботи Slither, щоб ви могли ефективніше використовувати його для пошуку помилок та розуміння коду.
Представлення коду
На відміну від динамічного аналізу, який розглядає один шлях виконання, статичний аналіз розглядає всі шляхи одночасно. Для цього він покладається на інше представлення коду. Двома найпоширенішими є абстрактне синтаксичне дерево (AST) і граф потоку керування (CFG).
Абстрактні синтаксичні дерева (AST)
AST використовуються щоразу, коли компілятор парсить код. Це, мабуть, найпростіша структура, на якій можна виконувати статичний аналіз.
Якщо коротко, AST — це структуроване дерево, де зазвичай кожен лист містить змінну або константу, а внутрішні вузли є операндами або операціями потоку керування. Розгляньте такий код:
1function safeAdd(uint a, uint b) pure internal returns(uint){2 if(a + b <= a){3 revert();4 }5 return a + b;6}Відповідний AST показано в:
Slither використовує AST, експортований solc.
Хоча AST легко побудувати, він є вкладеною структурою. Іноді це не найпростіша для аналізу структура. Наприклад, щоб визначити операції, які використовуються у виразі a + b <= a, ви повинні спочатку проаналізувати <= а потім +. Поширеним підходом є використання так званого шаблону «Відвідувач» (visitor pattern), який рекурсивно переміщується по дереву. Slither містить загальний відвідувач у ExpressionVisitor (opens in a new tab).
Наступний код використовує ExpressionVisitor, щоб визначити, чи містить вираз додавання:
1from slither.visitors.expression.expression import ExpressionVisitor2from slither.core.expressions.binary_operation import BinaryOperationType34class HasAddition(ExpressionVisitor):56 def result(self):7 return self._result89 def _post_binary_operation(self, expression):10 if expression.type == BinaryOperationType.ADDITION:11 self._result = True1213visitor = HasAddition(expression) # expression is the expression to be tested14print(f'The expression {expression} has a addition: {visitor.result()}')Показати всеГраф потоку керування (CFG)
Другим за поширеністю представленням коду є граф потоку керування (CFG). Як випливає з назви, це графове представлення, яке розкриває всі шляхи виконання. Кожен вузол містить одну або кілька інструкцій. Ребра в графі представляють операції потоку керування (if/then/else, цикл тощо). CFG нашого попереднього прикладу:
CFG — це представлення, на основі якого будується більшість аналізів.
Існує багато інших представлень коду. Кожне представлення має переваги та недоліки залежно від аналізу, який ви хочете виконати.
Аналіз
Найпростіший тип аналізу, який можна виконати за допомогою Slither, — це синтаксичний аналіз.
Синтаксичний аналіз
Slither може переміщуватися різними компонентами коду та їхніми представленнями, щоб знаходити невідповідності й недоліки, використовуючи підхід, подібний до зіставлення зі зразком.
Наприклад, наведені нижче детектори шукають проблеми, пов'язані із синтаксисом:
-
Затінення змінної стану (opens in a new tab): ітерує по всіх змінних стану та перевіряє, чи не затіняє якась із них змінну з успадкованого контракту (state.py#L51-L62 (opens in a new tab))
-
Неправильний інтерфейс ERC20 (opens in a new tab): пошук неправильних сигнатур функцій ERC20 (incorrect_erc20_interface.py#L34-L55 (opens in a new tab))
Семантичний аналіз
На відміну від синтаксичного аналізу, семантичний аналіз заглиблюється і аналізує "значення" коду. Ця родина включає деякі широкі типи аналізів. Вони дають потужніші та корисніші результати, але їх також складніше писати.
Семантичний аналіз використовується для найсучасніших виявлень вразливостей.
Аналіз залежностей даних
Кажуть, що змінна variable_a залежить від даних змінної variable_b, якщо існує шлях, у якому на значення variable_a впливає variable_b.
У наведеному нижче коді variable_a залежить від variable_b:
1// ...2variable_a = variable_b + 1;Slither має вбудовані можливості залежності даних (opens in a new tab) завдяки своєму проміжному представленню (обговорюється в наступному розділі).
Приклад використання залежності даних можна знайти в детекторі небезпечної суворої рівності (opens in a new tab). Тут Slither шукатиме порівняння суворої рівності з небезпечним значенням (incorrect_strict_equality.py#L86-L87 (opens in a new tab)) і повідомить користувача, що слід використовувати >= або <= замість ==, щоб не дати зловмиснику заблокувати контракт. Крім іншого, детектор вважатиме небезпечним значення, що повертається викликом balanceOf(address) (incorrect_strict_equality.py#L63-L64 (opens in a new tab)), і використовуватиме механізм залежності даних для відстеження його використання.
Обчислення з нерухомою точкою
Якщо ваш аналіз проходить через CFG і слідує за ребрами, ви, ймовірно, побачите вже відвідані вузли. Наприклад, якщо цикл представлений, як показано нижче:
1for(uint i; i < range; ++){2 variable_a += 13}Ваш аналіз повинен знати, коли зупинитися. Тут є дві основні стратегії: (1) ітерувати кожен вузол скінченну кількість разів, (2) обчислити так звану нерухому точку (fixpoint). Нерухома точка по суті означає, що аналіз цього вузла більше не дає жодної значущої інформації.
Приклад використання нерухомої точки можна знайти в детекторах повторного входу: Slither досліджує вузли та шукає зовнішні виклики, запис у сховище та читання з нього. Щойно він досягає нерухомої точки (reentrancy.py#L125-L131 (opens in a new tab)), він припиняє дослідження та аналізує результати, щоб побачити, чи є повторний вхід, за допомогою різних шаблонів повторного входу (reentrancy_benign.py (opens in a new tab), reentrancy_read_before_write.py (opens in a new tab), reentrancy_eth.py (opens in a new tab)).
Написання аналізів з використанням ефективного обчислення нерухомої точки вимагає доброго розуміння того, як аналіз поширює свою інформацію.
Проміжне представлення
Проміжне представлення (IR) — це мова, яка має бути більш придатною для статичного аналізу, ніж вихідна. Slither перетворює Solidity у власне проміжне представлення: SlithIR (opens in a new tab).
Розуміння SlithIR не є необхідним, якщо ви хочете писати лише базові перевірки. Однак, це стане в пригоді, якщо ви плануєте писати розширений семантичний аналіз. Принтери SlithIR (opens in a new tab) та SSA (opens in a new tab) допоможуть вам зрозуміти, як перекладається код.
Основи API
Slither має API, який дозволяє досліджувати основні атрибути контракту та його функції.
Щоб завантажити кодову базу:
1from slither import Slither2slither = Slither('/path/to/project')3Дослідження контрактів і функцій
Об'єкт 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): повертає функцію за її сигнатуроюget_modifier_from_signature (str): повертає модифікатор за його сигнатуроюget_state_variable_from_name (str): повертає змінну стану за її назвою
Об’єкт Function або Modifier має:
name (str): назва функціїcontract (contract): контракт, у якому оголошено функціюnodes (list(Node)): список вузлів, що складають CFG функції/модифікатораentry_point (Node): точка входу в CFGvariables_read (list(Variable)): список прочитаних зміннихvariables_written (list(Variable)): список записаних зміннихstate_variables_read (list(StateVariable)): список прочитаних змінних стану (підмножинаvariables_read)state_variables_written (list(StateVariable)): список записаних змінних стану (підмножинаvariables_written)
Останні оновлення сторінки: 3 лютого 2025 р.

