В интернете огромная проблема из-за C/C++, но программисты не хотят ее решать

Что общего у уязвимостей Heartbleed, WannaCry, и ошибок в iPhone ценой в миллионы долларов?

Алекс Гейнор, 15 ноября 2018, 15:39, оригинал

Алекс -- специалист по безопасности софта в проекте Mozilla, где он занимается "песочницей" и борется с уязвимостями в проекте Firefox. Ранее он был программистом в компании the United States Digital Service, а также членом совета директоров двух организаций: Python Software Foundation и Django Software Foundation.

© Ф.В.Ткачев, перевод на русский язык, 2018-11-19.
Выделения жирным шрифтом в тексте и [примечания] сделаны переводчиком.
Обсуждение: https://forum.oberoncore.ru/viewtopic.php?f=89&t=6312

Одна ошибка поражает iPhone, другая -- Windows, третья -- серверы, работающие на Linux. На первый взгляд инциденты не связаны друг с другом, но в реальности все три оказались возможны из-за того, что программы были написан на языках программирования, допускающих ошибки из категории "некорректная работа с памятью" (memory unsafety). Допуская такие ошибки в программах, языки вроде C и C++ открывают шлюзы для потока критических уязвимостей компьютерных систем, не иссякающего годами.

Представим себе программу со списком из 10 чисел. Что произойдёт, если запросить 11-е число из списка? Большинство скажет, что должна произойти какая-то ошибка, и в языке, гарантирующем корректную работу с памятью, (например, в питоне или Java [или в Обероне --прим. перев.]) так и случится.

В языке без таких гарантий программа обратится к тому участку памяти, где находился бы 11-й элемент (если бы он существовал) и попытается прочесть его. Иногда это вызовет аварийную остановку, но во многих случаях программа продолжит работать с этим участком памяти, даже если он не имеет никакого отношения к нашему списку. Уязвимость этого типа называется “переполнение буфера”, и это один из самых распространенных видов некорректного доступа к памяти.
Уязвимость HeartBleed, поразившая 17% защищённых серверов в интернете, входила в эту категорию и позволяла прочитать 60 килобайт данных за пределами списка, включая пароли и другие данные пользователей.

Но в программах, написанных на C/C++, есть и другие типы ошибок, связанные с некорректным доступом к памяти.
Вот примеры:
“ошибки типизации” ("type confusion” -- ошибочная интерпретация данных, хранящихся в некотором месте памяти);
“использование после освобождения" ("use after free" -- обращение к участку памяти после того, как программа сообщила операционной системе, что она закончила работать с этим участком);
“использование неинициализированной памяти” (“use of uninitialized memory” -- чтение данных из участка памяти до того, как программа туда что-то записала).
Подобные ошибки создают уязвимости из числа наиболее часто встречающихся в таком широко используемом программном обеспечении, как Firefox, Chrome, Windows, Android и iOS. Я больше года
отслеживал информацию о проблемах с безопасностью в этих проектах, и почти в каждом релизе этих продуктов больше половины уязвимостей связаны с некорректной работой с памятью.
Ещё больше беспокоит тот факт, что уязвимости высокого и критического уровней (дающие возможность атакующему выполнять на вашем компьютере произвольный код; это самые опасные уязвимости) -- почти всегда используют некорректный доступ к памяти.
За последний год, изучая защищённость широко используемых библиотек для обработки изображений
ImageMagick и GraphicsMagic, я обнаружил более 400 уязвимостей этой категории.

Если эти уязвимости так широко распространены, могут причинять такой ущерб, и при этом есть языки, их исключающие, то почему C and C++ всё еще широко используются?

Во-первых, хотя сейчас есть выбор языков, исключающих некорректную работу с памятью, так было не всегда. C и C++ существуют десятки лет и чрезвычайно популярны, тогда как языки, обеспечивающие защиту памяти и пригодные для низкоуровневого программирования в браузерах и операционных системах, вроде Rust и Swift, становятся популярными только сейчас. [Оберон, лучший язык этого класса, был доступен с начала 1990-х --прим. перев.]

Более серьёзная проблема состоит в том, что, когда разработчики выбирают язык программирования для нового проекта, они обычно принимают решение, основываясь на том, с какими языками знакомы их программисты, на соображениях производительности и наличия экосистемы библиотек, которые можно использовать. Безопасность почти никогда не находится в центре внимания. То есть языки, которые делают упор на безопасность за счёт простоты использования, проигрывают.

Далее, многие программные проекты из числа самых важных для безопасности в интернете не новы, они стартовали десять и более лет назад, например Linux, OpenSSL и веб-сервер Apache -- все старше двадцати лет.
Для таких больших проектов простое переписывание всего сразу на новом языке исключено; миграция должна выполняться постепенно. Это означает, что проекты должны будут писаться на двух языках вместо одного, что повышает сложность. Это может также потребовать переучивания огромной команды, на что нужны время и деньги.
[
Следует признать, что понимание этой проблемы, отраженное в языковом механизме статической типизации, у лучших специалистов имелось со времен Паскаля и в дальнейшем только углублялось -- ср, языки Модула-2 и Ада. Оберон, где этот механизм доведен до тотального совершенства, был объявлен ещё в 1988 году. Прискорбно, что промышленный мейнстрим оказался неспособен воспринять это понимание и начать решать проблему некорректной работы с памятью тогда, когда сделать это было гораздо дешевле. --прим. перев.]

Наконец, самая большая проблема в том, что многие разработчики не видят здесь проблемы. Многие программисты уверены, что проблема не в том, что языки вроде C/C++ открывают шлюзы для таких уязвимостей, но что другие программисты пишут плохой код. Согласно этой теории, проблема не в том, что попытка получить 11-й элемент в списке из 10 элементов может приводить к критической уязвимости, а в том прежде всего, что кто-то написал код, обращающийся к 11-му элементу, что этот программист был недостаточно хорош или недостаточно дисциплинирован. Другими словами, распространено мнение, что суть проблемы не в языке программирования, а в неумении им пользоваться. Многие разработчики разделяют этот взгляд, несмотря на горы фактов, свидетельствующих об обратном, -- такие уязвимости вездесущи, они поражают компании с самыми большими бюджетами на безопасность и с самыми талантливыми программистами!

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

Однако есть и хорошие новости. Не все отрицают существование проблемы. Rust (предупреждение: основной спонсор Rust’а -- мой работодатель, Mozilla) -- это сравнительно новый язык программирования, спроектированный так, чтобы решать любую задачу, решаемую на C и C++, но гарантируя корректную работу с памятью и, таким образом, исключая соответствующие уязвимости. Rust набирает популярность, его используют Mozilla, Google, Dropbox, и Facebook; мне кажется, что это показывает, что многие специалисты уже ищут системные решения для проблемы некорректной работы с памятью. Язык программирования Swift компании Apple тоже гарантирует корректную работу с памятью, в отличие от своего предшественника Objective-C. [Разумеется, нельзя не назвать в этом ряду Оберон --прим. перев.]

Можно сделать многое, чтобы ускорить поиск всеобъемлющего решения для бедствия, каким является некорректная работа с памятью.
Во-первых, можно попытаться получше оценить ущерб, наносимый уязвимостями из-за некорректной работы с памятью. Я отслеживал некоторые проекты в этом отношении, но здесь возможен и более систематический подход. В проекте
CVE, публичной базе данных известных уязвимостей, можно было бы для каждой уязвимости указывать, связана ли она с некорректной работой с памятью, и можно ли было ее предотвратить выбором безопасного языка. Это помогло бы ответить на вопросы вроде "Какие проекты получили бы наибольшую пользу от языка программирования, исключающего некорректную работу с памятью?"

Во-вторых, можно изучать вопрос, как лучше переводить существующие большие программные проекты на безопасные языки.
На данный момент трудно даже помыслить задачу перевода, скажем, ядра Линукса на другой язык программирования.
Целенаправленные исследования того, какие инструменты могли бы помочь с этим, или как проектировать языки программирования для облегчения такой задачи, могли бы радикально сократить стоимость улучшения старых больших проектов.

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

В настоящее время некорректная работа с памятью -- бич нашей индустрии. Нельзя считать нормальным, что в каждом обновлении системы Windows или браузера Firefox исправляются десятки уязвимостей, которые можно было бы предотвратить.
Мы должны перестать смотреть на каждую уязвимость из-за некорректной работы с памятью как на изолированный случай, потому что они представляют собой
глубоко укоренённую системную проблему.
И тогда мы должны инвестировать в исследования и разработки более адекватных инструментов для решения этой проблемы.
Если приложить усилия и провести такие изменения, то компьютерная безопасность радикально повысилась бы для всех пользователей, а Heartbleed, WannaCry и ошибки в iPhone ценой в миллионы долларов случались бы гораздо реже.