首页 网络安全 正文

NanoMQ函数拒绝服务漏洞CVE-2026-36590

摘要

栋科技漏洞库关注到 NanoMQ 在0.24.9中的nni_qos_db_set 函数拒绝服务漏洞,漏洞已追踪为CVE-2026-36590,CVSS 3.X评分7.5。

NanoMQ是EMQ推出、纯C语言开发的轻量边缘MQTT消息服务器,专为网关、嵌入式等低资源设备设计,完整兼容MQTT 3.1.1/5.0。

一、基本情况

EMQ NanoMQ是一个开源的超轻量 MQTT 消息代理,常用于边缘计算与车联网场景,内存最低仅200KB,多线程性能远超Mosquitto。

EMQ NanoMQ自带云边桥接、简易规则引擎,可转换WebSocket、ZeroMQ多类物联网协议,可适配ARM/RISC-V等各类嵌入式硬件。

NanoMQ函数拒绝服务漏洞CVE-2026-36590

EMQ NanoMQ支持MQTT over QUIC、消息本地缓存断网续传、其内置的 NanoNNG 子模块实现了 MQTT 协议栈与 QoS 数据库抽象。

栋科技漏洞库关注到 NanoMQ 在0.24.9中的nni_qos_db_set 函数拒绝服务漏洞,漏洞已追踪为CVE-2026-36590,CVSS 3.X评分7.5。

二、漏洞分析

CVE-2026-36590影响(NanoMQ v0.24.9,对应提交 2d80f7cff59fab51ec1b7bfe478e3860af91fc39;依赖的 NanoNNG 同一版本)。

当二进制未定义NNG_SUPP_SQLITE宏而运行时配置却启用sqlite.enable = true,s->sqlite_db 与 pipe->nano_qos_db均保持 NULL;

后续在 nmq_pipe_send_start_v4(nng/src/sp/transport/mqtt/poker_tcp.c)对 QoS>0 的 PUBLISH 消息执行 Clone 后,

调用 nni_qos_db_set(src/supplemental/mqtt/mqtt_qos_db_api.c),

该函数命中 db==NULL 分支后直接 return,既未持久化也未调用 nni_msg_free(msg) 平衡引用计数,单条消息泄漏一份内存;

远程攻击者通过持续发送 QoS>0 的 PUBLISH 即可让 poker_tcp.c 处理路径不断累积泄漏,最终导致进程内存耗尽并拒绝服务。

具体来说,若编译NanoMQ v0.24.9时未开启SQLite支持,但运行配置文件中`sqlite.enable`设置为true,QoS数据库指针会保持空值。

处理MQTT QoS消息时,`nni_qos_db_set`函数提前返回,未释放已复制消息的引用计数。

持续发送QoS等级大于0的MQTT发布报文会造成内存持续泄漏,最终触发拒绝服务。

(一)漏洞仍需复核的原因

该问题触发依赖非默认运行配置,但存在以下客观问题,需进一步评估漏洞风险:

1、NanoMQ读取冲突配置后会正常持续运行,不会提示当前程序编译未搭载SQLite功能;

2、此类配置冲突具备实际发生场景:用户使用常规命令编译程序,后续直接复制示例配置文件并启用SQLite持久化配置;

3、漏洞代码路径未校验该异常状态:配置开启SQLite但数据库指针为空时,`nni_qos_db_set`直接退出,未释放传入的消息对象;

4、加载冲突配置后,远程MQTT客户端即可触发漏洞。持续推送QoS>0报文会反复进入泄漏逻辑,内存持续堆积;

5、分别执行1轮、50轮、500轮复现测试并配合ASAN检测,

泄漏内存与内存分配量随触发次数同步增长,属于随输入量累积的持续性内存泄漏,非单次微量泄漏。

(二)缓解措施

1、管控NanoMQ服务访问权限,禁止向不可信客户端开放MQTT端口;

2、编译时未启用SQLite的程序,不要在配置文件开启SQLite持久化存储;

3、厂商修复建议:程序启动时校验SQLite配置;在`nni_qos_db_set`数据库指针为空的分支内补充消息引用释放代码。

(三)NanoMQ内存泄漏研究配套附件

- Analysis_Report.md:完整分析文档,包含消息生命周期流程图、详细日志追踪记录

- 249exploit_leak.py:用于复现内存泄漏的Python验证脚本

- nanomq.conf:触发配置冲突的漏洞配置文件

- runtime_logs.log:记录引用计数异常的插桩运行日志

- Docker/:可稳定复现、可控验证漏洞的容器化环境

容器部署完整编译、环境搭建步骤见 Docker/Docker_Reproduction_Guide.md

- 249asan_log.txt:NanoMQ v0.24.9 AddressSanitizer运行日志,记录内存泄漏及异常内存行为

(四)技术分析总述

远程攻击者可发送特定QoS等级MQTT报文耗尽服务器内存,实现拒绝服务攻击。

笔者对该内存泄漏问题开展深度分析,通过动态插桩调试与源码审计定位漏洞触发条件:

运行配置开启SQLite持久化(sqlite.enable=true),但程序编译时未开启SQLite编译宏(未定义NNG_SUPP_SQLITE)。

下文梳理核心结论,完整技术细节(ASAN全量日志、生命周期图、插桩日志)参考附件Analysis_Report.md。

(五)分析流程

1、漏洞初定位(ASAN内存检测)

借助AddressSanitizer工具,定位泄漏内存分配源为`tcptran_pipe_recv_cb`函数内`nni_msg_alloc`,内存分配后未完成完整释放流程。

2、消息引用计数生命周期建模(基准:3次分配对应3次释放)

梳理`nni_msg`消息引用计数机制,标准QoS 1消息完整生命周期流程:

分配(引用+1):网络接收报文

复制1(引用+1):应用层消息分发

复制2(引用+1):持久化/重传逻辑

总引用计数:3,需执行3次释放操作才能回收内存。

3、动态追踪验证

高并发场景下GDB调试效率不足,采用动态插桩追踪指定内存对象(示例地址0x60e00002ffa0)生命周期,日志证实生命周期失衡:

实际分配次数:3次(原始分配、复制1、复制2)

实际释放次数:2次(释放1、释放2)

结果:消息引用计数永久保留1,造成内存泄漏。缺失的释放操作为`nmq_pipe_send_start_v4`生成的第二条复制消息。

4、根因定位

梳理代码执行链路,确认第三次释放操作缺失诱因:

配置冲突:编译参数未添加`-DNNG_SUPP_SQLITE`,`nano_sock_setdb`初始化SQLite逻辑被裁剪;但配置文件开启sqlite.enable=true,数据库db指针恒为空。

缺陷逻辑(提前返回漏洞点):引用计数为3的消息进入`nni_qos_db_set`持久化存储逻辑时,函数检测db指针为空:

// 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)`释放消息,消息指针永久丢失,对应内存无法回收。

(六)结论与漏洞危害

1、根本成因

`nni_qos_db_set`函数缺少防御性编程逻辑,数据库异常提前返回时,未处理传入消息指针的资源所有权。

2、危害

产生隐性内存耗尽拒绝服务:正常客户端流量或恶意攻击者持续推送QoS>0报文,内存会不断堆积,

最终触发操作系统OOM内存杀死机制,MQTT消息代理服务崩溃。

3、修复建议

(1)快速失败启动校验:程序启动阶段(nano_sock_setdb函数或主函数)增加配置校验。

若配置开启SQLite,但编译未搭载SQLite或SQLite组件异常,程序直接报错退出,或强制关闭SQLite开关并输出告警日志。

(2)资源安全容错处理:在`nni_qos_db_set`函数db==NULL提前返回分支内增加资源释放代码,

调用`nni_msg_free(msg)`平衡引用计数,杜绝内存泄漏。

三、影响范围

EMQ NanoMQ v0.24.9

四、修复建议

EMQ NanoMQ> v0.24.9

五、参考链接

管理员已设置登录后刷新可查看



扫描二维码,在手机上阅读
评论
更换验证码
友情链接