ХЕЛЛОУИН СЕЙЛ-10%
До 31 октября-10%
ХЕЛЛОУИН СЕЙЛ-10%
До 31 октября-10%
ХЕЛЛОУИН СЕЙЛ-10%
До 31 октября-10%
ХЕЛЛОУИН СЕЙЛ-10%
До 31 октября-10%
ХЕЛЛОУИН СЕЙЛ-10%
До 31 октября-10%
ХЕЛЛОУИН СЕЙЛ-10%
До 31 октября-10%

Фаззинг смарт-контрактов с помощью Echidna

Дмитрий Н. из Oxorio сравнивает методы тестирования смарт-контрактов и объясняет, как фаззер Echidna проверяет инварианты и находит уязвимости.
Дмитрий Н., Oxorio

Материал вышел в марте 2024 года. Возможности инструментов описаны на тот момент.

Что такое фаззинг

Фаззинг очень хорошо подходит для анализа безопасности смарт-контрактов. Пользуются им пока далеко не все, хотя он довольно простой и эффективный. Фаззинг позволяет находить уязвимости, которые трудно найти другими инструментами.

Echidna как раз инструмент для фаззинга.

Четыре техники тестирования

1. Юнит-тестирование (unit testing). Этот метод знаком практически всем разработчикам. Он давно стал мейнстримом.

2. Ручной анализ (manual analysis). Этим мы занимаемся в Oxorio: нам дают код, мы его читаем и ищем ошибки, опираясь на свои знания и опыт.

3. Полностью автоматический анализ (fully automated analysis). Для кода на Solidity этот метод реализует Slither: запускаешь его на коде, и он выдаёт ошибки.

4. Полуавтоматический анализ (semi-automated analysis). К нему относится фаззинг.

У каждого метода есть достоинства и недостатки. Юнит-тесты ловят ошибки, когда поведение кода отклоняется от набора кейсов, которые мы считаем правильными. Если мы что-то поменяли в коде и какой-то тест не прошёл, значит, появилась ошибка.

Ручной анализ трудоёмкий: нужна команда, которая будет долго сидеть, читать код и искать в нём ошибки. Это дорого и не всегда эффективно. Есть и ещё один существенный минус: код, как правило, анализируют на конкретном коммите, а код постоянно меняется. Даже если мы убедились, что в конкретной версии нет ошибок и уязвимостей, после следующего коммита с небольшими правками мы уже не можем этого гарантировать. Поэтому ручной анализ подходит для фиксированной версии кода, которую не собираются обновлять.

Минус автоматического анализа в том, что он часто выдаёт ложноположительные и ложноотрицательные результаты. Что-то он помогает найти, но, как правило, не очень эффективно, особенно если уязвимость сложная.

Последний метод, фаззинг, называют полуавтоматическим, потому что просто взять код и запустить на нём фаззер не получится, кое-что придётся дописать самим. Для фаззера, как и для юнит-тестов, мы задаём набор условий, или инвариантов, которые описывают, как должен работать код, и фаззер проверяет код на соответствие этим условиям. В этом смысле он похож на юнит-тестирование.

Как работает фаззер

Устроен фаззер очень просто. Он берёт наш код с набором публичных методов, public или external. У методов есть параметры, и фаззер в случайном порядке вызывает функции, подставляя в них случайные значения. После каждого вызова он проверяет тесты и свойства (properties), которые мы написали: выполняются ли они и не нарушен ли какой-нибудь инвариант, то есть заранее заданное свойство системы. Если при каком-то случайном наборе параметров свойство нарушается, значит, в коде есть ошибка.

Что можно найти фаззингом

Фаззингом можно обнаружить, например, некорректный контроль доступа. У контракта есть владелец, администратор, и только он может вызывать определённый набор функций. Предполагается, что случайный пользователь или третье лицо не может стать владельцем и выполнять привилегированные функции.

Юнит-тестами такие проблемы удаётся найти далеко не всегда, а Echidna порой находит то, с чем юнит-тесты не справляются. Другой пример касается некорректного состояния контракта. Когда контракт поставлен на паузу, переводы и другие операции с токенами должны быть невозможны. Это можно задать как свойство: если контракт на паузе, пересылать токены нельзя. Если Echidna найдёт набор вызовов, который позволяет переслать токены при остановленном контракте, мы обнаружим уязвимость.

Ещё один пример связан с некорректной арифметикой, когда пользователь может получить бесконечное количество токенов, вызвав какие-то функции с какими-то параметрами. Когда мы пишем юнит-тесты, нам нужно примерно понимать, какие параметры для этого передать, и мы это понимаем. Но если система большая и сложная, а операции с токенами раскиданы по большой кодовой базе и множеству контрактов, Echidna преодолевает эту сложность случайным поиском. Она перебирает все возможные методы со всеми возможными параметрами и каждый раз проверяет, что баланс пользователя не превышает заданного нами разумного предела.

Если свойство нарушится, Echidna покажет, какой набор вызовов к этому привёл.

Два типа инвариантов

Инварианты, или свойства, бывают двух основных типов. Им соответствуют два подхода к тестированию с помощью фаззера.

1. Простые инварианты на уровне функции (function-level invariants). Например, для операции сложения должна выполняться коммутативность: a + b = b + a. Фаззер за секунды выявит, если код нарушает такое свойство. Так мы тестируем код на уровне отдельной функции и проверяем её работоспособность.

2. Инварианты системного уровня (system-level invariants), которые зависят уже от всей системы. Здесь понадобится задеплоенный контракт: протестировать отдельный компонент или функцию так не получится.

Internal- и external-тестирование

Допустим, мы хотим протестировать с помощью Echidna токен. Мы наследуемся от контракта токена и в наследнике пишем одно свойство, которое проверяем, например «баланс пользователя всегда не больше total supply». Запускаем тест в Echidna. Это internal-тестирование: благодаря наследованию у нас есть доступ к internal-функциям контракта.

В простых случаях это работает. Но если контрактов несколько, они должны быть задеплоены и ссылаются друг на друга по адресам, то такой подход уже не сработает: унаследоваться и вызывать internal-функции не получится. Протестировать такую систему можно только external-тестированием. В конструкторе тестового контракта-обёртки мы разворачиваем набор контрактов, затем вызываем их public- и external-функции, ведь только они нам и доступны, и после каждого вызова проверяем инварианты.

Все слайды выступления собраны в презентации.

Поделиться
Интересно погрузиться в крипту?
Поможем выбрать направление!