Аналіз вразливостей компілятора Solidity та стратегії реагування
Компіллятор є однією з основних складових сучасних комп'ютерних систем. Це комп'ютерна програма, яка виконує функцію перетворення вихідного коду на високорівневих мовах програмування, зрозумілій та зручній для написання людьми, в інструкційний код, який може виконуватись процесором комп'ютера або віртуальною машиною байт-коду.
Більшість розробників та фахівців з безпеки зазвичай зосереджують увагу на безпеці коду програмних додатків, але можуть ігнорувати безпеку самого компілятора. Насправді компілятор, як комп'ютерна програма, також може містити вразливості безпеки, а вразливості безпеки, що виникають через компілятор, можуть у певних випадках призвести до серйозних ризиків безпеки. Наприклад, під час компіляції та аналізу виконання коду Javascript на стороні клієнта браузер може піддатися атаці через вразливість у движку аналізу Javascript, що дозволяє зловмиснику виконати віддалений код при відвідуванні користувачем шкідливого веб-сайту, зрештою отримавши контроль над браузером жертви або навіть над операційною системою. Дослідження показують, що помилки компілятора Clang C++ також можуть призвести до серйозних наслідків, таких як віддалене виконання коду.
Компілятор Solidity не є винятком, відповідно до попередження про безпеку команди розробників Solidity, у кількох різних версіях компілятора Solidity існують вразливості.
Уразливість компілятора Solidity
Роль компілятора Solidity полягає в перетворенні коду смарт-контрактів, написаного розробниками, в код інструкцій Ethereum Virtual Machine (EVM). Ці інструкції EVM упаковуються в транзакції та завантажуються в Ethereum, а в кінцевому підсумку виконуються EVM.
Необхідно відрізняти вразливості компілятора Solidity від вразливостей самого EVM. Вразливості EVM - це безпекові проблеми, які виникають під час виконання інструкцій віртуальної машини. Оскільки зловмисники можуть завантажувати будь-який код в Ethereum, цей код в кінцевому підсумку буде виконуватись у кожній програмі клієнта Ethereum P2P. Якщо EVM має вразливості безпеки, це вплине на всю мережу Ethereum, що може призвести до відмови в обслуговуванні (DoS) або навіть до повного контролю зловмисниками над усім ланцюгом. Однак через те, що сам EVM спроектований відносно просто і його основний код не підлягає частим оновленням, ймовірність виникнення вказаних проблем є відносно низькою.
Уразливість компілятора Solidity стосується вразливостей, які виникають під час перетворення Solidity в код EVM. На відміну від ситуацій, коли браузер компілює та виконує Javascript на комп'ютері користувача, процес компіляції Solidity відбувається лише на комп'ютері розробника смарт-контрактів і не виконується в мережі Ethereum. Тому уразливості компілятора Solidity безпосередньо не впливають на саму мережу Ethereum.
Однією з основних загроз вразливостей компілятора Solidity є те, що це може призвести до того, що згенерований код EVM не відповідатиме очікуванням розробників смарт-контрактів. Оскільки смарт-контракти на Ethereum зазвичай пов'язані з криптовалютними активами користувачів, будь-які помилки, які виникають через компілятор, можуть призвести до втрати активів користувачів і мати серйозні наслідки.
Розробники та аудитори контрактів можуть зосередитися на питаннях реалізації логіки коду контракту, а також на проблемах безпеки на рівні Solidity, таких як повторний вхід, переповнення цілих чисел тощо. Щодо вразливостей компілятора Solidity, лише шляхом аудиту логіки вихідного коду контракту важко виявити їх. Необхідно поєднати аналіз конкретної версії компілятора та специфічних кодових шаблонів, щоб визначити, чи підлягає смарт-контракт впливу вразливостей компілятора.
Приклад вразливості компілятора Solidity
Наступні кілька реальних випадків вразливостей компілятора Solidity демонструють конкретні форми, причини та загрози вразливостей компілятора Solidity.
SOL-2016-9 HighOrderByteCleanStorage
Ця уразливість існує в більш ранніх версіях компілятора Solidity (>=0.1.6 <0.4.4).
Розгляньте наступний код:
солідність
контракт C {
uint32 a = 0x12345678;
uint16 b = 0x1234;
функція f() публічна {
a = a + 1;
}
функція run() публічний перегляд повертає (uint16) {
повернути b;
}
}
У змінній storage b не було внесено жодних змін, тому функція run() повинна повертати значення за замовчуванням 0. Але насправді в коді, згенерованому вразливими версіями компілятора, run() поверне 1.
Без розуміння вразливостей компілятора, звичайним розробникам важко виявити помилки в наведеному коді простим переглядом коду. Наведений код є лише простим прикладом, тому не завдасть особливо серйозної шкоди. Але якщо змінна b використовується для перевірки прав доступу, обліку активів тощо, така невідповідність очікуванням може призвести до дуже серйозних наслідків.
Причина виникнення вищезгаданої аномалії полягає в тому, що EVM використовує стекову віртуальну машину, де кожен елемент стека має розмір 32 байти (, тобто розмір змінної uint256 ). З іншого боку, кожен слот у базовому сховищі storage також має розмір 32 байти. Однак на рівні мови Solidity підтримуються такі типи даних, як uint32 та інші, менші за 32 байти, і компілятор, обробляючи змінні такого типу, повинен належним чином очищати їх старші біти (clean up) для забезпечення коректності даних. У наведеній ситуації, коли при додаванні виникає переповнення цілих чисел, компілятор не правильно очищає старші біти результату, що призводить до запису 1 біта старшого біту в storage після переповнення, в результаті чого змінна b перекриває значення змінної a, змінюючи значення змінної b на 1.
SOL-2022-4 InlineAssemblyMemoryПобічні ефекти
Ця уразливість існує в компіляторах версій >=0.8.13 <0.8.15. Компілятор Solidity під час перетворення мови Solidity у код EVM виконує не лише простий переклад. Він також проводить глибокий аналіз контролю потоку та даних, реалізуючи різні процеси оптимізації компіляції для зменшення обсягу згенерованого коду та оптимізації споживання газу під час виконання. Такі операції з оптимізації є звичайними для компіляторів різних високорівневих мов, але через складність ситуацій, які потрібно враховувати, можуть виникати помилки або уразливості безпеки.
Розгляньте наступний код:
солідність
контракт C {
функція f() публічна чиста повертає (uint256) {
збірка {
mstore(0, 0x42)
}
uint256 x;
збірка {
x := mload(0)
}
повернути x;
}
}
Вразливість наведеного коду виникає внаслідок оптимізації компіляції. У деяких випадках, якщо в функції є код, що змінює дані за адресою пам'яті 0, але в подальшому жодне місце не використовує ці дані, тоді фактично можна безпосередньо видалити код, що змінює пам'ять 0, заощаджуючи газ і не впливаючи на подальшу логіку програми.
Ця оптимізаційна стратегія сама по собі не має проблем, але в конкретній реалізації коду компілятора Solidity такі оптимізації застосовуються лише в одному блоці assembly. У наведеному прикладі, запис і доступ до пам'яті 0 знаходяться в двох різних блоках assembly, тоді як компілятор аналізує та оптимізує лише окремий блок assembly. Оскільки в першому блоці assembly після запису в пам'ять 0 немає жодної операції зчитування, то вважається, що ця інструкція запису є зайвою, і вона буде видалена, що призведе до помилки. У версії з уразливістю функція f() поверне значення 0, тоді як насправді наведених вище код має повертати правильне значення 0x42.
Ця уразливість впливає на компілятори версій >= 0.5.8 < 0.8.16. Розгляньте наступний код:
солідність
контракт C {
функція f(bytes calldata data) зовнішні чисті повертає (bytes memory) {
bytes4[1] пам'ять a = [bytes4(дані)];
return abi.encode(a);
}
}
У нормальних умовах вищезазначений код має повертати змінну a, яка повинна бути "aaaa". Але в уразливій версії він поверне порожній рядок "".
Причиною вразливості є те, що Solidity неправильно виконав операцію abi.encode над масивом типу calldata, помилково очистивши деякі дані, що призвело до зміни сусідніх інших даних, що викликало несумісність даних після кодування та декодування.
Слід зазначити, що при виконанні external call та emit event, Solidity неявно виконує abi.encode для параметрів, тому ймовірність виникнення наведеного вище вразливого коду буде вищою, ніж можна було б подумати.
Ця вразливість була адаптована в відомій безпековій конкуренції 0ctf 2022 як задача з блокчейну, яка демонструє вплив вразливостей компіляторів на смарт-контракти в реальних сценаріях розробки.
Рекомендації щодо безпеки
Щодо загроз від вразливостей компілятора Solidity та заходів реагування, можна дати такі рекомендації:
Для розробників:
Використовуйте новішу версію компілятора Solidity. Хоча нові версії можуть впроваджувати нові проблеми безпеки, відомі проблеми безпеки зазвичай менш численні, ніж у старих версіях.
Поліпшити юніт-тестування. Більшість помилок на рівні компілятора призводять до того, що результати виконання коду не відповідають очікуванням. Ці проблеми важко виявити під час перевірки коду, але їх легко виявити на етапі тестування. Тому підвищення покриття коду може максимально зменшити ймовірність виникнення таких проблем.
Намагайтеся уникати використання вбудованого асемблера, складних операцій кодування та декодування ABI для багатовимірних масивів і складних структур, уникайте сліпого використання нових можливостей мови та експериментальних функцій без чіткої потреби. Більшість вразливостей пов'язані з вбудованим асемблером, операціями кодування ABI тощо. Компілятори частіше за все мають помилки при обробці складних мовних можливостей. З іншого боку, розробники також можуть потрапити в пастку, використовуючи нові можливості, що призводить до проблем безпеки.
До безпеки:
При проведенні аудиту безпеки коду Solidity не слід ігнорувати ризики безпеки, які можуть бути внесені компілятором Solidity. Відповідний пункт перевірки в Smart Contract Weakness Classification(SWC) - це SWC-102: Застаріла версія компілятора.
У внутрішньому процесі безпечної розробки закликати команду розробників оновити версію компілятора Solidity та розглянути можливість впровадження автоматичної перевірки версії компілятора в процес CI/CD.
Але не варто надто турбуватися про вразливості компілятора, більшість вразливостей компілятора спрацьовує лише в специфічних кодових патернах. Контракти, скомпільовані за допомогою вразливих версій компілятора, не обов'язково мають безпекові ризики; необхідно оцінити фактичний вплив на безпеку відповідно до конкретної ситуації проекту.
Деякі корисні ресурси:
Регулярні сповіщення про безпеку від команди Solidity:
Список помилок, що регулярно оновлюється в офіційному репозиторії Solidity:
Список помилок компілятора для всіх версій:
Code у верхньому правому куті трикутний знак оклику може вказувати на наявність вразливостей у поточній версії компілятора.
Підсумок
У цьому документі розглядається концепція вразливості компілятора Solidity, аналізуються можливі ризики безпеки, які вона може спричинити в середовищі розробки Ethereum, а також надаються практичні рекомендації для розробників і фахівців з безпеки. Хоча вразливості компілятора зустрічаються нечасто, їхні наслідки можуть бути серйозними, тому на них слід звернути увагу розробникам і фахівцям з безпеки.
Ця сторінка може містити контент третіх осіб, який надається виключно в інформаційних цілях (не в якості запевнень/гарантій) і не повинен розглядатися як схвалення його поглядів компанією Gate, а також як фінансова або професійна консультація. Див. Застереження для отримання детальної інформації.
22 лайків
Нагородити
22
7
Репост
Поділіться
Прокоментувати
0/400
fork_in_the_road
· 07-19 10:48
Компилятор також може мати вразливості? Чому хвилюватися!
Переглянути оригіналвідповісти на0
gas_fee_therapist
· 07-19 08:57
Цей недолік написаний досить зрозуміло~
Переглянути оригіналвідповісти на0
GateUser-75ee51e7
· 07-18 18:19
Краще зловити баг, ніж займатися Майнінгом.
Переглянути оригіналвідповісти на0
MetaverseHobo
· 07-16 19:40
Компилятор тоже провалился? Ця ситуація викликала паніку.
Переглянути оригіналвідповісти на0
DeepRabbitHole
· 07-16 19:39
Жахливо, стільки вразливостей, а ви все ще граєте в закриту позицію?
Переглянути оригіналвідповісти на0
GasWaster
· 07-16 19:37
Є вразливості і в компіляторах? yyds
Переглянути оригіналвідповісти на0
ForkTongue
· 07-16 19:23
Карткові вразливості для виведення грошей - це найбільш надійний варіант.
Аналіз вразливостей компілятора Solidity: вплив, приклади та стратегії реагування
Аналіз вразливостей компілятора Solidity та стратегії реагування
Компіллятор є однією з основних складових сучасних комп'ютерних систем. Це комп'ютерна програма, яка виконує функцію перетворення вихідного коду на високорівневих мовах програмування, зрозумілій та зручній для написання людьми, в інструкційний код, який може виконуватись процесором комп'ютера або віртуальною машиною байт-коду.
Більшість розробників та фахівців з безпеки зазвичай зосереджують увагу на безпеці коду програмних додатків, але можуть ігнорувати безпеку самого компілятора. Насправді компілятор, як комп'ютерна програма, також може містити вразливості безпеки, а вразливості безпеки, що виникають через компілятор, можуть у певних випадках призвести до серйозних ризиків безпеки. Наприклад, під час компіляції та аналізу виконання коду Javascript на стороні клієнта браузер може піддатися атаці через вразливість у движку аналізу Javascript, що дозволяє зловмиснику виконати віддалений код при відвідуванні користувачем шкідливого веб-сайту, зрештою отримавши контроль над браузером жертви або навіть над операційною системою. Дослідження показують, що помилки компілятора Clang C++ також можуть призвести до серйозних наслідків, таких як віддалене виконання коду.
Компілятор Solidity не є винятком, відповідно до попередження про безпеку команди розробників Solidity, у кількох різних версіях компілятора Solidity існують вразливості.
Уразливість компілятора Solidity
Роль компілятора Solidity полягає в перетворенні коду смарт-контрактів, написаного розробниками, в код інструкцій Ethereum Virtual Machine (EVM). Ці інструкції EVM упаковуються в транзакції та завантажуються в Ethereum, а в кінцевому підсумку виконуються EVM.
Необхідно відрізняти вразливості компілятора Solidity від вразливостей самого EVM. Вразливості EVM - це безпекові проблеми, які виникають під час виконання інструкцій віртуальної машини. Оскільки зловмисники можуть завантажувати будь-який код в Ethereum, цей код в кінцевому підсумку буде виконуватись у кожній програмі клієнта Ethereum P2P. Якщо EVM має вразливості безпеки, це вплине на всю мережу Ethereum, що може призвести до відмови в обслуговуванні (DoS) або навіть до повного контролю зловмисниками над усім ланцюгом. Однак через те, що сам EVM спроектований відносно просто і його основний код не підлягає частим оновленням, ймовірність виникнення вказаних проблем є відносно низькою.
Уразливість компілятора Solidity стосується вразливостей, які виникають під час перетворення Solidity в код EVM. На відміну від ситуацій, коли браузер компілює та виконує Javascript на комп'ютері користувача, процес компіляції Solidity відбувається лише на комп'ютері розробника смарт-контрактів і не виконується в мережі Ethereum. Тому уразливості компілятора Solidity безпосередньо не впливають на саму мережу Ethereum.
Однією з основних загроз вразливостей компілятора Solidity є те, що це може призвести до того, що згенерований код EVM не відповідатиме очікуванням розробників смарт-контрактів. Оскільки смарт-контракти на Ethereum зазвичай пов'язані з криптовалютними активами користувачів, будь-які помилки, які виникають через компілятор, можуть призвести до втрати активів користувачів і мати серйозні наслідки.
Розробники та аудитори контрактів можуть зосередитися на питаннях реалізації логіки коду контракту, а також на проблемах безпеки на рівні Solidity, таких як повторний вхід, переповнення цілих чисел тощо. Щодо вразливостей компілятора Solidity, лише шляхом аудиту логіки вихідного коду контракту важко виявити їх. Необхідно поєднати аналіз конкретної версії компілятора та специфічних кодових шаблонів, щоб визначити, чи підлягає смарт-контракт впливу вразливостей компілятора.
Приклад вразливості компілятора Solidity
Наступні кілька реальних випадків вразливостей компілятора Solidity демонструють конкретні форми, причини та загрози вразливостей компілятора Solidity.
SOL-2016-9 HighOrderByteCleanStorage
Ця уразливість існує в більш ранніх версіях компілятора Solidity (>=0.1.6 <0.4.4).
Розгляньте наступний код:
солідність контракт C { uint32 a = 0x12345678; uint16 b = 0x1234;
функція f() публічна { a = a + 1; }
функція run() публічний перегляд повертає (uint16) { повернути b; } }
У змінній storage b не було внесено жодних змін, тому функція run() повинна повертати значення за замовчуванням 0. Але насправді в коді, згенерованому вразливими версіями компілятора, run() поверне 1.
Без розуміння вразливостей компілятора, звичайним розробникам важко виявити помилки в наведеному коді простим переглядом коду. Наведений код є лише простим прикладом, тому не завдасть особливо серйозної шкоди. Але якщо змінна b використовується для перевірки прав доступу, обліку активів тощо, така невідповідність очікуванням може призвести до дуже серйозних наслідків.
Причина виникнення вищезгаданої аномалії полягає в тому, що EVM використовує стекову віртуальну машину, де кожен елемент стека має розмір 32 байти (, тобто розмір змінної uint256 ). З іншого боку, кожен слот у базовому сховищі storage також має розмір 32 байти. Однак на рівні мови Solidity підтримуються такі типи даних, як uint32 та інші, менші за 32 байти, і компілятор, обробляючи змінні такого типу, повинен належним чином очищати їх старші біти (clean up) для забезпечення коректності даних. У наведеній ситуації, коли при додаванні виникає переповнення цілих чисел, компілятор не правильно очищає старші біти результату, що призводить до запису 1 біта старшого біту в storage після переповнення, в результаті чого змінна b перекриває значення змінної a, змінюючи значення змінної b на 1.
SOL-2022-4 InlineAssemblyMemoryПобічні ефекти
Ця уразливість існує в компіляторах версій >=0.8.13 <0.8.15. Компілятор Solidity під час перетворення мови Solidity у код EVM виконує не лише простий переклад. Він також проводить глибокий аналіз контролю потоку та даних, реалізуючи різні процеси оптимізації компіляції для зменшення обсягу згенерованого коду та оптимізації споживання газу під час виконання. Такі операції з оптимізації є звичайними для компіляторів різних високорівневих мов, але через складність ситуацій, які потрібно враховувати, можуть виникати помилки або уразливості безпеки.
Розгляньте наступний код:
солідність контракт C { функція f() публічна чиста повертає (uint256) { збірка { mstore(0, 0x42) } uint256 x; збірка { x := mload(0) } повернути x; } }
Вразливість наведеного коду виникає внаслідок оптимізації компіляції. У деяких випадках, якщо в функції є код, що змінює дані за адресою пам'яті 0, але в подальшому жодне місце не використовує ці дані, тоді фактично можна безпосередньо видалити код, що змінює пам'ять 0, заощаджуючи газ і не впливаючи на подальшу логіку програми.
Ця оптимізаційна стратегія сама по собі не має проблем, але в конкретній реалізації коду компілятора Solidity такі оптимізації застосовуються лише в одному блоці assembly. У наведеному прикладі, запис і доступ до пам'яті 0 знаходяться в двох різних блоках assembly, тоді як компілятор аналізує та оптимізує лише окремий блок assembly. Оскільки в першому блоці assembly після запису в пам'ять 0 немає жодної операції зчитування, то вважається, що ця інструкція запису є зайвою, і вона буде видалена, що призведе до помилки. У версії з уразливістю функція f() поверне значення 0, тоді як насправді наведених вище код має повертати правильне значення 0x42.
SOL-2022-6 AbiReencodingHeadOverflowWithStaticArrayCleanup
Ця уразливість впливає на компілятори версій >= 0.5.8 < 0.8.16. Розгляньте наступний код:
солідність контракт C { функція f(bytes calldata data) зовнішні чисті повертає (bytes memory) { bytes4[1] пам'ять a = [bytes4(дані)]; return abi.encode(a); } }
У нормальних умовах вищезазначений код має повертати змінну a, яка повинна бути "aaaa". Але в уразливій версії він поверне порожній рядок "".
Причиною вразливості є те, що Solidity неправильно виконав операцію abi.encode над масивом типу calldata, помилково очистивши деякі дані, що призвело до зміни сусідніх інших даних, що викликало несумісність даних після кодування та декодування.
Слід зазначити, що при виконанні external call та emit event, Solidity неявно виконує abi.encode для параметрів, тому ймовірність виникнення наведеного вище вразливого коду буде вищою, ніж можна було б подумати.
Ця вразливість була адаптована в відомій безпековій конкуренції 0ctf 2022 як задача з блокчейну, яка демонструє вплив вразливостей компіляторів на смарт-контракти в реальних сценаріях розробки.
Рекомендації щодо безпеки
Щодо загроз від вразливостей компілятора Solidity та заходів реагування, можна дати такі рекомендації:
Для розробників:
Використовуйте новішу версію компілятора Solidity. Хоча нові версії можуть впроваджувати нові проблеми безпеки, відомі проблеми безпеки зазвичай менш численні, ніж у старих версіях.
Поліпшити юніт-тестування. Більшість помилок на рівні компілятора призводять до того, що результати виконання коду не відповідають очікуванням. Ці проблеми важко виявити під час перевірки коду, але їх легко виявити на етапі тестування. Тому підвищення покриття коду може максимально зменшити ймовірність виникнення таких проблем.
Намагайтеся уникати використання вбудованого асемблера, складних операцій кодування та декодування ABI для багатовимірних масивів і складних структур, уникайте сліпого використання нових можливостей мови та експериментальних функцій без чіткої потреби. Більшість вразливостей пов'язані з вбудованим асемблером, операціями кодування ABI тощо. Компілятори частіше за все мають помилки при обробці складних мовних можливостей. З іншого боку, розробники також можуть потрапити в пастку, використовуючи нові можливості, що призводить до проблем безпеки.
До безпеки:
При проведенні аудиту безпеки коду Solidity не слід ігнорувати ризики безпеки, які можуть бути внесені компілятором Solidity. Відповідний пункт перевірки в Smart Contract Weakness Classification(SWC) - це SWC-102: Застаріла версія компілятора.
У внутрішньому процесі безпечної розробки закликати команду розробників оновити версію компілятора Solidity та розглянути можливість впровадження автоматичної перевірки версії компілятора в процес CI/CD.
Але не варто надто турбуватися про вразливості компілятора, більшість вразливостей компілятора спрацьовує лише в специфічних кодових патернах. Контракти, скомпільовані за допомогою вразливих версій компілятора, не обов'язково мають безпекові ризики; необхідно оцінити фактичний вплив на безпеку відповідно до конкретної ситуації проекту.
Деякі корисні ресурси:
Регулярні сповіщення про безпеку від команди Solidity:
Список помилок, що регулярно оновлюється в офіційному репозиторії Solidity:
Список помилок компілятора для всіх версій:
Code у верхньому правому куті трикутний знак оклику може вказувати на наявність вразливостей у поточній версії компілятора.
Підсумок
У цьому документі розглядається концепція вразливості компілятора Solidity, аналізуються можливі ризики безпеки, які вона може спричинити в середовищі розробки Ethereum, а також надаються практичні рекомендації для розробників і фахівців з безпеки. Хоча вразливості компілятора зустрічаються нечасто, їхні наслідки можуть бути серйозними, тому на них слід звернути увагу розробникам і фахівцям з безпеки.