Introducción
Redis es un almacén de datos en memoria de código abierto, ampliamente utilizado como caché, intermediario de mensajes y base de datos NoSQL de alto rendimiento. Ofrece estructuras de datos enriquecidas como cadenas, hashes, listas, conjuntos, conjuntos ordenados, mapas de bits, HyperLogLogs y flujos, respaldadas por operaciones atómicas y una latencia muy baja. La persistencia está disponible mediante instantáneas RDB y AOF, y la alta disponibilidad se logra a través de la replicación, Sentinel y Cluster. Redis también admite scripts del lado del servidor con Lua para ejecutar operaciones complejas de forma atómica. Su velocidad, flexibilidad y ecosistema consolidado lo han convertido en un componente fundamental para los sistemas modernos sensibles a la latencia.
En octubre de 2025, Redis lanzó la versión 8.2.2 , una actualización de seguridad que corrige cuatro vulnerabilidades en el motor Lua integrado. La más crítica es una vulnerabilidad de uso de memoria liberada en Lua que permite a los atacantes escapar del entorno aislado de scripting y ejecutar código en el host cuando usuarios no autorizados pueden ejecutar código Lua arbitrario. La actualización también corrige un desbordamiento de enteros en `unpack`, una vulnerabilidad de ejecución de scripts entre usuarios y una lectura fuera de límites en el analizador léxico de Lua. En esta publicación, analizamos cada CVE, revisamos los parches y describimos pasos prácticos para fortalecer sus implementaciones de Redis.
Descripción general de las vulnerabilidades
Antes de adentrarnos en el código, aquí tenéis un resumen rápido de las correcciones que incluye la versión 8.2.2 en el motor Lua.
CVE-2025-46817: Desbordamiento de enteros en desempaquetado (CVSS 9.8, Crítico)
Los argumentos extremos de `unpack(tbl, i, j)` pueden provocar un desbordamiento del contador interno de valores a devolver. Este desbordamiento puede eludir las comprobaciones de pila y provocar fallos o corrupción de memoria, convirtiéndolo en una herramienta útil para la explotación.
CVE-2025-46818: Ejecución de scripts entre usuarios a través de un estado de tiempo de ejecución compartido (CVSS 7.3, Alto)
Los controles laxos sobre las metatablas y las API relacionadas con el entorno permitían que un script influyera en la ejecución de otros. En algunas configuraciones, esto significaba que un usuario con menos privilegios podía afectar la ejecución en el contexto de otro usuario, debilitando el aislamiento.
CVE-2025-46819: Lectura fuera de límites de Lua en el análisis de cadenas largas (CVSS 7.1, Alta)
El manejo poco fiable de delimitadores de cadenas/comentarios largos ([=[ … ]=], etc.) en el analizador léxico de Lua podría provocar que se desborde su búfer. Esto puede causar el fallo de Redis y, en casos extremos, exponer memoria adyacente.
CVE-2025-49844: Uso después de liberación en Lua en el analizador sintáctico (CVSS 10.0, Crítico)
Un script Lua cuidadosamente diseñado puede manipular la recolección de basura y el análisis sintáctico de forma que se produzca un error de uso después de liberación en el motor Lua integrado.
A continuación, analizaremos cada uno de estos aspectos con más detalle, con los fragmentos de parche correspondientes.
Análisis detallado de las soluciones: Vulnerabilidades
CVE-2025-46817: Desbordamiento de enteros en desempaquetado
Este problema afecta a la función `unpack(tbl, i, j)` de Lua 5.1 integrada en Redis. Con valores extremos de `i` y `j`, el cálculo de la cantidad de resultados a devolver podría desbordarse, generando un recuento erróneo y eludiendo las comprobaciones de la pila. Esto puede provocar un fallo del sistema o corrupción de memoria y puede utilizarse como componente básico para la ejecución remota de código en escenarios hostiles.
El parche para esta vulnerabilidad (CVE) se puede consultar aquí: fc9abc7
Análisis de parches
La solución hace que el manejo de rangos de unpack sea explícito y seguro.
- El cálculo de conteo se cambió a aritmética sin signo segura.
Antes del parche :
static int luaB_unpack (lua_State *L) {
entero i, e, n;
luaL_checktype(L, 1, LUA_TTABLE);
i = luaL_optint(L, 2, 1);
e = luaL_opt(L, luaL_checkint, 3, luaL_getn(L, 1));
Si (i > e) devuelve 0; /* rango vacío */
n = e – i + 1; /* puede producirse un desbordamiento (con signo) */
si (n <= 0 || !lua_checkstack(L, n))
return luaL_error(L, “demasiados resultados para desempaquetar”);
/* enviar n resultados… */
}
Aquí, e – i + 1 se realiza en enteros con signo. Con valores extremos (por ejemplo, 0 y 2147483647), esto puede provocar un desbordamiento, y n <= 0 no es una protección fiable.
Después del parche :
static int luaB_unpack (lua_State *L) {
entero i, e;
entero sin signo n;
luaL_checktype(L, 1, LUA_TTABLE);
i = luaL_optint(L, 2, 1);
e = luaL_opt(L, luaL_checkint, 3, luaL_getn(L, 1));
Si (i > e) devuelve 0; /* rango vacío */
n = (unsigned int)e – (unsigned int)i; /* elementos menos 1 */
si (n >= INT_MAX || !lua_checkstack(L, ++n))
return luaL_error(L, “demasiados resultados para desempaquetar”);
lua_rawgeti(L, 1, i); /* insertar arg[i] */
mientras (i++ < e) {
lua_rawgeti(L, 1, i);
}
devolver n;
}
Puntos clave:
- Utiliza enteros sin signo para el intervalo para evitar valores negativos/desbordados.
- Trata a n como “contador – 1” y lo incrementa una vez antes de la comprobación de la pila.
- Impone un límite superior duro (n >= INT_MAX → error).
- Garantiza que la pila tenga espacio para todos los resultados antes Empujar cualquier cosa.
- Indexación de matrices hecha explícita
Ajuste relacionado en el código de la tabla:
Después del parche
si (1 <= clave && clave <= t->tamañoarray)
devolver &t->array[clave-1];
En lugar de recurrir a un truco de conversión sin signo, utiliza una comprobación de límites clara, lo que evita comportamientos extraños para índices enormes o negativos.
En conjunto, estos cambios implican que los rangos de desempaquetado extremos ya no provocan recuentos desbordados ni envíos inseguros. Las llamadas de gran tamaño ahora fallan con un claro error de «demasiados resultados para desempaquetar» en lugar de arriesgarse a un fallo del sistema o a la corrupción de memoria.
CVE-2025-46819: Lectura fuera de límites en Lua durante el análisis de cadenas largas
Este problema reside en el analizador léxico de Lua, específicamente en cómo procesa cadenas largas y comentarios extensos como [=[ … ]=]. En versiones vulnerables, ciertos patrones de delimitadores malformados o extremos podrían provocar que el analizador léxico sobrepase el final de su búfer de entrada. Esto puede provocar el fallo de Redis (ataque de denegación de servicio) y, en algunos casos extremos, exponer bytes de memoria adyacente.
El parche para esta vulnerabilidad (CVE) se puede consultar aquí: 3a1624d
Análisis de parches
La solución hace que el manejo de cadenas largas sea más estricto y menos frágil.
El análisis de delimitadores se vuelve inequívoco.
La función auxiliar que analiza secuencias como [===[ y ]===] se actualiza para usar un tipo más seguro y devolver valores claros y consistentes:
Después del parche
static size_t skip_sep(LexState *ls) {
tamaño_t recuento = 0;
int s = ls->current; /* '[' o ']' */
lua_assert(s == '[' || s == ']');
guardar_y_siguiente(ls); /* consumirlo */
while (ls->current == '=') { /* contar '=' */
guardar_y_siguiente(ls);
recuento ++;
}
if (ls->current == s) /* coincide con el segundo '[' o ']' */
return count + 2; /* delimitador largo válido */
else if (count == 0)
return 1; /* no es una cadena/comentario largo */
más
return 0; /* malformado */
}
- Los delimitadores de cadenas largas válidos ahora devuelven una longitud bien definida (>= 2).
- Llano [ cae hacia atrás limpiamente (1).
- Los patrones malformados [==… devuelven 0 y se tratan como errores.
Esto cierra la brecha donde patrones extraños o incompletos podrían confundir al analizador léxico y hacerle leer más allá del búfer.
Se corrigen los desplazamientos para el corte del cuerpo.
Al extraer el contenido real de la cadena, el parche alinea las operaciones matemáticas de segmentación con el resultado de skip_sep:
Tras el parche:
si (seminfo) {
seminfo->ts = luaX_newstring(
yo,
luaZ_buffer(ls->buff) + sep,
luaZ_bufflen(ls->buff) – 2 * septiembre
);
}
Anteriormente, los desplazamientos no coincidían correctamente con la semántica del delimitador, lo que podía provocar errores sutiles de desplazamiento. Ahora, el inicio y la longitud se derivan directamente de sep, por lo que el analizador léxico se mantiene dentro de los límites.
Los tipos coinciden con lo que realmente hace el código.
Los contadores e índices involucrados en esta lógica ahora utilizan tipos sin signo/tamaño apropiados en lugar de enteros con signo sobrecargados, lo que reduce el riesgo de que se produzcan valores negativos o desbordamientos en la aritmética de punteros.
Todo esto significa que, incluso con secuencias [=…=[ … ]=…=] enormes o intencionadamente fragmentadas, Lua ahora las analiza correctamente o las rechaza con un error normal. Ya no existe la posibilidad de que un script malicioso pueda forzar al analizador léxico a examinar más allá del final del búfer.
CVE-2025-46818: Un script Lua puede ejecutarse en el contexto de otro usuario.
Este problema radica en mantener los scripts de Lua limitados al usuario y al entorno al que pertenecen . En versiones vulnerables, los elementos de tiempo de ejecución compartidos, como las metatablas en los tipos principales y las API de entornos heredadas, eran lo suficientemente flexibles como para que un script pudiera influir en la ejecución de otros. En algunas implementaciones, esto podía dificultar el aislamiento entre usuarios.
El parche para esta vulnerabilidad (CVE) se puede consultar aquí: 45eac02
Análisis de parches
La solución logra dos cosas principales: protege las metatablas principales y limita el uso de API heredadas riesgosas.
- Las metatablas de tipo principal están protegidas.
Anteriormente, los tipos básicos de Lua expuestos a los scripts (cadenas, números, booleanos, etc.) no contaban con una barrera estricta que impidiera cambios en las metatablas desde los scripts de usuario. Esto significaba que una modificación a una metatabla compartida podía afectar a otro código que se ejecutara en el mismo motor.
El código modificado refuerza explícitamente la seguridad de esas metatablas durante la configuración del entorno Lua. En forma simplificada:
Código corregido: marcar las metatablas primitivas como protegidas
static void luaProtectPrimitiveMetatables(lua_State *L) {
tipos int constantes [] = {LUA_TSTRING, LUA_TNUMBER, LUA_TBOOLEAN, LUA_TNIL};
for (size_t i = 0; i < sizeof(types)/sizeof(types[0]); i++) {
luaL_getmetatable(L, lua_typename(L, types[i]));
if (!lua_isnil(L, -1)) {
lua_pushliteral(L, “protegido”);
lua_setfield(L, -2, “__metatable”); /* bloquear metatabla */
}
lua_pop(L, 1);
}
}
Esta protección __metatable significa que los scripts de usuario aún pueden ver estos tipos, pero no pueden reemplazar ni reescribir sus metatablas globalmente. Cualquier intento de hacerlo fallará limpiamente en lugar de cambiar silenciosamente el comportamiento para todos los demás.
- Las API de entornos heredados requieren una activación explícita.
Las API de Lua más antiguas que pueden manipular entornos ahora están deshabilitadas de forma predeterminada y solo se exponen cuando un operador las habilita explícitamente.
Conceptualmente, el cambio se ve así:
Antes del parche: las API obsoletas siempre estaban disponibles en el entorno del script.
constante estática luaL_Reg redis_compat_funcs[] = {
{“getfenv”, luaB_getfenv},
{“setfenv”, luaB_setfenv},
{“newproxy”, luaB_newproxy},
{NULL, NULL}
};
scriptCreateEnv(…) {
luaL_register(L, “_G”, redis_compat_funcs);
}
Tras la actualización: solo se registrará cuando esté activada la opción lua-enable-deprecated-api.
scriptCreateEnv(…) {
if (server.lua_enable_deprecated_api) {
luaL_register(L, “_G”, redis_compat_funcs);
}
}
Si lua-enable-deprecated-api no está habilitado, estas funciones simplemente no están presentes en el entorno del script, eliminando un poderoso conjunto de herramientas para la manipulación entre contextos.
En conjunto, estos cambios garantizan:
- Los scripts no pueden corregir silenciosamente el comportamiento fundamental de todos los demás scripts reescribiendo las metatablas principales.
- Las primitivas de manipulación del entorno son único Disponibles si un operador las activa deliberadamente.
Esto hace que el entorno de ejecución de Lua sea más predecible y mantiene cada script mucho más cerca de sus privilegios y contexto previstos; algo importante para cualquier implementación de Redis compartida o multiinquilino.
CVE-2025-49844: Uso después de liberación en Lua en el analizador sintáctico
Este error reside en la forma en que Redis integra el analizador sintáctico de Lua. Bajo ciertas condiciones, un script de Lua manipulado puede activar la recolección de basura en el momento menos oportuno, provocando que Redis lea un puntero a memoria que ya ha sido liberada.
El parche para esta vulnerabilidad (CVE) se puede consultar aquí: d5728cb5795
Análisis de parches
El cambio clave es que ahora Redis mantiene una referencia adecuada a los objetos Lua que utiliza el analizador (como el nombre del fragmento) en la pila Lua durante el análisis, por lo que el recolector de basura los ve como vivos y no puede liberarlos ni moverlos prematuramente.

Anteriormente, el fragmento “nombre” se creaba en línea y se pasaba directamente al analizador léxico:
luaX_setinput(L, &lexstate, z, luaS_new(L, name));
Debido a que ese valor no estaba claramente anclado (por ejemplo, en la pila de Lua), una desafortunada combinación de GC o defrag más scripting agresivo podría, en teoría, dejar al analizador manteniendo un puntero a algo que había sido movido o liberado.
El código corregido deja explícita esa propiedad:
TString *tname = luaS_new(L, nombre);
setsvalue2s(L, L->top, tname); /* mantener el nombre en la pila */
incr_top(L);
luaX_setinput(L, &lexstate, z, tname); /* usarlo de forma segura para análisis léxico/sintáctico */
/* … analizar el fragmento … */
–L->top; /* elimina la referencia cuando termines */
Al mantener este estado referenciado durante todo el análisis, el recolector de basura y la desfragmentación lo tratan como activo y no pueden recuperarlo ni reubicarlo durante la ejecución. Esto elimina efectivamente el estrecho margen de tiempo que hacía posible el uso después de la liberación.
Escenarios de despliegue en riesgo
Estas cuestiones son especialmente relevantes en entornos donde:
- Las aplicaciones utilizan scripts Lua en Redis.
- El mismo clúster de Redis se comparte entre equipos o inquilinos.
- Se puede acceder a Redis, directamente o a través de su aplicación, desde entradas no confiables o controladas por el usuario.
Acciones recomendadas
1. Parchear primero
- Actualizar a la versión parcheada de Redis 8.2.2.
- Considere esto como de alta prioridad en cualquier lugar donde se pueda ejecutar Lua mediante código no confiable o semi-confiable (por ejemplo, a través de EVAL / SCRIPT LOAD) o donde los clústeres de Redis se compartan entre equipos/inquilinos.
2. Bloquear la programación en Lua
- Utilice listas de control de acceso (ACL) para restringir la ejecución de scripts Lua únicamente a servicios de confianza.
- Mantén Redis en redes privadas, sin exposición directa a internet.
- Deje deshabilitada la opción lua-enable-deprecated-api a menos que tenga una necesidad clara y comprobada.
3. Cargas de trabajo sensibles a los segmentos
- Ejecutar código Lua no confiable o controlado por el cliente en instancias de Redis separadas.
- No mezcle scripts controlados por el inquilino con clústeres que contengan datos críticos o confidenciales.
4. Utilice controles de seguridad para aumentar la visibilidad (no como sustituto de la aplicación de parches).
- Configure los sistemas IPS/IDS/WAF para detectar y bloquear el acceso directo a Redis y el uso sospechoso del protocolo Redis.
- Utilice protección EDR/AV/de tiempo de ejecución en los hosts de Redis para detectar comportamientos similares a los de un exploit.
Referencias:
Autores:
Vinay Kumar
Adrip Mukherjee
Nandini Seth
Suvarnjeet Jagtap



