Анализ уязвимостей компилятора Solidity и стратегии противодействия
Компилятор является одной из основных частей современных компьютерных систем. Это специальная компьютерная программа, которая отвечает за преобразование исходного кода на высокоуровневом языке программирования, понятного и удобного для человека, в машинный код, который может выполняться процессором или виртуальной машиной байт-кода.
Хотя большинство разработчиков и экспертов по безопасности обычно больше беспокоят безопасность кода приложений, безопасность самого компилятора также не должна игнорироваться. Будучи видом компьютерной программы, компилятор также может иметь уязвимости, которые в некоторых случаях могут представлять серьезный риск для безопасности. Например, при компиляции и анализе выполнения кода Javascript на стороне клиента браузер может подвергнуться атаке из-за уязвимостей в движке анализа Javascript, что позволяет злоумышленникам использовать уязвимости для удаленного выполнения кода, в конечном итоге захватывая браузер жертвы или даже всю операционную систему.
Компилятор Solidity не исключение, в нескольких различных версиях существуют уязвимости безопасности.
Уязвимость компилятора Solidity
Основная функция компилятора Solidity заключается в преобразовании кода смарт-контрактов, написанного разработчиками, в исполняемый код инструкций для виртуальной машины Ethereum (EVM). Эти инструкции 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). Каждый слот в нижнем уровне хранилища также имеет размер 32 байта. При этом язык Solidity поддерживает такие типы данных, как uint32 и другие, меньшие 32 байт, и компилятор при обработке этих переменных должен корректно очищать старшие биты (clean up), чтобы обеспечить правильность данных. В указанной ситуации, когда при сложении происходит переполнение целого числа, компилятор не очистил старшие биты результата должным образом, что привело к тому, что старший бит 1 был записан в хранилище, в конечном итоге перезаписав переменную a на переменную b, изменив значение переменной b на 1.
Уязвимость существует в компиляторах версий >=0.8.13 <0.8.15. Рассмотрим следующий код:
солидность
контракт C {
функция f() публичная чистая возвращает (uint) {
сборка {
mstore(0, 0x42)
}
uint x;
сборка {
x := mload(0)
}
вернуть x;
}
}
Компилятор Solidity не просто переводит язык Solidity в код EVM; он также выполняет глубокий анализ управления потоком и данных, реализуя различные процессы оптимизации компиляции для уменьшения объема сгенерированного кода и оптимизации потребления газа в процессе выполнения. Такие операции оптимизации довольно распространены в компиляторах различных высокоуровневых языков, но из-за сложности учитываемых случаев они также могут приводить к ошибкам или уязвимостям.
Уязвимость в приведенном коде возникает из-за таких оптимизационных операций. Компилятор считает, что если в функции есть код, изменяющий данные по смещению 0 в памяти, но в дальнейшем эти данные не используются ни в одном месте, то можно просто удалить код, изменяющий память 0, тем самым сэкономив газ и не влияя на дальнейшую логику программы.
Эта стратегия оптимизации сама по себе не имеет недостатков, но в конкретной реализации кода компилятора Solidity такие оптимизации применяются только в одном блоке assembly. В приведенном выше коде PoC операции записи и доступа к памяти 0 находятся в двух разных блоках assembly, в то время как компилятор проводит анализ и оптимизацию только для отдельного блока assembly. Поскольку в первом блоке assembly после записи в память 0 нет никаких операций чтения, то эта команда записи считается избыточной и будет удалена, что приводит к ошибке. В уязвимой версии функция f( будет возвращать значение 0, в то время как правильное возвращаемое значение этого кода должно быть 0x42.
Уязвимость затрагивает компиляторы версий >= 0.5.8 < 0.8.16. Рассмотрим следующий код:
солидность
контракт C {
функция f###string( calldata a[1] внешняя чистая возвращает )string memory( {
вернуть abi.decode)abi.encode(a(, )строка([1]));
}
}
В нормальных условиях переменная a, возвращаемая вышеуказанным кодом, должна быть "aaaa". Однако в уязвимой версии будет возвращена пустая строка "".
Причиной уязвимости является неправильная очистка некоторых данных при выполнении операции abi.encode над массивом типа calldata в Solidity, что привело к изменению соседних данных и, как следствие, к несоответствию закодированных и декодированных данных.
Следует обратить внимание, что при выполнении внешних вызовов и эмитировании событий в Solidity параметры неявно кодируются с помощью abi.encode, поэтому вероятность появления вышеупомянутого уязвимого кода будет выше, чем может показаться на первый взгляд.
![Анализ уязвимостей компилятора Solidity и меры по их устранению][0]https://img-cdn.gateio.im/webp-social/moments-c97428f89ed62d5ad8551cdb2ba30867.webp(
Рекомендации по безопасности
После анализа модели угроз уязвимостей компилятора Solidity и изучения исторических уязвимостей, мы предлагаем следующие рекомендации для разработчиков и специалистов по безопасности.
) Для разработчиков:
Используйте более новую версию компилятора Solidity. Хотя новые версии также могут вводить новые проблемы безопасности, известных проблем безопасности обычно меньше, чем у старых версий.
Улучшение юнит-тестов. Большинство ошибок на уровне компилятора приводят к тому, что результаты выполнения кода не совпадают с ожидаемыми. Такие проблемы трудно обнаружить при ревью кода, но они легко могут проявиться на этапе тестирования. Поэтому, повысив покрытие кода, можно в максимальной степени избежать подобных проблем.
Старайтесь избегать использования встроенного ассемблера, сложных операций с ABI кодированием и декодированием для многомерных массивов и сложных структур. Избегайте слепого использования новых особенностей языка и экспериментальных функций без явной необходимости. Согласно анализу исторических уязвимостей, большинство из них связано с встроенным ассемблером и операциями ABI. Компиляторы чаще сталкиваются с ошибками при обработке сложных языковых особенностей. С другой стороны, разработчики также могут допускать ошибки в использовании новых функций, что приводит к проблемам безопасности.
Для сотрудников безопасности:
При проведении аудита безопасности кода Solidity не следует игнорировать потенциальные риски безопасности, которые могут быть вызваны компилятором Solidity. Соответствующий пункт проверки в классификации уязвимостей смарт-контрактов ###SWC( — SWC-102: Устаревшая версия компилятора.
Внутри процесса разработки SDL настоятельно рекомендуется команде разработчиков обновить версию компилятора Solidity и рассмотреть возможность внедрения автоматической проверки версии компилятора в процессе CI/CD.
Но не стоит чрезмерно паниковать по поводу уязвимостей компилятора; большинство уязвимостей компилятора срабатывают только в определенных кодовых шаблонах, и это не означает, что контракты, скомпилированные с использованием уязвимой версии компилятора, обязательно имеют риски безопасности. Фактическое влияние на безопасность необходимо оценивать в зависимости от конкретных условий проекта.
Полезные ресурсы
Статьи о безопасности, регулярно публикуемые командой Solidity
Список ошибок, регулярно обновляемый официальным репозиторием Solidity
Список ошибок компилятора для всех версий. На его основе можно автоматически проверять версию компилятора в процессе CI/CD и предупреждать о существующих уязвимостях в текущей версии.
Code на Etherscan треугольный восклицательный знак в правом верхнем углу указывает на существующие уязвимости в компиляторе текущей версии.
![Анализ уязвимостей компилятора Solidity и меры по их устранению])https://img-cdn.gateio.im/webp-social/moments-84f5083d8748f2aab71fd92671d999a7.webp(
Резюме
В данной статье рассматриваются основные концепции компилятора, описываются уязвимости компилятора Solidity и анализируются потенциальные риски безопасности, которые они могут вызвать в реальной среде разработки Ethereum. В заключение предоставляются несколько практических рекомендаций для разработчиков и специалистов по безопасности. Поняв эти уязвимости и приняв соответствующие меры предосторожности, мы можем лучше защитить безопасность смарт-контрактов и снизить риск потенциальных потерь активов.
На этой странице может содержаться сторонний контент, который предоставляется исключительно в информационных целях (не в качестве заявлений/гарантий) и не должен рассматриваться как поддержка взглядов компании Gate или как финансовый или профессиональный совет. Подробности смотрите в разделе «Отказ от ответственности» .
12 Лайков
Награда
12
6
Поделиться
комментарий
0/400
0xLuckbox
· 10ч назад
Логические дыры действительно нападают на мозг. Убегаю, убегаю.
Посмотреть ОригиналОтветить0
LidoStakeAddict
· 07-30 09:56
Переполнение, код нужно изменить.
Посмотреть ОригиналОтветить0
StablecoinArbitrageur
· 07-30 09:26
*корректирует очки* хмм... статистически говоря, риски компилятора серьезно недооценены в расчетах TVL в DeFi
Посмотреть ОригиналОтветить0
BoredStaker
· 07-30 09:24
Когда можно говорить по-человечески!
Посмотреть ОригиналОтветить0
ArbitrageBot
· 07-30 09:24
Снова придется заниматься проблемами компилятора?
Посмотреть ОригиналОтветить0
APY追逐者
· 07-30 09:15
Только когда охотился на Газ, вспомнил о уязвимости компилятора
Анализ уязвимостей компилятора Solidity и практика обеспечения безопасности
Анализ уязвимостей компилятора Solidity и стратегии противодействия
Компилятор является одной из основных частей современных компьютерных систем. Это специальная компьютерная программа, которая отвечает за преобразование исходного кода на высокоуровневом языке программирования, понятного и удобного для человека, в машинный код, который может выполняться процессором или виртуальной машиной байт-кода.
Хотя большинство разработчиков и экспертов по безопасности обычно больше беспокоят безопасность кода приложений, безопасность самого компилятора также не должна игнорироваться. Будучи видом компьютерной программы, компилятор также может иметь уязвимости, которые в некоторых случаях могут представлять серьезный риск для безопасности. Например, при компиляции и анализе выполнения кода Javascript на стороне клиента браузер может подвергнуться атаке из-за уязвимостей в движке анализа Javascript, что позволяет злоумышленникам использовать уязвимости для удаленного выполнения кода, в конечном итоге захватывая браузер жертвы или даже всю операционную систему.
Компилятор Solidity не исключение, в нескольких различных версиях существуют уязвимости безопасности.
Уязвимость компилятора Solidity
Основная функция компилятора Solidity заключается в преобразовании кода смарт-контрактов, написанного разработчиками, в исполняемый код инструкций для виртуальной машины Ethereum (EVM). Эти инструкции 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). Каждый слот в нижнем уровне хранилища также имеет размер 32 байта. При этом язык Solidity поддерживает такие типы данных, как uint32 и другие, меньшие 32 байт, и компилятор при обработке этих переменных должен корректно очищать старшие биты (clean up), чтобы обеспечить правильность данных. В указанной ситуации, когда при сложении происходит переполнение целого числа, компилятор не очистил старшие биты результата должным образом, что привело к тому, что старший бит 1 был записан в хранилище, в конечном итоге перезаписав переменную a на переменную b, изменив значение переменной 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; он также выполняет глубокий анализ управления потоком и данных, реализуя различные процессы оптимизации компиляции для уменьшения объема сгенерированного кода и оптимизации потребления газа в процессе выполнения. Такие операции оптимизации довольно распространены в компиляторах различных высокоуровневых языков, но из-за сложности учитываемых случаев они также могут приводить к ошибкам или уязвимостям.
Уязвимость в приведенном коде возникает из-за таких оптимизационных операций. Компилятор считает, что если в функции есть код, изменяющий данные по смещению 0 в памяти, но в дальнейшем эти данные не используются ни в одном месте, то можно просто удалить код, изменяющий память 0, тем самым сэкономив газ и не влияя на дальнейшую логику программы.
Эта стратегия оптимизации сама по себе не имеет недостатков, но в конкретной реализации кода компилятора Solidity такие оптимизации применяются только в одном блоке assembly. В приведенном выше коде PoC операции записи и доступа к памяти 0 находятся в двух разных блоках assembly, в то время как компилятор проводит анализ и оптимизацию только для отдельного блока assembly. Поскольку в первом блоке assembly после записи в память 0 нет никаких операций чтения, то эта команда записи считается избыточной и будет удалена, что приводит к ошибке. В уязвимой версии функция f( будет возвращать значение 0, в то время как правильное возвращаемое значение этого кода должно быть 0x42.
) SOL-2022-6 AbiReencodingHeadOverflowWithStaticArrayCleanup
Уязвимость затрагивает компиляторы версий >= 0.5.8 < 0.8.16. Рассмотрим следующий код:
солидность контракт C { функция f###string( calldata a[1] внешняя чистая возвращает )string memory( { вернуть abi.decode)abi.encode(a(, )строка([1])); } }
В нормальных условиях переменная a, возвращаемая вышеуказанным кодом, должна быть "aaaa". Однако в уязвимой версии будет возвращена пустая строка "".
Причиной уязвимости является неправильная очистка некоторых данных при выполнении операции abi.encode над массивом типа calldata в Solidity, что привело к изменению соседних данных и, как следствие, к несоответствию закодированных и декодированных данных.
Следует обратить внимание, что при выполнении внешних вызовов и эмитировании событий в Solidity параметры неявно кодируются с помощью abi.encode, поэтому вероятность появления вышеупомянутого уязвимого кода будет выше, чем может показаться на первый взгляд.
![Анализ уязвимостей компилятора Solidity и меры по их устранению][0]https://img-cdn.gateio.im/webp-social/moments-c97428f89ed62d5ad8551cdb2ba30867.webp(
Рекомендации по безопасности
После анализа модели угроз уязвимостей компилятора Solidity и изучения исторических уязвимостей, мы предлагаем следующие рекомендации для разработчиков и специалистов по безопасности.
) Для разработчиков:
Используйте более новую версию компилятора Solidity. Хотя новые версии также могут вводить новые проблемы безопасности, известных проблем безопасности обычно меньше, чем у старых версий.
Улучшение юнит-тестов. Большинство ошибок на уровне компилятора приводят к тому, что результаты выполнения кода не совпадают с ожидаемыми. Такие проблемы трудно обнаружить при ревью кода, но они легко могут проявиться на этапе тестирования. Поэтому, повысив покрытие кода, можно в максимальной степени избежать подобных проблем.
Старайтесь избегать использования встроенного ассемблера, сложных операций с ABI кодированием и декодированием для многомерных массивов и сложных структур. Избегайте слепого использования новых особенностей языка и экспериментальных функций без явной необходимости. Согласно анализу исторических уязвимостей, большинство из них связано с встроенным ассемблером и операциями ABI. Компиляторы чаще сталкиваются с ошибками при обработке сложных языковых особенностей. С другой стороны, разработчики также могут допускать ошибки в использовании новых функций, что приводит к проблемам безопасности.
Для сотрудников безопасности:
При проведении аудита безопасности кода Solidity не следует игнорировать потенциальные риски безопасности, которые могут быть вызваны компилятором Solidity. Соответствующий пункт проверки в классификации уязвимостей смарт-контрактов ###SWC( — SWC-102: Устаревшая версия компилятора.
Внутри процесса разработки SDL настоятельно рекомендуется команде разработчиков обновить версию компилятора Solidity и рассмотреть возможность внедрения автоматической проверки версии компилятора в процессе CI/CD.
Но не стоит чрезмерно паниковать по поводу уязвимостей компилятора; большинство уязвимостей компилятора срабатывают только в определенных кодовых шаблонах, и это не означает, что контракты, скомпилированные с использованием уязвимой версии компилятора, обязательно имеют риски безопасности. Фактическое влияние на безопасность необходимо оценивать в зависимости от конкретных условий проекта.
Полезные ресурсы
![Анализ уязвимостей компилятора Solidity и меры по их устранению])https://img-cdn.gateio.im/webp-social/moments-84f5083d8748f2aab71fd92671d999a7.webp(
Резюме
В данной статье рассматриваются основные концепции компилятора, описываются уязвимости компилятора Solidity и анализируются потенциальные риски безопасности, которые они могут вызвать в реальной среде разработки Ethereum. В заключение предоставляются несколько практических рекомендаций для разработчиков и специалистов по безопасности. Поняв эти уязвимости и приняв соответствующие меры предосторожности, мы можем лучше защитить безопасность смарт-контрактов и снизить риск потенциальных потерь активов.