← Blog

Bleeding Llama (CVE-2026-7482): Tu LLM Local Es una Fuga de Memoria Sin Autenticar

· 8 min de lectura · EN

El 1 de mayo de 2026 se publicó CVE-2026-7482 — apodado Bleeding Llama. Es un heap out-of-bounds read en el cargador de modelos GGUF de Ollama, descubierto por investigadores de Cyera. CVSS 9.1 Crítico.

Ollama es la forma más extendida de ejecutar modelos de lenguaje en local: ~170.000 estrellas en GitHub, 100M+ descargas en Docker Hub. Escucha en 0.0.0.0 por defecto, sin autenticación. Cyera cita alrededor de 300.000 instancias expuestas en internet en el momento de la divulgación — frente a las ~175.000 estimadas en enero de 2026.

Esta es la parte que debería preocuparte: un atacante sin autenticar puede volcar la memoria del proceso del servidor — prompts de usuario, system prompts de otros modelos y variables de entorno incluyendo credenciales de proveedores cloud — en tres llamadas API, sin dejar rastro relevante.

Casi todo lo escrito hasta ahora dice lo mismo: “actualiza a 0.17.1”. Es correcto e insuficiente. Este post cubre la cadena de ataque y, después, la parte que nadie está escribiendo: cómo un defensor detecta esto de verdad en su propio entorno.


El Bug: Tamaño Declarado vs. Tamaño Real

GGUF es el formato binario que usa Ollama para guardar los pesos del modelo. Como muchos formatos binarios, declara la forma de cada tensor en una cabecera — cuántos elementos contiene — de forma separada a los bytes de datos reales que vienen después.

La vulnerabilidad es que nada valida que el número de elementos declarado coincida con el tamaño real de los datos.

Cuando Ollama cuantiza o convierte un modelo, recorre los tensores y se fía del número declarado. La ruta de conversión — WriteToggml.ConvertToF32 — pasa q.from.Elements() (el número declarado por el atacante) directamente a rutinas de conversión de bajo nivel como ggml_fp16_to_fp32_row. Ese código usa el paquete unsafe de Go, que salta las garantías de seguridad de memoria del lenguaje.

Así que si declaras un tensor de, digamos, un millón de elementos pero solo aportas unos pocos bytes de datos reales, el bucle de conversión lee un millón de elementos de memoria — corriendo mucho más allá del final del buffer asignado y directo a la memoria heap adyacente. Esa memoria vecina es lo que tuviera el proceso: prompts, system prompts de otros modelos cargados, variables de entorno, secretos.

Declarado:  forma del tensor = 1.000.000 de elementos
Aportado:   un puñado de bytes reales
El bucle lee 1.000.000 de elementos → se sale del buffer
         → captura memoria heap adyacente en la salida

Un out-of-bounds read de manual (CWE-125). El giro está en lo que viene después — el atacante se queda con los bytes filtrados y los saca de la máquina.


La Cadena de Ataque: Tres Llamadas

La investigación de Cyera expone la cadena completa. Abusa de tres endpoints sin autenticar:

1. Subir el modelo malicioso. El atacante sube un blob GGUF manipulado vía POST /api/blobs/sha256:<digest>, con el contenido del fichero en el cuerpo HTTP.

2. Disparar el out-of-bounds read. El atacante llama a POST /api/create pidiendo cuantización. El GGUF manipulado declara una forma de tensor enorme (la demo usó un millón de elementos) sobre datos reales minúsculos. El bucle de conversión sobre-lee el heap. Y algo clave: el atacante pone el tipo origen a F16 y el destino a F32 — esa conversión no tiene pérdida, y el paso siguiente F32→F32 no hace nada, así que los bytes filtrados del heap llegan al disco intactos, sin que la matemática de cuantización los altere.

3. Exfiltrar. Los nombres de modelo en Ollama sirven también como destino de push. El atacante nombra el modelo como una URI que controla — p.ej. servidor-atacante.com/leak. Al llamar a POST /api/push, PushModel trata ese nombre como un destino HTTP y sube el modelo — con el heap filtrado incluido — al servidor del atacante. Nada valida que un nombre de modelo no sea una URI arbitraria.

Tres llamadas. Sin credenciales. En la demostración de Cyera, la memoria filtrada incluía credenciales AWS y variables de entorno de proveedores de modelos cloud que estaban en el proceso.


Por Qué Es Peor Que una Fuga de Memoria Normal

Dos propiedades lo convierten de “malo” en “peligroso a nivel operativo”:

  • La postura por defecto es la exposición. Ollama escucha en 0.0.0.0 sin auth nada más instalarlo. El estado vulnerable no es algo en lo que tengas que caer por mala configuración — es el valor por defecto de fábrica en el momento en que el puerto es alcanzable.
  • La exfiltración viene incluida. La mayoría de OOB reads filtran memoria en algún sitio del que luego tienes que encontrar la forma de leerla. Aquí, la misma funcionalidad del producto (/api/push a un destino con nombre) le entrega al atacante un canal de exfiltración limpio. La lectura y la exfiltración son el mismo flujo de trabajo.

Y es silencioso. No hay log de auth fallida, no hay crash, no hay anomalía obvia en un montaje ingenuo. Por eso mismo la detección importa más que la nota de parcheo.


Detección: Lo Que Nadie Más Está Escribiendo

Parchear a Ollama 0.17.1 (la versión corregida; ver el PR de fix upstream #14406 / commit 88d57d0) cierra el bug. Pero en cualquier organización real tienes dos problemas que el parche no resuelve:

  1. No sabes cuántas instancias de Ollama existen en tu red — el shadow AI es real; los ingenieros las corren en estaciones de trabajo y máquinas de desarrollo.
  2. Quieres saber si alguien ya lo intentó antes de que parchearas.

1. Encuentra tus instancias expuestas primero

La superficie vulnerable es “un Ollama alcanzable desde donde no debería”. Encuéntralo antes que el atacante.

# Barrido interno: puerto Ollama por defecto, raíz de la API sin auth
# (ejecútalo solo contra redes que poseas / estés autorizado a probar)
nmap -p 11434 --open -oG - 10.0.0.0/8 | awk '/11434\/open/{print $2}'

# Confirma que es Ollama y alcanzable sin auth:
curl -s http://<host>:11434/api/tags | head

Externamente, el mismo fingerprint que usan los investigadores en Shodan (product:"Ollama", puerto por defecto 11434) es el que usa un atacante. Si tu ASN aparece ahí, eso es tu incidente, no una hipótesis.

La comprobación más importante es la dirección de bind. 0.0.0.0 significa “todas las interfaces, incluidas las que dan a tus usuarios”:

# En cada host que ejecute Ollama:
ss -tlnp | grep 11434
# 0.0.0.0:11434  → expuesto a la red
# 127.0.0.1:11434 → solo local (bien)

2. Caza el ataque en tus logs

El ataque tiene una secuencia distintiva de tres endpoints desde un único origen en una ventana corta: subida de blob, create/cuantización, y luego un push a un destino externo. Cualquiera de ellos por separado es normal. La secuencia — especialmente un /api/push a un host fuera de tu registro — no lo es.

Si tienes un reverse proxy o logs de acceso delante de Ollama, la lógica de detección es:

ALERTA cuando, desde una IP origen dentro de N minutos:
  POST /api/blobs/sha256:*         (subida de modelo)
  Y POST /api/create               (se pide cuantización)
  Y POST /api/push                 (push hacia fuera)
  Y el host destino del push  ∉  {tus registros de modelos conocidos}

Pseudo-regla (adáptala al lenguaje de consulta de tu SIEM):

source.ip                      = <valor único>
http.request.method            = POST
url.path IN ("/api/blobs/*", "/api/create", "/api/push")
| sequence by source.ip within 10m
  [ url.path : "/api/blobs/*" ]
  [ url.path : "/api/create" ]
  [ url.path : "/api/push" and NOT destination.host in (registros_permitidos) ]

Dos señales más baratas que merece la pena alertar aunque no tengas la lógica de secuencia completa:

  • Cualquier /api/push a un destino fuera de tu allowlist. En la mayoría de organizaciones los modelos se descargan mucho más de lo que se suben, y los push van a un registro conocido. Un push saliente a un host arbitrario es anómalo por sí solo.
  • /api/create con cuantización desde un origen inesperado. La creación de modelos es una acción de admin/CI, no algo que hagan clientes aleatorios. Una llamada create desde una subred de usuarios merece una mirada.

3. El egress es tu red de seguridad

Bleeding Llama exfiltra sobre un push HTTP saliente. Si tus hosts de Ollama no tienen razón de negocio para hacer conexiones salientes arbitrarias, un egress default-deny convierte una fuga crítica sin autenticar en un intento fallido y contenido — incluso en una máquina sin parchear. Este es el control que te salva cuando el parcheo va por detrás de la realidad.


La Lección de Fondo

El tooling de LLM local creció optimizando para “funciona en cinco minutos en mi portátil”. Ese valor por defecto — escucha en todo, no autentiques nada — está bien en un portátil y es catastrófico en el momento en que aterriza en una red alcanzable. La configuración peligrosa rara vez parece peligrosa; parece una función cómoda (“accede a mi modelo desde otro dispositivo”).

La conclusión de detection engineering no es específica de Ollama. A medida que la infraestructura de IA se cuela en todas las organizaciones por la puerta de atrás, el trabajo del blue team es el mismo de siempre: saber qué está escuchando, saber cómo es el tráfico normal, y restringir el egress para que una fuga no tenga a dónde ir. Un CVE con parche es la mitad fácil. Encontrar la instancia fantasma que lo ejecuta en 0.0.0.0 es la mitad que de verdad te protege.

Parchea a 0.17.1. Haz bind a 127.0.0.1. Y luego ve a cazar las copias que no sabías que tenías.


Si esto te ha resultado útil, quizá te interese Harness Engineering: 6 Capas de Defensa que Construí Alrededor de Claude Code — el mismo razonamiento de “restringe lo que el agente puede alcanzar”, aplicado a sistemas de IA autónomos.