Может ли ASAN GCC обеспечить ту же безопасность памяти, что и Rust?

Rust известен как безопасный для памяти язык, но в GCC есть функция безопасности AddressSanitizer (ASAN):

./configure CFLAGS="-fsanitize=address -g" CXXFLAGS="-fsanitize=address -g" LDFLAGS="-fsanitize=address"
make
make check

Может ли ASAN обеспечить ту же безопасность памяти, что и Rust, или Rust имеет больше хитростей? Можно ли даже сравнить два?

Отказ от ответственности: я не программист.

3 ответа

Решение

Дезинфицирующие средства

И GCC, и Clang имеют набор дезинфицирующих средств; до сих пор они разрабатывались в Clang, а затем портировались в GCC, поэтому у Clang самые продвинутые версии:

  • Address Sanitizer (ASan): обнаруживает выход за пределы допустимого, использование после освобождения, использование после области, двойное освобождение / недопустимое освобождение и добавление поддержки утечек памяти (ожидаемые накладные расходы памяти 3x),
  • Memory Sanitizer (MemSan): обнаруживает неинициализированные чтения (ожидаемое замедление в 3 раза),
  • Thread Sanitizer (TSan): обнаруживает гонки данных (ожидаемое замедление 5x-15x, перегрузка памяти 5x-10x),
  • https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html (UBSan): различные локальные неопределенные поведения, такие как невыровненные указатели, переполнения целых / плавающих точек и т. Д. (Минимальное замедление, небольшое увеличение размера кода).

Также ведутся работы по дезинфицирующему средству.


Дезинфицирующие средства против ржавчины

К сожалению, доведение C++ до уровня безопасности Rust с помощью дезинфицирующих средств невозможно; даже объединение всех существующих дезинфицирующих средств все равно оставит пробелы, они, как известно, являются неполными.

Вы можете увидеть презентацию Джона Регера о неопределенном поведении на CppCon 2017, слайды можно найти на github, откуда мы получаем текущее покрытие:

И это не учитывает того факта, что дезинфицирующие средства несовместимы друг с другом. То есть, даже если вы готовы принять совместное замедление (15x-45x?) И накладные расходы памяти (15x-30x?), Вам все равно НЕ удастся сделать программу на C++ такой же безопасной, как Rust.


Усиление против отладки

Причина, по которой дезинфицирующие средства так сильно загружают процессор / память, заключается в том, что они являются средствами отладки; они пытаются дать разработчикам как можно более точную диагностику, чтобы быть максимально полезными для отладки.

Для запуска кода в производственной среде вам нужно только усилить защиту. Усиление - это устранение неопределенного поведения с минимально возможными накладными расходами. Clang, например, поддерживает несколько способов укрепить двоичный файл:

Эти инструменты можно комбинировать и оказывать минимальное (< 1%) влияние на производительность. К сожалению, они занимают гораздо меньше территории, чем дезинфицирующие средства, и, в частности, они не пытаются охватить использование после освобождения / использование после области действия или гонки данных, которые являются частыми целями атак.


Заключение

Я не вижу способа довести C++ до уровня безопасности, который сочетает Rust, без:

  • очень серьезные ограничения по языку: см. рекомендации MISRA/JSF,
  • очень серьезная потеря производительности: дезинфицирующие средства, отключение оптимизации,...
  • полный пересмотр стандартных библиотек и методов кодирования, с которых и начинается базовое руководство.

С другой стороны, стоит отметить, что сам Rust использует unsafe код; И его unsafe Код также должен быть проверен (см. проект Rust Belt) и будет полезен для всех вышеперечисленных проходов дезинфицирующих средств / инструментов для закалки.

Нет, эти две функции не сопоставимы.

Санация адресов не является функцией безопасности и не обеспечивает безопасность памяти: это инструмент отладки. У программистов уже есть инструменты, чтобы обнаружить, что у написанного ими кода есть проблемы с памятью, такие как использование после освобождения или утечки памяти. Valgrind, пожалуй, самый известный пример. Эта функция gcc предоставляет (некоторые из них) ту же функциональность: единственное новое - это то, что она интегрирована с компилятором, поэтому ее проще использовать.

Эта функция не будет включена в производство: она предназначена только для отладки. Вы компилируете свои тесты с этим флагом, и они автоматически обнаруживают ошибки памяти, вызванные тестом. Если ваших тестов недостаточно, чтобы вызвать проблему, то у вас все еще есть проблема, и она все равно вызовет те же недостатки безопасности в производстве.

Модель владения Rust предотвращает эти дефекты, делая программы, содержащие такие дефекты, недействительными: компилятор не будет их компилировать. Вам не нужно беспокоиться о том, чтобы ваши тесты не вызывали проблему, потому что, если код компилируется, проблемы быть не может.

Две функции для разных наборов проблем. Одной из особенностей очистки адресов является обнаружение утечек памяти (выделение памяти и пренебрежение, чтобы освободить ее позже). Rust усложняет написание утечек памяти, чем в C или C++, но это все же возможно (если у вас есть циклические ссылки). Модель владения Rust предотвращает гонки данных в последовательных и многопоточных ситуациях (см. Ниже). Санитарная обработка адресов не ставит целью выявить ни один из этих случаев.

Пример гонки данных в последовательном коде - это если вы перебираете коллекцию объектов, добавляя или удаляя элементы. В C++ изменение большинства коллекций сделает недействительными любые итераторы, но программист должен понять, что это произошло: он не обнаружен (хотя некоторые коллекции имеют дополнительные проверки в отладочных сборках). В Rust невозможно изменить коллекцию, пока существует итератор, потому что модель владения предотвращает это.

Примером гонки данных в многопоточном коде является наличие двух потоков, совместно использующих объект, с доступом, защищенным мьютексом. В C++ программист может забыть заблокировать мьютекс при изменении объекта. В Rust сам мьютекс владеет объектом, который он защищает, поэтому небезопасный доступ к нему невозможен. (Однако есть много других ошибок параллелизма, так что не увлекайтесь!)

Не слышал об этой опции, но похоже, что она изменяет программу вывода. Другими словами, он проверяет во время работы программы.

Rust, с другой стороны, проверяет, когда программа создана (или скомпилирована на языке программиста), поэтому, во-первых, этих ошибок безопасности памяти нет.

Связанная статья упоминает, что она в любом случае охватывает только один случай, используйте после возврата.

Другие вопросы по тегам