
Advisory pubblico e analisi tecnica per CVE-2026-36590, una vulnerabilità denial-of-service in NanoMQ v0.24.9.
Questo repository contiene l'advisory pubblico e l'analisi tecnica per CVE-2026-36590, una vulnerabilità di negazione del servizio che colpisce EMQ NanoMQ v0.24.9.
Questo repository è pubblico per fornire un riferimento tecnico accessibile per la revisione della CVE. La classificazione finale di CVE-2026-36590 sarà determinata dal CVE Team o dal CNA competente.
Il team di NanoMQ ha contestato questo problema e lo considera legato alla configurazione. Questo repository si concentra sulle evidenze tecniche, inclusi materiali di riproduzione, risultati ASAN, log di runtime e analisi del codice sorgente.
/nanomq/nng/src/supplemental/mqtt/mqtt_qos_db_api.c/ nni_qos_db_setQuando NanoMQ v0.24.9 viene compilato senza supporto SQLite mentre sqlite.enable è configurato su true, il puntatore al database QoS può rimanere NULL. Durante la gestione dei messaggi MQTT con QoS, nni_qos_db_set può uscire prematuramente senza rilasciare un riferimento a un messaggio clonato. Messaggi MQTT PUBLISH ripetuti con QoS > 0 possono causare un consumo continuo di memoria, portando infine alla negazione del servizio.
Il problema dipende da una configurazione runtime non predefinita, ma i seguenti fatti mostrano perché richiede ancora un'ulteriore revisione:
NanoMQ accetta la configurazione runtime e continua a funzionare, invece di segnalare che il supporto SQLite non è disponibile nella build corrente.
Questa discrepanza di configurazione può verificarsi nella pratica. Un utente può compilare NanoMQ con il normale comando di build, ma successivamente copiare o abilitare la sezione di persistenza SQLite da un file di configurazione di esempio.
Il percorso di codice interessato non convalida completamente questo stato incoerente. Quando la configurazione runtime considera SQLite abilitato ma il puntatore al database è NULL, nni_qos_db_set() esce prematuramente senza rilasciare l'oggetto messaggio passato.
Dopo che questa configurazione viene accettata, il problema può essere innescato dal traffico MQTT remoto. Messaggi PUBLISH ripetuti con QoS > 0 possono entrare nel percorso interessato e causare perdite di memoria accumulate.
I test ASAN con 1, 50 e 500 cicli di riproduzione mostrano che i byte e le allocazioni persi aumentano con il numero di tentativi di attivazione. Ciò suggerisce che il problema dipende dal numero di input, piuttosto che essere una piccola perdita una tantum.
Gli utenti dovrebbero limitare l'accesso alle istanze di NanoMQ, evitare di esporre i servizi MQTT a client non affidabili ed evitare di abilitare la persistenza SQLite in build senza supporto SQLite. Il fornitore dovrebbe aggiungere una convalida all'avvio per la configurazione SQLite e rilasciare il riferimento al messaggio nel ramo db == NULL di nni_qos_db_set.
Docker/Docker_Reproduction_Guide.mdRiepilogo
Questo repository contiene la Proof of Concept (PoC) e l'analisi dettagliata di una vulnerabilità di perdita di memoria trovata in NanoMQ. Il problema consente ad attaccanti remoti di causare una negazione del servizio (DoS) esaurendo la memoria di sistema tramite specifici messaggi MQTT con QoS.
Ho condotto un'analisi approfondita di un fenomeno di perdita di memoria in Nanomq. Attraverso strumentazione dinamica e revisione del codice, ho identificato un percorso critico di perdita di risorse attivato quando la configurazione runtime abilita SQLite (sqlite.enable=true) ma il binario è compilato senza supporto SQLite (NNG_SUPP_SQLITE non definito).
Per mantenere questo problema conciso, ho riassunto i risultati chiave di seguito. Per la ripartizione tecnica completa (incluse tracce ASAN dettagliate, diagrammi del ciclo di vita e log di strumentazione), fare riferimento all'allegato Analysis_Report.md.
Utilizzando AddressSanitizer, ho individuato la fonte della memoria persa in nni_msg_alloc all'interno di tcptran_pipe_recv_cb (nng/src/sp/transport/mqtt/broker_tcp.c). La memoria veniva allocata ma mai completamente rilasciata.
Ho analizzato il meccanismo di conteggio dei riferimenti di nni_msg. Un normale ciclo di vita di un messaggio QoS 1 dovrebbe coinvolgere:
Poiché GDB era impraticabile per questo scenario ad alta concorrenza, ho utilizzato la strumentazione dinamica per tracciare il ciclo di vita di specifici oggetti di memoria (ad esempio, 0x60e00002ffa0).
Il risultato: I log hanno confermato una discrepanza nel ciclo di vita:
nmq_pipe_send_start_v4.Il tracciamento del flusso di esecuzione ha rivelato il motivo per cui il 3° Free mancava:
La discrepanza: Il binario è stato compilato senza -DNNG_SUPP_SQLITE, causando la rimozione della logica di inizializzazione in nano_sock_setdb. Tuttavia, nanomq.conf aveva sqlite.enable = true. Questo ha fatto sì che il puntatore db rimanesse NULL.
La logica difettosa (l'"Early Return"):
Quando il messaggio (Ref=3) entra in nni_qos_db_set per l'archiviazione, la funzione verifica il puntatore NULL:
// File: /nanomq/nng/src/supplemental/mqtt/mqtt_qos_db_api.c
void nni_qos_db_set(..., void *db, ..., nng_msg *msg) {
if (db == NULL) {
// CRITICAL FLAW:
// Early return triggers because db is NULL.
// The function holds ownership of 'msg' (Ref++) but fails to release it.
return;
}
// ...
}
La funzione termina silenziosamente senza chiamare nni_msg_free(msg). Ciò comporta la perdita irreversibile del puntatore msg, rendendo di fatto orfano il blocco di memoria.
nni_qos_db_set. Non gestisce la proprietà del puntatore msg quando esegue un early return a causa di uno stato DB non valido.Raccomandazioni:
Fail Fast (controllo all'avvio): Aggiungere la logica di convalida durante la fase di avvio del sistema (funzione nano_sock_setdb o main) per garantire che SQLite funzioni correttamente. Se viene rilevato conf->sqlite.enable == true ma la macro NNG_SUPP_SQLITE non è definita o SQLite è anomalo, il sistema dovrebbe terminare con un errore o impostare forzatamente enable su false e stampare un avviso.
Sicurezza delle risorse (Fail-Safe): Aggiungere la logica di rilascio delle risorse nel ramo Early Return della funzione nni_qos_db_set. Se db == NULL, deve essere chiamato nni_msg_free(msg) per bilanciare il conteggio dei riferimenti e prevenire perdite di memoria.