Подробный анализ уязвимостей компилятора Solidity и стратегии противодействия
Компилятор является одной из основных составных частей современных компьютерных систем. Это компьютерная программа, основная функция которой заключается в преобразовании исходного кода на языке высокого уровня в исполняемый код инструкций, который может выполнять центральный процессор компьютера или виртуальная машина.
Хотя большинство разработчиков и специалистов по безопасности обычно больше беспокоятся о безопасности кода приложений, безопасность самого компилятора также важна. Как компьютерная программа, компилятор также может иметь уязвимости, которые в некоторых случаях могут представлять серьезные риски для безопасности. Например, когда браузер компилирует и анализирует выполнение кода JavaScript на фронтенде, уязвимости в движке анализа JavaScript могут привести к тому, что пользователи, посещая вредоносные веб-страницы, могут быть использованы злоумышленниками, что в конечном итоге приведет к контролю над браузером жертвы или даже операционной системой.
Компилятор Solidity также имеет уязвимости. Согласно предупреждению о безопасности от команды разработчиков Solidity, в нескольких различных версиях компилятора Solidity были обнаружены проблемы с безопасностью.
Уязвимость компилятора Solidity
Основная функция компилятора Solidity заключается в преобразовании кода смарт-контрактов в машинный код команд Ethereum Virtual Machine (EVM). Эти команды EVM упаковываются в транзакции и загружаются в Ethereum, а затем интерпретируются и выполняются EVM.
Следует отметить, что уязвимости компилятора Solidity отличаются от уязвимостей самого EVM. Уязвимости EVM относятся к проблемам безопасности, возникающим при выполнении инструкций виртуальной машины. Поскольку злоумышленники могут загружать произвольный код в Ethereum, если в EVM есть уязвимость безопасности, это повлияет на всю сеть Ethereum, что может привести к отказу в обслуживании (DoS) и даже к захвату всей блокчейн-системы злоумышленниками. Однако дизайн EVM относительно прост, а обновления основного кода происходят нечасто, поэтому вероятность возникновения подобных проблем невелика.
Уязвимости компилятора Solidity относятся к проблемам, возникающим при преобразовании кода Solidity в код EVM. В отличие от браузеров, которые компилируют и выполняют JavaScript на клиентской машине пользователя, процесс компиляции Solidity осуществляется только на компьютере разработчика смарт-контрактов и не выполняется в сети Ethereum. Таким образом, уязвимости компилятора Solidity не оказывают непосредственного влияния на саму сеть Ethereum.
Одной из основных угроз уязвимости компилятора Solidity является возможность того, что сгенерированный код EVM будет не соответствовать ожиданиям разработчика. Поскольку смарт-контракты на Ethereum часто связаны с криптовалютными активами пользователей, любые ошибки в контрактах, вызванные компилятором, могут привести к потерям активов пользователей, что имеет серьезные последствия.
Разработчики и аудиторы контрактов могут в основном сосредоточиться на проблемах реализации логики кода контракта, а также на вопросах безопасности на уровне Solidity, таких как повторный вход и переполнение целого числа. Однако уязвимости компилятора часто трудно обнаружить только путем аудита исходного кода контракта. Необходимо проводить анализ в сочетании с конкретной версией компилятора и конкретными шаблонами кода, чтобы определить, подвержен ли смарт-контракт уязвимостям компилятора.
Пример уязвимости компилятора Solidity
Вот несколько примеров реальных уязвимостей компилятора Solidity, демонстрирующих их конкретные формы, причины и последствия.
SOL-2016-9 HighOrderByteCleanStorage
Уязвимость существует в ранних версиях компилятора Solidity (>=0.1.6 <0.4.4).
Рассмотрите следующий код:
солидность
контракт C {
uint32 a = 0x12345678;
uint32 b = 0;
функция f() публичная {
a = a + 1;
}
функция run() public view возвращает (uint) {
вернуть b;
}
}
Переменная хранения b не была изменена, поэтому функция run() должна возвращать значение по умолчанию 0. Однако в коде, сгенерированном уязвимой версией компилятора, run() будет возвращать 1.
Не понимая уязвимости этого компилятора, обычным разработчикам трудно обнаружить этот баг простым код-ревью. Хотя этот пример относительно прост и не приведет к особенно серьезным последствиям, если переменная b используется для проверки прав доступа, учета активов и т.д., такое несоответствие ожиданиям может привести к очень серьезным последствиям.
Причина возникновения этого явления заключается в том, что EVM использует стековую виртуальную машину, где каждый элемент стека имеет размер 32 байта (, то есть размер переменной uint256 ). С другой стороны, каждый слот в базовом хранилище (storage) также имеет размер 32 байта. Язык Solidity поддерживает такие типы данных, как uint32, которые меньше 32 байт. Компилятор при обработке этих типов должен выполнять соответствующую операцию очистки высоких разрядов (clean up), чтобы гарантировать правильность данных. В приведенной ситуации, когда происходит переполнение целого числа при сложении, компилятор неправильно выполняет очистку высоких разрядов результата, что приводит к тому, что бит 1 в высоком разряде записывается в хранилище, в конечном итоге перезаписывая переменную 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. В приведенном выше коде PoC запись и доступ к памяти 0 находятся в двух разных блоках assembly, а компилятор анализирует и оптимизирует только отдельный блок assembly. Поскольку в первом блоке assembly после записи в память 0 нет никаких операций чтения, эта инструкция записи считается избыточной и будет удалена, что приводит к ошибке. В уязвимой версии функция f() будет возвращать значение 0, в то время как правильное возвращаемое значение должно быть 0x42.
Уязвимость затрагивает компиляторы версий >= 0.5.8 < 0.8.16.
Рассмотрите следующий код:
солидность
контракт C {
function f(string[1] calldata a) public pure возвращает (string memory) {
вернуть abi.decode(abi.encode(a), (строка[1]))[0];
}
}
В нормальных условиях переменная a, возвращаемая приведенным выше кодом, должна быть "aaaa". Но в уязвимой версии будет возвращена пустая строка "".
Причиной уязвимости является то, что Solidity неправильно очистила некоторые данные при выполнении операции abi.encode над массивом типа calldata, что привело к изменению соседних данных и вызвало несоответствие между закодированными и декодированными данными.
Стоит отметить, что при выполнении внешнего вызова и эмиссии события в Solidity параметры неявно кодируются с помощью abi.encode, поэтому вероятность появления вышеупомянутого уязвимого кода будет выше, чем можно было бы предположить.
Рекомендации по безопасности
В связи с угрозами уязвимостей компилятора Solidity, предлагаются следующие рекомендации для разработчиков и специалистов по безопасности:
Для разработчиков:
Используйте более новую версию компилятора Solidity. Хотя новые версии могут вводить новые проблемы с безопасностью, обычно в них меньше известных проблем с безопасностью по сравнению со старыми версиями.
Улучшите тестовые случаи для юнит-тестирования. Большинство ошибок на уровне компилятора могут привести к тому, что результаты выполнения кода будут отличаться от ожидаемых. Такие проблемы трудно выявить при код-ревью, но они легко обнаруживаются на этапе тестирования. Повышение покрытия кода может максимально предотвратить такие проблемы.
Старайтесь избегать использования встроенного ассемблера, сложных операций, таких как кодирование и декодирование ABI для многомерных массивов и сложных структур, и избегайте слепого использования новых языковых особенностей и экспериментальных функций без явной необходимости. Большинство исторических уязвимостей связано с такими операциями, как встроенный ассемблер и кодировщики ABI. Компиляторы чаще всего сталкиваются с ошибками при обработке сложных языковых особенностей. С другой стороны, разработчики также могут столкнуться с ошибками при использовании новых особенностей, что приводит к проблемам безопасности.
Для охранного персонала:
При проведении аудита безопасности кода Solidity не следует игнорировать потенциальные риски безопасности, которые могут быть введены компилятором. Соответствующий пункт проверки в классификации уязвимостей смарт-контрактов ( SWC) - SWC-102: Устаревшая версия компилятора.
Внутри процесса разработки SDL настоятельно призывается команда разработчиков обновить версию компилятора Solidity и рассмотреть возможность введения автоматической проверки версии компилятора в процессе CI/CD.
Но не стоит чрезмерно паниковать по поводу уязвимостей компилятора, большинство уязвимостей компилятора срабатывают только в определенных кодовых шаблонах, и не всегда использование уязвимой версии компилятора для компиляции контракта означает наличие рисков безопасности; фактическое влияние на безопасность необходимо оценивать в зависимости от конкретного проекта.
Некоторые полезные ресурсы:
Сообщения об оповещениях безопасности, регулярно публикуемые командой Solidity
Список ошибок, регулярно обновляемый официальным репозиторием Solidity
Список ошибок компилятора для различных версий. Можно внедрить автоматическую проверку версии компилятора в процессе CI/CD, чтобы уведомлять о существующих уязвимостях в текущей версии.
На странице Code контракта на Etherscan в правом верхнем углу значок треугольного восклицательного знака указывает на существующие уязвимости в текущей версии компилятора.
Резюме
Статья начинается с основных понятий компилятора и представляет уязвимости компилятора Solidity, анализируя потенциальные риски безопасности, которые они могут вызвать в реальной среде разработки Ethereum, а также предоставляет разработчикам и специалистам по безопасности несколько практических рекомендаций по безопасности. Понимание и внимание к уязвимостям компилятора могут более полно обеспечить безопасность смарт-контрактов.
Посмотреть Оригинал
This page may contain third-party content, which is provided for information purposes only (not representations/warranties) and should not be considered as an endorsement of its views by Gate, nor as financial or professional advice. See Disclaimer for details.
18 Лайков
Награда
18
9
Поделиться
комментарий
0/400
DAOplomacy
· 07-11 19:33
возможно, еще одна не оптимальная реализация с точки зрения управления...
Посмотреть ОригиналОтветить0
LightningLady
· 07-11 19:11
Что такого, если у нас есть несколько уязвимостей компиляции?
Посмотреть ОригиналОтветить0
SandwichDetector
· 07-10 06:32
Разве это не то, что мы делаем?
Посмотреть ОригиналОтветить0
GateUser-00be86fc
· 07-08 20:59
Компилятор тоже нужно аудитировать.
Посмотреть ОригиналОтветить0
CryptoTherapist
· 07-08 20:58
давайте уделим немного внимания, чтобы осознать этот стресс от компилятора... если честно, это похоже на классическую травму системы
Посмотреть ОригиналОтветить0
CryptoPhoenix
· 07-08 20:56
Исходный код более оригинален, чем сам источник, падение приведет к возрождению.
Посмотреть ОригиналОтветить0
OnlyOnMainnet
· 07-08 20:50
Снова придется напрячь мозги, чтобы писать код.
Посмотреть ОригиналОтветить0
FrogInTheWell
· 07-08 20:47
Хорошо, что сейчас код больше не пишут на sol.
Посмотреть ОригиналОтветить0
GasGuzzler
· 07-08 20:30
Уязвимость снова начинает привлекать людей для аудита.
Анализ уязвимостей компилятора Solidity и стратегии реагирования
Подробный анализ уязвимостей компилятора Solidity и стратегии противодействия
Компилятор является одной из основных составных частей современных компьютерных систем. Это компьютерная программа, основная функция которой заключается в преобразовании исходного кода на языке высокого уровня в исполняемый код инструкций, который может выполнять центральный процессор компьютера или виртуальная машина.
Хотя большинство разработчиков и специалистов по безопасности обычно больше беспокоятся о безопасности кода приложений, безопасность самого компилятора также важна. Как компьютерная программа, компилятор также может иметь уязвимости, которые в некоторых случаях могут представлять серьезные риски для безопасности. Например, когда браузер компилирует и анализирует выполнение кода JavaScript на фронтенде, уязвимости в движке анализа JavaScript могут привести к тому, что пользователи, посещая вредоносные веб-страницы, могут быть использованы злоумышленниками, что в конечном итоге приведет к контролю над браузером жертвы или даже операционной системой.
Компилятор Solidity также имеет уязвимости. Согласно предупреждению о безопасности от команды разработчиков Solidity, в нескольких различных версиях компилятора Solidity были обнаружены проблемы с безопасностью.
Уязвимость компилятора Solidity
Основная функция компилятора Solidity заключается в преобразовании кода смарт-контрактов в машинный код команд Ethereum Virtual Machine (EVM). Эти команды EVM упаковываются в транзакции и загружаются в Ethereum, а затем интерпретируются и выполняются EVM.
Следует отметить, что уязвимости компилятора Solidity отличаются от уязвимостей самого EVM. Уязвимости EVM относятся к проблемам безопасности, возникающим при выполнении инструкций виртуальной машины. Поскольку злоумышленники могут загружать произвольный код в Ethereum, если в EVM есть уязвимость безопасности, это повлияет на всю сеть Ethereum, что может привести к отказу в обслуживании (DoS) и даже к захвату всей блокчейн-системы злоумышленниками. Однако дизайн EVM относительно прост, а обновления основного кода происходят нечасто, поэтому вероятность возникновения подобных проблем невелика.
Уязвимости компилятора Solidity относятся к проблемам, возникающим при преобразовании кода Solidity в код EVM. В отличие от браузеров, которые компилируют и выполняют JavaScript на клиентской машине пользователя, процесс компиляции Solidity осуществляется только на компьютере разработчика смарт-контрактов и не выполняется в сети Ethereum. Таким образом, уязвимости компилятора Solidity не оказывают непосредственного влияния на саму сеть Ethereum.
Одной из основных угроз уязвимости компилятора Solidity является возможность того, что сгенерированный код EVM будет не соответствовать ожиданиям разработчика. Поскольку смарт-контракты на Ethereum часто связаны с криптовалютными активами пользователей, любые ошибки в контрактах, вызванные компилятором, могут привести к потерям активов пользователей, что имеет серьезные последствия.
Разработчики и аудиторы контрактов могут в основном сосредоточиться на проблемах реализации логики кода контракта, а также на вопросах безопасности на уровне Solidity, таких как повторный вход и переполнение целого числа. Однако уязвимости компилятора часто трудно обнаружить только путем аудита исходного кода контракта. Необходимо проводить анализ в сочетании с конкретной версией компилятора и конкретными шаблонами кода, чтобы определить, подвержен ли смарт-контракт уязвимостям компилятора.
Пример уязвимости компилятора Solidity
Вот несколько примеров реальных уязвимостей компилятора Solidity, демонстрирующих их конкретные формы, причины и последствия.
SOL-2016-9 HighOrderByteCleanStorage
Уязвимость существует в ранних версиях компилятора Solidity (>=0.1.6 <0.4.4).
Рассмотрите следующий код:
солидность контракт C { uint32 a = 0x12345678; uint32 b = 0; функция f() публичная { a = a + 1; } функция run() public view возвращает (uint) { вернуть b; } }
Переменная хранения b не была изменена, поэтому функция run() должна возвращать значение по умолчанию 0. Однако в коде, сгенерированном уязвимой версией компилятора, run() будет возвращать 1.
Не понимая уязвимости этого компилятора, обычным разработчикам трудно обнаружить этот баг простым код-ревью. Хотя этот пример относительно прост и не приведет к особенно серьезным последствиям, если переменная b используется для проверки прав доступа, учета активов и т.д., такое несоответствие ожиданиям может привести к очень серьезным последствиям.
Причина возникновения этого явления заключается в том, что EVM использует стековую виртуальную машину, где каждый элемент стека имеет размер 32 байта (, то есть размер переменной uint256 ). С другой стороны, каждый слот в базовом хранилище (storage) также имеет размер 32 байта. Язык Solidity поддерживает такие типы данных, как uint32, которые меньше 32 байт. Компилятор при обработке этих типов должен выполнять соответствующую операцию очистки высоких разрядов (clean up), чтобы гарантировать правильность данных. В приведенной ситуации, когда происходит переполнение целого числа при сложении, компилятор неправильно выполняет очистку высоких разрядов результата, что приводит к тому, что бит 1 в высоком разряде записывается в хранилище, в конечном итоге перезаписывая переменную 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. В приведенном выше коде PoC запись и доступ к памяти 0 находятся в двух разных блоках assembly, а компилятор анализирует и оптимизирует только отдельный блок assembly. Поскольку в первом блоке assembly после записи в память 0 нет никаких операций чтения, эта инструкция записи считается избыточной и будет удалена, что приводит к ошибке. В уязвимой версии функция f() будет возвращать значение 0, в то время как правильное возвращаемое значение должно быть 0x42.
SOL-2022-6 AbiReencodingHeadOverflowWithStaticArrayCleanup
Уязвимость затрагивает компиляторы версий >= 0.5.8 < 0.8.16.
Рассмотрите следующий код:
солидность контракт C { function f(string[1] calldata a) public pure возвращает (string memory) { вернуть abi.decode(abi.encode(a), (строка[1]))[0]; } }
В нормальных условиях переменная a, возвращаемая приведенным выше кодом, должна быть "aaaa". Но в уязвимой версии будет возвращена пустая строка "".
Причиной уязвимости является то, что Solidity неправильно очистила некоторые данные при выполнении операции abi.encode над массивом типа calldata, что привело к изменению соседних данных и вызвало несоответствие между закодированными и декодированными данными.
Стоит отметить, что при выполнении внешнего вызова и эмиссии события в Solidity параметры неявно кодируются с помощью abi.encode, поэтому вероятность появления вышеупомянутого уязвимого кода будет выше, чем можно было бы предположить.
Рекомендации по безопасности
В связи с угрозами уязвимостей компилятора Solidity, предлагаются следующие рекомендации для разработчиков и специалистов по безопасности:
Для разработчиков:
Используйте более новую версию компилятора Solidity. Хотя новые версии могут вводить новые проблемы с безопасностью, обычно в них меньше известных проблем с безопасностью по сравнению со старыми версиями.
Улучшите тестовые случаи для юнит-тестирования. Большинство ошибок на уровне компилятора могут привести к тому, что результаты выполнения кода будут отличаться от ожидаемых. Такие проблемы трудно выявить при код-ревью, но они легко обнаруживаются на этапе тестирования. Повышение покрытия кода может максимально предотвратить такие проблемы.
Старайтесь избегать использования встроенного ассемблера, сложных операций, таких как кодирование и декодирование ABI для многомерных массивов и сложных структур, и избегайте слепого использования новых языковых особенностей и экспериментальных функций без явной необходимости. Большинство исторических уязвимостей связано с такими операциями, как встроенный ассемблер и кодировщики ABI. Компиляторы чаще всего сталкиваются с ошибками при обработке сложных языковых особенностей. С другой стороны, разработчики также могут столкнуться с ошибками при использовании новых особенностей, что приводит к проблемам безопасности.
Для охранного персонала:
При проведении аудита безопасности кода Solidity не следует игнорировать потенциальные риски безопасности, которые могут быть введены компилятором. Соответствующий пункт проверки в классификации уязвимостей смарт-контрактов ( SWC) - SWC-102: Устаревшая версия компилятора.
Внутри процесса разработки SDL настоятельно призывается команда разработчиков обновить версию компилятора Solidity и рассмотреть возможность введения автоматической проверки версии компилятора в процессе CI/CD.
Но не стоит чрезмерно паниковать по поводу уязвимостей компилятора, большинство уязвимостей компилятора срабатывают только в определенных кодовых шаблонах, и не всегда использование уязвимой версии компилятора для компиляции контракта означает наличие рисков безопасности; фактическое влияние на безопасность необходимо оценивать в зависимости от конкретного проекта.
Некоторые полезные ресурсы:
Резюме
Статья начинается с основных понятий компилятора и представляет уязвимости компилятора Solidity, анализируя потенциальные риски безопасности, которые они могут вызвать в реальной среде разработки Ethereum, а также предоставляет разработчикам и специалистам по безопасности несколько практических рекомендаций по безопасности. Понимание и внимание к уязвимостям компилятора могут более полно обеспечить безопасность смарт-контрактов.