RCE sin autenticar en el backend RPC de inferencia distribuida de llama.cpp
Una comprobación de límites ausente en el backend RPC de llama.cpp permite que cualquier cliente con acceso al puerto del servidor lea y escriba la memoria del proceso y logre ejecución remota de código. Corregido en b8492.
En resumen La mayoría de los textos sobre seguridad de LLM tratan de prompts. Este trata de un socket de red. llama.cpp incluye un backend RPC opcional para repartir la inferencia entre varias máquinas. Una comprobación de límites ausente en el análisis de tensores permite que cualquier cliente capaz de alcanzar el puerto RPC lea y escriba la memoria del servidor, evada el ASLR mediante filtraciones de punteros a nivel de protocolo y logre ejecución remota de código, sin autenticación, sin prompt y sin interacción del usuario. El aviso del mantenedor (GHSA-j8rj-fmpv-wcxw) se publicó el 26 de marzo de 2026; la corrección llegó en la build b8492. La propia documentación del proyecto ya advertía de no exponer este servicio en una red; aquí está el motivo.
¿Qué es esto?
llama.cpp es el motor C/C++ de referencia para ejecutar localmente modelos de pesos abiertos, con muy por encima de 100 000 estrellas en GitHub. Más allá del uso en una sola máquina, ofrece un backend RPC de inferencia distribuida: se compila con -DGGML_RPC=ON, se arranca un ggml-rpc-server en cada host con GPU y un proceso principal delega los cálculos de tensores por TCP. Así se ejecuta un modelo demasiado grande para una sola máquina repartido entre varias.
El fallo es un error de seguridad de memoria en el analizador de peticiones del servidor RPC. Se reportó al CERT/CC en febrero de 2026, se publicó como aviso de seguridad del mantenedor el 26 de marzo de 2026 y recibió una puntuación CVSS 3.1 9.8 (crítica), clasificado como CWE-119 (restricción indebida de operaciones dentro de los límites de un búfer de memoria). Afecta a las builds anteriores a b8492, donde se corrigió.
Cómo funciona
Cuando el servidor RPC ejecuta un grafo de cómputo, reconstruye cada tensor a partir de los bytes recibidos por la red mediante una función auxiliar. Esa función solo valida el puntero data proporcionado por el atacante cuando el campo buffer del tensor es distinto de cero. Al fijar buffer en 0, la comprobación de límites se omite por completo, mientras que el puntero data se sigue tomando directamente de la petición y se entrega a los núcleos de cómputo:
// simplificado: buffer = 0 -> nullptr, así que el bloque de validación de abajo nunca se ejecuta
result->buffer = reinterpret_cast<ggml_backend_buffer_t>(tensor->buffer);
if (result->buffer) {
// las comprobaciones de límites / coherencia sobre result->data viven SOLO en este bloque
}
result->data = reinterpret_cast<void *>(tensor->data); // tomado de la petición sin condición
Esa única brecha otorga al atacante una primitiva de lectura/escritura arbitraria contra el proceso servidor. Como el protocolo RPC también devuelve direcciones de memoria en bruto cuando un cliente reserva un búfer, no hace falta adivinar nada: las filtraciones de punteros evaden el ASLR, y un puntero de función situado en la estructura interna de un búfer puede sobrescribirse para redirigir una petición posterior hacia una operación de memoria controlada por el atacante. Omitimos deliberadamente una cadena de explotación funcional: aquí interesa la clase de error, no un arma lista para copiar y pegar. El informe del reportante y el aviso contienen los detalles de reproducción para los defensores que los necesiten.
Dos hechos lo hacen concreto y no teórico. Primero, no existe autenticación: el protocolo no la contempla, por lo que la accesibilidad equivale al compromiso. Segundo, la misma causa raíz estaba detrás de dos fallos de lectura fuera de límites corregidos en esta misma función en 2024; aquellas correcciones endurecieron los comandos de lectura y escritura de tensores, pero el comando de ejecución de grafo toma una ruta de código distinta que nunca se cubrió. La debilidad subyacente está presente desde la primera integración del backend RPC en mayo de 2024.
Ejemplo de prompt
El fragmento siguiente muestra la causa raíz: el analizador de tensores RPC omite su comprobación de límites cuando buffer es 0, de modo que un puntero data controlado por el atacante llega directo a los núcleos de cómputo. No se muestra ningún exploit funcional — esto es para defensores.
# llama.cpp RPC graph-compute RCE (illustrative, defensive)
# The tensor parser only bounds-checks data when buffer != 0:
if tensor.buffer: # attacker sets buffer = 0 to skip this
validate(tensor.data) # [bounds check lives only here]
result.data = tensor.data # [payload] taken from the wire unconditionally
# Root cause: reachability == compromise; the RPC protocol has no auth.
# Defense: upgrade to build b8492+, bind ggml-rpc-server to 127.0.0.1,
# never publish port 50052, and tunnel nodes over mTLS/WireGuard.
Por qué importa
Un nodo de inferencia es un objetivo de alto valor. Guarda pesos de modelo, prompts en curso y a menudo claves de API, y suele residir en un host GPU con amplio alcance en la red interna. La ejecución de código en un servidor RPC puede significar movimiento lateral por un clúster, exfiltración de secretos y modelos, o simplemente GPU secuestradas. El puerto de escucha por defecto (50052) es fácil de identificar, y la propia razón de ser del backend —inferencia entre máquinas— empuja a los operadores hacia exactamente la exposición de red que convierte esto en ejecución remota de código. Los despliegues Docker que publican el puerto o escuchan en 0.0.0.0 son directamente accesibles, a menudo como root.
Defensas
- Actualizar a la build b8492 o posterior (la corrección, PR #20908), que restablece la validación de límites en la ruta de ejecución de grafo.
- Nunca exponer el puerto RPC en una red no confiable. El README de RPC del proyecto es explícito: el backend es una prueba de concepto, «frágil e inseguro», y jamás debe ejecutarse en una red abierta. Trátelo como un servicio reservado a una LAN de confianza.
- Escuchar en
127.0.0.1y tunelizar. Para una inferencia multi-host real, mantenga cadaggml-rpc-serveren el bucle local y conecte los nodos mediante una red superpuesta con autenticación mutua (WireGuard, túnel mTLS, VPC privada) en lugar de TCP en bruto sobre una interfaz enrutable. - No ejecutar los contenedores de inferencia con
--network=host, y nunca publicar 50052 en Internet. Exponga solo la API HTTP prevista, tras autenticación. - Segmentar y monitorizar. Coloque los nodos de inferencia GPU en una zona de red aislada; alerte sobre conexiones inesperadas a puertos RPC y sobre procesos de inferencia que lancen shells.
- Buscar exposición. Escanee sus propios rangos en busca de puertos RPC accesibles y confirme que ninguno responde desde fuera de la frontera de confianza.
Estado
| Elemento | Detalle |
|---|---|
| Referencia | CVE-2026-34159 · GHSA-j8rj-fmpv-wcxw · CWE-119 |
| Afectado | Backend RPC de llama.cpp, builds anteriores a b8492 |
| Severidad | CVSS 3.1 9.8 (crítica) — red, sin autenticar, sin interacción |
| Reportado | CERT/CC, 8 feb. 2026 (VU#748698) |
| Aviso | Publicado el 26 de marzo de 2026 |
| Corrección | Build b8492 (PR #20908) |
| Anteriores relacionados | Misma función, dos correcciones de lectura fuera de límites en 2024 (CVE-2024-42478 / CVE-2024-42479) |
| Nota | El backend RPC está documentado como inseguro y fuera del alcance de vulnerabilidades del proyecto |
Sources
- → https://nvd.nist.gov/vuln/detail/CVE-2026-34159
- → https://github.com/ggml-org/llama.cpp/security/advisories/GHSA-j8rj-fmpv-wcxw
- → https://github.com/ggml-org/llama.cpp/pull/20908
- → https://github.com/ggml-org/llama.cpp/blob/master/tools/rpc/README.md
- → https://www.sentinelone.com/vulnerability-database/cve-2026-34159/