
공개 권고 및 기술 분석: CVE-2026-36590, NanoMQ v0.24.9 서비스 거부(DoS) 취약점
이 저장소는 EMQ NanoMQ v0.24.9에 영향을 미치는 서비스 거부 취약점인 CVE-2026-36590에 대한 공개 권고 및 기술 분석을 포함합니다.
이 저장소는 CVE 검토를 위한 접근 가능한 기술 참고 자료를 제공하기 위해 공개되어 있습니다. CVE-2026-36590의 최종 분류는 CVE 팀 또는 관련 CNA가 결정합니다.
NanoMQ 팀은 이 이슈에 대해 이의를 제기했으며 이를 구성(설정) 관련 문제로 간주합니다. 이 저장소는 재현 자료, ASAN 결과, 런타임 로그, 소스 코드 분석을 포함한 기술적 증거에 초점을 맞춥니다.
/nanomq/nng/src/supplemental/mqtt/mqtt_qos_db_api.c/ nni_qos_db_setNanoMQ v0.24.9가 SQLite 지원 없이 빌드된 상태에서 sqlite.enable이 true로 구성된 경우, QoS 데이터베이스 포인터가 NULL로 남아 있을 수 있습니다. MQTT QoS 메시지 처리 중에 nni_qos_db_set은 복제된 메시지 참조를 해제하지 않고 조기 반환할 수 있습니다. 반복적인 QoS > 0 MQTT PUBLISH 메시지는 지속적인 메모리 소비를 유발하여 결국 서비스 거부로 이어질 수 있습니다.
이 이슈는 기본값이 아닌 런타임 구성에 의존하지만, 다음 사실은 왜 여전히 추가 검토가 필요한지 보여줍니다:
NanoMQ는 현재 빌드에서 SQLite 지원이 불가능하다는 것을 보고하는 대신 런타임 구성을 수락하고 계속 실행됩니다.
이러한 구성 불일치는 실제로 발생할 수 있습니다. 사용자가 일반 빌드 명령으로 NanoMQ를 빌드한 후, 예제 구성 파일에서 SQLite 영속성 섹션을 복사하거나 활성화할 수 있습니다.
영향을 받는 코드 경로는 이 비일관된 상태를 완전히 검증하지 않습니다. 런타임 구성이 SQLite를 활성화된 것으로 간주하지만 데이터베이스 포인터가 NULL인 경우, nni_qos_db_set()은 전달된 메시지 객체를 해제하지 않고 조기 반환합니다.
이 구성이 수락된 후에는 원격 MQTT 트래픽에 의해 이슈가 트리거될 수 있습니다. 반복적인 QoS > 0 PUBLISH 메시지가 영향받는 경로에 진입하여 누적된 메모리 누수를 유발할 수 있습니다.
1회, 50회, 500회 재현 라운드로 수행한 ASAN 테스트는 누수된 바이트와 할당량이 트리거 시도 횟수에 따라 증가함을 보여줍니다. 이는 이 이슈가 일회성 소규모 누수가 아니라 입력 횟수에 의존적임을 시사합니다.
사용자는 NanoMQ 인스턴스에 대한 접근을 제한하고, 신뢰할 수 없는 클라이언트에 MQTT 서비스를 노출하지 않으며, SQLite를 지원하지 않는 빌드에서 SQLite 영속성을 활성화하지 않아야 합니다. 공급업체는 SQLite 구성에 대한 시작 시 검증을 추가하고, nni_qos_db_set의 db == NULL 분기에서 메시지 참조를 해제해야 합니다.
Docker/Docker_Reproduction_Guide.md요약 이 저장소에는 NanoMQ에서 발견된 메모리 누수 취약점에 대한 PoC(Proof of Concept)와 상세 분석이 포함되어 있습니다. 이 이슈는 원격 공격자가 특정 QoS MQTT 메시지를 통해 시스템 메모리를 고갈시켜 서비스 거부(DoS)를 유발할 수 있게 합니다.
저는 NanoMQ에서 메모리 누수 현상에 대한 심층 분석을 수행했습니다. 동적 계측(dynamic instrumentation)과 코드 검토를 통해, 런타임 구성이 SQLite를 활성화(sqlite.enable=true)하지만 바이너리가 SQLite 지원 없이 컴파일된 경우(NNG_SUPP_SQLITE 미정의) 트리거되는 중요한 리소스 누수 경로를 식별했습니다.
이 이슈를 간결하게 유지하기 위해 아래에 주요 발견 사항을 요약했습니다. 전체 기술 분석(상세 ASAN 추적, 수명주기 다이어그램, 계측 로그 포함)은 첨부된 Analysis_Report.md를 참조하십시오.
AddressSanitizer를 사용하여 누수된 메모리의 출처를 tcptran_pipe_recv_cb(nng/src/sp/transport/mqtt/broker_tcp.c) 내의 nni_msg_alloc로 특정했습니다. 메모리는 할당되었지만 완전히 해제되지 않았습니다.
저는 nni_msg의 참조 카운팅 메커니즘을 분석했습니다. 정상적인 QoS 1 메시지 수명주기는 다음을 포함해야 합니다:
GDB는 이 높은 동시성 시나리오에서 실용적이지 않았기 때문에, 특정 메모리 객체(예: 0x60e00002ffa0)의 수명주기를 추적하기 위해 동적 계측을 사용했습니다.
발견 사항: 로그는 수명주기 불일치를 확인했습니다:
nmq_pipe_send_start_v4에서 생성된 Clone 2에 해당합니다.실행 흐름을 추적한 결과 3번째 해제(Free)가 누락된 이유가 밝혀졌습니다:
-DNNG_SUPP_SQLITE 없이 컴파일되어 nano_sock_setdb의 초기화 로직이 제거되었습니다. 그러나 nanomq.conf에는 sqlite.enable = true가 설정되어 있었습니다. 이로 인해 db 포인터가 NULL로 유지되었습니다.nni_qos_db_set에 진입하면, 함수는 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;
}
// ...
}
함수는 nni_msg_free(msg)를 호출하지 않고 아무런 오류 없이 반환합니다. 이로 인해 msg 포인터가 되돌릴 수 없이 유실되어 사실상 메모리 블록이 고아(orphan) 상태가 됩니다.
nni_qos_db_set의 방어적 프로그래밍 부재. 잘못된 DB 상태로 인한 조기 반환 시 msg 포인터의 소유권을 처리하지 않습니다.권장 사항:
nano_sock_setdb 또는 main 함수)에 검증 로직을 추가합니다. conf->sqlite.enable == true가 감지되었지만 NNG_SUPP_SQLITE 매크로가 정의되지 않았거나 SQLite가 비정상인 경우, 시스템은 오류와 함께 종료하거나 강제로 enable을 false로 설정하고 경고를 출력해야 합니다.nni_qos_db_set 함수의 조기 반환 분기에 리소스 해제 로직을 추가합니다. db == NULL인 경우, 참조 카운트의 균형을 맞추고 메모리 누수를 방지하기 위해 nni_msg_free(msg)를 호출해야 합니다.