Security review Solana-програми — це систематичний процес перевірки смарт-контракту на вразливості до того, як він опиниться в mainnet-beta. Нижче наведено практичний підхід, який можна застосувати самостійно або як підготовку до зовнішнього аудиту. Методика не замінює формальний аудит, але суттєво знижує кількість критичних знахідок і скорочує час роботи залучених аудиторів.
Чеклист для самостійного огляду
Самостійний огляд охоплює ті вразливості, які найчастіше зустрічаються в Solana-програмах. Перевірку варто проводити після завершення функціональної розробки, але до інтеграційного тестування.
Перевірка access control
Access control у Solana-програмах реалізується переважно через перевірку signer-прапорця та правильність деривації PDA (Program Derived Address — адреса, яка генерується детерміновано з програми та набору сидів). Помилки в цій області зазвичай призводять до повного виведення коштів.
- Перевірка
signerна кожному entry point. Якщо інструкція змінює стан або переказує кошти, переконайтеся, що обліковий запис ініціатора має прапорецьsigner = true. У Anchor це забезпечується макросом#[account(mut)]разом із типомSigner, але ручні перевірки черезctx.accounts.authority.is_signerтеж потребують контролю. - Коректність сидів PDA. Кожен PDA має створюватися з фіксованим набором сидів. Якщо сид містить змінну частину (наприклад,
mint_key), перевірте, що атакувальник не може підставити довільне значення та отримати контроль над чужим PDA. - Відсутність перевірки
has_oneтам, де вона потрібна. Якщо обліковий запис посилається на інший обліковий запис (наприклад,vault.mint == mint.key()), ця залежність має бути явно задекларована черезhas_oneабо перевірена вручну. Відсутність такої перевірки дозволяє підставити чужийmint. - Authority revocation. Перевірте, чи програма не залишає постійного
authorityнад критичними обліковими записами після завершення ініціалізації. Якщоauthorityможна скинути вNoneабо передати multisig — це окрема тема, яка розкрита в матеріалі про Multisig для Solana-програми.
Аналіз математичних операцій
Арифметичні помилки в Rust не викликають паніки за замовчуванням у release-режимі, що робить їх особливо небезпечними для фінансових програм.
- Переповнення та антипереповнення. Усі операції додавання, віднімання та множення над кількісними величинами (баланси, кількість токенів, ціни) мають використовувати методи з явною перевіркою:
checked_add,checked_sub,checked_mul. Звичайні оператори+,-,*у release-білдах обертаються за модулем двійкового представлення без сповіщення. - Точність дробових обчислень. Якщо програма працює з цінами або курсами, перевірте порядок операцій. Ділення до множення призводить до втрати точності на користь однієї зі сторін. Фіксована точка (fixed-point arithmetic) має застосовуватися послідовно.
- Slippage та мінімальні пороги. У swap-операціях та ліквідаціях перевірте наявність параметрів
min_outабоmax_inта їхню обов'язковість. Відсутність slippage-захисту дозволяє MEV-ботам витягувати значення з транзакцій користувачів. - Округлення в інтересах протоколу. Усі випадки округлення мають відбуватися на користь протоколу або бути симетричними. Перевірте кожне ділення — хто отримує залишок.
Додаткові пункти чеклисту, які не залежать від конкретної доменної області:
- Реініціалізація облікових записів. Перевірте, що обліковий запис не можна повторно ініціалізувати після закриття. Anchor додає
init-обмеження автоматично, але ручне створення облікових записів черезcreate_accountпотребує явної перевірки. - Duplicate mutable accounts. Одна транзакція не може передати один і той самий обліковий запис як
mutдвічі. Але якщо програма приймає два різних облікові записи, які вказують на одні й ті самі дані (наприклад, через різні PDAs з однаковими сидами), це може призвести до подвійного списання. - CPI-виклики. Кожен крос-програмний виклик має перевіряти, що цільова програма є саме тією, яку ви очікуєте. Підроблений CPI до шкідливої програми з правильним інтерфейсом — реальний вектор атаки.
- Розмір даних облікового запису. Якщо програма читає або записує дані за індексом, перевірте, що розмір облікового запису відповідає очікуваній структурі. Зміна розміру без міграції призводить до пошкодження даних.
Використання статичних аналізаторів
Статичні аналізатори не знаходять логічних вразливостей, але автоматизують рутинні перевірки та виявляють класи помилок, які важко помітити при ручному огляді.
Cargo clippy. Базовий лінтер Rust. У контексті Solana-програм зверніть увагу на попередження категорій clippy::arithmetic_side_effects та clippy::integer_arithmetic. Увімкніть їх явно у clippy.toml або через #![deny(clippy::arithmetic_side_effects)] на рівні крейту. Це перетворює неявну арифметику на помилку компіляції.
Cargo audit. Сканує залежності за базою даних RustSec. Запускайте після кожного оновлення Cargo.lock. Звертайте увагу не лише на критичні, а й на помірні (moderate) вразливості, якщо вони стосуються криптографічних крейтів або серіалізації.
Anchor verify. Команда anchor verify порівнює байт-код розгорнутої програми з локально зібраним. Це не виявляє вразливостей, але гарантує, що розгорнутий код відповідає тому, що проходить review. Без цієї перевірки весь огляд може бути проведений над кодом, який не відповідає mainnet-версії.
Спеціалізовані інструменти. Існують інструменти аналізу байт-коду Solana-програм, які шукають відомі патерни вразливостей (наприклад, відсутність перевірки signer у CPI). Перед використанням такого інструменту перевірте його актуальність: версія Solana runtime, яку він підтримує, має збігатися з версією вашого deployed-коду. Застарілий аналізатор може давати хибнонегативні результати.
Обмеження статичних аналізаторів: вони не розуміють бізнес-логіку протоколу. Аналізатор не скаже, що withdraw дозволяє вивести більше, ніж депозит, якщо арифметика формально коректна, але логіка обчислення ліміту хибна.
Коли залучати зовнішніх аудиторів
Зовнішній аудит обов'язковий, якщо програма керує коштами користувачів, має привілейовані операції або інтегрується з DeFi-протоколами як стратегія чи router. Самоогляд не замінює незалежну перевірку через когнітивне зациклення: розробник схильний перевіряти те, як програма має працювати, а не те, як вона може зламатися.
Критерії готовності до аудиту:
- Функціональна заморожена версія. Код не повинен змінюватися під час аудиту. Кожна зміна після старту аудиту зводить нанівець частину перевіреної площі та збільшує ризик пропуску вразливості.
- Повний набір тестів. Аудитори працюють ефективніше, коли можуть запустити тести та переконатися в базовій коректності. Тести також фіксують очікувану поведінку, що допомагає відрізнити баг від фічі.
- Специфікація протоколу. Документ, який описує, що програма робить, а не як. Аудитор без специфікації перевіряє код на відповідність власним припущенням, що менш надійно.
- Пройдений самоогляд за чеклистом вище. Аудит, який починається з виявлення відсутніх
checked_*викликів, витрачає бюджет на рутину замість пошуку нетривіальних вразливостей.
Щодо кількості аудиторів: для некритичних утилітарних програм достатньо одного аудиту. Для протоколів з TVL (Total Value Locked) рекомендується два незалежні аудити від різних компаній — це знижує ризик пропуску вразливості через сліпу пляму однієї команди.
План відкату: якщо аудит виявив критичну вразливість після розгортання, але програма ще не використовується в production, виправте код, проведіть повторний самоогляд змінених ділянок та запросіть аудитора на перевірку патчу. Якщо програма вже в mainnet-beta і містить кошти — оцініть можливість тимчасового призупинення через upgrade authority (якщо вона збережена) перед застосуванням виправлення.
Документування результатів review
Результати security review мають бути зафіксовані в письмовому вигляді. Це необхідно для внутрішнього контролю, комунікації з аудиторами та відповідальності перед користувачами протоколу.
Формат звіту про самоогляд:
| Розділ | Зміст |
|---|---|
| Метадані | Хеш коміту програми, гілка, дата проведення, хто проводив |
| Обсяг перевірки | Перелік інструкцій та модулів, що увійшли до review; що навмисно виключено |
| Методологія | Чеклист, інструменти, версії аналізаторів |
| Знахідки | Кожна знахідка з: описом, розташуванням у коді, критичністю (Critical / High / Medium / Low / Informational), кроками відтворення, наслідками, запропонованим виправленням |
| Статус виправлень | Для кожної знахідки — статус (Fixed / Accepted / Won't Fix) із обґрунтуванням |
| Залишкові ризики | Що не було перевірено та чому; які класи вразливостей виходять за межі самоогляду |
Критичність визначається за комбінацією двох факторів: ймовірність експлуатації та масштаб наслідків. Вразливість, яка вимагає складної багатокрокової атаки та впливає лише на одного користувача — це Medium. Вразливість, що дозволяє будь-кому вивести всі кошти протоколу в одній транзакції — Critical.
Звіт зберігайте у репозиторії проєкту (зазвичай у директорії docs/security/) разом із кодом, до якого він стосується. Якщо код змінюється після review, звіт має бути оновлений або доповнений додатковим записом про зміни.
Для зовнішніх аудитів зберігайте оригінальний звіт аудиторської компанії без змін. Додатково можна створити внутрішній документ із маппінгом: яка знахідка аудиту яким комітом виправлена. Це дозволяє швидко відповісти на запити спільноти щодо статусу виправлень без розкриття технічних деталей вразливості до стабілізації патчу.