Что такое «безопасность памяти» и почему о ней все говорят

Безопасность памяти — это свойство программного обеспечения, гарантирующее отсутствие уязвимостей вроде переполнения буфера, двойного освобождения памяти и других подобных проблем. Последние десять лет эта тема стала особенно популярной в сообществе разработчиков и приобрела особую известность вместе с ростом языка Rust.

Rust не изобрёл концепцию безопасности памяти и даже не стал первым языком, её обеспечивающим. Его заслуга в другом: Rust прорвал последний рубеж — сферу системного программирования, где безопасность памяти долгое время считалась невозможной без потери производительности.

Главная особенность Rust — это проверка безопасности памяти на этапе компиляции, с помощью статического анализа, выросшего из результатов академических исследований, вроде языка Cyclone (безопасного подмножества C). Rust впервые предложил «безопасность по умолчанию» для мира системного кода — того, где создаются операционные системы, базы данных, файловые системы, прошивки и встроенные решения. И тем самым внёс новую идею для политиков и руководителей инженерных команд: массовое снижение числа уязвимостей, связанных с памятью, во всех отраслях.

Безопасность, доказанная на практике

С момента появления Rust крупные технологические компании начали делиться отчётами о внедрении языка — будь то переписывание старых модулей или создание новых компонентов. Результаты оказались удивительно схожими: примерно на 70% меньше уязвимостей, связанных с памятью.

Rust не просто обещает безопасность — он доказывает её делом. Это превращает абстрактные разговоры о «качестве кода» в ощутимое снижение бизнес‑рисков и затрат (а уязвимости, как известно, стоят дорого). Поэтому интерес к Rust и к самой идее безопасных языков вышел далеко за пределы инженерных отделов — на уровень руководства и государственных программ.

Проблема выбора языка

Выбор языка — всегда спорная тема. Он не существует в вакууме и зависит от множества факторов: знаний разработчиков, сроков, бюджета, требований к корректности и производительности, планов по найму и обучению, а в случае open source — ещё и от того, какой язык привлечёт больше контрибьюторов. Да и часто выбор сделан уже давно — иногда десятилетия назад.

Что делать с таким наследием? Если ваш код написан на C или C++, и он небезопасен с точки зрения работы с памятью, — как добиться безопасности, не выбросив весь проект на помойку?

«Переписать всё на Rust!» — заманчиво, но опасно

В некоторых кругах ответ звучит просто: переписать всё на Rust. Аргументы сторонников очевидны: современный язык, хорошие инструменты, сопоставимая производительность и — главное — безопасность памяти.

Но опытные разработчики знают: глобальные переписывания — это дорого, рискованно и зачастую бессмысленно. Часто призывы «начать с нуля» исходят не из рационального анализа, а из эстетического раздражения старым кодом — «уродливым», «устаревшим». Парадоксально, но нередко такие призывы исходят от тех, кто просто не умеет или не хочет разбираться в сложных, но работающих системах.

Между «смириться с небезопасностью» и «всё переписать заново» существует множество промежуточных путей. Отказ от движения к безопасности памяти по принципу «всё или ничего» — ошибка, особенно когда её можно достичь постепенно, без катастроф для бюджета и сроков.

Если вы пока не уверены, что безопасность памяти действительно того стоит, — эта статья написана для вас. Я постараюсь показать, что путь к безопасности памяти заслуживает серьёзного отношения, даже для старого кода.

Rust — не обязателен. Главное — начать.

В рамках бюджета и по графику

Программные системы не существуют в вакууме: они создаются ради пользы — чтобы ускорить процессы, автоматизировать работу, обслуживать клиентов. Главные ограничения в разработке — это время и деньги, а не теоретические возможности языков.

В отчёте «The Case for Memory Safe Roadmaps», подготовленном совместно правительственными агентствами стран «Пяти глаз» (США, Великобритания, Австралия, Новая Зеландия и Канада), прямо рекомендовано создание Дорожных карт перехода к безопасным языкам.

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

Почему переписать все сложно — и опасно

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

Поэтому переписывание таких систем — риск само по себе. Любая ошибка в процессе может нарушить работу всей компании. Даже постепенная замена модулей может привести к сбоям, если что‑то пойдёт не так.

Есть и другие ограничения. Старые системы часто невозможно остановить — они работают круглосуточно, и любое отключение недопустимо. Иногда они превращаются в «чёрные ящики»: оригинальные разработчики давно ушли, а документации или передачи знаний не осталось.

Добавьте к этому отсутствие дополнительных средств: если нет бюджета на расширение команды, то ресурсы для переписывания придётся брать из текущей разработки. То есть меньше новых функций, больше рисков — что в реальном бизнесе может быть неприемлемо.

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

Но и выгоды не только в безопасности

Переход на безопасные языки даёт не только защиту от уязвимостей. Ошибки работы с памятью — например, разыменование null или выход за границы массива — часто приводят к сбоям и падениям сервисов. Это — нарушения SLA, ночные звонки инженерам техподдержки, потерянные клиенты и выручка.

Кроме того, такие ошибки — любимый инструмент злоумышленников. Ещё в знаменитой статье «Smashing the Stack for Fun and Profit» (1996 год) описано, как переполнение буфера можно превратить в удалённое исполнение кода. С этого начинается всё: кража данных, расширение привилегий, заражение сетей, ботнеты, вымогатели.

Исправлять ошибки дешевле, чем бороться с их последствиями. И ещё дешевле — не допускать их изначально. Вот почему безопасность памяти — это не просто техническая, а экономическая стратегия.

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

Определим рамки: что такое «безопасность памяти»?

Казалось бы, на фоне всех разговоров о ней должно существовать чёткое определение термина memory safety. Однако до сих пор нет единой, строгой и всеобщей формулировки. Недавно в журнале Communications of the ACM даже объявили о запуске инициативы по созданию стандарта определения «безопасности памяти», — но работа только начинается.

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

Самое ёмкое определение принадлежит Майклу Хиксу, специалисту по языкам программирования:

«Программа считается безопасной с точки зрения памяти, если во время её работы не происходят следующие ошибки доступа к памяти:

• переполнение буфера;
• разыменование нулевого указателя;
• использование памяти после освобождения;
• использование неинициализированной памяти;
• незаконное освобождение (повторное или освобождение чужого указателя).»

Пространственная и временная безопасность

Эти ошибки часто делят на две категории: пространственные и временные.

  • Пространственная безопасность (spatial) касается доступа туда, куда программа лезть не должна — например, выход за пределы буфера.

  • Временная (temporal) — это действия с памятью, выполненные в неправильный момент: чтение до инициализации, повторное освобождение, использование указателя после free.

Существует и официальная таксономия — CWE (Common Weakness Enumeration), где каждая категория расписана ещё подробнее. Например, «переполнение буфера» там разбивается на шесть подтипов. Это полезно для аудита и стандартизации, но слишком детализировано для общего обсуждения в данной статье.

Итак, кратко: программа безопасна по памяти, если она гарантированно не совершает этих ошибок. Эту гарантию можно обеспечить либо на этапе компиляции (например, Rust), либо во время выполнения (например, с помощью сборщика мусора в Java, Go и других языках). Главное — чтобы гарантия выполнялась всегда.

«Но Rust же тоже небезопасен — там есть unsafe!»

Да, и это частый упрёк. Rust действительно допускает использование небезопасных блоков кода с ключевым словом unsafe. Но любой специалист по безопасности скажет: значение имеет то, что является «по умолчанию».

Почему важны настройки «по умолчанию»

Пример из жизни: ремни безопасности. Законы о их обязательном использовании появились в США между 1980-ми и 1990-ми. В 1985 году ремни использовали всего 21% водителей, а к 1994-му — уже 58%. В 2017 году показатель вырос до почти 90%. По оценке Национальной администрации безопасности дорожного движения (NHTSA), только за 2017 год ремни спасли жизни 15 000 человек. Что изменилось? Всего лишь дефолт: ремни стали обязательными.

То же самое в мире программирования. До версии 4.0.0 (2017 г.) популярная база данных Redis по умолчанию не имела защиты доступа. Пользователи случайно выставляли свои инстансы в интернет, и те становились мишенью для атак. После введения режима «protected mode» Redis по умолчанию стал слушать только локальные подключения — и число публично доступных инстансов по данным Shodan.io сократилось почти вдвое.

Иными словами: разумные настройки по умолчанию спасают жизни — и сервера.

Языки бывают безопасные «по умолчанию» и нет

OpenSSF (Open Source Security Foundation) предлагает рассматривать языки как континуум:

  1. Языки безопасные по умолчанию

  2. Языки безопасные по умолчанию, но взаимодействующие с небезопасными

  3. Языки небезопасные по умолчанию

К первой группе относятся не только Rust, но и все сборщики мусора: Java, C#, Go, Swift, Python, Ruby и др. Ко второй — гибридные решения вроде Python с C‑расширениями. А к третьей — C и C++, а также, возможно неожиданно для некоторых, Zig.

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

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

Стратегии обеспечения безопасности

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

Пишите новый код безопасным

Самое очевидное решение: новый код должен быть написан на безопасном языке. Однако даже здесь есть нюансы.

Во‑первых, если вы продолжаете писать новый код в перемешку — часть на Rust или Go, а часть на C или C++, — выгоды будут минимальны. Представьте себе: у вас есть кодовая база, которую вы регулярно проверяете тестами, ревью и фаззингом. Со временем количество уязвимостей в ней снижается, как и вероятность находить новые. Но каждый раз, когда вы вносите изменения, — вы рискуете внести и новую ошибку, особенно если язык не обеспечивает безопасность памяти.

Команды Google Chrome и Android активно делились опытом перехода на безопасные языки.
Они ввели «правило двух»: каждый новый код должен быть либо написан в безопасном языке, либо выполняться в песочнице. А так как песочницы — дело сложное, большинство команд естественным образом стали выбирать первый вариант.

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

Переписывайте только критические компоненты

Иногда переписывание действительно оправдано — но лишь когда вы точно понимаете, где и зачем.

Хороший пример — компания Mozilla. Хотя Rust создавался внутри Mozilla, и его флагманским проектом был движок Servo, первым кодом на Rust, который попал в Firefox, стал вовсе не Servo, а парсер MP4-файлов. Почему? Потому что именно он постоянно становился источником уязвимостей, а парсинг недоверенных видеофайлов напрямую влиял на безопасность пользователей. Это был небольшой, но критически важный участок, который идеально подходил для замены на Rust.

Полезное эмпирическое правило сформулировала Келли Шортридж — правило SUX:
переписывайте код, который Sandbox‑free, Unsafe и eXogenous. Иными словами: ищите участки, которые

  • не изолированы (нет песочницы),

  • написаны на небезопасном языке,

  • и обрабатывают внешние (потенциально вредоносные) данные.
    Именно там вероятность эксплуатации уязвимостей максимальна.

Оборачивайте небезопасный код безопасными интерфейсами

Когда переписать всё невозможно — а чаще всего именно так — можно изолировать старый код, обернув его в безопасный интерфейс.

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

Так устроены, к примеру, контейнеры стандартной библиотеки Rust: внутри они используют unsafe‑код для быстрой работы с буферами и указателями, но наружу не дают пользователю доступа к сырым данным. То есть опасная часть спрятана под надёжным API.

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

«Просто пишите хороший код» — не стратегия

Один из любимых аргументов скептиков звучит так:

«Программисты должны просто писать аккуратнее.»

Или, в более жёсткой форме:

«Если вам нужны костыли вроде Rust, значит, вы плохой программист.»

Давайте скажем прямо: это чушь. Закос под мачо под видом технических аргументов.

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

Люди не стали безопаснее водить машины сами по себе — появились законы о ремнях безопасности. Меньше ошибок в больницах — не потому что врачи стали «внимательнее»,
а потому что внедрили чек‑листы и аварийные тележки со стандартным набором инструментов.

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

Когда кто‑то говорит, что индустрия должна «просто писать лучше», он не предлагает решение, а рисует невозможное будущее, противопоставляя его реальной, достижимой цели — повышению безопасности.

Запомните: массового просветления не будет. Но инструменты, которые делают ошибки менее вероятными, — уже есть. И это куда серьёзнее, чем вера в идеальных программистов.

Регулирование и требования

Нет, правительства не запрещают C и C++

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

На деле — ничего подобного нет. Ни одно правительственное агентство, ни в США, ни где‑либо ещё, не запрещает и не ограничивает использование языков, не являющихся безопасными по умолчанию. Более того, ни в одном государстве пока нет даже требований использовать безопасные языки при разработке программного обеспечения.

Упомянутый ранее отчёт «The Case for Memory Safe Roadmaps» («Дорожные карты перехода к безопасным языкам»), подготовленный странами «Пяти глаз», — не нормативный документ,
а рекомендация. Он советует организациям разработать планы перехода, но не требует их исполнения. Никаких поправок в федеральные правила закупок софта в США или других странах не внесено. Иными словами, C и C++ не запрещены.

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

Почему законы не появятся быстро

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

Во‑первых, чтобы регулировать вопрос безопасности памяти, в США нужно, чтобы уполномоченное агентство имело достаточный вес и юридическую силу. После отмены принципа Chevron deference (когда суды были обязаны доверять толкованию закона самим агентствам) решением Верховного суда США в 2024 году (Loper Bright Enterprises v. Raimondo), регуляторам стало гораздо труднее издавать правила без прямого одобрения Конгресса. Значит, любая норма о «безопасных языках» должна пройти через законодателей.

Во‑вторых, даже для простых покупок софта государством существует огромная бюрократическая машина — FAR (Federal Acquisition Regulation). Например, в 2020 году президент Байден подписал указ, предписывающий включить в требования к закупкам пункт о наличии SBOM (Software Bill of Materials — перечень компонентов программы). Прошло уже несколько лет — и изменения до сих пор не внесены. Так что ждать поправок про «только безопасные языки» в ближайшее время точно не стоит.

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

Роль правительства сегодня — не «запрещать», а «вдохновлять»

На данный момент правительство США выступает в роли пропагандиста идеи, а не контролёра. Оно издаёт отчёты, поддерживает исследования и разъясняет,
почему безопасность памяти важна для кибербезопасности в целом.

Среди этих инициатив:

  • Доклад Офиса национального директора по кибербезопасности «Future Software Should Be Memory Safe» («Будущее ПО должно быть безопасным по памяти»);

  • Совместный отчёт CISA и партнёров «The Case for Memory Safe Roadmaps»;

  • И рекомендации CISA по включению мер безопасности памяти в программу Secure by Design («Безопасность по замыслу»).

Правительствам не нужно запрещать C и C++

Даже без государственного принуждения, индустрия уже двигается в этом направлении.

В 2023 году тот же Офис кибердиректора США провёл опрос (RFI), в котором спрашивал, как лучше укрепить безопасность открытого ПО и стоит ли поощрять использование безопасных языков.

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

Что думает индустрия

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

  • меньше баг‑репортов,

  • меньше выплат по баунти‑программам,

  • меньше экстренных патчей,

  • меньше кризисов безопасности.

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

«C++ под угрозой?» — и да, и нет

На этом фоне в сообществе C++ появилась тревога. Бьёрн Страуструп, создатель C++ и участник ISO‑группы, которая развивает стандарт, всё чаще предупреждает: если C++ не решит проблему безопасности памяти, его могут вытеснить из индустрии естественным путём.

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

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

Безопасность стоит того, чтобы её добиваться

Языки с безопасностью памяти дают сегодня самую реальную возможность улучшить безопасность софта в целом. Да, они не устраняют все типы уязвимостей, но убирают один из самых опасных и дорогостоящих классов — ошибки обращения к памяти. Есть и другие пути — например, аппаратные решения вроде CHERI, но они молоды, сложны в применении и пока не готовы к масштабному внедрению.

Мы находимся примерно там же, где когда‑то была автопромышленность перед появлением обязательных ремней безопасности. Когда производители начали ставить ремни в базовой комплектации, а штаты обязали их использовать, количество смертей и тяжёлых аварий резко снизилось. Ремни не решили все проблемы — но доказали, что улучшение возможно дёшево и эффективно.

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

Комментарии (2)


  1. rsashka
    11.11.2025 16:11

    Безопасность памяти, это только одна из граней безопасной разработки и дело не только в ней.


  1. aeder
    11.11.2025 16:11

    Открою вам большой секрет - только вы не обижайтесь.

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

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

    Поэтому говорить "переписали говнокод на Раст и сразу стало хорошо" - смысла не имеет. Можно переписать на кондовый С - и тоже будет хорошо.

    Потому что это - переписанный код.

    ------------------------

    Ну, а по поводу "безопасности памяти" в целом: знаете, если бы люди реально хотели "безопасной памяти" - то просто запретили бы выпускать цифровые устройства любого типа с памятью без ECC. Память с чётностью - и случайных падений и странных зависаний становится на порядок меньше.