Files
blackbird/docs/security/libmsg_SECURITY_AUDIT_20260806.md
T
qing 685c5c1754 refactor(libmsg): 将审计结论合入代码注释并精简审计报告
- 在 libmsg 各头文件修复位置补充 [libmsg-audit <编号>] 标签(27 处),
  覆盖 16 条安全审计发现(P0-1/P0-2/P1-1~3/P2-1~8/P3-1~3),
  将已完成修复锚定到源码
- 从审计文档删除 335 行逐条详细发现(问题描述/修复方案/触发场景/代码块),
  替换为 16 行「已解决发现」紧凑台账 + 源码定位指针
- 文档保留:严重等级表、修复进度汇总、已有安全机制、已知限制 L-1~L-12、
  变更记录、审计结论
2026-08-10 01:26:10 +08:00

174 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# libmsg 模块安全审计报告
**审计日期**: 2026-08-07(第四轮审计)
**首次审计**: 2026-08-06
**审计范围**: `libmsg/inc/us/MsgServer.hpp`、`libmsg/inc/us/RpcServer.hpp`、`libmsg/inc/us/Msg.hpp`、`libmsg/inc/us/RpcClient.hpp`、`libmsg/inc/us/AsyncQueue.hpp`、`libmsg/inc/us/Msg_Basic.hpp`、`libmsg/src/Msg_Basic.cpp`
**代码版本**: main 分支(基于 e92ef05 进行修复)
**审计工具**: 人工代码审查 + Qoder Security SAST 原则
---
## 问题严重等级
| 等级 | 说明 | 响应时间 |
| ----- | --------------------------- | ----- |
| P0 🔴 | 可导致服务崩溃、DoS 或远程接管、数据发往错误客户端 | 立即修复 |
| P1 🟠 | 可导致资源耗尽、信息泄露、拒绝服务 | 72小时内 |
| P2 🟡 | 健壮性/配置问题、校验缺失 | 一周内 |
| P3 🟢 | 优化建议 | 版本迭代 |
---
## 修复进度汇总
| 审计轮次 | 等级 | 总数 | 已修复 | 待修复 | 修复率 |
| -------------------- | ------ | ------ | ------ | ----- | -------- |
| 第一轮 (2026-08-06) | P0 | 1 | 1 | 0 | 100% |
| 第一轮 (2026-08-06) | P1 | 1 | 1 | 0 | 100% |
| 第一轮 (2026-08-06) | P2 | 1 | 1 | 0 | 100% |
| 第二轮 (2026-08-07) | P0 | 1 | 1 | 0 | 100% |
| 第二轮 (2026-08-07) | P2 | 4 | 4 | 0 | 100% |
| 第三轮 (2026-08-07) | P1 | 1 | 1 | 0 | 100% |
| 第三轮 (2026-08-07) | P2 | 1 | 1 | 0 | 100% |
| 第三轮 (2026-08-07) | P3 | 1 | 1 | 0 | 100% |
| **第四轮 (2026-08-07)** | **P1** | **1** | **1** | **0** | **100%** |
| **第四轮 (2026-08-07)** | **P2** | **2** | **2** | **0** | **100%** |
| **第四轮 (2026-08-07)** | **P3** | **2** | **2** | **0** | **100%** |
| **合计** | **—** | **16** | **16** | **0** | **100%** |
---
## 已解决发现(16 项,100% 修复)
> 以下全部发现的根因分析、触发场景与修复说明已作为 `[libmsg-audit]` 注释合并到源码,便于在代码处直接对照审计结论。源码位置:`libmsg/inc/us/{MsgServer,Msg,Msg_Basic,RpcServer,RpcClient,AsyncQueue}.hpp`(在源码中搜索 `[libmsg-audit]` 即可定位到各修复点)。
| 发现 | 等级 | 轮次 | 源码位置 | CWE | 修复摘要 |
| ---- | ----- | --- | ------------------------------------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------- |
| P0-1 | P0 🔴 | 第一轮 | MsgServer.hpp: broadcast / reply / \_remove_client | CWE-362 | 持 per-connection send_lock 再 close,防 fd 关闭后被 OS 复用导致数据发往错误客户端 |
| P0-2 | P0 🔴 | 第二轮 | MsgServer.hpp: \_close_all_clients | CWE-362 | 批量关闭路径同样持 send_lock 后 shutdown+close,修复第一轮只覆盖单路径的竞态回归 |
| P1-1 | P1 🟠 | 第一轮 | MsgServer.hpp: \_worker_loop / \_handle_client;Msg.hpp: recvExactDeadline | CWE-400 | workers>0 时 recv 移交 worker 线程,事件循环不再阻塞,防 Slowloris DoS |
| P1-2 | P1 🟠 | 第三轮 | MsgServer.hpp: \_accept_client | CWE-400 | 新连接设置 SO_SNDTIMEO=5s,慢客户端不读数据时 send 超时抛异常,防服务级 DoS |
| P1-3 | P1 🟠 | 第四轮 | MsgServer.hpp: ConnectionLock / reply / broadcast / \_remove_client | CWE-362 | 引入 atomic active 标志,close 前设为 false,彻底消除 reply/broadcast 与 \_remove_client 的残余竞态 |
| P2-1 | P2 🟡 | 第一轮 | RpcServer.hpp: \_sanitize_error | CWE-209 | 脱敏正则扩展为 `/\S+(?:/\S*)*`,覆盖含特殊字符路径,防内部路径泄露 |
| P2-2 | P2 🟡 | 第二轮 | Msg.hpp: pmsg_recv(int) 注释警告 | CWE-400/672 | 文档标注 SO_RCVTIMEO 前置条件,推荐 deadline 模式,防误用永久阻塞(说明并入 P2-3 文档) |
| P2-3 | P2 🟡 | 第二轮 | Msg.hpp / MsgServer.hpp / Rpc*.hpp / AsyncQueue.hpp 各入口 | CWE-20 | 新增 validateTimeout / validatePort 及全量配置边界校验,拒绝非法输入 |
| P2-4 | P2 🟡 | 第二轮 | Msg.hpp: isValidUtf8 | CWE-20 | 接收路径新增完整 UTF-8 校验,与 Python decode('utf-8') 对齐 |
| P2-5 | P2 🟡 | 第二轮 | MsgServer.hpp: 构造函数 | CWE-20 | 配置项范围校验(已并入 P2-3):recv_timeout / max_connections / max_queue_size / workers / poll_timeout / listen_backlog |
| P2-6 | P2 🟡 | 第三轮 | MsgServer.hpp: reply | CWE-703 | reply 发送失败标记 \_failed,由主循环统一清理,与 broadcast 一致 |
| P2-7 | P2 🟡 | 第四轮 | MsgServer.hpp: \_server_fd | CWE-362/662 | \_server_fd 改为 std::atomic<int>,消除 stop() 与 \_event_loop 数据竞争 UB |
| P2-8 | P2 🟡 | 第四轮 | MsgServer.hpp: start | CWE-20 | start() 校验 on_message 非空,防 bad_function_call |
| P3-1 | P3 🟢 | 第三轮 | RpcClient.hpp: call | CWE-20 | 调用 as_bool() 前校验 success 为 bool,类型不符抛 RpcError |
| P3-2 | P3 🟢 | 第四轮 | RpcServer.hpp: \_dispatch | CWE-209 | \_route 缺失 vs 类型不符返回不同错误信息 |
| P3-3 | P3 🟢 | 第四轮 | MsgServer.hpp: \_accept_client | — | setsockopt 移到加入 \_clients 之前,先完成 socket 配置再暴露给其他线程 |
---
## ✅ 已有安全机制(更新后)
| 机制 | 位置 | 说明 |
| -------------------------------- | ----------------------------------------------- | ---------------------------------------------- |
| 消息大小上限 64 MiB | `Msg_Basic.hpp` `kMaxMsgSize` | 防止超大消息耗尽内存 |
| deadline 模式 recv 超时 | `Msg.hpp` `recvFrameDeadline` | header+payload 共享总超时,防 Slowloris |
| 入口参数边界校验 | `Msg.hpp/MsgServer.hpp/AsyncQueue.hpp/Rpc*.hpp` | timeout/port/配置项全范围校验,拒绝非法输入 |
| UTF-8 载荷校验 | `Msg.hpp` `isValidUtf8()` | 与 Python decode('utf-8') 对齐,拒绝非法字节序列 |
| 最大连接数限制 | `MsgServer.hpp` `max_connections=1000` | 超限拒绝新连接 |
| 有界任务队列 | `MsgServer.hpp` `max_queue_size=1000` | 队列满时断开连接,防 OOM |
| per-connection 发送锁 + active 标志 | `MsgServer.hpp` `ConnectionLock` | reply/broadcast 线程安全防帧交错 + active 检查防 fd 复用后发送 |
| 服务端发送超时 SO_SNDTIMEO | `MsgServer.hpp` `_accept_client()` | 慢客户端不读数据时 send 超时抛异常,防拒绝服务(P1-2) |
| reply() 失败标记 failed | `MsgServer.hpp` `reply()` | 与 broadcast 一致,失败连接由主循环清理(P2-6) |
| RpcClient success 类型校验 | `RpcClient.hpp` `call()` | 非 bool 时抛 RpcError,而非未捕获的 boost 异常(P3-1) |
| \_close_all_clients 批量关闭也持锁 | `MsgServer.hpp` `_close_all_clients()` | 修复批量关闭路径的竞态(P0-2) |
| **新增**: ConnectionLock active 标志 | `MsgServer.hpp` `ConnectionLock::active` | reply/broadcast 获取锁后检查 active,彻底消除残余竞态(P1-3) |
| **新增**: \_server_fd atomic 访问 | `MsgServer.hpp` `std::atomic<int> _server_fd` | 消除 stop() 与 \_event_loop 的数据竞争 UB(P2-7) |
| **新增**: start() 空回调校验 | `MsgServer.hpp` `start()` | 防止 bad_function_call 异常(P2-8) |
| **新增**: \_accept_client 先配置后注册 | `MsgServer.hpp` `_accept_client()` | socket 选项在加入 \_clients 前设置完整(P3-3) |
| 同一连接消息串行 | `MsgServer.hpp` `_busy` 集合 | 回调执行中暂停该连接 select 监听 |
| JSON 解析深度限制 | `RpcServer.hpp` `opts.max_depth=32` | 防深度嵌套 JSON 导致栈溢出 |
| 异常信息脱敏 | `RpcServer.hpp` `_sanitize_error()` | 过滤绝对路径,完整异常由 on_error 钩子记录 |
| RpcClient 响应格式校验 | `RpcClient.hpp` | 校验 success 字段,非法响应抛 RpcError |
| worker 线程异常隔离 | `MsgServer.hpp` `_worker_loop()` | handler 异常仅断开该连接,不影响服务 |
| select FD_SETSIZE 检查 | `MsgServer.hpp` `_accept_client()` | fd >= FD_SETSIZE 时拒绝连接,防 select 越界 |
| TCP_NODELAY | `MsgServer.hpp` `_accept_client()` | 新连接禁用 Nagle 算法,降低小包延迟 |
| MSG_NOSIGNAL 兼容 | `Msg.hpp` | macOS 兼容,防止 send 到关闭连接触发 SIGPIPE |
| AsyncQueue 背压保护 | `AsyncQueue.hpp` `max_queue_size` | 队列满时丢最旧数据,防内存无限增长 |
| AsyncQueue fail-safe | `AsyncQueue.hpp` | handler 异常触发 on_error,不传播到业务线程 |
---
## 待修复 / 已知限制(架构层面,非代码缺陷)
| # | 问题 | 优先级 | 说明 |
| ---- | ----------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| L-1 | 无认证/鉴权机制 | P0(已缓解) | 仅依赖绑定 127.0.0.1 网络层隔离,任意本地进程可调用所有路由。设计如此,内网微服务信任模型。 |
| L-2 | 无 TLS 加密传输 | P1 | 内网明文 TCP,进程间通信可被本地 root 用户抓包。如跨主机部署需增加 TLS 层。 |
| L-3 | 同步模式(workers=0)仍在主线程阻塞 recv | P2 | 无线程池时无法并发,Slowloris 防护依赖 recv_timeout 截止时间(10秒)。建议生产环境使用 workers>0。 |
| L-4 | select 模型上限 FD_SETSIZE(通常 1024) | P2 | 高并发场景建议改用 epoll/kqueue,或在 fd 超限时告警/拒绝。已在 \_accept_client 加检查防止越界。 |
| L-5 | 安全事件无结构化日志 | P3 | 超时、超大消息、UTF-8 校验失败、JSON 解析失败等仅 fprintf(stderr),无可观测性。建议接入结构化日志。 |
| L-6 | pmsg_recv(int) 单参数版本仍依赖外部超时设置 | P3 | 已添加文档警告,推荐使用 deadline 模式。考虑未来版本弃用单参数版本或内部设置安全默认超时。 |
| L-7 | AsyncQueue 关闭时 flush 失败数据只记 on_error | P3 | \_flush_all() 中 handler 失败后数据不重试,符合非关键路径设计,但应确保 on_error 有持久化/告警机制。 |
| L-8 | 半连接(只发部分帧头)仍可占用一个 worker 线程 recv_timeout 秒 | P2 | workers>0 时消息接收在 worker 线程完成,慢连接可占用 worker 最多 recv_timeout(默认10秒)。数量受 max_connections 与 recv_timeout 有界约束,已缓解。可用更小 recv_timeout 或独立接收线程池进一步收紧。 |
| L-9 | pmsg_send 发送端不校验 UTF-8,与 Python 版 encode('utf-8') 不对称 | P3 | 接收端已在 pmsg_recv 校验 UTF-8(P2-4),非法载荷会被远端断开。发送端未校验,若业务传入二进制可能被远端拒绝。若存在纯二进制传输场景,发送端校验会破坏兼容,故暂不强制,仅记录。 |
| L-10 | pmsg_send 公共 API 依赖 SO_SNDTIMEO | P3 | sendAll() 在 send() 返回 EAGAIN 时抛超时异常,但前提是 socket 已设置 SO_SNDTIMEO。已添加文档警告,MsgClient 和 MsgServer 内部均已设置。直接使用 pmsg_send 的外部调用方需自行设置。 |
| L-11 | MsgServer 未设置 TCP keepalive | P3 | 客户端崩溃(不发送 FIN)时连接会残留直到 select 检测异常或 send 失败。建议生产环境设置 SO_KEEPALIVE 以更快检测死连接。 |
| L-12 | \_remove_client 同步模式下无总超时限制 | P3 | 同步模式下 \_handle_client 中 pmsg_recv 有 recv_timeout 限制,但回调执行无超时。慢回调会阻塞事件循环。建议将慢操作移至线程池模式或业务层自行限时。 |
---
## 变更记录
### 2026-08-07(第四轮审计修复)
**代码变更文件**:
| 文件 | 变更内容 |
| --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [MsgServer.hpp](file:///Users/qingsong/Documents/blackbird/libmsg/inc/us/MsgServer.hpp) | **P1修复**: 引入 `ConnectionLock` 结构(mutex + atomic active),`reply()`/`broadcast()` 获取锁后检查 `active`,`_remove_client()`/`_close_all_clients()` close 前设置 `active=false`,彻底消除残余竞态(P1-3);**P2修复**: `_server_fd` 改为 `std::atomic<int>`,所有访问使用 `.load()`/`.store()`/`.exchange()`(P2-7);**P2修复**: `start()` 校验 `on_message` 非空(P2-8);**P3修复**: `_accept_client()` 中 setsockopt 移到加入 `_clients` 之前(P3-3) |
| [RpcServer.hpp](file:///Users/qingsong/Documents/blackbird/libmsg/inc/us/RpcServer.hpp) | **P3修复**: `_dispatch()` 区分 `_route` 字段缺失和类型不符的错误信息(P3-2) |
| [Msg.hpp](file:///Users/qingsong/Documents/blackbird/libmsg/inc/us/Msg.hpp) | **文档**: `pmsg_send` 添加 SO_SNDTIMEO 前置条件警告(L-10) |
### 2026-08-07(第三轮审计修复)
**代码变更文件**:
| 文件 | 变更内容 |
| --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| [MsgServer.hpp](file:///Users/qingsong/Documents/blackbird/libmsg/inc/us/MsgServer.hpp) | **P1修复**: `_accept_client()` 对新连接设置 `SO_SNDTIMEO=5s`,慢客户端不读数据时 send 超时抛异常,防服务级 DoS(P1-2);**P2修复**: `reply()` 发送失败标记 `_failed`,由主循环清理连接(P2-6) |
| [RpcClient.hpp](file:///Users/qingsong/Documents/blackbird/libmsg/inc/us/RpcClient.hpp) | **P3修复**: `call()` 调用 `as_bool()` 前校验 `success` 字段为 bool,类型不符抛 RpcError(P3-1) |
### 2026-08-07(第二轮审计修复)
**代码变更文件**:
| 文件 | 变更内容 |
| ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Msg.hpp](file:///Users/qingsong/Documents/blackbird/libmsg/inc/us/Msg.hpp) | 新增 isValidUtf8()/validateTimeout()/validatePort();recvFrameDeadline 添加参数校验和UTF-8校验;openConnection 添加 port/timeout 校验;pmsg_recv(int) 添加UTF-8校验和文档警告;MsgClient 构造/call 添加参数校验 |
| [MsgServer.hpp](file:///Users/qingsong/Documents/blackbird/libmsg/inc/us/MsgServer.hpp) | **P0修复**: \_close_all_clients() 改为持 send_lock 关闭并 shutdown;构造函数添加全量配置校验 |
| [AsyncQueue.hpp](file:///Users/qingsong/Documents/blackbird/libmsg/inc/us/AsyncQueue.hpp) | 添加 <stdexcept>/<string> 头文件;构造函数校验 handler 非空、batch_size、flush_interval、poll_timeout 范围 |
| [RpcClient.hpp](file:///Users/qingsong/Documents/blackbird/libmsg/inc/us/RpcClient.hpp) | 构造函数校验 timeout、retry_interval 范围 |
| [RpcServer.hpp](file:///Users/qingsong/Documents/blackbird/libmsg/inc/us/RpcServer.hpp) | add_route() 校验 name 非空、handler 非空 |
---
## 审计结论
四轮审计累计发现 **16 个问题,全部已修复(100%)**。
- **第一轮(2026-08-06)**: 3 个问题(P0 竞态、P1 Slowloris、P2 脱敏正则)已修复。
- **第二轮(2026-08-07)**: 5 个问题(P0 批量关闭竞态回归、4 个 P2 健壮性)已修复。
- **第三轮(2026-08-07)**: 3 个新问题(P1 服务端发送超时缺失、P2 reply 失败未清理、P3 RpcClient success 类型校验)已修复。
- **第四轮(2026-08-07)**: 5 个新问题(P1 reply/broadcast 残余竞态、P2 \_server_fd 数据竞争、P2 start 空回调校验、P3 \_route 错误信息、P3 setsockopt 顺序)已修复。
结合四轮审计结果,libmsg 核心通信层的安全状况良好:
- **内存安全**: 无缓冲区溢出、无 use-after-free(fd 生命周期通过 ConnectionLock + shared_ptr + atomic active 严格管理)
- **DoS 防护**: 64MiB 消息上限、deadline 模式接收超时(防Slowloris)、服务端发送超时 SO_SNDTIMEO(防慢客户端耗尽资源)、max_connections 连接数限制、max_queue_size 队列上限、poll_timeout 防忙轮询、全量参数边界校验
- **竞态安全**: 所有 fd 关闭路径均持有 per-connection send_mtx 并设置 active=false,reply/broadcast 获取锁后检查 active,**彻底消除 CWE-362 竞态(含正向和反向场景)**;\_server_fd 使用 atomic 消除数据竞争 UB
- **协议健壮性**: UTF-8 收发端校验与 Python 版对齐、RpcClient 响应字段类型校验统一抛 RpcError、RpcServer \_route 类型校验
- **异常安全**: worker 线程异常隔离、AsyncQueue fail-safe、Handler 异常不崩溃服务、RPC 错误脱敏、reply/broadcast 失败连接统一清理、start 空回调校验
遗留的 L-1~L-12 项为架构层面的已知限制/设计决策,非代码缺陷,可根据实际部署需求决定是否增强。