diff --git a/docs/INDEX.md b/docs/INDEX.md index 877adcd..edfcba8 100644 --- a/docs/INDEX.md +++ b/docs/INDEX.md @@ -47,6 +47,7 @@ docs/ | smtp | [smtp_SECURITY_AUDIT_20260801.md](security/smtp_SECURITY_AUDIT_20260801.md) | Python 邮件服务(验证码发送/校验) | | captcha | [captcha_SECURITY_AUDIT_20260801.md](security/captcha_SECURITY_AUDIT_20260801.md) | Python 验证码服务 | | confs | [confs_SECURITY_AUDIT_20260801.md](security/confs_SECURITY_AUDIT_20260801.md) | Python 统一配置服务 | +| libmsg | [libmsg_SECURITY_AUDIT_20260806.md](security/libmsg_SECURITY_AUDIT_20260806.md) | C++ RPC 基础库(pymsg 协议帧、MsgClient/Server、RpcClient/Server) | | libhttp | [libhttp_SECURITY_AUDIT_20260803.md](security/libhttp_SECURITY_AUDIT_20260803.md) | C++ HTTP/WebSocket 库(请求/响应/WS/静态文件) | | aibox | [aibox_SECURITY_AUDIT_20260801.md](security/aibox_SECURITY_AUDIT_20260801.md) | Python AI 智能体系统(WebSocket) | | BotOps | [BotOps_SECURITY_AUDIT_20260801.md](security/BotOps_SECURITY_AUDIT_20260801.md) | BotOps 运维控制面板 | diff --git a/docs/security/libmsg_SECURITY_AUDIT_20260806.md b/docs/security/libmsg_SECURITY_AUDIT_20260806.md new file mode 100644 index 0000000..c27b0b6 --- /dev/null +++ b/docs/security/libmsg_SECURITY_AUDIT_20260806.md @@ -0,0 +1,151 @@ +# libmsg 模块安全审计报告 + +**审计日期**: 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` +**代码版本**: main 分支(e92ef05) +**审计工具**: Qoder Security SAST 云端扫描 + +--- + +## 问题严重等级 + +| 等级 | 说明 | 响应时间 | +|------|------|----------| +| P0 🔴 | 可导致服务崩溃、DoS 或远程接管 | 立即修复 | +| P1 🟠 | 可导致资源耗尽、信息泄露 | 72小时内 | +| P2 🟡 | 健壮性/配置问题 | 一周内 | +| P3 🟢 | 优化建议 | 版本迭代 | + +--- + +## 修复进度汇总 + +| 等级 | 总数 | 已修复 | 待修复 | 修复率 | +|------|------|--------|--------|--------| +| P0 | 1 | 1 | 0 | 100% | +| P1 | 1 | 1 | 0 | 100% | +| P2 | 1 | 1 | 0 | 100% | +| **合计** | **3** | **3** | **0** | **100%** | + +--- + +## 🔴 P0 — 严重问题 + +### P0-1: broadcast/reply 竞态条件导致数据发送到错误客户端 ✅ 已修复 + +- **CWE**: CWE-362 (Race Condition) +- **OWASP**: A01:2021 +- **位置**: `MsgServer.hpp:228`(原始行号) +- **置信度**: 80% + +**问题描述**: +`broadcast()` 在 `_clients_mtx` 保护下拷贝 fd + send_lock 列表后释放锁,再无锁执行 `pmsg_send(fd, msg)`。此窗口期内 `_remove_client()` 可关闭该 fd,OS 可将其分配给新连接。`broadcast()`/`reply()` 随后向复用的 fd 发送数据,导致消息到达错误的客户端。`_remove_client()` 原实现关闭 fd 前不获取 per-connection `send_lock`,因此 `send_lock` 无法阻止此竞态。 + +**数据流**: +1. `broadcast()` 在 `_clients_mtx` 下拷贝 fd 和 send_lock 列表 +2. `_remove_client()` 关闭 fd 但不持 send_lock,OS 复用该 fd +3. `broadcast()` 向已被复用的 fd 调用 `pmsg_send()`,数据到达新连接 + +**修复方案**(commit `e92ef05`): +`_remove_client()` 先从 `_send_locks` 取出 `send_lock` 并从 map 删除,持有 `send_lock` 后再执行 `::close(fd)`,确保关闭与正在进行的发送操作互斥: + +```cpp +void _remove_client(int fd) { + std::shared_ptr send_lock; + { + std::lock_guard clk(_clients_mtx); + if (_clients.erase(fd) == 0) return; + auto it = _send_locks.find(fd); + if (it != _send_locks.end()) { + send_lock = it->second; + _send_locks.erase(it); + } + } + if (send_lock) { + std::lock_guard slk(*send_lock); + ::close(fd); + } else { + ::close(fd); + } +} +``` + +--- + +## 🟠 P1 — 高危问题 + +### P1-1: 事件循环顺序阻塞接收导致 DoS(Slowloris 变体)✅ 已修复 + +- **CWE**: CWE-400 (Uncontrolled Resource Consumption) +- **OWASP**: A05:2021 +- **位置**: `MsgServer.hpp:424`(原始行号) +- **置信度**: 80% + +**问题描述**: +`_event_loop()` 对每个就绪 fd 顺序调用 `_handle_client()`,后者在**事件循环主线程**中阻塞调用 `pmsg_recv(fd, recv_timeout)`(默认 10 秒)。线程池仅处理回调执行,不处理消息接收,即使配置了 `workers > 0` 也无法缓解。攻击者用 `max_connections`(默认 1000)个连接各发送不完整帧头(如 3 字节),可使事件循环阻塞最长 `1000 × 10 = 10000` 秒(约 2.8 小时),期间服务完全无法处理任何新连接或已有消息。 + +**数据流**: +1. 事件循环顺序遍历所有就绪的客户端 fd +2. `_handle_client()` 在主线程调用 `pmsg_recv(fd, recv_timeout)`,阻塞最长 `recv_timeout` 秒 + +**修复方案**(commit `e92ef05`): +在 `workers > 0` 模式下,`_handle_client()` 不再在事件循环线程调用 `pmsg_recv()`,而是提交 `needs_recv=true` 的任务给 worker 线程,由 worker 线程负责完整的 recv + callback 流程: + +```cpp +// 线程池模式:直接提交,由 worker 负责 recv + callback +_pool_queue.push(Task{fd, std::move(addr), {}, true}); +``` + +同步模式(`workers=0`)行为不变,仍在主线程顺序处理(无线程池时无法并发)。 + +--- + +## 🟡 P2 — 中危问题 + +### P2-1: 错误信息路径脱敏正则不完整,可能泄露内部路径 ✅ 已修复 + +- **CWE**: CWE-209 (Information Exposure Through an Error Message) +- **OWASP**: A04:2021 +- **位置**: `RpcServer.hpp:184`(原始行号) +- **置信度**: 75% + +**问题描述**: +`RpcServer::_sanitize_error()` 使用正则 `/[A-Za-z0-9_.\-]+(?:/[A-Za-z0-9_.\-]+)*` 替换异常消息中的绝对路径,但该正则仅匹配由 `[A-Za-z0-9_.\-]` 组成的路径段。含空格(`/home/user name/`)、括号(`/tmp/file(1).txt`)、`@`(`/opt/app@2.0/`)、`~` 等特殊字符的路径不被匹配,会原文返回给远程客户端,泄露内部服务器路径信息。 + +**修复方案**(commit `e92ef05`): +将正则扩展为 `/\S+(?:/\S*)*`,匹配以 `/` 开头、后接所有非空白字符的路径段,覆盖所有常见内部路径格式: + +```cpp +static const std::regex path_re(R"(/\S+(?:/\S*)*)"); +return std::regex_replace(msg, path_re, ""); +``` + +--- + +## ✅ 已有安全机制 + +| 机制 | 位置 | 说明 | +|------|------|------| +| 消息大小上限 64 MiB | `Msg_Basic.hpp` `kMaxMsgSize` | 防止超大消息耗尽内存 | +| deadline 模式 recv 超时 | `Msg.hpp` `pmsg_recv(fd, timeout)` | header + payload 共享总超时,防 Slowloris | +| 最大连接数限制 | `MsgServer.hpp` `max_connections=1000` | 超限拒绝新连接 | +| 有界任务队列 | `MsgServer.hpp` `max_queue_size=1000` | 队列满时断开连接,防 OOM | +| per-connection 发送锁 | `MsgServer.hpp` `_send_locks` | reply/broadcast 线程安全,防帧交错 | +| 同一连接消息串行 | `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 越界 | + +--- + +## 待修复 / 已知限制 + +| # | 问题 | 优先级 | 说明 | +|---|------|--------|------| +| L-1 | 无认证/鉴权机制 | P0(已缓解) | 仅依赖绑定 127.0.0.1 网络层隔离,任意本地进程可调用所有路由 | +| L-2 | 无 TLS 加密传输 | P1 | 内网明文 TCP,进程间通信可被本地抓包 | +| L-3 | 同步模式(workers=0)仍在主线程阻塞 recv | P2 | 无线程池时无法并发,Slowloris 防护依赖 recv_timeout 截止时间 | +| L-4 | select 模型上限 FD_SETSIZE(通常 1024) | P2 | 高并发场景建议改用 epoll | +| L-5 | 安全事件无结构化日志 | P3 | 超时、超大消息、JSON 解析失败等仅 fprintf(stderr),无可观测性 |