La solicitud falló. El código ya se había ejecutado.
La solicitud falló. El código ya se había ejecutado. Ese es el problema de orden en LMCache CVE-2026-105192: un deserializador inseguro se ejecuta mientras se decodifican los argumentos, antes de que el manejador de negocio tenga ocasión de rechazarlos. Una respuesta de error llega demasiado tarde para demostrar que la solicitud era inofensiva.
El aviso abarca LMCache de la 0.3.9 a la 0.5.5, las versiones preliminares afectadas de la 0.5.6 y las compilaciones de desarrollo anteriores a la corrección. La versión 0.5.6 incluye la reparación. Esto es una revisión del código fuente y del parche, no una reproducción; la distinción importa sobre todo al hablar de las pruebas negativas más adelante. La exposición se refiere al transporte multiproceso afectado y alcanzable, no a toda aplicación que use vLLM. [1][2]
¿Cómo llega la decodificación de MessagePack a pickle?
El transporte acepta MessagePack, pero su hook de extensión personalizado da a ciertos bytes una segunda interpretación. El código de extensión 1 llega a DeviceIPCWrapper.Deserialize, cuya implementación vulnerable llama a pickle.loads. En la ruta REGISTER_KV_CACHE, esto ocurre mientras el servidor todavía está construyendo los argumentos que recibirá el manejador. Comprobar esos argumentos después no puede deshacer un efecto secundario que ya se ha producido. [1][3]
Así es como rastrearía el límite en una revisión: empezar por el decodificador de red, enumerar los hooks y los conversores personalizados, y seguir cada uno hasta que los bytes se conviertan en datos ordinarios o en una reconstrucción ejecutable. El formato que figura en un diagrama de arquitectura no basta. Un formato externo orientado a datos puede contener una extensión que restaure todo el comportamiento peligroso que la capa externa parecía evitar.
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.El desarrollador necesitaba recuperar la subclase correcta
El código vulnerable explica su objetivo de ingeniería: conservar el tipo concreto del wrapper de dispositivo a través de la red, para que el receptor despache a la implementación correcta de conversión de tensores. Los wrappers de CPU y de acelerador cumplen funciones distintas. Serializar el objeto resolvía ese despacho de forma cómoda, pero también permitía que un par aportara bytes a la reconstrucción de objetos de Python. El error de seguridad está en la representación elegida para un requisito legítimo. [3]
Por eso la pregunta de revisión es más útil que una prohibición general de usar pickle: ¿qué hechos deben cruzar el límite y qué decisiones de implementación deben quedarse en local? Un receptor puede necesitar un tipo de dispositivo, una forma o un descriptor de almacenamiento. No necesita aceptar una receta arbitraria para construir objetos de Python. Poner esa distinción por escrito da al parche un invariante que sobrevive a un cambio de transporte.
El parche devuelve la elección de la implementación al código local
En la 0.5.6, Deserialize decodifica un descriptor tipado WireIPCData y después usa su tipo para seleccionar una entrada de WRAPPER_TYPES. La selección del módulo y de la clase procede de esa correspondencia local. La presencia de importlib en la ruta corregida no constituye, por sí sola, un nuevo hallazgo de inyección: lo importante es si la entrada controla el nombre importado o solo selecciona un descriptor admitido de forma explícita. [4]
El parche conserva la interfaz de serialización existente, pero rechaza los datos pickle heredados. Mi criterio de aceptación es que los datos de red malformados o no admitidos fallen antes de cualquier reconstrucción arbitraria de objetos, sin una rama de compatibilidad que vuelva a intentar pickle en silencio. En una revisión, probaría etiquetas desconocidas, campos inesperados y descriptores antiguos, junto con entradas válidas admitidas. Un parche de seguridad debe preservar el trabajo previsto y eliminar a la vez la interpretación peligrosa.
# 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.Siga el decodificador compartido más allá del primer socket
ZeroMQ no es el único llamador que merece revisión. El PR de la corrección identifica un códec gRPC que usa el mismo deserializador a través de un campo llamado pickled_payload. Ese nombre de campo se mantiene por compatibilidad aunque su contenido pase a ser el nuevo descriptor. Buscar con grep el nombre antiguo del campo puede exagerar el peligro; buscar solo el primer transporte puede pasar por alto el límite compartido. Siga el flujo de datos real. [5]
La corrección también es más limitada que eliminar todos los usos de pickle en LMCache. Los mantenedores identifican otras rutas impulsadas por el motor que quedan fuera de esta reparación y las describen como deshabilitadas por defecto y no alcanzables por red en esa configuración. Es su declaración de alcance, no un resultado de alcanzabilidad demostrado aquí. El parche no establece la autenticación de RPC ni la autorización para usar un recurso de memoria compartida referenciado. La sintaxis, la identidad y la propiedad de los recursos son comprobaciones distintas. [5][6]
Un descriptor válido no demuestra la propiedad del recurso
El nuevo esquema hace algo más que cambiar de biblioteca de serialización. TensorMetadata restringe los campos numéricos y comprueba que la forma y los strides tengan rangos coincidentes; el descriptor de memoria compartida de CPU comprueba que la vista de tensor descrita cabe en el tamaño declarado. Estas comprobaciones acotan lo que significa un descriptor antes de que un wrapper lo use. Son propiedades concretas que un revisor puede convertir en casos límite, en lugar de aceptar la palabra «tipado» como garantía de seguridad. [7]
Queda una pregunta distinta: ¿quién está autorizado a nombrar o usar el recurso subyacente? Una descripción coherente internamente no la responde por sí sola. El PR separa de forma explícita la validación del descriptor de la autorización sobre la propiedad de la memoria compartida. Registraría la comprobación de propiedad del recurso y su ubicación por separado de la comprobación de decodificación, y exigiría evidencias de ambas allí donde el modelo de confianza del sistema las necesite. No es la afirmación de una nueva vulnerabilidad en la versión parcheada; es el límite de lo que este parche establece. La misma disciplina se aplica a cualquier corrección de un analizador: enumere el invariante que ahora impone e identifique después las decisiones de seguridad independientes que nunca prometió tomar. Una prueba del analizador superada no puede certificar esas decisiones por asociación. [5]
Que falte un marcador exige demostrar que el procesamiento terminó
Una prueba puede informar de un fallo antes de que el servidor vulnerable haya procesado su entrada. El autor del PR aborda de forma explícita este problema de sincronía asíncrona: una solicitud posterior en la misma conexión establece que el procesamiento ha terminado antes de examinar el marcador. El resultado de antes y después que se comunica pertenece a ese autor; no es un resultado de mi laboratorio. La lección general es demostrar que la comprobación se hizo después del trabajo relevante. [5]
Exigiría cuatro observaciones de un entorno de regresión local: que la solicitud llegó al decodificador previsto, que el procesamiento terminó, que el efecto secundario prohibido estuvo ausente y que una solicitud válida siguió funcionando. Un tiempo de espera agotado no sustituye a un rechazo. Tampoco un mensaje de excepción sustituye a la observación de los efectos secundarios: la ruta vulnerable puede lanzar la excepción después de ejecutar. Vincule cada aserción negativa a la ruta de código y a la condición de finalización que realmente demuestra.
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 absentParchee a los participantes y después compruebe quién puede alcanzarlos
Empiece la revisión del despliegue por el listener. El valor predeterminado documentado se enlaza a localhost; los despliegues multiproceso que lo exponen mediante --host tienen otro límite, y los mantenedores señalan su anterior ejemplo para Kubernetes. Localhost reduce la exposición en red, pero no autentica a un par local. Determine el modo, la versión cargada, la dirección de enlace y los clientes que pueden alcanzarlo antes de tratar la puntuación de severidad como la evaluación de su despliegue. [1][6]
Actualice a la vez los workers y los servidores que se comunican entre sí, porque el formato del descriptor ha cambiado; una flota mixta no es la estrategia de compatibilidad prevista. La versión 0.5.6 registra la reparación, mientras que los mantenedores describen por separado trabajos de seguridad adicionales. Mantenga restringido el listener después de actualizar. La conclusión que puede defender es que una ruta de reconstrucción concreta ha cambiado, no que un servicio de inferencia sea ahora seguro para clientes arbitrarios. [2][5]
Pida a una revisión de código que demuestre el límite
Un hallazgo útil debe nombrar el punto de entrada, el hook personalizado, la operación peligrosa, sus condiciones previas y las comprobaciones que llegan demasiado tarde. Su verificación debe identificar la revisión desplegada y cubrir todos los llamadores del decodificador compartido. Es un entregable más sólido que un resultado de búsqueda que dice que un proyecto contiene pickle, porque indica al equipo qué cambiar y cómo saber que el cambio llegó a producción.
Este es el tipo de límite que rastreo en una revisión de seguridad del código: el momento en que unos bytes no fiables adquieren comportamiento, y la suposición que hizo que ese comportamiento pareciera necesario. Si sus servicios pasan objetos a través de cachés, colas o utilidades de IPC, empiece por ahí. La parte difícil suele estar varias llamadas por debajo del endpoint que todos creían haber revisado ya.
Fuentes
- [1] LMCache: aviso GHSA-w5f2-9q9c-ff4h
- [2] LMCache: versión v0.5.6
- [3] LMCache: wrapper IPC de dispositivo vulnerable en la v0.5.5
- [4] LMCache: wrapper IPC de dispositivo corregido en la v0.5.6
- [5] LMCache: PR de la corrección #5644 y validación del autor
- [6] LMCache: actualización de los mantenedores sobre CVE-2026-105192
- [7] LMCache: esquema IPC tipado en la v0.5.6
¿Necesita que se compruebe esto en su propio producto?
Revisión de seguridad del código¿Quiere aprender a encontrar vulnerabilidades como esta por su cuenta?
Curso individual de revisión de código seguro