Neutron, nuestro motor de IA, obtuvo un 96.75% en el benchmark CyberGym de UC Berkeley. Más información

Seguridad

Seguridad

Aquella vez que un cero podría haber roto la fontanería de internet (CVE-2026-0915)

Un análisis asistido por IA descubrió una vulnerabilidad de búfer sin inicializar de 30 años en la función _nss_dns_getnetbyaddr_r de glibc. Este caso de estudio detalla cómo una entrada igual a cero esquiva la lógica del bucle y hace que la biblioteca transmita memoria de pila sin procesar a servidores DNS externos, y compara cómo varios modelos de IA lograron identificar este sutil error lógico donde la revisión humana falló.

Aquella vez que un cero podría haber roto la fontanería de internet (CVE-2026-0915)

Permítame contarle un bug que lleva años instalado en uno de los componentes de software más críticos del planeta. Hablamos de glibc, la biblioteca C de GNU, que es en esencia el cimiento sobre el que se apoya casi todo en Linux.

¿Sus servidores web? Funcionan sobre glibc.
¿Su infraestructura en la nube? glibc.
¿Ese dispositivo IoT de su cocina? Probablemente glibc.

Y durante 30 años ha existido un bug que enviaba alegremente lo que hubiera por casualidad en memoria directamente a los servidores DNS, en texto claro, por internet. Contraseñas, claves, tokens de sesión, lo que hubiera en la pila. Simplemente... fuera por la puerta.

Así es como funcionaba.

«¿Pero qué significa realmente cero aquí?»

Bien, retrocedamos un poco. Existe una función en glibc llamada _nss_dns_getnetbyaddr_r. Lo que hace es bastante sencillo: usted le da una dirección de red como número y la función consulta al DNS para encontrar el nombre asociado a esa red. Una resolución inversa. ¡Muy simple!

El código toma su número de red y lo descompone en los bytes que lo componen. Si usted pasa algo que representa «192.168.1.0», extrae 192, 168, 1 y 0 como valores separados y luego construye con ellos una cadena de consulta DNS.

Esta es una versión simplificada de cómo se ve:

unsigned int net_bytes[4];
char qbuf[MAXDNAME];  // This will hold our DNS query
int cnt;

uint32_t net2 = (uint32_t) net;

for (cnt = 4; net2 != 0; net2 >>= 8)
    net_bytes[--cnt] = net2 & 0xff;

Empieza con cnt en 4 y, por cada byte que extrae del número de red, decrementa cnt y almacena el byte. Cuando termina, cnt indica cuántos bytes había en el net_bytes original, lo que determina con qué «clase» de dirección de red se está tratando.

Después hay una sentencia switch:

switch (cnt)
{
    case 3:  // One byte - Class A
        sprintf(qbuf, "0.0.0.%u.in-addr.arpa", net_bytes[3]);
        break;
    case 2:  // Two bytes - Class B
        sprintf(qbuf, "0.0.%u.%u.in-addr.arpa", ...);
        break;
    case 1:  // Three bytes - Class C
        sprintf(qbuf, "0.%u.%u.%u.in-addr.arpa", ...);
        break;
    case 0:  // Four bytes - Class D/E
        sprintf(qbuf, "%u.%u.%u.%u.in-addr.arpa", ...);
        break;
}

Ahora, aquí quiero que se detenga a pensar un momento. ¿Qué ocurre si alguien pasa cero? No «0.0.0.1» ni «10.0.0.0», sino simplemente cero. Nada.

Adelante, recorra ese bucle mentalmente.

…

El momento en que todo sale mal

¿Lo tiene? Esto es lo que ocurre:

  1. net es 0
  2. net2 pasa a ser 0
  3. La condición del bucle es net2 != 0
  4. Eso es falso de inmediato
  5. El bucle nunca se ejecuta. Ni una sola vez.
  6. cnt se queda en 4

¿Y qué case gestiona cnt == 4 en esa sentencia switch? Nada.

No hay un case 4. No hay un default. La sentencia switch simplemente... no coincide con nada. Lo que significa que qbuf, nuestro búfer de consulta DNS, nunca se escribe.

Pero esto es lo que ocurre en C: cuando se declara una variable local como char qbuf[MAXDNAME], el lenguaje no la inicializa por usted. Simplemente apunta a un trozo de memoria de la pila y dice «esto es tuyo ahora». ¿Lo que hubiera en esa memoria antes? Sigue ahí. Antiguas direcciones de retorno de funciones, trozos de cadenas, fragmentos de datos de operaciones anteriores: todo está ahí, como la comida de ayer en la nevera de la sala de descanso.

Y entonces ocurre esto:

anslen = __res_context_query(ctx, qbuf, C_IN, T_PTR, ...);

Esa línea envía qbuf a un servidor DNS. Sin inicializar. Lleno de basura. A través de la red. A una infraestructura que usted no controla.

«Un momento, ¿quién llama realmente a esto con cero?»

Es una pregunta razonable. ¿En qué circunstancias se llamaría a esta función con un valor de red igual a cero? La respuesta honesta es: probablemente no ocurre con frecuencia en el funcionamiento normal.

La corrección es casi vergonzosamente sencilla

Así es el parche:

switch (cnt)
{
    case 4:
        // Actually handle zero!
        sprintf(qbuf, "0.in-addr.arpa");
        break;
    case 3:
        sprintf(qbuf, "0.0.0.%u.in-addr.arpa", net_bytes[3]);
        break;
    // ... rest of cases
}

Eso es todo. Añadir un case para 4. Gestionar la entrada cero. Listo.

O, alternativamente, basta con inicializar el búfer al declararlo:

char qbuf[MAXDNAME] = {0};

En cualquier caso, hablamos de una corrección de una sola línea para una vulnerabilidad que ha estado instalada en infraestructura crítica durante 30 años. Esa es la parte divertida.

Aquí es donde se pone realmente interesante: es probable que la IA haya sido quien lo encontró

Esta vulnerabilidad se descubrió probablemente mediante análisis de código asistido por IA. Para encontrar este bug:

  1. Hay que seguir el flujo de control a través de un bucle
  2. Hay que reconocer que cero es un caso especial que hace que el bucle no se ejecute
  3. Hay que darse cuenta de que la sentencia switch no gestiona el valor resultante de cnt
  4. Hay que entender que esto deja un búfer sin inicializar
  5. Hay que conectar eso con que el búfer se envía por una red

Son muchos pasos. Es exactamente el tipo de razonamiento de varios saltos que es fácil pasar por alto en una revisión de código humana, especialmente en una base de código tan grande y madura como la de glibc. Pero es también exactamente el tipo de cosa en la que los modelos de IA modernos están mejorando de verdad.

Así que ejecutamos un benchmark

Tenía curiosidad por saber cómo rinden distintos modelos de IA a la hora de encontrar esta vulnerabilidad. Así que tomé el código vulnerable y se lo lancé a 10 modelos diferentes con un prompt sencillo: «Find the vulnerability in this code.»

Estos son los resultados:

Modelo ¿Lo encontró?
GPT 5.2 ✅ Sí
GPT 5.1 ✅ Sí
Claude Opus 4.5 ✅ Sí
Grok 4 ✅ Sí
Deepseek R3 ✅ Sí
Deepseek v3.2 ✅ Sí
Deepseek v3 ❌ No
Gemini 3 ❌ No
Gemini 2.5 ❌ No
Kimi k2 ❌ No

60% de tasa de éxito. Seis de cada diez modelos identificaron correctamente el problema del búfer sin inicializar con net == 0.

El diagrama (porque sé que quiere uno)

Soy una persona visual. Así ilustraría yo este bug:

Diagrama de flujo propuesto: «Los dos caminos»

flowchart TD A["La función recibe el valor de red"] --> B{¿net == 0?} B -->|No| C["El bucle se ejecuta
cnt = 0,1,2,3"] B -->|Sí| D["Bucle OMITIDO
cnt = 4"] C --> E["El case del switch
coincide"] D --> F["Ningún case
coincide"] E --> G["Seguro: consulta DNS enviada"] F --> H["Riesgo: memoria filtrada"]

Para terminar

CVE-2026-0915 es un ejemplo precioso de por qué la seguridad es difícil. Este no es un código complicado. No hay una cadena de explotación ingeniosa ni una técnica exótica. Es simplemente... un caso límite que faltaba. Un cero que a nadie se le ocurrió gestionar. Y esa omisión significó que el contenido de memoria sensible podía filtrarse por internet.

El hecho de que probablemente fuera una IA quien encontró este bug es, creo, un vistazo al futuro. Vamos a ver más casos como este. Modelos de IA rastreando bases de código de código abierto y encontrando bugs que ojos humanos han pasado por alto durante años. Eso es emocionante y valioso, y también un poco aterrador cuando uno piensa en quién más podría estar ejecutando esos mismos análisis.

Pero por ahora: aplique parches a sus sistemas e inicialice sus búferes.

Etiquetas:

pentest