flawopen.com/Teardowns/cve-2024-26642-linux-nftables-anonymous-set-timeout
CVE-2024-26642: conjunto anónimo con flag de timeout en nf_tables de Linux
CVE-2024-26642 (CVSS 7.8 según el NIST): nf_tables_newset() aceptaba conjuntos anónimos que además llevaban la flag de timeout, una combinación que ninguna herramienta de espacio de usuario crea, lo que exponía conjuntos ligados a una regla a la expiración de elementos y a la recolección de basura. Linux 6.8 la rechaza con -EOPNOTSUPP.
Un guardarropa tiene dos sistemas. Los ganchos numerados pertenecen a un solo invitado durante una noche y se vacían en cuanto se marcha. Los tiques con hora los recorre cada hora un empleado que retira todo lo caducado. El formulario permite marcar ambas casillas para el mismo abrigo, así que el gancho se vacía cuando el invitado se va y además en la ronda del empleado. Dos personas creen encargarse del mismo gancho y ninguna está preparada para el horario de la otra. La solución es sencilla: el formulario ya no acepta ese par de casillas.
Conceptos Clave y Términos
nf_tables set- Estructura del kernel con direcciones, puertos u otras claves que comparan las reglas de nftables; se crea con un mensaje netlink
NFT_MSG_NEWSETy la gestionanf_tables_newset(). Conjunto anónimo (NFT_SET_ANONYMOUS)- Conjunto sin nombre, como
{ 22, 80 }escrito dentro de una regla. Está ligado a esa única regla y se destruye con ella en la misma transacción. NFT_SET_TIMEOUT- Flag que da a los elementos del conjunto una fecha de caducidad. Los elementos caducados los elimina el mecanismo de recolección de basura del conjunto.
NFT_SET_EVAL- Flag para conjuntos que se actualizan desde la ruta de paquetes, usada por las sentencias
meterheredadas. La corrección mantiene permitido anónimo + timeout + eval para ellas. CAP_NET_ADMIN en un user namespace- nf_tables acepta configuración de cualquier proceso con CAP_NET_ADMIN en su namespace de red, que un usuario sin privilegios obtiene creando un user namespace donde la distribución lo permite.
Análisis de Causa Raíz
nf_tables_newset() en net/netfilter/nf_tables_api.c solo comprobaba en las flags del conjunto los bits desconocidos y los conflictos MAP+OBJECT y EVAL+OBJECT. Aceptaba NFT_SET_ANONYMOUS | NFT_SET_TIMEOUT, de modo que un conjunto cuya vida está ligada a una sola regla y transacción también podía tener elementos que caducan y recoge la recolección de basura. El espacio de usuario nunca produce esa combinación y el código de ciclo de vida no se diseñó para ella. La corrección upstream la rechaza con -EOPNOTSUPP salvo que también esté NFT_SET_EVAL. El registro público no detalla el error de memoria resultante; el NIST le da 7.8 (confidencialidad, integridad y disponibilidad altas) y la CNA del kernel, 5.5 (solo disponibilidad).
Flujo de Ataque Paso a Paso
Obtener CAP_NET_ADMIN
Un usuario local crea un user namespace y un namespace de red (donde los user namespaces sin privilegios están habilitados) y obtiene así CAP_NET_ADMIN sobre el estado de nf_tables de ese namespace.
Pedir el conjunto no válido
Dentro de un lote netlink, el proceso envía NFT_MSG_NEWSET con NFTA_SET_FLAGS = NFT_SET_ANONYMOUS | NFT_SET_TIMEOUT y liga el conjunto a una regla nueva mediante una expresión de lookup.
El kernel lo acepta
nf_tables_newset() no encuentra ningún par de flags prohibido y crea un conjunto anónimo ligado a la regla cuyos elementos tienen caducidad.
Rutas de ciclo de vida sin probar
Confirmar, abortar o borrar la regla pone en marcha a la vez la destrucción del conjunto y la recolección de basura de elementos, en rutas que el código de transacciones de nf_tables no maneja de forma segura.
Código Fuente: Vulnerable vs. Seguro
/* net/netfilter/nf_tables_api.c: nf_tables_newset(), before the fix */
if (nla[NFTA_SET_FLAGS] != NULL) {
flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS]));
if (flags & ~(NFT_SET_ANONYMOUS | NFT_SET_CONSTANT |
NFT_SET_INTERVAL | NFT_SET_TIMEOUT |
NFT_SET_MAP | NFT_SET_EVAL |
NFT_SET_OBJECT | NFT_SET_CONCAT | NFT_SET_EXPR))
return -EOPNOTSUPP;
/* Only one of these operations is supported */
if ((flags & (NFT_SET_MAP | NFT_SET_OBJECT)) ==
(NFT_SET_MAP | NFT_SET_OBJECT))
return -EOPNOTSUPP;
if ((flags & (NFT_SET_EVAL | NFT_SET_OBJECT)) ==
(NFT_SET_EVAL | NFT_SET_OBJECT))
return -EOPNOTSUPP;
/* BUG: NFT_SET_ANONYMOUS | NFT_SET_TIMEOUT is accepted. A rule-bound
* anonymous set now also gets per-element expiry and garbage
* collection, a combination nft never creates and the set
* lifetime code was not written for. */
}
/* net/netfilter/nf_tables_api.c: nf_tables_newset(), commit 16603605b667 */
if (nla[NFTA_SET_FLAGS] != NULL) {
flags = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS]));
if (flags & ~(NFT_SET_ANONYMOUS | NFT_SET_CONSTANT |
NFT_SET_INTERVAL | NFT_SET_TIMEOUT |
NFT_SET_MAP | NFT_SET_EVAL |
NFT_SET_OBJECT | NFT_SET_CONCAT | NFT_SET_EXPR))
return -EOPNOTSUPP;
/* Only one of these operations is supported */
if ((flags & (NFT_SET_MAP | NFT_SET_OBJECT)) ==
(NFT_SET_MAP | NFT_SET_OBJECT))
return -EOPNOTSUPP;
if ((flags & (NFT_SET_EVAL | NFT_SET_OBJECT)) ==
(NFT_SET_EVAL | NFT_SET_OBJECT))
return -EOPNOTSUPP;
/* FIX: anonymous sets are never used with timeouts from userspace,
* so reject the combination before the set exists. NFT_SET_EVAL
* stays allowed so legacy meters keep working. */
if ((flags & (NFT_SET_ANONYMOUS | NFT_SET_TIMEOUT | NFT_SET_EVAL)) ==
(NFT_SET_ANONYMOUS | NFT_SET_TIMEOUT))
return -EOPNOTSUPP;
}
Lista de Verificación de Seguridad para Ingeniería
- ✓Ejecute un kernel que contenga el commit 16603605b667 (Linux 6.8, o un kernel estable o de distribución cuyo changelog mencione
CVE-2024-26642). - ✓Donde las cargas no los necesiten, desactive los user namespaces sin privilegios (
kernel.en Debian y Ubuntu,unprivileged_userns_clone=0user.o la restricción de userns de AppArmor en Ubuntu) para que los usuarios locales no lleguen a nf_tables.max_user_namespaces=0 - ✓En hosts que no usen nftables, impida que se cargue el módulo
nf_tables. - ✓En código de kernel y de API, exprese las combinaciones de flags aceptadas como una lista de permitidas explícita y rechace todo lo que ningún cliente soportado produce, en lugar de rechazar solo los pares malos conocidos.
- ✓Haga fuzzing de las interfaces netlink con descripciones de syzkaller que cubran todos los bits de flag, para que las combinaciones poco habituales lleguen primero en pruebas al código de ciclo de vida de los objetos.