Безопасное Использование C++

От переводчика.

Этот текст является переводом документа Безопасное использование C++ , опубликованный командой Chromium/Chrome в Google. Текст активен обсуждается на Reddit , и команда PVS-Studio решила, что аудитории Хабра может быть интересно познакомиться с его русскоязычной версией.

Для перевода использовался текст от 20 сентября 2021 года, и на момент прочтения он может отличаться от текста по ссылке.

Этот документ находится в открытом доступе.

Коммиттеры Chromium могут оставлять свои комментарии по адресу оригинальный документ .

Если вы хотели бы оставить комментарий, но не можете этого сделать, отправьте электронное письмо по адресу Palmer@.

Наслаждайся чтением! Сценаристы/редакторы: Адетейлор, Палмер Участники: ajgo, danakj, davidben, dcheng, dmitrig, enh, jannh, jdoerrie, joenotcharles, kcc, markbrand, mmoroz, mpdenton, pkasting, rsesek, tsepez, awhalley, you!



Введение

Служба безопасности Chrome попросил рассмотреть [ссылка только для Google, извините] Как сделать C++ более безопасным.

В этом документе описаны различные механизмы, которые мы можем использовать, чтобы сделать C++ более безопасным.

Некоторые из этих механизмов радикальны.

Использование некоторых (или большинства) из этих механизмов может привести к тому, что код будет выглядеть совсем не так, как ожидают программисты C++.

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

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

Например, Chromium уже использует -fno-исключения , и здесь мы предлагаем -ftrapv , -fwrapv или fsanitize = целочисленное переполнение со знаком .

Некоторые из этих механизмов уже используются в сборках Chromium с разной степенью успеха.

Например: интеллектуальный указатель, устойчивый к UAF. ЧудоПтр проходит тестирование производительности.

Мы расширили использование Oilpan до PDFium, а усиление //base, WTF и Abseil является значительным и доказало свою эффективность.

Другие механизмы, которые мы предлагаем, представляют собой важные новые направления для C++ и Chromium и могут даже потребовать новых исследований и разработок в открытых проблемных областях (например, новые формы статического анализа).

Примечание переводчика.

И вообще, похоже, команде Chromium стоит больше внимания уделять статическому анализу :).

Каждый раз, когда команда PVS-Studio проверяла Chromium, выявлялось большое количество ошибок.

И это не абстрактные слова.

Сборники ошибок: 2011-1 , 2011-2 , 2013-1 , 2013-2 , 2016 , 2018-1 , 2018-2 , 2018-3 , 2018-4 , 2018-5 , 2018-6 , 2018-7 .

Язык и культура C++ имеют тенденцию отдавать предпочтение эффективности, а не безопасности.

Поэтому многие из предлагаемых изменений сложны, противоречивы и не так надежны, как аналогичные изменения в другом языке.

Кроме того, они иногда могут влиять на микро- и макроэффективность (время, пространство или размер объектного кода).



Предыдущая работа

Безопасный C++ — мечта многих.

Существует Проект C++ Core Guidelines И Безопасный инструмент C++ (а также его Библиотека SaferCPlusPlus И инструмент автоматического перевода ).

Мы не предлагаем новый язык, а, например, CCлечено И Циклон были интересные попытки создать новые варианты языка C, совместимые с существующим C. Также смотрите анализ Как тегирование памяти может изменить ситуацию в сфере безопасности [только Google].



Предварительные условия

Существует два основных типа безопасности памяти: пространственная и временная.

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

Примеры включают границы массива, доступ к объединению структур и полей, а также доступ к итератору.

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

Примеры включают использование после освобождения (UAF), двойное освобождение, использование перед инициализацией и использование после перемещения (UAM).

Временные нарушения безопасности часто выглядят как путаница типов.

Например, программа по ошибке использует недавно освобожденный объект. Собака , как если бы это был предмет Кот .

(Злоумышленники часто создают полностью поддельные объекты, используя виртуальные таблицы, что дает им контроль над работой программы.

) Из двух типов безопасности относительно легче обеспечить пространственную безопасность (путем внесения изменений в код Chromium и/или путем упаковки целей в WASM), хотя и с минимальными затратами на повышение эффективности.

Например, вам нужно выполнить проверку границ массива, и это может стоить дороже, чем ее отсутствие.

Это чисто эмпирический вопрос, который можно решить только в контексте реальной программы, и результаты могут оказаться неожиданными.

Временную безопасность обеспечить труднее и дороже.

Решения включают повсеместный подсчет ссылок (например, Objective-C с ARC, Swift), запрет общего изменяемого состояния и встраивание средства проверки заимствований в компилятор (например, Rust) или полностью базовую сборку мусора (Go, JavaScript и другие).

Мы верим что, при условии достаточной толерантности к регрессии микроэффективности Мы могли бы существенно устранить пространственную небезопасность C++ в машинном коде.

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

Обратите внимание, что хотя это Может быть и обычно технически относительно проста, эта работа вызывает споры в сообществах C++, включая Chromium. Мы не можем исключить или устранить временную небезопасность в C++, не приняв одно из известных решений, таких как сборка мусора.

Микроэффективная и эргономичная безопасность синхронизации остается открытой проблемой при разработке программного обеспечения.

Однако ценой некоторых потенциально значительных затрат на повышение эффективности мы можем уменьшить распространенность и возможность использования временной незащищенности.

*Сканирование — многообещающая возможность , с которым мы экспериментируем.



Неопределенное поведение

Большинство проблем C/C++ связано с неопределённым поведением (UB), встроенным в спецификации языка и библиотек.

(Даже самые недавние языковые дополнения продолжают эту традицию.

) Пространственная и временная безопасность — это подтипы неопределенного поведения; другие примеры включают переполнение целых чисел со знаком.

Простой список всех неопределенных поведений в C++ является проектом с открытым исходным кодом.

.

Авторы компилятора рассматривают неопределенное поведение как возможность микрооптимизации.

, пока злоумышленники рассматривают неопределенное поведение как возможность для эксплуатации .

Что касается программного обеспечения, работающего в непредсказуемых и даже враждебных средах, таких как Интернет, все чаще признается, что написание надежного и безопасного программного обеспечения на C/C++ — чрезвычайно сложная задача из-за многих форм неопределенного поведения, связанных с безопасностью и эргономикой.



Цель этого документа

Имея это в виду, наша цель — перечислить некоторые возможные шаги, которые участники Chromium могли бы предпринять, чтобы уменьшить общее использование и непригодность UB при использовании C++ в Chromium. Невозможно полностью исправить C++ без его фундаментального переопределения.

Это не наша цель.

Вместо этого мы хотим выявить и сократить некоторые из наиболее стойких и эффективных типов небезопасных UB при использовании Chromium.

Расстановка приоритетов и мотивация

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

Это чистое количество ошибок, но учтите, что злоумышленники предпочитают использовать разные классы ошибок:

Безопасное использование C++

( источник ; ссылка предназначена только для Google, но результаты можно воспроизвести с помощью публичный трекер ошибок ) Предложения и текущая работа, описанные в оставшейся части этой статьи, примерно упорядочены по влиянию, которое они могут оказать на наши наиболее важные классы ошибок.



Управление ожиданиями

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

Хотя мы считаем, что большинство из них необходимы, мы также знаем, что их недостаточно.

В этом суть проблемы C++.

С этими словами мы представляем их вам.



Удаление/уменьшение необработанных указателей

Проблема : Ручное управление сроком службы и владением оказалось слишком сложным, чтобы даже очень опытные инженеры могли надежно выполнять правильные действия.

Это приводит к ошибкам UAF, а также к утечкам памяти.

Решение : Запретить прямое использование необработанных указателей, новый И удалить .

Вместо этого попросите разработчиков использовать, например, одну из реализаций MiraclePtr. Обратите внимание, что упаковка указателей в определенный синтаксис полезна для большинства подходов (Oilpan, MiraclePtr, *Scan и т. д.) и ценна сама по себе.

Текущее состояние : Работа продолжается для полей T* в процессе браузера (по состоянию на август 2021 г.

).

Проект MiraclePtr направлен на создание интеллектуального указателя, который сделает UAF непригодным для использования, не слишком влияя на производительность.

Затраты : Производительность.

По состоянию на август 2021 года мы активно оцениваем влияние нескольких реализаций, поэтому этот момент будет обновлен.

Отклонение от норм языкового сообщества C++.

C++ за пределами Chrome с гораздо меньшей вероятностью будет использовать этот подход. Таким образом, он не получит временной безопасности, которую обеспечивает MiraclePtr. Например, код Google, вставленный в Chrome, зависимости с открытым исходным кодом.

Сложность диагностики сбоев в том, что у MiraclePtr только один стек вызовов, а не три у ASAN: allocate, free, use. Если мы хотим применить эту защиту к стороннему коду, нам нужно будет разветвить репозитории, чтобы другие их потребители также использовали нестандартный C++.

Большинство UAF встречаются в собственном коде.

.

Постоянные затраты на переписывание большого количества кода — конфликты слияния и т. д. Преимущества: На долю ВСУ приходится около 48% серьезных ошибок безопасности (и их число растет).

MiraclePtr планирует охватить ~50% из них.

и теоретически может покрыть еще 20% в будущем, запретив необработанные указатели в контейнерах, локальных переменных и параметрах функций.

На практике использование MiraclePtr в локальных переменных и циклах, критичных к производительности, может привести к непомерным затратам производительности.

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

Для получения дополнительной информации см.

" Идеи безопасности вывесок ".

Однако Project Zero считает, что внутри процессов рендеринга большинство UAF не являются исправлениями MiraclePtr. Вместо этого они приводят к аннулированию итераторов и другим сбоям в течение всего срока службы.



Пожизненная аннотация

Проблема: Время жизни в C++ неизвестно компилятору и не может быть отслежено с помощью статического анализатора.

Решение: В некоторых случаях мы можем аннотировать время жизни с помощью [[clang::lifetimebound]] .

Это говорит нам о том, что время жизни привязано к объекту.

Атрибут имеет множество ограничений.

Это не решение для обеспечения безопасности памяти, но оно может помочь в некоторых важных сценариях.

Ограничения:

  • Невозможно провести различие между разными периодами жизни.

  • Невозможно аннотировать статическое время жизни.

  • Атрибут прикрепляется к параметрам функции и всегда неявно ссылается на самый внешний ссылочный тип; его нельзя прикрепить к части типа — например, к T* в константный стандарт::вектор & .

  • Единственное время жизни неявно применяется к внешнему ссылочному типу в возвращаемом типе функции или к значению сконструированного объекта в случае конструктора.

    Опять же, невозможно связать время жизни с внутренними ссылочными типами в возвращаемом значении - например, T* в константный стандарт::вектор & .

  • Невозможно добавить параметр времени жизни в состав .

    Это означает, что параметры могут быть привязаны к времени жизни объекта только в том случае, если они указаны в конструкторе, а не в других методах установки.

Теоретически мы должны применить это к:
  • Любой ссылочный параметр конструктора, хранящийся в поле.

    Однако в нем отсутствуют даже тривиальные примеры: https://godbolt.org/z/Ysq41G6vb

  • Любой параметр указателя конструктора, который может содержаться в константном элементе.

    Другими словами, указатель никогда не будет переназначен.

    Однако в нем отсутствуют даже тривиальные примеры: https://godbolt.org/z/Ma7P8q8WG

  • Любой метод класса, который возвращает ссылку или указатель на член класса, но, к сожалению, не на указатели внутри членов класса.

    Однако в нем отсутствуют даже тривиальные примеры: https://godbolt.org/z/9er4WE6zK , https://godbolt.org/z/GW9j4zrdT

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

    Сюда входят функции шаблона, которые возвращают один из своих входных данных, например min/max/clamp.

Единственный раз, когда этот атрибут действительно фиксируется только в данный момент, это когда незаконное использование временных объектов .

Хотя это действительная/важная ошибка безопасности памяти, она не соответствует типу ошибок, которые мы видим в наших ошибках безопасности UAF.

Некоторые ключевые потенциальные места для этого:
  • конструктор base::span
  • конструкции base::StringPiece
  • база::зажим
  • ????
  • Методы, возвращающие ссылки/указатели, встречаются повсюду, но только если мы сможем показать, что атрибут действительно помогает (противоположные примеры см.

    в разделе «Godbols» выше).

Обратите внимание, что база::промежуток предназначен для хранения недопустимого указателя вне контейнера, на который он «указывает», и анализ времени жизни не может помочь решить эту проблему.

Это проблема безопасности пространственной памяти, встроенная в C++.

Но потенциально может помочь использование база::промежуток за пределами срока существования объекта, на который он указывает, если атрибут был использован неправильно.

Текущее состояние: Работа не началась .

Затраты: В коде присутствуют визуально зашумленные аннотации (куча макросов Abseil).

Мы познакомимся с ними поближе.

Но есть много мест, где эти аннотации использовать нельзя.

Аннотация может быть написана неправильно, если она более ограничительна, чем требуется для объекта.

Или оно может со временем стать неверным, если объект будет изменен.

Например, если база::промежуток создал метод переназначения указателей.

Поскольку атрибут генерирует предупреждения только в случае обнаружения нарушения, он фактически не гарантирует, что нарушения будут обнаружены.

И похоже, что большинство из них таковыми не являются (см.

примеры божьих болтов выше).

Это может создать ложное впечатление о безопасности, что может привести к тому, что разработчики попытаются полагаться на атрибут, который улавливает их ошибки и фактически пишет более УАФ.

Кроме того, невозможно обеспечить присутствие аннотаций там, где это возможно, что позволило бы писать новый код без них.

Авторы кода, которые полагаются на аннотации для проверки их правильности, останутся без каких-либо проверок.

Преимущества: Ошибки компилятора при написании определенного набора простых ошибок времени жизни.



Реализация автоматического управления памятью

Проблема: Временная безопасность и корректность (UAF, утечки).

Решение: Подсчет ссылок (например, похожих АРК семантика) и/или полный запуск сборщика мусора.

Текущее состояние: Oilpan теперь представляет собой многоразовую библиотеку общего назначения (больше не специфичную для Blink).

Плюс мы портировали его в PDFium чтобы решить многие из наших временных проблем безопасности в этом проекте.

Например, это позволило нам реализовать поддержку XFA Forms! Однако на данный момент он отключен по умолчанию из-за пробелов в функциональности.

Затраты: Подсчет ссылок должен быть атомарным, что потребует микрозатрат с точки зрения времени.

Полностью универсальный сборщик мусора может стоить дорого.

Преимущества: На долю ВСУ приходится около 48% серьезных ошибок безопасности (и число растет).

Этот подход является альтернативой универсальному использованию проверяемых типов указателей ( см.

выше ).

Кроме того, сбор мусора имеет отличную эргономику для разработчика.



Проведение анализа собственности

Проблема: Временная безопасность и корректность (UAF, утечки).

Решения: Во время выполнения убедитесь, что у любого объекта есть один «владелец» и что объект может быть изменен только станд::переместить .

Включите заимствование с помощью правил, подобных Rust, которые предотвращают одновременное существование нескольких изменяемых ссылок и гарантируют, что к объектам не будет доступа после окончания их срока службы.

Решение кажется плохо подходящим для C++, но оно все же предлагается.

Поэтому обсуждение этого здесь может быть важным.

Rust достигает этой модели благодаря довольно сложному поддержка компилятора .

Большинству объектов не требуется никаких дополнительных затрат времени выполнения для такого рода проверки владения; это все статично.

Вместо этого разработчики могут дополнительно использовать версию времени выполнения ( РефСелл<> ), который выполняет те же проверки во время выполнения.

Мы полагаем, что эта модель будет слишком дорогой, если каждый объект отслеживался во время исполнения, и мы не видим способа обеспечить статическое выполнение во время сборки на C++ без радикальных изменений в компиляторе и языке.

.

(Кланг добавил ограничения по времени жизнь для простых случаев, но см.

выше ).

Текущее состояние: У нас есть несколько ранних экспериментов по обеспечению владения во время выполнения .

Безопасность во время компиляции невозможный без фундаментальных изменений в C++, таких как новые ссылочные типы.

Есть работа по ограниченной безопасности в Предупреждения Clang, которые помогут обнаружить висячие ссылки используя анализ потока управления, но по своей конструкции они не будут перехватывать недопустимые указатели кучи.

Затраты: Стоимость выполнения эквивалентна подсчету ссылок.

Необходимо различать «владелец_птр» и «заимствованный_птр».

Преимущества: На долю ВСУ приходится около 48% серьезных ошибок безопасности (и число растет).

Этот подход является альтернативой универсальному использованию проверяемых типов указателей (см.

выше).



Использование -Wdangling-gsl

Google преуспел в этом, находя и исправляя ошибки UAF. Chrome тоже должен попробовать это.

Есть несколько ложных срабатываний, но есть и множество истинных срабатываний.



Определение всех стандартных поведений библиотеки

(Где возможно) Проблема: Стандартная библиотека пронизана потенциально уязвимым неопределённым поведением.

Это не включает проверку границ (например, std::span::operator[] ) и достоверность ( std::optional::operator* ).

Доступность std::string_view для ВСУ , однако, это отдельная тема.

Это особенно прискорбно в недавних дополнениях библиотек, поскольку уже были доступны быстрые, но небезопасные варианты.

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

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

Например, Абсель.

В качестве альтернативы мы могли бы выделить персонал для работы с исходной версией libcxx, чтобы обеспечить надежность и поддержку жесткого режима.

Решения: Добавьте «защищенный» режим (выбираемый во время компиляции) в реализацию стандартной библиотеки.

Это позволит вам изменить неопределенное поведение на четко определенное и безопасное.

Это довольно «просто» с точки зрения пространственной безопасности.

О временной безопасности см.

Теги: #информационная безопасность #ошибки #программирование #Google #Google Chrome #открытый код #C++ #si #статический анализ кода #качество кода #Chromium #ошибки в коде #неопределённое поведение #переполнение буфера #переполнение буфера #переполнение буфера #неопределённое поведение # нулевые указатели #asan

Вместе с данным постом часто просматривают: