Анализ уязвимостей компилятора Solidity и стратегии реагирования
Компилятор является одной из основных компонентов современных компьютерных систем. Это программа для компьютера, основная функция которой заключается в преобразовании исходного кода на высокоуровневом языке программирования, который легко понимать и писать человеку, в исполняемый код инструкций для низкоуровневого CPU или виртуальной машины байт-кода.
Большинство разработчиков и специалистов по безопасности обычно больше всего беспокоятся о безопасности программного кода приложений, но могут игнорировать безопасность самого компилятора. На самом деле, компилятор как программа также имеет уязвимости, и в некоторых случаях эти уязвимости могут представлять серьезные риски безопасности. Например, в процессе компиляции и анализа выполнения кода Javascript на стороне клиента браузер может подвергнуться атакам злоумышленников из-за уязвимостей в движке разбора Javascript, что может привести к удаленному выполнению кода при посещении вредоносного веб-сайта и, в конечном итоге, к контролю над браузером жертвы или даже операционной системой.
Компилятор Solidity не является исключением, в различных версиях существуют уязвимости безопасности.
Уязвимость компилятора Solidity
Роль компилятора Solidity заключается в преобразовании кода смарт-контракта, написанного разработчиками, в машинный код (EVM) для виртуальной машины Ethereum. Эти инструкции EVM упаковываются и загружаются в Ethereum через транзакции, а затем в конечном итоге интерпретируются и выполняются EVM.
Необходимо различать уязвимости компилятора Solidity и уязвимости самой EVM. Уязвимости EVM относятся к проблемам безопасности, возникающим при выполнении инструкций виртуальной машины. Поскольку злоумышленники могут загружать произвольный код в Ethereum, этот код в конечном итоге будет выполняться в каждом клиентском P2P-программе Ethereum. Если в EVM существуют уязвимости безопасности, это повлияет на всю сеть Ethereum, возможно, вызвав отказ в обслуживании (DoS) и даже приведя к полной захвату сети злоумышленниками. Однако, поскольку сама EVM спроектирована относительно просто и основной код не обновляется часто, вероятность возникновения вышеупомянутых проблем относительно низка.
Уязвимости компилятора Solidity относятся к проблемам, возникающим при преобразовании Solidity в код EVM. В отличие от браузеров, которые компилируют и выполняют Javascript на клиентских компьютерах пользователей, процесс компиляции Solidity происходит только на компьютере разработчика смарт-контрактов и не выполняется в Ethereum. Таким образом, уязвимости компилятора Solidity не оказывают непосредственного влияния на саму сеть Ethereum.
Одна из основных угроз уязвимости компилятора Solidity заключается в том, что она может привести к несоответствию сгенерированного EVM-кода ожиданиям разработчиков смарт-контрактов. Поскольку смарт-контракты на Ethereum обычно связаны с криптовалютными активами пользователей, любые ошибки смарт-контрактов, вызванные компилятором, могут привести к потерям активов пользователей и иметь серьезные последствия.
Разработчики и аудиторы контрактов могут сосредоточиться на проблемах реализации логики кода контракта, а также на вопросах безопасности на уровне Solidity, таких как повторные входы и переполнение целых чисел. Однако выявить уязвимости компилятора Solidity только с помощью аудита логики исходного кода контракта довольно сложно. Необходимо совместно анализировать конкретные версии компилятора и определенные шаблоны кода, чтобы определить, подвержен ли смарт-контракт уязвимостям компилятора.
Пример уязвимости компилятора Solidity
Ниже приведены примеры нескольких реальных уязвимостей компиляторов Solidity, демонстрирующие их конкретные формы, причины и последствия.
SOL-2016-9 HighOrderByteCleanStorage
Уязвимость существует в ранних версиях компилятора Solidity (>=0.1.6 <0.4.4).
Рассмотрим следующий код:
солидность
контракт C {
uint32 a = 0x1234;
uint32 b = 0;
функция f() публичная {
a += 1;
}
функция run() публичный просмотр возвращает (uint) {
вернуть b;
}
}
Переменная storage b не подвергалась никаким изменениям, следовательно, функция run() должна возвращать значение по умолчанию 0. Но на самом деле, в сгенерированном коде уязвимой версии компилятора, run() вернет 1.
Не понимая уязвимости компилятора, обычному разработчику трудно будет обнаружить баги в приведенном выше коде с помощью простого аудита кода. Этот пример кода достаточно прост и может не вызвать особенно серьезного вреда. Но если переменная b используется для проверки прав, учета активов и т.д., такое несоответствие ожиданиям может привести к очень серьезным последствиям.
Причина возникновения этого исключительного явления заключается в том, что EVM использует стековую виртуальную машину, где каждый элемент стека имеет размер 32 байта (, то есть размер переменной uint256 ). С другой стороны, каждый слот в базовом хранилище storage также имеет размер 32 байта. Однако язык Solidity поддерживает такие типы данных, как uint32 и другие, которые меньше 32 байт. Компилятор при обработке таких переменных должен выполнять соответствующие операции по очистке старших разрядов (clean up) для обеспечения правильности данных. В вышеприведенном случае, когда происходит переполнение целого числа при сложении, компилятор не правильно очистил старшие разряды результата, что привело к тому, что старший бит 1 был записан в storage, в конечном итоге перезаписав переменную a и изменив значение переменной b на 1.
Уязвимость существует в компиляторах версий >=0.8.13 <0.8.15. Рассмотрим следующий код:
солидность
контракт C {
функция f() публичная чистая возвращает (uint) {
сборка {
mstore(0, 0x42)
}
uint x;
сборка {
x := mload(0)
}
вернуть x;
}
}
Компилятор Solidity не просто переводит язык Solidity в код EVM, но и проводит глубокий анализ управления потоком и данных, реализуя различные процессы оптимизации компиляции для сокращения объема генерируемого кода и оптимизации расхода газа в процессе выполнения. Такие операции оптимизации распространены в компиляторах различных высокоуровневых языков, но из-за сложности ситуации легко могут возникнуть ошибки или уязвимости безопасности.
Уязвимость приведенного выше кода возникает из-за таких оптимизационных действий. Рассмотрим такую ситуацию: если в каком-то функции есть код, который изменяет данные по нулевому смещению памяти, но в дальнейшем эти данные нигде не используются, то на самом деле можно просто удалить код, изменяющий память по нулевому смещению, сэкономив газ и не повлияв на последующую логику программы.
Эта стратегия оптимизации сама по себе не является проблемой, но в конкретной реализации кода компилятора Solidity такие оптимизации применяются только в пределах одного блока assembly. В приведенном примере кода запись и доступ к памяти 0 находятся в двух разных блоках assembly, и компилятор анализировал и оптимизировал только отдельный блок assembly. Поскольку в первом блоке assembly после записи в память 0 нет никаких операций чтения, то эта запись считается избыточной, и команда будет удалена, что приведет к ошибке. В уязвимой версии функция f( будет возвращать значение 0, тогда как на самом деле приведенный код должен возвращать правильное значение 0x42.
Уязвимость затрагивает компиляторы версий >= 0.5.8 < 0.8.16. Рассмотрите следующий код:
солидность
контракт C {
функция f###uint8( calldata a[4] общедоступная чистая возвращает )string memory( {
вернуть строку)abi.encode(a();
}
}
В нормальных условиях переменная a, возвращаемая указанным выше кодом, должна быть "aaaa". Однако в уязвимой версии будет возвращена пустая строка "".
Причиной этой уязвимости является то, что Solidity неправильно очищает некоторые данные при выполнении операции abi.encode с массивами типа calldata, что приводит к изменению соседних данных и вызывает несоответствие между закодированными и декодированными данными.
Стоит отметить, что при выполнении внешних вызовов и генерации событий в Solidity параметры неявно кодируются с помощью abi.encode, поэтому вероятность появления вышеуказанного уязвимого кода будет выше, чем можно было бы предположить.
![Анализ уязвимостей компилятора Solidity и меры по их устранению])https://img-cdn.gateio.im/webp-social/moments-c97428f89ed62d5ad8551cdb2ba30867.webp(
Рекомендации по безопасности
На основе анализа модели угроз уязвимостей компилятора Solidity и рассмотрения исторических уязвимостей предлагаются следующие рекомендации для разработчиков и специалистов по безопасности.
Для разработчиков:
Используйте более новую версию компилятора Solidity. Хотя новые версии могут также вводить новые проблемы безопасности, известных проблем безопасности обычно меньше, чем в старых версиях.
Улучшите юнит-тесты. Большинство ошибок на уровне компилятора приводят к тому, что результаты выполнения кода не соответствуют ожиданиям. Эти проблемы трудно выявить при код-ревью, но их легко обнаружить на этапе тестирования. Поэтому, увеличивая покрытие кода, можно в максимальной степени избежать подобных проблем.
Старайтесь избегать использования встроенного ассемблера, сложных операций с многомерными массивами и декодирования ABI для сложных структур, избегайте слепого использования новых особенностей языка и экспериментальных функций без явной необходимости. На основе анализа исторических уязвимостей, большинство из них связано с встроенным ассемблером, операциями с кодировщиками ABI и т.д. Компилятор действительно более подвержен ошибкам при обработке сложных языковых особенностей. С другой стороны, разработчики также могут легко допускать ошибки при использовании новых функций, что приводит к проблемам с безопасностью.
Для сотрудников безопасности:
При проведении аудита безопасности кода Solidity не игнорируйте потенциальные риски безопасности, которые могут быть вызваны компилятором Solidity. Соответствующий пункт проверки в Smart Contract Weakness Classification)SWC( - это SWC-102: Устаревшая версия компилятора.
В процессе внутренней разработки SDL настоятельно призываем команду разработчиков обновить версию компилятора Solidity и рассмотреть возможность внедрения автоматической проверки версии компилятора в процессе CI/CD.
Но не стоит чрезмерно паниковать из-за уязвимостей компилятора, большинство уязвимостей компилятора активируются только в определенных кодовых паттернах, и это не значит, что контракты, скомпилированные с использованием уязвимых версий компилятора, обязательно подвергаются риску безопасности; фактическое влияние на безопасность необходимо оценивать в зависимости от конкретной ситуации проекта.
Некоторые полезные ресурсы:
Команда Solidity регулярно публикует предупреждения о безопасности:
Список ошибок, регулярно обновляемый официальным репозиторием Solidity:
Список ошибок компилятора для всех версий:
На странице Code контракта на Etherscan в правом верхнем углу треугольный восклицательный знак может указать на существующие уязвимости в текущей версии компилятора.
Итоги
В статье рассматриваются основные концепции компилятора, обсуждаются уязвимости компилятора Solidity и анализируются потенциальные риски безопасности, которые могут возникнуть в реальной среде разработки Ethereum. В заключение представлены несколько практических рекомендаций для разработчиков и специалистов по безопасности.
![Анализ уязвимостей компилятора Solidity и меры по их устранению])https://img-cdn.gateio.im/webp-social/moments-84f5083d8748f2aab71fd92671d999a7.webp(
На этой странице может содержаться сторонний контент, который предоставляется исключительно в информационных целях (не в качестве заявлений/гарантий) и не должен рассматриваться как поддержка взглядов компании Gate или как финансовый или профессиональный совет. Подробности смотрите в разделе «Отказ от ответственности» .
14 Лайков
Награда
14
5
Поделиться
комментарий
0/400
ZenZKPlayer
· 07-30 05:45
Уязвимость переполнения немного беспокоит
Посмотреть ОригиналОтветить0
OffchainWinner
· 07-30 00:55
Писать код, совершенно не обращая внимания на ошибки.
Посмотреть ОригиналОтветить0
OnchainSniper
· 07-30 00:47
С компилятором проблемы? Я с этим уже сталкивался.
Посмотреть ОригиналОтветить0
UnluckyValidator
· 07-30 00:44
Компиляторы имеют уязвимости, пугают людей.
Посмотреть ОригиналОтветить0
LiquiditySurfer
· 07-30 00:41
Скажи, чтобы разработчик быстро исправил уязвимость
Подробный анализ уязвимостей компилятора Solidity и стратегии их предотвращения
Анализ уязвимостей компилятора Solidity и стратегии реагирования
Компилятор является одной из основных компонентов современных компьютерных систем. Это программа для компьютера, основная функция которой заключается в преобразовании исходного кода на высокоуровневом языке программирования, который легко понимать и писать человеку, в исполняемый код инструкций для низкоуровневого CPU или виртуальной машины байт-кода.
Большинство разработчиков и специалистов по безопасности обычно больше всего беспокоятся о безопасности программного кода приложений, но могут игнорировать безопасность самого компилятора. На самом деле, компилятор как программа также имеет уязвимости, и в некоторых случаях эти уязвимости могут представлять серьезные риски безопасности. Например, в процессе компиляции и анализа выполнения кода Javascript на стороне клиента браузер может подвергнуться атакам злоумышленников из-за уязвимостей в движке разбора Javascript, что может привести к удаленному выполнению кода при посещении вредоносного веб-сайта и, в конечном итоге, к контролю над браузером жертвы или даже операционной системой.
Компилятор Solidity не является исключением, в различных версиях существуют уязвимости безопасности.
Уязвимость компилятора Solidity
Роль компилятора Solidity заключается в преобразовании кода смарт-контракта, написанного разработчиками, в машинный код (EVM) для виртуальной машины Ethereum. Эти инструкции EVM упаковываются и загружаются в Ethereum через транзакции, а затем в конечном итоге интерпретируются и выполняются EVM.
Необходимо различать уязвимости компилятора Solidity и уязвимости самой EVM. Уязвимости EVM относятся к проблемам безопасности, возникающим при выполнении инструкций виртуальной машины. Поскольку злоумышленники могут загружать произвольный код в Ethereum, этот код в конечном итоге будет выполняться в каждом клиентском P2P-программе Ethereum. Если в EVM существуют уязвимости безопасности, это повлияет на всю сеть Ethereum, возможно, вызвав отказ в обслуживании (DoS) и даже приведя к полной захвату сети злоумышленниками. Однако, поскольку сама EVM спроектирована относительно просто и основной код не обновляется часто, вероятность возникновения вышеупомянутых проблем относительно низка.
Уязвимости компилятора Solidity относятся к проблемам, возникающим при преобразовании Solidity в код EVM. В отличие от браузеров, которые компилируют и выполняют Javascript на клиентских компьютерах пользователей, процесс компиляции Solidity происходит только на компьютере разработчика смарт-контрактов и не выполняется в Ethereum. Таким образом, уязвимости компилятора Solidity не оказывают непосредственного влияния на саму сеть Ethereum.
Одна из основных угроз уязвимости компилятора Solidity заключается в том, что она может привести к несоответствию сгенерированного EVM-кода ожиданиям разработчиков смарт-контрактов. Поскольку смарт-контракты на Ethereum обычно связаны с криптовалютными активами пользователей, любые ошибки смарт-контрактов, вызванные компилятором, могут привести к потерям активов пользователей и иметь серьезные последствия.
Разработчики и аудиторы контрактов могут сосредоточиться на проблемах реализации логики кода контракта, а также на вопросах безопасности на уровне Solidity, таких как повторные входы и переполнение целых чисел. Однако выявить уязвимости компилятора Solidity только с помощью аудита логики исходного кода контракта довольно сложно. Необходимо совместно анализировать конкретные версии компилятора и определенные шаблоны кода, чтобы определить, подвержен ли смарт-контракт уязвимостям компилятора.
Пример уязвимости компилятора Solidity
Ниже приведены примеры нескольких реальных уязвимостей компиляторов Solidity, демонстрирующие их конкретные формы, причины и последствия.
SOL-2016-9 HighOrderByteCleanStorage
Уязвимость существует в ранних версиях компилятора Solidity (>=0.1.6 <0.4.4).
Рассмотрим следующий код:
солидность контракт C { uint32 a = 0x1234; uint32 b = 0; функция f() публичная { a += 1; } функция run() публичный просмотр возвращает (uint) { вернуть b; } }
Переменная storage b не подвергалась никаким изменениям, следовательно, функция run() должна возвращать значение по умолчанию 0. Но на самом деле, в сгенерированном коде уязвимой версии компилятора, run() вернет 1.
Не понимая уязвимости компилятора, обычному разработчику трудно будет обнаружить баги в приведенном выше коде с помощью простого аудита кода. Этот пример кода достаточно прост и может не вызвать особенно серьезного вреда. Но если переменная b используется для проверки прав, учета активов и т.д., такое несоответствие ожиданиям может привести к очень серьезным последствиям.
Причина возникновения этого исключительного явления заключается в том, что EVM использует стековую виртуальную машину, где каждый элемент стека имеет размер 32 байта (, то есть размер переменной uint256 ). С другой стороны, каждый слот в базовом хранилище storage также имеет размер 32 байта. Однако язык Solidity поддерживает такие типы данных, как uint32 и другие, которые меньше 32 байт. Компилятор при обработке таких переменных должен выполнять соответствующие операции по очистке старших разрядов (clean up) для обеспечения правильности данных. В вышеприведенном случае, когда происходит переполнение целого числа при сложении, компилятор не правильно очистил старшие разряды результата, что привело к тому, что старший бит 1 был записан в storage, в конечном итоге перезаписав переменную a и изменив значение переменной b на 1.
SOL-2022-4 ВстроеннаяАссемблернаяПамятьПобочныеЭффекты
Уязвимость существует в компиляторах версий >=0.8.13 <0.8.15. Рассмотрим следующий код:
солидность контракт C { функция f() публичная чистая возвращает (uint) { сборка { mstore(0, 0x42) } uint x; сборка { x := mload(0) } вернуть x; } }
Компилятор Solidity не просто переводит язык Solidity в код EVM, но и проводит глубокий анализ управления потоком и данных, реализуя различные процессы оптимизации компиляции для сокращения объема генерируемого кода и оптимизации расхода газа в процессе выполнения. Такие операции оптимизации распространены в компиляторах различных высокоуровневых языков, но из-за сложности ситуации легко могут возникнуть ошибки или уязвимости безопасности.
Уязвимость приведенного выше кода возникает из-за таких оптимизационных действий. Рассмотрим такую ситуацию: если в каком-то функции есть код, который изменяет данные по нулевому смещению памяти, но в дальнейшем эти данные нигде не используются, то на самом деле можно просто удалить код, изменяющий память по нулевому смещению, сэкономив газ и не повлияв на последующую логику программы.
Эта стратегия оптимизации сама по себе не является проблемой, но в конкретной реализации кода компилятора Solidity такие оптимизации применяются только в пределах одного блока assembly. В приведенном примере кода запись и доступ к памяти 0 находятся в двух разных блоках assembly, и компилятор анализировал и оптимизировал только отдельный блок assembly. Поскольку в первом блоке assembly после записи в память 0 нет никаких операций чтения, то эта запись считается избыточной, и команда будет удалена, что приведет к ошибке. В уязвимой версии функция f( будет возвращать значение 0, тогда как на самом деле приведенный код должен возвращать правильное значение 0x42.
) SOL-2022-6 AbiReencodingHeadOverflowWithStaticArrayCleanup
Уязвимость затрагивает компиляторы версий >= 0.5.8 < 0.8.16. Рассмотрите следующий код:
солидность контракт C { функция f###uint8( calldata a[4] общедоступная чистая возвращает )string memory( { вернуть строку)abi.encode(a(); } }
В нормальных условиях переменная a, возвращаемая указанным выше кодом, должна быть "aaaa". Однако в уязвимой версии будет возвращена пустая строка "".
Причиной этой уязвимости является то, что Solidity неправильно очищает некоторые данные при выполнении операции abi.encode с массивами типа calldata, что приводит к изменению соседних данных и вызывает несоответствие между закодированными и декодированными данными.
Стоит отметить, что при выполнении внешних вызовов и генерации событий в Solidity параметры неявно кодируются с помощью abi.encode, поэтому вероятность появления вышеуказанного уязвимого кода будет выше, чем можно было бы предположить.
![Анализ уязвимостей компилятора Solidity и меры по их устранению])https://img-cdn.gateio.im/webp-social/moments-c97428f89ed62d5ad8551cdb2ba30867.webp(
Рекомендации по безопасности
На основе анализа модели угроз уязвимостей компилятора Solidity и рассмотрения исторических уязвимостей предлагаются следующие рекомендации для разработчиков и специалистов по безопасности.
Для разработчиков:
Используйте более новую версию компилятора Solidity. Хотя новые версии могут также вводить новые проблемы безопасности, известных проблем безопасности обычно меньше, чем в старых версиях.
Улучшите юнит-тесты. Большинство ошибок на уровне компилятора приводят к тому, что результаты выполнения кода не соответствуют ожиданиям. Эти проблемы трудно выявить при код-ревью, но их легко обнаружить на этапе тестирования. Поэтому, увеличивая покрытие кода, можно в максимальной степени избежать подобных проблем.
Старайтесь избегать использования встроенного ассемблера, сложных операций с многомерными массивами и декодирования ABI для сложных структур, избегайте слепого использования новых особенностей языка и экспериментальных функций без явной необходимости. На основе анализа исторических уязвимостей, большинство из них связано с встроенным ассемблером, операциями с кодировщиками ABI и т.д. Компилятор действительно более подвержен ошибкам при обработке сложных языковых особенностей. С другой стороны, разработчики также могут легко допускать ошибки при использовании новых функций, что приводит к проблемам с безопасностью.
Для сотрудников безопасности:
При проведении аудита безопасности кода Solidity не игнорируйте потенциальные риски безопасности, которые могут быть вызваны компилятором Solidity. Соответствующий пункт проверки в Smart Contract Weakness Classification)SWC( - это SWC-102: Устаревшая версия компилятора.
В процессе внутренней разработки SDL настоятельно призываем команду разработчиков обновить версию компилятора Solidity и рассмотреть возможность внедрения автоматической проверки версии компилятора в процессе CI/CD.
Но не стоит чрезмерно паниковать из-за уязвимостей компилятора, большинство уязвимостей компилятора активируются только в определенных кодовых паттернах, и это не значит, что контракты, скомпилированные с использованием уязвимых версий компилятора, обязательно подвергаются риску безопасности; фактическое влияние на безопасность необходимо оценивать в зависимости от конкретной ситуации проекта.
Некоторые полезные ресурсы:
Команда Solidity регулярно публикует предупреждения о безопасности:
Список ошибок, регулярно обновляемый официальным репозиторием Solidity:
Список ошибок компилятора для всех версий:
На странице Code контракта на Etherscan в правом верхнем углу треугольный восклицательный знак может указать на существующие уязвимости в текущей версии компилятора.
Итоги
В статье рассматриваются основные концепции компилятора, обсуждаются уязвимости компилятора Solidity и анализируются потенциальные риски безопасности, которые могут возникнуть в реальной среде разработки Ethereum. В заключение представлены несколько практических рекомендаций для разработчиков и специалистов по безопасности.
![Анализ уязвимостей компилятора Solidity и меры по их устранению])https://img-cdn.gateio.im/webp-social/moments-84f5083d8748f2aab71fd92671d999a7.webp(