Содержание страницы
Протокол подключения удаленной отладки¶Remote debugging attachment protocol
Этот протокол позволяет внешним инструментам подключаться к работающему процессу CPython и удаленно выполнять код Python.
На большинстве платформ для подключения к другому процессу Python требуются повышенные привилегии.
Отключение удаленной отладки¶Disabling remote debugging
Чтобы отключить поддержку удаленной отладки, используйте любой из следующих способов:
Установите переменную окружения
PYTHON_DISABLE_REMOTE_DEBUGв1перед запуском интерпретатора.Используйте параметр командной строки
-X disable_remote_debug.Скомпилируйте Python с флагом сборки
--without-remote-debug.
Требования к разрешениям¶Permission requirements
Подключение к работающему процессу Python для удаленной отладки требует повышенных привилегий на большинстве платформ. Конкретные требования и шаги по устранению неполадок зависят от вашей операционной системы:
Linux
Процесс-трассировщик должен иметь возможность CAP_SYS_PTRACE или эквивалентные
привилегии. Вы можете трассировать только те процессы, которыми владеете и которым можете отправлять сигналы. Трассировка может
не удаться, если процесс уже трассируется, или если он выполняется с
set-user-ID или set-group-ID. Модули безопасности, такие как Yama, могут дополнительно ограничивать
трассировку.
Чтобы временно ослабить ограничения ptrace (до перезагрузки), выполните:
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
Примечание
Отключение ptrace_scope снижает защиту системы и должно выполняться только
в доверенных средах.
Если запуск производится внутри контейнера, используйте --cap-add=SYS_PTRACE или
--privileged, и при необходимости запустите от root.
Попробуйте повторно выполнить команду с повышенными привилегиями:
sudo -E !!
macOS
Чтобы подключиться к другому процессу, обычно требуется запустить инструмент отладки
с повышенными привилегиями. Это можно сделать, используя sudo или запустив от
root.
Даже при подключении к собственным процессам macOS может блокировать отладку, если отладчик не запущен с правами root из-за ограничений безопасности системы.
Windows
Чтобы подключиться к другому процессу, обычно требуется запустить инструмент отладки с правами администратора. Запустите командную строку или терминал от имени Администратора.
Некоторые процессы могут оставаться недоступными даже с правами Администратора,
если у вас не включена привилегия SeDebugPrivilege.
Чтобы решить проблемы с доступом к файлу или папке, измените разрешения безопасности:
Щёлкните правой кнопкой мыши по файлу или папке и выберите Свойства.
Перейдите на вкладку Безопасность, чтобы просмотреть пользователей и группы, имеющие доступ.
Нажмите Изменить, чтобы изменить разрешения.
Выберите свою учётную запись.
В разделе Разрешения отметьте Чтение или Полный доступ по мере необходимости.
Нажмите Применить, затем ОК для подтверждения.
Примечание
Убедитесь, что выполнены все требования к разрешениям, прежде чем продолжить.
В этом разделе описывается низкоуровневый протокол, который позволяет внешним инструментам внедрять и выполнять скрипт Python внутри работающего процесса CPython.
Этот механизм лежит в основе функции sys.remote_exec(), которая
инструктирует удалённый процесс Python выполнить файл .py. Однако в данном
разделе не документируется использование этой функции. Вместо этого даётся
подробное описание базового протокола, который принимает на вход
pid целевого процесса Python и путь к исходному файлу Python для выполнения.
Эта информация позволяет независимо перереализовать протокол, независимо от языка программирования.
Предупреждение
Выполнение внедрённого скрипта зависит от того, достигнет ли интерпретатор безопасной точки вычислений. В результате выполнение может быть отложено в зависимости от состояния выполнения целевого процесса.
После внедрения скрипт выполняется интерпретатором в целевом процессе при следующем достижении безопасной точки вычислений. Этот подход обеспечивает возможность удалённого выполнения без изменения поведения или структуры работающего приложения Python.
Последующие разделы содержат пошаговое описание протокола, включая методы поиска структур интерпретатора в памяти, безопасного доступа к внутренним полям и запуска выполнения кода. Учитываются платформозависимые вариации, где это применимо, и включены примеры реализаций для пояснения каждой операции.
Поиск структуры PyRuntime¶Locating the PyRuntime structure
CPython помещает структуру PyRuntime в специальный раздел двоичного файла, чтобы
помочь внешним инструментам найти её во время выполнения. Имя и формат этого раздела
зависят от платформы. Например, на системах ELF используется .PyRuntime, а на macOS –
__DATA,__PyRuntime. Инструменты могут найти смещение этой структуры, изучив двоичный файл на диске.
Структура PyRuntime содержит глобальное состояние интерпретатора CPython и
предоставляет доступ к другим внутренним данным, включая список интерпретаторов,
состояния потоков и поля поддержки отладчика.
Для работы с удалённым процессом Python отладчик должен сначала найти адрес памяти
структуры PyRuntime в целевом процессе. Этот адрес нельзя жёстко задать или вычислить по имени символа,
потому что он зависит от того, куда операционная система загрузила двоичный файл.
Метод поиска PyRuntime зависит от платформы, но шаги в целом одинаковы:
Найдите базовый адрес, по которому двоичный файл Python или общая библиотека были загружены в целевой процесс.
Используйте двоичный файл на диске, чтобы найти смещение раздела
.PyRuntime.Прибавьте смещение раздела к базовому адресу, чтобы вычислить адрес в памяти.
Разделы ниже объясняют, как это сделать на каждой поддерживаемой платформе, и содержат примеры кода.
Linux (ELF)
Чтобы найти структуру PyRuntime в Linux:
Прочитайте карту памяти процесса (например,
/proc/<pid>/maps), чтобы найти адрес, по которому был загружен исполняемый файл Python илиlibpython.Проанализируйте заголовки разделов ELF в двоичном файле, чтобы получить смещение раздела
.PyRuntime.Прибавьте это смещение к базовому адресу из шага 1, чтобы получить адрес памяти
PyRuntime.
Ниже приведён пример реализации:
def find_py_runtime_linux(pid: int) -> int:
# Шаг 1: Попытаться найти исполняемый файл Python в памяти
binary_path, base_address = find_mapped_binary(
pid, name_contains="python"
)
# Шаг 2: Запасной вариант – разделяемая библиотека, если исполняемый файл не найден
if binary_path is None:
binary_path, base_address = find_mapped_binary(
pid, name_contains="libpython"
)
# Шаг 3: Разобрать заголовки ELF, чтобы получить смещение секции .PyRuntime
section_offset = parse_elf_section_offset(
binary_path, ".PyRuntime"
)
# Шаг 4: Вычислить адрес PyRuntime в памяти
return base_address + section_offset
В системах Linux есть два основных подхода к чтению памяти из другого
процесса. Первый – через файловую систему /proc, а именно чтение из
/proc/[pid]/mem, что обеспечивает прямой доступ к памяти процесса. Это
требует соответствующих разрешений – либо быть тем же пользователем, что и целевой
процесс, либо иметь права root. Второй подход – использование системного вызова
process_vm_readv(), который предоставляет более эффективный способ копирования
памяти между процессами. Хотя операция PTRACE_PEEKTEXT ptrace также может
использоваться для чтения памяти, она значительно медленнее, так как читает только одно слово за раз
и требует множества переключений контекста между трассирующим и трассируемым
процессами.
Для анализа разделов ELF процесс включает чтение и интерпретацию структур формата файла ELF из двоичного файла на диске. Заголовок ELF содержит указатель на таблицу заголовков разделов. Каждый заголовок раздела содержит метаданные о разделе, включая его имя (хранящееся в отдельной таблице строк), смещение и размер. Чтобы найти конкретный раздел, например .PyRuntime, необходимо пройтись по этим заголовкам и сопоставить имя раздела. Затем заголовок раздела предоставляет смещение, по которому этот раздел находится в файле, которое можно использовать для вычисления его адреса во время выполнения при загрузке двоичного файла в память.
Подробнее о формате файла ELF можно узнать в спецификации ELF.
macOS (Mach-O)
Чтобы найти структуру PyRuntime в macOS:
Вызовите
task_for_pid(), чтобы получить порт задачиmach_port_tдля целевого процесса. Этот дескриптор необходим для чтения памяти с помощью таких API, какmach_vm_read_overwriteиmach_vm_region.Просканируйте области памяти, чтобы найти ту, которая содержит исполняемый файл Python или
libpython.Загрузите бинарный файл с диска и проанализируйте заголовки Mach-O, чтобы найти секцию с именем
PyRuntimeв сегменте__DATA. В macOS имена символов автоматически получают префикс с подчёркиванием, поэтому символPyRuntimeотображается как_PyRuntimeв таблице символов, но на имя секции это не влияет.
Ниже приведён пример реализации:
def find_py_runtime_macos(pid: int) -> int:
# Шаг 1: Получить доступ к памяти процесса
handle = get_memory_access_handle(pid)
# Шаг 2: Попытаться найти исполняемый файл Python в памяти
binary_path, base_address = find_mapped_binary(
handle, name_contains="python"
)
# Шаг 3: Запасной вариант – libpython, если исполняемый файл не найден
if binary_path is None:
binary_path, base_address = find_mapped_binary(
handle, name_contains="libpython"
)
# Шаг 4: Разобрать заголовки Mach-O, чтобы получить смещение секции __DATA,__PyRuntime
section_offset = parse_macho_section_offset(
binary_path, "__DATA", "__PyRuntime"
)
# Шаг 5: Вычислить адрес PyRuntime в памяти
return base_address + section_offset
В macOS для доступа к памяти другого процесса требуется использовать специфичные для Mach-O API
и форматы файлов. Первый шаг – получение дескриптора task_port через
task_for_pid(), который предоставляет доступ к пространству памяти целевого процесса.
Этот дескриптор позволяет выполнять операции с памятью через такие API, как
mach_vm_read_overwrite().
Память процесса можно исследовать с помощью mach_vm_region() для сканирования
виртуального адресного пространства, а proc_regionfilename() помогает определить, какие бинарные
файлы загружены в каждой области памяти. Когда бинарный файл Python или библиотека
найдены, необходимо проанализировать их заголовки Mach-O, чтобы найти структуру PyRuntime.
Формат Mach-O организует код и данные в сегменты и секции. Структура
PyRuntime находится в секции с именем __PyRuntime внутри
сегмента __DATA. Вычисление фактического адреса во время выполнения включает поиск
сегмента __TEXT, который служит базовым адресом бинарного файла, а затем определение
сегмента __DATA, содержащего целевую секцию. Итоговый адрес вычисляется
путём комбинации базового адреса с соответствующими смещениями секций из заголовков Mach-O.
Обратите внимание, что доступ к памяти другого процесса в macOS обычно требует повышенных привилегий – либо прав root, либо специальных разрешений безопасности, предоставленных отладочному процессу.
Windows (PE)
Чтобы найти структуру PyRuntime в Windows:
Используйте API ToolHelp для перечисления всех модулей, загруженных в целевой процесс. Это делается с помощью таких функций, как CreateToolhelp32Snapshot, Module32First и Module32Next.
Определите модуль, соответствующий
python.exeилиpythonXY.dll, гдеXиY– старший и младший номера версии Python, и запишите его базовый адрес.Найдите секцию
PyRuntim. Из-за ограничения формата PE в 8 символов на имена секций (определено какIMAGE_SIZEOF_SHORT_NAME), исходное имяPyRuntimeусекается. Эта секция содержит структуруPyRuntime.Получите относительный виртуальный адрес (RVA) секции и добавьте его к базовому адресу модуля.
Ниже приведён пример реализации:
def find_py_runtime_windows(pid: int) -> int:
# Шаг 1: Попытаться найти исполняемый файл Python в памяти
binary_path, base_address = find_loaded_module(
pid, name_contains="python"
)
# Шаг 2: Запасной вариант – общая pythonXY.dll, если исполняемый файл не
# найден
if binary_path is None:
binary_path, base_address = find_loaded_module(
pid, name_contains="python3"
)
# Шаг 3: Разобрать заголовки секций PE, чтобы получить RVA с секции PyRuntime
# секции. Имя секции отображается как "PyRuntim" из-за
# ограничения в 8 символов, установленного форматом PE (IMAGE_SIZEOF_SHORT_NAME).
section_rva = parse_pe_section_offset(binary_path, "PyRuntim")
# Шаг 4: Вычислить адрес PyRuntime в памяти
return base_address + section_rva
В Windows для доступа к памяти другого процесса требуется использовать функции Windows API,
такие как CreateToolhelp32Snapshot() и Module32First()/Module32Next(),
для перечисления загруженных модулей. Функция OpenProcess() предоставляет дескриптор для
доступа к пространству памяти целевого процесса, позволяя выполнять операции с памятью через
ReadProcessMemory().
Память процесса можно исследовать, перечисляя загруженные модули, чтобы найти
бинарный файл Python или DLL. Когда найдено, необходимо проанализировать его заголовки PE, чтобы найти
структуру PyRuntime.
Формат PE организует код и данные в секции. Структура PyRuntime
находится в секции с именем «PyRuntim» (усечено из «PyRuntime» из-за ограничения PE
в 8 символов на имя). Вычисление фактического адреса во время выполнения включает поиск
базового адреса модуля из записи модуля, а затем определение целевой
секции в заголовках PE. Итоговый адрес вычисляется путём комбинации базового
адреса с виртуальным адресом секции из заголовков секций PE.
Обратите внимание, что доступ к памяти другого процесса в Windows обычно требует
соответствующих привилегий – либо административного доступа, либо привилегии SeDebugPrivilege,
предоставленной отладочному процессу.
Чтение _Py_DebugOffsets¶Reading _Py_DebugOffsets
После того как адрес структуры PyRuntime определён, следующим
шагом является чтение структуры _Py_DebugOffsets, расположенной в начале
блока PyRuntime.
Эта структура предоставляет версионно-зависимые смещения полей, которые необходимы для безопасного чтения памяти интерпретатора и состояния потоков. Эти смещения различаются между версиями CPython и должны быть проверены перед использованием, чтобы убедиться в их совместимости.
Для чтения и проверки отладочных смещений выполните следующие шаги:
Прочитайте память целевого процесса, начиная с адреса
PyRuntime, покрывая то же количество байт, что и структура_Py_DebugOffsets. Эта структура находится в самом начале блока памятиPyRuntime. Её расположение определено во внутренних заголовках CPython и остаётся неизменным в пределах одной минорной версии, но может меняться в мажорных версиях.Проверьте, что структура содержит корректные данные:
Поле
cookieдолжно соответствовать ожидаемой отладочной метке.Поле
versionдолжно соответствовать версии интерпретатора Python, используемого отладчиком.Если отладчик или целевой процесс использует предрелизную версию (например, альфа, бета или кандидат на выпуск), версии должны совпадать в точности.
Поле
free_threadedдолжно иметь одинаковое значение как в отладчике, так и в целевом процессе.
Если структура корректна, содержащиеся в ней смещения можно использовать для поиска полей в памяти. Если какая-либо проверка не пройдена, отладчик должен остановить операцию, чтобы избежать чтения памяти в неверном формате.
Ниже приведён пример реализации для чтения и проверки
_Py_DebugOffsets:
def read_debug_offsets(pid: int, py_runtime_addr: int) -> DebugOffsets:
# Шаг 1: Прочитать память из целевого процесса по адресу PyRuntime
data = read_process_memory(
pid, address=py_runtime_addr, size=DEBUG_OFFSETS_SIZE
)
# Шаг 2: Десериализовать сырые байты в структуру _Py_DebugOffsets
debug_offsets = parse_debug_offsets(data)
# Шаг 3: Проверить содержимое структуры
if debug_offsets.cookie != EXPECTED_COOKIE:
raise RuntimeError("Invalid or missing debug cookie")
if debug_offsets.version != LOCAL_PYTHON_VERSION:
raise RuntimeError(
"Mismatch between caller and target Python versions"
)
if debug_offsets.free_threaded != LOCAL_FREE_THREADED:
raise RuntimeError("Mismatch in free-threaded configuration")
return debug_offsets
Предупреждение
Рекомендуется приостановка процесса
Чтобы избежать состояний гонки и обеспечить согласованность памяти, настоятельно рекомендуется приостановить целевой процесс перед выполнением любых операций, которые читают или записывают внутреннее состояние интерпретатора. Среда выполнения Python может одновременно изменять структуры данных интерпретатора – например, создавать или уничтожать потоки – в ходе нормального выполнения. Это может привести к недопустимым чтениям или записям в память.
Отладчик может приостановить выполнение, присоединившись к процессу с помощью ptrace или отправив сигнал SIGSTOP. Выполнение следует возобновлять только после завершения операций с памятью на стороне отладчика.
Примечание
Некоторые инструменты, такие как профилировщики или семплирующие отладчики, могут работать с запущенным процессом без приостановки. В таких случаях инструменты должны быть явно спроектированы для обработки частично обновлённой или несогласованной памяти. Для большинства реализаций отладчиков приостановка процесса остаётся самым безопасным и надёжным подходом.
Поиск интерпретатора и состояния потока¶Locating the interpreter and thread state
Прежде чем код можно будет внедрить и выполнить в удалённом процессе Python, отладчик должен выбрать поток, в котором запланировать выполнение. Это необходимо, потому что управляющие поля, используемые для удалённого внедрения кода, находятся в структуре _PyRemoteDebuggerSupport, которая встроена в объект PyThreadState. Эти поля изменяются отладчиком для запроса выполнения внедрённых сценариев.
Структура PyThreadState представляет поток, выполняющийся внутри интерпретатора Python. Она поддерживает контекст выполнения потока и содержит поля, необходимые для координации отладчика. Поиск допустимого PyThreadState является, таким образом, ключевым предварительным условием для удалённого запуска выполнения.
Поток обычно выбирается на основе его роли или идентификатора. В большинстве случаев используется главный поток, но некоторые инструменты могут нацеливаться на конкретный поток по его нативному идентификатору. После выбора целевого потока отладчик должен найти в памяти как интерпретатор, так и соответствующие структуры состояния потока.
Соответствующие внутренние структуры определены следующим образом:
PyInterpreterStateпредставляет изолированный экземпляр интерпретатора Python. Каждый интерпретатор поддерживает свой собственный набор импортированных модулей, встроенное состояние и список состояний потоков. Хотя большинство приложений Python используют один интерпретатор, CPython поддерживает несколько интерпретаторов в одном процессе.PyThreadStateпредставляет поток, выполняющийся в рамках интерпретатора. Он содержит состояние выполнения и управляющие поля, используемые отладчиком.
Для поиска потока:
Используйте смещение
runtime_state.interpreters_headдля получения адреса первого интерпретатора в структуреPyRuntime. Это точка входа в связанный список активных интерпретаторов.Используйте смещение
interpreter_state.threads_mainдля доступа к состоянию главного потока, связанному с выбранным интерпретатором. Обычно это самый надёжный поток для целевого воздействия.При необходимости используйте смещение
interpreter_state.threads_headдля итерации по связанному списку всех состояний потоков. Каждая структураPyThreadStateсодержит полеnative_thread_id, которое можно сравнить с идентификатором целевого потока, чтобы найти конкретный поток.После того как найден корректный
PyThreadState, его адрес может быть использован на последующих этапах протокола, таких как запись управляющих полей отладчика и планирование выполнения.
Ниже приведён пример реализации, который находит состояние главного потока:
def find_main_thread_state(
pid: int, py_runtime_addr: int, debug_offsets: DebugOffsets,
) -> int:
# Шаг 1: Прочитать interpreters_head из PyRuntime
interp_head_ptr = (
py_runtime_addr + debug_offsets.runtime_state.interpreters_head
)
interp_addr = read_pointer(pid, interp_head_ptr)
if interp_addr == 0:
raise RuntimeError("No interpreter found in the target process")
# Шаг 2: Прочитать указатель threads_main из интерпретатора
threads_main_ptr = (
interp_addr + debug_offsets.interpreter_state.threads_main
)
thread_state_addr = read_pointer(pid, threads_main_ptr)
if thread_state_addr == 0:
raise RuntimeError("Main thread state is not available")
return thread_state_addr
Следующий пример демонстрирует, как найти поток по его нативному идентификатору потока:
def find_thread_by_id(
pid: int,
interp_addr: int,
debug_offsets: DebugOffsets,
target_tid: int,
) -> int:
# Начать с threads_head и пройти по связному списку
thread_ptr = read_pointer(
pid,
interp_addr + debug_offsets.interpreter_state.threads_head
)
while thread_ptr:
native_tid_ptr = (
thread_ptr + debug_offsets.thread_state.native_thread_id
)
native_tid = read_int(pid, native_tid_ptr)
if native_tid == target_tid:
return thread_ptr
thread_ptr = read_pointer(
pid,
thread_ptr + debug_offsets.thread_state.next
)
raise RuntimeError("Thread with the given ID was not found")
После того как найдено корректное состояние потока, отладчик может приступить к изменению его управляющих полей и планированию выполнения, как описано в следующем разделе.
Запись управляющей информации¶Writing control information
После идентификации корректной структуры PyThreadState отладчик может изменить управляющие поля внутри неё, чтобы запланировать выполнение указанного скрипта Python. Эти управляющие поля периодически проверяются интерпретатором, и при правильной установке они запускают выполнение удалённого кода в безопасной точке цикла вычислений.
Каждый PyThreadState содержит структуру _PyRemoteDebuggerSupport, используемую для связи между отладчиком и интерпретатором. Расположение её полей определяется структурой _Py_DebugOffsets и включает следующее:
debugger_script_path: Буфер фиксированного размера, содержащий полный путь к исходному файлу Python (.py). Этот файл должен быть доступен и читаем целевым процессом при запуске выполнения.debugger_pending_call: Целочисленный флаг. Установка этого значения в1сообщает интерпретатору, что скрипт готов к выполнению.eval_breaker: Поле, проверяемое интерпретатором во время выполнения. Установка бита 5 (_PY_EVAL_PLEASE_STOP_BIT, значение1U << 5) в этом поле заставляет интерпретатор приостановиться и проверить активность отладчика.
Для завершения внедрения отладчик должен выполнить следующие шаги:
Запишите полный путь скрипта в буфер
debugger_script_path.Устанавливает
debugger_pending_callв1.Прочитайте текущее значение
eval_breaker, установите бит 5 (_PY_EVAL_PLEASE_STOP_BIT) и запишите обновлённое значение обратно. Это сигнализирует интерпретатору о необходимости проверить активность отладчика.
Ниже приведён пример реализации:
def inject_script(
pid: int,
thread_state_addr: int,
debug_offsets: DebugOffsets,
script_path: str
) -> None:
# Вычислить базовое смещение _PyRemoteDebuggerSupport
support_base = (
thread_state_addr +
debug_offsets.debugger_support.remote_debugger_support
)
# Шаг 1: Записать путь к скрипту в debugger_script_path
script_path_ptr = (
support_base +
debug_offsets.debugger_support.debugger_script_path
)
write_string(pid, script_path_ptr, script_path)
# Шаг 2: Установить debugger_pending_call в 1
pending_ptr = (
support_base +
debug_offsets.debugger_support.debugger_pending_call
)
write_int(pid, pending_ptr, 1)
# Шаг 3: Установить _PY_EVAL_PLEASE_STOP_BIT (бит 5, значение 1 << 5) в
# eval_breaker
eval_breaker_ptr = (
thread_state_addr +
debug_offsets.debugger_support.eval_breaker
)
breaker = read_int(pid, eval_breaker_ptr)
breaker |= (1 << 5)
write_int(pid, eval_breaker_ptr, breaker)
После установки этих полей отладчик может возобновить процесс (если он был приостановлен). Интерпретатор обработает запрос в следующей безопасной точке вычислений, загрузит скрипт с диска и выполнит его.
Отладчик обязан убедиться, что файл скрипта остаётся доступным и читаемым для целевого процесса во время выполнения.
Примечание
Выполнение скрипта асинхронно. Файл скрипта нельзя удалять сразу после внедрения. Отладчик должен дождаться, пока внедрённый скрипт не произведёт наблюдаемый эффект, прежде чем удалять файл. Этот эффект зависит от того, что должен делать скрипт. Например, отладчик может ждать, пока удалённый процесс не подключится обратно к сокету, прежде чем удалить скрипт. Как только такой эффект наблюдается, можно с уверенностью считать, что файл больше не нужен.
Сводка¶Summary
Чтобы внедрить и выполнить Python-скрипт в удаленном процессе:
Найдите структуру
PyRuntimeв памяти целевого процесса.Прочитайте и проверьте структуру
_Py_DebugOffsetsв началеPyRuntime.С помощью смещений найдите корректный
PyThreadState.Запишите путь к Python-скрипту в
debugger_script_path.Установите флаг
debugger_pending_callв значение1.Установите
_PY_EVAL_PLEASE_STOP_BITв полеeval_breaker.Возобновите процесс (если он был приостановлен). Скрипт выполнится в следующей безопасной точке выполнения.
Безопасность и модель угроз¶Security and threat model
Протокол удаленной отладки опирается на те же примитивы операционной системы, которые
используют нативные отладчики, такие как GDB и LLDB. Подключение к процессу
требует тех же привилегий, что и эти отладчики, например
ptrace / Yama LSM в Linux, task_for_pid в macOS и
SeDebugPrivilege в Windows. Python не создает новых путей повышения привилегий;
если злоумышленник уже обладает необходимыми разрешениями для подключения к процессу, он
с тем же успехом может использовать GDB для чтения памяти или внедрения
кода.
Следующие принципы определяют, что считается уязвимостью безопасности в этой функции, а что нет:
- Подключение требует привилегий уровня ОС
На всех поддерживаемых платформах операционная система ограничивает межпроцессный доступ к памяти проверками привилегий (
CAP_SYS_PTRACE, root или права администратора). Сообщение о проблеме, проявившейся только после получения этих привилегий, – это не уязвимость в CPython, поскольку граница безопасности ОС уже была пересечена.- Сбои и ошибки памяти при чтении скомпрометированного процесса не считаются уязвимостями
Инструмент, читающий внутреннее состояние интерпретатора из целевого процесса, должен доверять корректности этой памяти. Если целевой процесс поврежден или находится под контролем злоумышленника, отладчик или профилировщик может упасть, выдать мусор или вести себя непредсказуемо. Это тот же риск, который принимает любой отладчик на основе
ptrace. Ошибки этой категории (переполнения буфера, ошибки сегментации или неопределенное поведение, вызванное чтением поврежденного состояния) не рассматриваются как проблемы безопасности, хотя исправления, повышающие надежность, приветствуются.- Уязвимости в целевом процессе не входят в область ответственности
Если отлаживаемый процесс Python уже скомпрометирован, злоумышленник управляет выполнением в этом процессе. Демонстрация дальнейшего воздействия, исходя из этого состояния, не является уязвимостью в протоколе удаленной отладки.
Когда использовать PYTHON_DISABLE_REMOTE_DEBUG¶When to use PYTHON_DISABLE_REMOTE_DEBUG
Переменная окружения PYTHON_DISABLE_REMOTE_DEBUG (и
эквивалентный флаг -X disable_remote_debug) позволяет операторам отключить
внутрипроцессную сторону протокола в качестве меры защиты в глубину. Это
может быть полезно в усиленных или изолированных средах, где не ожидается
отладка или профилирование процесса, а уменьшение поверхности атаки
является приоритетом, хотя проверки привилегий уровня ОС уже предотвращают
непривилегированный доступ.
Установка этой переменной не затрагивает другие интерфейсы отладки уровня ОС
(ptrace, /proc, task_for_pid и т.д.), которые остаются доступными
согласно их собственным моделям разрешений.