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