Cайт веб-разработчика, программиста Ruby on Rails ESV Corp. Екатеринбург, Москва, Санкт-Петербург, Новосибирск, Первоуральск

Почему Python не вошёл в список "безопасных языков" в отчете ONCD (26 февраля 2024 США Белый дом)

26 февраля 2024 года в США. Белый дом через Управление национального директора по киберпространству.
Новый 19-страничный отчет ONCD дал C и C++ в качестве двух примеров языков программирования с уязвимостями безопасности памяти, и он назвал Rust примером языка программирования, который он считает безопасным. Кроме того, в информационном листе АНБ по кибербезопасности от ноября 2022 года в качестве языков программирования C#, Go, Java, Ruby и Swift, помимо Rust, они считаются безопасными для памяти.
[ источник ]

Python не упомянут в докладе как язык программирования memory-safety

Для сравнения приведём пример Ruby, который так же является интерпретируемым языком, но в отличии от Python является memory-safety и рекомендован к использованию. Pyhon в отчёте упомянут лишь как один из наиболее популярных языков, по популярности сравнимым с C и C++.

Архитектурные различия: Ruby vs. Python

Ruby: "Безопасность по дизайну"

  • Всё — объект, даже примитивы. Нет низкоуровневого доступа к памяти.
  • Жёсткий запрет на pointer arithmetic. Даже через FFI (например, в Ruby вы не можете случайно сделать buffer + 10 для доступа к произвольной памяти).
  • Нет "сырых" байтовых массивов как в Python (bytearray), которые могут имитировать опасное поведение C.
  • Строгая изоляция исполнения. Например, eval в Ruby не приводит к утечкам памяти, в отличие от Python.

Python: "Удобство с оговорками"

 Есть "тёмные углы". Например:

# Python: опасный доступ к памяти через ctypes
import ctypes
buffer = (ctypes.c_char * 10)()  # Буфер как в C
ctypes.memset(buffer, 0x41, 20)  # Переполнение! (но без segfault) — Это не вызовет краха, но может повредить данные.
  • Динамическая типизация + __slots__. Оптимизации памяти в Python требуют ручного контроля, что усложняет безопасность.
  • Сложность с многопоточностью, отсутствие параллелизма выполнения программ. GIL (Global Interpreter Lock) — "костыль", который маскирует проблемы, но не решает их на уровне памяти.

Конкретные примеры уязвимостей в Python

Утечка памяти через циклические ссылки

# Python: GC не всегда спасает
class Node:
    def __init__(self):
        self.parent = None

x = Node()
y = Node()
x.parent = y
y.parent = x  # Циклическая ссылка!
del x, y      # Объекты не удалятся, пока не сработает GC.

В Ruby такое невозможно — алгоритм GC (Mark & Sweep) эффективнее находит циклические ссылки.

Неочевидное поведение при работе с памятью

# Python: изменение неизменяемого объекта (!)
a = "Hello"
b = a
a += " world"  # Создаётся новый объект, но если бы это был list — изменение затронуло бы b.

В Ruby строки по умолчанию mutable, но это проекное решение, а не случайность.

Почему Python не в списке безопасных языков в докладе?

Непредсказуемость. Даже "чистый" код может вести себя неочевидно из-за:

  • Кэширования маленьких целых чисел (-5 до 256 — один объект).
  • Разницы между is и ==.

Слабая изоляция. Виртуальная машина Python (CPython) менее защищена, чем JVM (Java/Kotlin) или CRuby.

Исторические причины. Python создавался как "скриптовый язык", а Ruby — как "безопасный Perl".

Ruby vs. Python: синтаксическая мощь

Ruby более целостен: В Ruby attr_accessor — часть языка, в Python — декоратор (@property), который можно сломать.

Блоки и замыкания

# Ruby: блок — естественная часть синтаксиса
[1, 2, 3].each { |x| puts x * 2 }
# Python: лямбды ограничены
list(map(lambda x: print(x * 2), [1, 2, 3]))

ООП: В Ruby классы открыты для модификации, но это контролируемо, а в Python можно случайно переопределить даже системные методы.

Python исключён из списка memory-safe языков

  1. Не гарантирует полную безопасность даже в "чистом" виде.
  2. Допускает опасные паттерны (например, через ctypes).
  3. Уступает Ruby в целостности дизайна и предсказуемости

В Python нет возможности параллельного исполнения

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

Несколько слов о GIL

GIL —  это аббревиатура от Global Interpreter Lock – глобальная блокировка интерпретатора. Он является элементом эталонной реализации языка Python, которая носит название CPython. Суть GIL заключается в том, что выполнять байткод может только один поток. Это нужно для того, чтобы упростить работу с памятью (на уровне интерпретатора) и сделать комфортной разработку модулей на языке C. Это приводит к некоторым особенностям, о которых необходимо помнить. Условно, все задачи можно разделить на две большие группы: в первую входят те, что преимущественно используют процессор для своего выполнения, например, математические, их ещё называют CPU-bound, во вторую – задачи работающие с вводом выводом (диск, сеть и т.п.), такие задачи называют IO-bound. Если вы запустили в одном интерпретаторе несколько потоков, которые в основном используют процессор, то скорее всего получите общее замедление работы, а не прирост производительности. Пока выполняется одна задача, остальные простаивают (из-за GIL), переключение происходит через определенные промежутки времени. Таким образом, в каждый конкретный момент времени, будет выполняться только один поток, несмотря на то, что у вас может быть многоядерный процессор (или многопроцессорный сервер), плюс ко всему, будет тратиться время на переключение между задачами. Если код в потоках в основном выполняет операции ввода-вывода, то в этом случае ситуация будет в вашу пользу. В CPython все стандартные библиотечные функций, которые выполняют блокирующий ввод-вывод, освобождают GIL, это дает возможность поработать другим потокам, пока ожидается ответ от ОС.