Анализ уязвимостей компилятора Solidity и стратегии реагирования
Компилятор является одним из ключевых компонентов современных компьютерных систем. Он преобразует высокоуровневые языки программирования, понятные человеку, в низкоуровневые инструкции, исполняемые компьютером. Хотя разработчики и специалисты по безопасности обычно сосредоточены на безопасности кода приложения, проблемы безопасности самого компилятора также не следует игнорировать. Уязвимости компилятора в некоторых случаях могут привести к серьезным рискам безопасности.
Например, в браузере, при разборе и выполнении кода JavaScript, уязвимость JavaScript-движка может привести к тому, что пользователи при посещении вредоносных веб-страниц подвергнутся атакам удаленного выполнения кода, в конечном итоге позволяя злоумышленникам контролировать браузер жертвы или даже операционную систему. Кроме того, ошибки компилятора C++ также могут привести к серьезным последствиям, таким как удаленное выполнение кода.
Компилятор Solidity не является исключением, в нескольких версиях существуют уязвимости. Роль компилятора Solidity заключается в преобразовании кода смарт-контракта в байт-код команд Ethereum Virtual Machine (EVM), который в конечном итоге выполняется в EVM. Стоит отметить, что уязвимости компилятора Solidity отличаются от уязвимостей самой EVM. Уязвимости EVM относятся к проблемам безопасности при выполнении инструкций виртуальной машины, которые могут повлиять на всю сеть Ethereum. В то время как уязвимости компилятора Solidity возникают при преобразовании Solidity в код EVM.
Одним из рисков уязвимости компилятора Solidity является то, что сгенерированный EVM-код может не соответствовать ожиданиям разработчика. Поскольку смарт-контракты обычно связаны с криптовалютными активами пользователей, любые ошибки, вызванные компилятором, могут привести к потерям активов пользователей, что имеет серьезные последствия. Разработчики и аудиторы обычно сосредотачиваются на реализации логики контракта и распространенных уязвимостях, тогда как уязвимости компилятора требуют анализа с учетом конкретной версии и паттернов кода.
Ниже приведены несколько реальных случаев, демонстрирующих конкретные формы, причины и опасности уязвимостей компилятора Solidity.
SOL-2016-9 HighOrderByteCleanStorage
Уязвимость присутствует в более ранних версиях компилятора Solidity (>=0.1.6 <0.4.4).
Рассмотрите следующий код:
солидность
контракт C {
uint32 a = 0x12345678;
uint32 b = 0;
функция f() публичная {
a = a + 1;
}
функция run() публичный просмотр возвращает (uint32) {
вернуть b;
}
}
Переменная b не была изменена, функция run() должна возвращать значение по умолчанию 0. Но в коде, сгенерированном уязвимой версией компилятора, run() возвращает 1.
Это несоответствие трудно выявить простым кодовым ревью. Хотя пример кода имеет ограниченное влияние, если переменная b используется для проверки прав или учета активов, последствия будут весьма серьезными.
Причина данной проблемы заключается в том, что EVM использует элементы стека размером 32 байта, в то время как каждый слот в базовом хранилище также составляет 32 байта. Solidity поддерживает такие типы данных, как uint32, которые меньше 32 байт, и компилятор при обработке этих типов должен очищать старшие биты (clean up) для обеспечения правильности данных. В этом случае после переполнения сложения компилятор неправильно очистил старшие биты результата, что привело к тому, что переполненный 1 бит был записан в хранилище, перезаписав переменную b.
Уязвимость существует в компиляторах версий от 0.8.13 до 0.8.15. Рассмотрим следующий код:
солидность
контракт C {
функция f() публичная чистая возвращает (uint) {
сборка {
mstore(0, 0x42)
}
uint x;
сборка {
x := mload(0)
}
вернуть x;
}
}
Компилятор Solidity проводит глубокий анализ потоков управления и данных в процессе оптимизации, чтобы уменьшить объем генерируемого кода и оптимизировать расход газа. Хотя такая оптимизация распространена, из-за сложности ситуации легко могут возникнуть ошибки или уязвимости безопасности.
Проблема указанного кода возникает из-за такого рода оптимизации. Компилятор считает, что если какая-то функция изменяет данные по нулевому смещению в памяти, но последующее использование этих данных отсутствует, то он может удалить команду изменения для экономии газа. Но такая оптимизация применяется только внутри одного блока ассемблера.
В этом примере запись и доступ к памяти 0 происходят в двух различных блоках ассемблера. Компилятор анализировал только отдельные блоки ассемблера и посчитал, что запись в первом блоке избыточна, поэтому удалил её, что привело к ошибке. В уязвимой версии функция f() вернёт 0, тогда как правильное возвращаемое значение должно быть 0x42.
Этот уязвимость затрагивает версии компилятора с 0.5.8 по 0.8.16. Рассмотрим следующий код:
солидность
контракт C {
function f(string[1] calldata a) внешних чистых возвращает (string memory) {
возврат abi.decode(abi.encode(a), (string[1]))[0];
}
}
В нормальных условиях данный код должен возвращать значение переменной a "aaaa". Однако в уязвимой версии он будет возвращать пустую строку "".
Проблема заключается в том, что при выполнении операции abi.encode на массивах типа calldata в Solidity, некоторые данные ошибочно очищаются, что приводит к изменению соседних данных и вызовет несоответствие между закодированными и декодированными данными.
Стоит отметить, что при выполнении внешних вызовов и эмитировании событий в Solidity параметры неявно кодируются с помощью abi.encode, поэтому влияние этой уязвимости может быть больше, чем ожидалось.
Рекомендации по безопасности
На основе анализа уязвимостей компилятора Solidity, разработчикам и специалистам по безопасности предлагаются следующие рекомендации:
Для разработчиков:
Используйте более новую версию компилятора Solidity. Хотя новая версия может вводить новые проблемы, известные проблемы безопасности, как правило, встречаются реже.
Улучшите модульное тестирование. Большинство ошибок на уровне компилятора приводят к тому, что результаты выполнения не соответствуют ожиданиям; такие проблемы трудно обнаружить при код-ревью, но они легко выявляются в процессе тестирования. Повышение покрытия кода может максимально снизить вероятность таких проблем.
Избегайте использования встроенной сборки, сложной декодировки ABI и других подобных операций, не слепо используйте новые функции и экспериментальные возможности. Большинство исторических уязвимостей связано с этими сложными операциями.
К безопасности персонала:
Не игнорируйте потенциальные риски безопасности, которые могут быть вызваны компилятором во время аудита. Соответствующий пункт проверки в Smart Contract Weakness Classification(SWC) - это SWC-102: Устаревшая версия компилятора.
В процессе разработки SDL необходимо побуждать команду разработчиков обновлять версию компилятора и рассмотреть возможность внедрения автоматической проверки версии компилятора в CI/CD.
Не стоит чрезмерно беспокоиться о уязвимостях компилятора. Большинство уязвимостей срабатывают только при определенных кодовых паттернах, и использование контрактов, скомпилированных с уязвимой версией, не обязательно связано с риском, необходимо оценивать ситуацию в каждом конкретном случае.
Некоторые полезные ресурсы:
Регулярные уведомления о безопасности от команды Solidity
Регулярно обновляемый список ошибок в официальном репозитории Solidity
Список ошибок компилятора для различных версий, который можно использовать для автоматической проверки в CI/CD.
Знак предупреждения в правом верхнем углу страницы кода контракта Etherscan может сигнализировать о существующих уязвимостях в текущей версии компилятора.
Резюме
В этой статье рассматривается концепция уязвимостей компилятора Solidity, анализируются потенциальные риски безопасности, которые они могут вызвать в разработке на Ethereum, и предоставляются практические рекомендации для разработчиков и специалистов по безопасности. Хотя уязвимости компилятора встречаются нечасто, их последствия могут быть значительными, и на них стоит обратить внимание командам разработки и безопасности.
На этой странице может содержаться сторонний контент, который предоставляется исключительно в информационных целях (не в качестве заявлений/гарантий) и не должен рассматриваться как поддержка взглядов компании Gate или как финансовый или профессиональный совет. Подробности смотрите в разделе «Отказ от ответственности» .
10 Лайков
Награда
10
5
Поделиться
комментарий
0/400
0xInsomnia
· 07-24 15:11
Уязвимость на нижнем уровне, верхний уровень не спасает.
Посмотреть ОригиналОтветить0
0xSunnyDay
· 07-24 06:35
Кто еще смеет говорить, что компиляторы безопасны?
Посмотреть ОригиналОтветить0
BankruptcyArtist
· 07-24 06:29
Каждый день ищу уязвимости, каждый день на грани банкротства.
Посмотреть ОригиналОтветить0
bridge_anxiety
· 07-24 06:23
Сложность эксплуатации уязвимости ударила по всем фронтам.
Посмотреть ОригиналОтветить0
TokenDustCollector
· 07-24 06:13
Уязвимость безопасности — это начало того, как неудачники теряют деньги.
Анализ уязвимостей компилятора Solidity: влияние, примеры и стратегии реагирования
Анализ уязвимостей компилятора Solidity и стратегии реагирования
Компилятор является одним из ключевых компонентов современных компьютерных систем. Он преобразует высокоуровневые языки программирования, понятные человеку, в низкоуровневые инструкции, исполняемые компьютером. Хотя разработчики и специалисты по безопасности обычно сосредоточены на безопасности кода приложения, проблемы безопасности самого компилятора также не следует игнорировать. Уязвимости компилятора в некоторых случаях могут привести к серьезным рискам безопасности.
Например, в браузере, при разборе и выполнении кода JavaScript, уязвимость JavaScript-движка может привести к тому, что пользователи при посещении вредоносных веб-страниц подвергнутся атакам удаленного выполнения кода, в конечном итоге позволяя злоумышленникам контролировать браузер жертвы или даже операционную систему. Кроме того, ошибки компилятора C++ также могут привести к серьезным последствиям, таким как удаленное выполнение кода.
Компилятор Solidity не является исключением, в нескольких версиях существуют уязвимости. Роль компилятора Solidity заключается в преобразовании кода смарт-контракта в байт-код команд Ethereum Virtual Machine (EVM), который в конечном итоге выполняется в EVM. Стоит отметить, что уязвимости компилятора Solidity отличаются от уязвимостей самой EVM. Уязвимости EVM относятся к проблемам безопасности при выполнении инструкций виртуальной машины, которые могут повлиять на всю сеть Ethereum. В то время как уязвимости компилятора Solidity возникают при преобразовании Solidity в код EVM.
Одним из рисков уязвимости компилятора Solidity является то, что сгенерированный EVM-код может не соответствовать ожиданиям разработчика. Поскольку смарт-контракты обычно связаны с криптовалютными активами пользователей, любые ошибки, вызванные компилятором, могут привести к потерям активов пользователей, что имеет серьезные последствия. Разработчики и аудиторы обычно сосредотачиваются на реализации логики контракта и распространенных уязвимостях, тогда как уязвимости компилятора требуют анализа с учетом конкретной версии и паттернов кода.
Ниже приведены несколько реальных случаев, демонстрирующих конкретные формы, причины и опасности уязвимостей компилятора Solidity.
SOL-2016-9 HighOrderByteCleanStorage
Уязвимость присутствует в более ранних версиях компилятора Solidity (>=0.1.6 <0.4.4).
Рассмотрите следующий код:
солидность контракт C { uint32 a = 0x12345678; uint32 b = 0; функция f() публичная { a = a + 1; } функция run() публичный просмотр возвращает (uint32) { вернуть b; } }
Переменная b не была изменена, функция run() должна возвращать значение по умолчанию 0. Но в коде, сгенерированном уязвимой версией компилятора, run() возвращает 1.
Это несоответствие трудно выявить простым кодовым ревью. Хотя пример кода имеет ограниченное влияние, если переменная b используется для проверки прав или учета активов, последствия будут весьма серьезными.
Причина данной проблемы заключается в том, что EVM использует элементы стека размером 32 байта, в то время как каждый слот в базовом хранилище также составляет 32 байта. Solidity поддерживает такие типы данных, как uint32, которые меньше 32 байт, и компилятор при обработке этих типов должен очищать старшие биты (clean up) для обеспечения правильности данных. В этом случае после переполнения сложения компилятор неправильно очистил старшие биты результата, что привело к тому, что переполненный 1 бит был записан в хранилище, перезаписав переменную b.
SOL-2022-4 ВлиятельныеПобочныеЭффектыInlineAssemblyMemory
Уязвимость существует в компиляторах версий от 0.8.13 до 0.8.15. Рассмотрим следующий код:
солидность контракт C { функция f() публичная чистая возвращает (uint) { сборка { mstore(0, 0x42) } uint x; сборка { x := mload(0) } вернуть x; } }
Компилятор Solidity проводит глубокий анализ потоков управления и данных в процессе оптимизации, чтобы уменьшить объем генерируемого кода и оптимизировать расход газа. Хотя такая оптимизация распространена, из-за сложности ситуации легко могут возникнуть ошибки или уязвимости безопасности.
Проблема указанного кода возникает из-за такого рода оптимизации. Компилятор считает, что если какая-то функция изменяет данные по нулевому смещению в памяти, но последующее использование этих данных отсутствует, то он может удалить команду изменения для экономии газа. Но такая оптимизация применяется только внутри одного блока ассемблера.
В этом примере запись и доступ к памяти 0 происходят в двух различных блоках ассемблера. Компилятор анализировал только отдельные блоки ассемблера и посчитал, что запись в первом блоке избыточна, поэтому удалил её, что привело к ошибке. В уязвимой версии функция f() вернёт 0, тогда как правильное возвращаемое значение должно быть 0x42.
SOL-2022-6 AbiReencodingHeadOverflowWithStaticArrayCleanup
Этот уязвимость затрагивает версии компилятора с 0.5.8 по 0.8.16. Рассмотрим следующий код:
солидность контракт C { function f(string[1] calldata a) внешних чистых возвращает (string memory) { возврат abi.decode(abi.encode(a), (string[1]))[0]; } }
В нормальных условиях данный код должен возвращать значение переменной a "aaaa". Однако в уязвимой версии он будет возвращать пустую строку "".
Проблема заключается в том, что при выполнении операции abi.encode на массивах типа calldata в Solidity, некоторые данные ошибочно очищаются, что приводит к изменению соседних данных и вызовет несоответствие между закодированными и декодированными данными.
Стоит отметить, что при выполнении внешних вызовов и эмитировании событий в Solidity параметры неявно кодируются с помощью abi.encode, поэтому влияние этой уязвимости может быть больше, чем ожидалось.
Рекомендации по безопасности
На основе анализа уязвимостей компилятора Solidity, разработчикам и специалистам по безопасности предлагаются следующие рекомендации:
Для разработчиков:
К безопасности персонала:
Некоторые полезные ресурсы:
Резюме
В этой статье рассматривается концепция уязвимостей компилятора Solidity, анализируются потенциальные риски безопасности, которые они могут вызвать в разработке на Ethereum, и предоставляются практические рекомендации для разработчиков и специалистов по безопасности. Хотя уязвимости компилятора встречаются нечасто, их последствия могут быть значительными, и на них стоит обратить внимание командам разработки и безопасности.