Блог / Разборы CVE

LMCache CVE-2026-105192: выполнение до отказа

В LMCache CVE-2026-105192 pickle вызывается при декодировании. Разберите патч, транспортные пути и доказательства для отрицательного регрессионного теста.

Аспирантура по информационной безопасности завершенаВнутривузовская защита пройдена · Итоговая защита диссертации впереди

Запрос завершился ошибкой. Код уже выполнился.

Запрос завершился ошибкой. Код уже выполнился. Именно такой порядок опасен в LMCache CVE-2026-105192: небезопасный десериализатор срабатывает при декодировании аргументов, прежде чем прикладной обработчик сможет их отклонить. Ответ с ошибкой приходит слишком поздно, чтобы доказывать безвредность запроса.

Advisory охватывает LMCache 0.3.9–0.5.5, затронутые предварительные сборки 0.5.6 и версии разработки до исправления. Исправление входит в 0.5.6. Это анализ исходного кода и патча, а не воспроизведение; особенно важно помнить об этом при разборе отрицательных тестов ниже. Риск относится к доступному уязвимому транспорту multiprocess, а не ко всем приложениям на vLLM. [1][2]

Как декодирование MessagePack доходит до pickle?

Транспорт принимает MessagePack, но пользовательский обработчик расширения придаёт части байтов вторую интерпретацию. Код расширения 1 ведёт в DeviceIPCWrapper.Deserialize, уязвимая реализация которого вызывает pickle.loads. В пути REGISTER_KV_CACHE это происходит ещё при сборке аргументов для обработчика. Последующая проверка аргументов не отменяет уже случившийся побочный эффект. [1][3]

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

Affected decode path (schematic)

Peer-controlled frame
  -> MessagePack argument decode
  -> extension code 1
  -> DeviceIPCWrapper.Deserialize
  -> pickle.loads
  -> only then: handler / later validation

A later error cannot undo an earlier side effect.

Разработчику требовался правильный подкласс

Уязвимый код объясняет инженерную цель: сохранить конкретный тип обёртки устройства при передаче, чтобы получатель выбрал правильную реализацию преобразования в тензор. Обёртки CPU и ускорителей выполняют разные задачи. Сериализация объекта удобно решала диспетчеризацию, но одновременно позволяла другой стороне передавать байты механизму восстановления Python-объектов. Ошибка находится в представлении данных, выбранном для нормального требования. [3]

Поэтому вопрос для ревью полезнее общего запрета pickle: какие факты должны пересечь границу, а какие решения о реализации должны остаться локальными? Получателю могут понадобиться вид устройства, форма или дескриптор хранилища. Произвольный рецепт создания Python-объектов ему не нужен. Зафиксированное различие даёт патчу инвариант, который сохраняется при замене транспорта.

Патч возвращает выбор реализации локальному коду

В 0.5.6 Deserialize декодирует типизированный дескриптор WireIPCData, затем по его типу выбирает запись WRAPPER_TYPES. Модуль и класс определяет локальное отображение. Само присутствие importlib в исправленном пути ещё не означает новую инъекцию: важно, управляет ли вход именем импорта или лишь выбирает явно поддержанный дескриптор. [4]

Патч сохраняет интерфейс сериализации, но отвергает старые данные pickle. Мой критерий приёмки: некорректные или неподдерживаемые данные отклоняются до произвольного восстановления объектов, без скрытого повторного вызова pickle ради совместимости. Я бы проверял неизвестные теги, лишние поля и прежние дескрипторы вместе с допустимыми входами. Исправление должно сохранять нужное поведение, устраняя опасную интерпретацию.

# Selected source lines; surrounding checks omitted [3][4]
# Vulnerable:
return pickle.loads(data)

# Fixed:
descriptor = msgspec.msgpack.decode(data, type=WireIPCData)
module, name = WRAPPER_TYPES[type(descriptor)]

# The fixed path selects implementation from a local mapping.

Проследите общий декодер за пределами первого сокета

ZeroMQ — не единственный вызывающий код, который стоит проверить. В исправляющем PR указан кодек gRPC, использующий тот же десериализатор через поле pickled_payload. Имя поля сохраняется ради совместимости, хотя содержимое становится новым дескриптором. Поиск старого имени может завысить оценку опасности; поиск только первого транспорта — пропустить общую границу. Следуйте реальному потоку данных. [5]

Исправление также не означает удаления всех применений pickle из LMCache. Сопровождающие выделяют отдельные engine-driven пути вне этого патча и описывают их как отключённые по умолчанию и недоступные по сети в такой конфигурации. Это их описание области действия, а не проверенный здесь результат анализа достижимости. Патч не вводит RPC-аутентификацию и не доказывает право использовать указанный ресурс разделяемой памяти. Синтаксис, идентичность и владение ресурсом проверяются отдельно. [5][6]

Правильный дескриптор не доказывает право на ресурс

Новая схема делает больше, чем заменяет библиотеку сериализации. TensorMetadata ограничивает числовые поля и проверяет совпадение числа измерений shape и stride; дескриптор разделяемой памяти CPU проверяет, помещается ли представление тензора в указанный размер. Эти проверки ограничивают смысл дескриптора до его использования обёрткой. Ревьюер может превратить конкретные свойства в граничные тесты вместо принятия слова «типизированный» за гарантию безопасности. [7]

Остаётся отдельный вопрос: кто имеет право указывать или использовать сам ресурс? Внутренне согласованное описание на него не отвечает. PR прямо разделяет проверку дескриптора и авторизацию владения разделяемой памятью. Я бы отдельно записал проверку права на ресурс и её место, запросив доказательства обеих проверок там, где этого требует модель доверия. Это не заявление о новой уязвимости исправленной версии, а граница доказательств патча. Для любого исправления парсера полезна та же дисциплина: назовите обеспеченный инвариант, затем независимые решения безопасности, которых патч не обещал принимать. Успешный тест парсера не подтверждает их автоматически. [5]

Отсутствие маркера требует подтверждения завершения обработки

Тест может сообщить о неудаче раньше, чем уязвимый сервер обработает вход. Автор PR отдельно решает эту проблему асинхронности: дополнительный запрос через то же соединение подтверждает завершение до проверки маркера. Опубликованный результат до и после исправления принадлежит автору PR, это не мой лабораторный результат. Общий вывод: докажите, что проверка выполнена после нужной работы. [5]

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

Proposed regression evidence; not results from a run here

Affected build: path reached + completed + benign marker observed
Fixed build:    path reached + completed + marker absent
Valid input:   supported round trip still works
No completion: inconclusive, even when the marker is absent

Обновите участников обмена, затем проверьте доступность

Начните проверку развёртывания с listener. По документации он по умолчанию привязан к localhost; multiprocess-развёртывание с доступным адресом через --host имеет другую границу доверия, и сопровождающие отмечают свой прежний пример Kubernetes. Localhost сокращает сетевую доступность, но не аутентифицирует локального участника. Установите режим, загруженную версию, адрес привязки и круг клиентов, прежде чем считать оценку серьёзности оценкой вашей системы. [1][6]

Обновляйте взаимодействующие workers и серверы вместе: формат дескриптора изменился, смешанный парк не является предусмотренным способом совместимости. Релиз 0.5.6 фиксирует исправление; дальнейшие меры безопасности сопровождающие описывают отдельно. Ограничивайте доступ к listener и после обновления. Обоснованный вывод — изменение конкретного пути восстановления объектов, а не безопасность сервиса инференса для любых клиентов. [2][5]

Попросите ревью доказать границу доверия

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

Именно такую границу я прослеживаю при аудите кода: момент, когда недоверенные байты получают поведение, и допущение, из-за которого оно казалось необходимым. Если ваши сервисы передают объекты через кэши, очереди или IPC, начните с этих помощников. Сложная часть часто находится на несколько вызовов глубже эндпоинта, который уже считали проверенным.

Источники

Нужна такая проверка для вашего продукта?

Аудит безопасности кода

Хотите сами научиться находить такие уязвимости?

Курс аудита безопасности кода, индивидуально
Все статьи

08Контакт

Выпускаете продукт с данными пользователей?

Пришлите короткое описание. Я отвечу в течение трёх дней вопросами или наброском предложения, а стоимость согласуем письменно до начала работ.