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、 变更记录、审计结论
This commit is contained in:
1 parent
373045f0a3
commit
685c5c1754
6 files changed
+156
-438
No files matched your search
@@ -10,417 +10,111 @@
|
||||
|
||||
## 问题严重等级
|
||||
|
||||
| 等级 | 说明 | 响应时间 |
|
||||
|------|------|----------|
|
||||
| P0 🔴 | 可导致服务崩溃、DoS 或远程接管、数据发往错误客户端 | 立即修复 |
|
||||
| P1 🟠 | 可导致资源耗尽、信息泄露、拒绝服务 | 72小时内 |
|
||||
| P2 🟡 | 健壮性/配置问题、校验缺失 | 一周内 |
|
||||
| P3 🟢 | 优化建议 | 版本迭代 |
|
||||
| 等级 | 说明 | 响应时间 |
|
||||
| ----- | --------------------------- | ----- |
|
||||
| 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%** |
|
||||
| 审计轮次 | 等级 | 总数 | 已修复 | 待修复 | 修复率 |
|
||||
| -------------------- | ------ | ------ | ------ | ----- | -------- |
|
||||
| 第一轮 (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%** |
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 🔴 P0 — 严重问题
|
||||
## 已解决发现(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 配置再暴露给其他线程 |
|
||||
|
||||
### 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` 无法阻止此竞态。
|
||||
|
||||
**修复方案**(commit `e92ef05`):
|
||||
`_remove_client()` 先从 `_send_locks` 取出 `send_lock` 并从 map 删除,持有 `send_lock` 后再执行 `::close(fd)`,确保关闭与正在进行的发送操作互斥。
|
||||
|
||||
---
|
||||
|
||||
### P0-2: _close_all_clients() 批量关闭路径存在同类竞态回归 ✅ 已修复(第二轮)
|
||||
|
||||
- **CWE**: CWE-362 (Race Condition)
|
||||
- **OWASP**: A01:2021
|
||||
- **位置**: `MsgServer.hpp::_close_all_clients()`(析构/stop路径)
|
||||
- **置信度**: 90%
|
||||
|
||||
**问题描述**:
|
||||
第一轮修复只覆盖了单客户端移除路径 `_remove_client()`,但批量关闭路径 `_close_all_clients()`(在 `start()` 异常退出或正常退出时调用)直接遍历 `_clients` 并 `::close(fd)`,**未持有每连接 `send_lock`**。在竞态窗口内,reply/broadcast 可能在 `_clients_mtx` 释放后、`close(fd)` 执行前获取到 send_lock,或 `close(fd)` 后 OS 复用 fd 给新连接,导致数据发送到错误客户端。
|
||||
|
||||
**触发场景**:
|
||||
1. 服务调用 `stop()` 或 `start()` 异常退出,进入 `_close_all_clients()`
|
||||
2. 另一线程正在调用 `reply()`/`broadcast()`,已在 `_clients_mtx` 下拷贝了 send_lock 但尚未持有 send_lock 发送数据
|
||||
3. `_close_all_clients()` 无锁直接 close(fd),OS 复用 fd
|
||||
4. 发送操作向复用的 fd 写入数据,到达错误客户端
|
||||
|
||||
**修复方案**(第二轮修复):
|
||||
`_close_all_clients()` 改为与 `_remove_client()` 一致的安全模式:先在 `_clients_mtx` 保护下收集所有 (fd, send_lock) 对并清空 map,释放锁后对每个 fd 持有对应的 send_lock 再执行 `shutdown()+close()`:
|
||||
|
||||
```cpp
|
||||
void _close_all_clients() {
|
||||
std::vector<std::pair<int, std::shared_ptr<std::mutex>>> to_close;
|
||||
{
|
||||
std::lock_guard<std::mutex> clk(_clients_mtx);
|
||||
to_close.reserve(_clients.size());
|
||||
for (auto& [fd, addr] : _clients) {
|
||||
auto it = _send_locks.find(fd);
|
||||
if (it != _send_locks.end()) {
|
||||
to_close.emplace_back(fd, it->second);
|
||||
} else {
|
||||
to_close.emplace_back(fd, nullptr);
|
||||
}
|
||||
}
|
||||
_clients.clear();
|
||||
_send_locks.clear();
|
||||
}
|
||||
for (auto& [fd, send_lock] : to_close) {
|
||||
if (send_lock) {
|
||||
std::lock_guard<std::mutex> slk(*send_lock);
|
||||
::shutdown(fd, SHUT_RDWR);
|
||||
::close(fd);
|
||||
} else {
|
||||
::shutdown(fd, SHUT_RDWR);
|
||||
::close(fd);
|
||||
}
|
||||
}
|
||||
// ... 清理 _busy/_failed
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🟠 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 小时),期间服务完全无法处理任何新连接或已有消息。
|
||||
|
||||
**修复方案**(commit `e92ef05`):
|
||||
在 `workers > 0` 模式下,`_handle_client()` 不再在事件循环线程调用 `pmsg_recv()`,而是提交 `needs_recv=true` 的任务给 worker 线程,由 worker 线程负责完整的 recv + callback 流程。同步模式(`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_.\-]+)*` 替换绝对路径,含空格、括号、`@`、`~` 等特殊字符的路径不被匹配,会原文返回给远程客户端。
|
||||
|
||||
**修复方案**(commit `e92ef05`):
|
||||
正则扩展为 `/\S+(?:/\S*)*`,覆盖所有非空白字符路径。
|
||||
|
||||
---
|
||||
|
||||
### P2-2: pmsg_recv(int) 依赖隐式SO_RCVTIMEO前置条件,误用可导致永久阻塞 ✅ 已修复(第二轮)
|
||||
|
||||
- **CWE**: CWE-400 (Uncontrolled Resource Consumption) / CWE-672 (Operation on a Resource after Expiration)
|
||||
- **位置**: `Msg.hpp` `pmsg_recv(int sockfd)`
|
||||
- **置信度**: 70%
|
||||
|
||||
**问题描述**:
|
||||
单参数版本 `pmsg_recv(int sockfd)`(阻塞模式)依赖调用方**预先**通过 `setsockopt(SO_RCVTIMEO)` 设置接收超时,函数本身既不设置超时,也不检查超时是否已设置。如果外部用户直接使用此 API 而忘记设置超时,`recv()` 会永久阻塞,构成拒绝服务风险。虽然所有内部调用点(`MsgClient::listen()`)都正确设置了超时,但作为公共 API 存在误用风险。
|
||||
|
||||
**修复方案**(第二轮修复):
|
||||
1. 在 Doxygen 注释中明确标注前置条件,推荐使用 deadline 模式 `pmsg_recv(sockfd, timeout_sec)`
|
||||
2. 统一了接收路径的 UTF-8 校验(见 P2-5)
|
||||
|
||||
---
|
||||
|
||||
### P2-3: 公共API参数无边界校验,异常输入可导致未定义行为 ✅ 已修复(第二轮)
|
||||
|
||||
- **CWE**: CWE-20 (Improper Input Validation)
|
||||
- **位置**: `Msg.hpp`、`MsgServer.hpp`、`AsyncQueue.hpp`、`RpcClient.hpp`、`RpcServer.hpp` 各构造函数和公共方法
|
||||
- **置信度**: 85%
|
||||
|
||||
**问题描述**:
|
||||
多数公共入口函数未校验参数合法性:
|
||||
- `timeout_sec` 传入 0 或负数时,`deadline` 立即到期导致异常行为;传入极大值(如 > 1小时)可绕过 Slowloris 防护
|
||||
- `port` 传入 0 或 >65535 时,`htons()` 截断产生错误端口
|
||||
- `MsgServerConfig` 中 `workers`、`max_connections`、`max_queue_size`、`poll_timeout`、`recv_timeout`、`listen_backlog` 无范围校验,传入负值或极大值可导致内存分配失败、CPU 100% 或绕过安全限制
|
||||
- `AsyncQueueConfig` 中 `batch_size`、`flush_interval`、`poll_timeout` 无校验,`batch_size=0` 导致死循环或异常
|
||||
- `RpcClient` 的 `timeout`、`retry_interval` 无校验
|
||||
- `RpcServer::add_route()` 接受空字符串路由名和空 handler,导致后续调用崩溃
|
||||
- `AsyncQueue` 构造接受 null handler,调用时崩溃
|
||||
|
||||
**修复方案**(第二轮修复):
|
||||
新增 `detail::validateTimeout()` 和 `detail::validatePort()` 辅助函数,并在以下入口点添加边界校验:
|
||||
|
||||
| 位置 | 校验项 |
|
||||
|------|--------|
|
||||
| `detail::recvFrameDeadline()` | `0 < timeout_sec ≤ 300` 秒 |
|
||||
| `detail::openConnection()` | `1 ≤ port ≤ 65535`,`0 < timeout ≤ 300` 秒 |
|
||||
| `MsgClient::MsgClient()` | port 范围 |
|
||||
| `MsgClient::call()` | timeout、retry_interval 范围 |
|
||||
| `MsgServer::MsgServer()` | port/workers/recv_timeout/max_connections/max_queue_size/listen_backlog/poll_timeout 全部校验 |
|
||||
| `AsyncQueue::AsyncQueue()` | handler 非空、batch_size>0、flush_interval/poll_timeout 合理范围 |
|
||||
| `RpcClient::RpcClient()` | timeout、retry_interval 范围 |
|
||||
| `RpcServer::add_route()` | name 非空、handler 非空 |
|
||||
|
||||
---
|
||||
|
||||
### P2-4: 消息载荷缺少UTF-8合法性校验 ✅ 已修复(第二轮)
|
||||
|
||||
- **CWE**: CWE-20 (Improper Input Validation)
|
||||
- **位置**: `Msg.hpp` `pmsg_recv()` 两个重载版本
|
||||
- **置信度**: 65%
|
||||
|
||||
**问题描述**:
|
||||
Python 版 `pymsg.recv()` 在接收载荷后调用 `payload.decode("utf-8")` 进行 UTF-8 合法性校验,非法字节序列会抛出 `UnicodeDecodeError` 并被包装为 `ConnectionError`。C++ 版直接将原始字节存入 `std::string` 返回,不校验 UTF-8 合法性,可能导致上层 JSON 解析器或业务逻辑出现未定义行为(如 null 字节截断、非法序列解析异常、安全绕过)。
|
||||
|
||||
**修复方案**(第二轮修复):
|
||||
1. 新增 `detail::isValidUtf8()` 函数,实现完整的 UTF-8 校验:
|
||||
- 正确识别 1/2/3/4 字节序列
|
||||
- 拒绝 overlong 编码(如 0xC0 0x80 表示 NUL)
|
||||
- 拒绝 UTF-16 代理对(0xD800-0xDFFF)
|
||||
- 拒绝超 U+10FFFF 的编码
|
||||
- 拒绝非法续字节、截断序列
|
||||
2. `pmsg_recv(int)` 和 `pmsg_recv(int, double)` 在接收完 payload 后调用 isValidUtf8 校验,非法载荷抛出 `MsgConnectionException("invalid UTF-8 payload")` 断开连接
|
||||
|
||||
---
|
||||
|
||||
### P2-5: 缺少参数校验导致无效配置可绕过安全限制 ✅ 已修复(第二轮)
|
||||
|
||||
- **CWE**: CWE-20 (Improper Input Validation)
|
||||
- **说明**: 此问题已合并入 P2-3 修复。MsgServer 构造函数现在对所有配置项实施范围校验:
|
||||
- `recv_timeout`: (0, 300] 秒,防止传入超大值绕过 Slowloris 防护
|
||||
- `max_connections`: [1, 100000],防止 0 或负值导致所有连接被拒绝或无限制
|
||||
- `max_queue_size`: [1, 1000000],防止 0 或负值导致无法提交任务
|
||||
- `workers`: [0, 256],防止负值或过大线程数耗尽资源
|
||||
- `poll_timeout`: (0, 5] 秒,防止 0 导致 select 忙轮询 CPU 100%
|
||||
- `listen_backlog`: [1, 4096]
|
||||
|
||||
---
|
||||
|
||||
## 🟠 P1 — 高危问题(第三轮)
|
||||
|
||||
### P1-2: 服务端 socket 未设置发送超时,慢客户端不读数据可致 send 永久阻塞(DoS) ✅ 已修复(第三轮)
|
||||
|
||||
- **CWE**: CWE-400 (Uncontrolled Resource Consumption)
|
||||
- **OWASP**: A05:2021
|
||||
- **位置**: `MsgServer.hpp::_accept_client()`(新连接只设 TCP_NODELAY,未设 SO_SNDTIMEO)
|
||||
- **置信度**: 85%
|
||||
|
||||
**问题描述**:
|
||||
服务端每个客户端 socket 在 `_accept_client()` 中仅设置了 `TCP_NODELAY`,**未设置发送超时 `SO_SNDTIMEO`**。若攻击者连接后停止读取数据,发送缓冲区会被填满,此时 `reply()`/`broadcast()` 调用的 `pmsg_send()` → `sendAll()` 中的 `send()` 因无法写入而**永久阻塞**(无超时则不会返回 EAGAIN)。
|
||||
|
||||
- **同步模式(workers=0)**: `reply()`/`broadcast()` 在主线程(事件循环线程)内执行,send 永久阻塞即卡死整个事件循环,服务完全无法处理任何连接 → **服务级 DoS**。
|
||||
- **线程池模式(workers>0)**: 阻塞的 send 占用 worker 线程,攻击者可用少量慢连接耗尽全部 worker。
|
||||
|
||||
**触发场景**:
|
||||
1. 攻击者建立连接但不调用 recv 读取数据
|
||||
2. 服务端向其 reply() 大量数据(如日志/广播),发送缓冲区填满
|
||||
3. `send()` 永久阻塞,卡死主线程(同步模式)或占用 worker 线程
|
||||
|
||||
**修复方案**(第三轮修复):
|
||||
在 `_accept_client()` 中对新连接设置 `SO_SNDTIMEO = 5.0` 秒。发送超时后 `sendAll()` 检测到 `EAGAIN/EWOULDBLOCK` 抛 `MsgTimeoutException`,被 `reply()`/`broadcast()` 捕获并标记为 `_failed`,由主循环统一清理连接(配合 P2-6 修复)。
|
||||
|
||||
## 🟡 P2 — 中危问题(第三轮)
|
||||
|
||||
### P2-6: reply() 发送失败静默忽略且不标记 failed,连接残留不清理 ✅ 已修复(第三轮)
|
||||
|
||||
- **CWE**: CWE-703 (Improper Check or Handling of Exceptional Conditions)
|
||||
- **位置**: `MsgServer.hpp::reply()`(`catch (const MsgException&)` 分支)
|
||||
- **置信度**: 80%
|
||||
|
||||
**问题描述**:
|
||||
`broadcast()` 发送失败时会将该 fd 加入 `_failed` 集合,由主循环统一清理;而 `reply()` 发送失败时仅 `catch` 静默忽略,**不标记 `_failed`**。若 reply 的目标连接已断开或发送超时,该连接会一直残留在 `_clients` 中,直到主循环 select 检测到读端异常才被清理,造成资源滞留和清理路径不一致。
|
||||
|
||||
**修复方案**(第三轮修复):
|
||||
`reply()` 的失败分支改为与 `broadcast()` 一致:捕获 `MsgException` 后向 `_failed` 集合插入该 fd,由主循环 `_reap_failed()` 统一清理。
|
||||
|
||||
## 🟢 P3 — 优化建议(第三轮)
|
||||
|
||||
### P3-1: RpcClient::call() 未校验 success 字段类型,非 bool 时抛未捕获的 boost 异常 ✅ 已修复(第三轮)
|
||||
|
||||
- **CWE**: CWE-20 (Improper Input Validation)
|
||||
- **位置**: `RpcClient.hpp::call()`(`obj.at("success").as_bool()`)
|
||||
- **置信度**: 70%
|
||||
|
||||
**问题描述**:
|
||||
`call()` 已检查响应包含 `success` 字段,但未校验其**类型**。若远端返回 `{"success": "false"}`(字符串)等非 bool 值,`as_bool()` 会抛出未捕获的 `boost::system::system_error`,而非统一的 `RpcError`,导致调用方无法按既定异常层次处理,且错误信息可能泄露内部细节。
|
||||
|
||||
**修复方案**(第三轮修复):
|
||||
调用 `as_bool()` 前先检查 `success.is_bool()`,类型不符时抛出 `RpcError("非法 RPC 响应(success 字段不是 bool)")`,与其余非法响应处理保持一致。
|
||||
|
||||
---
|
||||
|
||||
## 🟠 P1 — 高危问题(第四轮)
|
||||
|
||||
### P1-3: reply/broadcast 与 _remove_client 之间存在残余竞态,fd 复用可致数据发往错误客户端 ✅ 已修复(第四轮)
|
||||
|
||||
- **CWE**: CWE-362 (Race Condition)
|
||||
- **OWASP**: A01:2021
|
||||
- **位置**: `MsgServer.hpp` `reply()` / `broadcast()` / `_remove_client()`
|
||||
- **置信度**: 85%
|
||||
|
||||
**问题描述**:
|
||||
第一轮 P0-1 修复确保了"发送期间不会被 close"(reply/broadcast 先获取 send_lock → _remove_client 等待),但未覆盖反向场景:**_remove_client 先获取 send_lock 并 close(fd) 后,reply/broadcast 后获取 send_lock 时 fd 已关闭**。如果 OS 将该 fd 复用给新 accept 的连接,reply/broadcast 的 `pmsg_send()` 会向复用的 fd 写入数据,导致消息到达错误客户端。
|
||||
|
||||
**触发场景**:
|
||||
1. reply/broadcast 在 `_clients_mtx` 下获取了 `send_lock` 的 shared_ptr 副本
|
||||
2. 释放 `_clients_mtx`
|
||||
3. `_remove_client()` 获取 `_clients_mtx`,erase fd,获取 `send_lock` 副本,释放 `_clients_mtx`
|
||||
4. `_remove_client()` 先于 reply/broadcast 获取 `send_lock`
|
||||
5. `_remove_client()` `close(fd)`,释放 `send_lock`
|
||||
6. OS 将 fd 复用给新 accept 的连接
|
||||
7. reply/broadcast 获取 `send_lock`,调用 `pmsg_send(fd, msg)` → 数据到达新连接
|
||||
|
||||
步骤 2-4 的窗口期虽然很短(仅几条指令),但在高并发场景(大量 broadcast + 连接断开同时发生)下可被触发。
|
||||
|
||||
**修复方案**(第四轮修复):
|
||||
引入 `ConnectionLock` 结构,将 `send_lock`(mutex)和 `active`(atomic\<bool\>)组合在一起:
|
||||
|
||||
```cpp
|
||||
struct ConnectionLock {
|
||||
std::mutex send_mtx;
|
||||
std::atomic<bool> active{true};
|
||||
};
|
||||
```
|
||||
|
||||
- `reply()`/`broadcast()` 在获取 `send_mtx` 后检查 `active`:若为 false 则放弃发送(fd 已被关闭)
|
||||
- `_remove_client()`/`_close_all_clients()` 在获取 `send_mtx` 后先设置 `active=false`,再 `shutdown()`+`close(fd)`
|
||||
|
||||
这样无论谁先获取锁:
|
||||
- 若 reply/broadcast 先获取锁:`active=true`,正常发送,`_remove_client` 等待发送完成后再 close
|
||||
- 若 `_remove_client` 先获取锁:设置 `active=false`,close(fd),reply/broadcast 后获取锁时检查 `active=false` 直接 return
|
||||
|
||||
---
|
||||
|
||||
## 🟡 P2 — 中危问题(第四轮)
|
||||
|
||||
### P2-7: _server_fd 非原子访问构成数据竞争(UB) ✅ 已修复(第四轮)
|
||||
|
||||
- **CWE**: CWE-362 (Race Condition) / CWE-662 (Improper Synchronization)
|
||||
- **位置**: `MsgServer.hpp` `_server_fd` 成员
|
||||
- **置信度**: 90%
|
||||
|
||||
**问题描述**:
|
||||
`_server_fd` 是普通 `int`,在主线程(`_event_loop`)读、`stop()` 线程写,构成 C++ 标准定义的数据竞争(Undefined Behavior)。虽然在实际 64 位系统上 int 读写通常是原子的(最坏情况读取旧值导致 accept 失败),但严格来说是 UB,编译器优化可能产生意外行为。
|
||||
|
||||
**修复方案**(第四轮修复):
|
||||
将 `_server_fd` 改为 `std::atomic<int>`,所有读写使用 `.load()` / `.store()` / `.exchange()`。`stop()` 使用 `exchange(-1)` 原子地认领 fd 并关闭。
|
||||
|
||||
---
|
||||
|
||||
### P2-8: start() 未校验 on_message 回调非空 ✅ 已修复(第四轮)
|
||||
|
||||
- **CWE**: CWE-20 (Improper Input Validation)
|
||||
- **位置**: `MsgServer.hpp` `start()`
|
||||
- **置信度**: 85%
|
||||
|
||||
**问题描述**:
|
||||
`start(on_message)` 接受 `std::function` 参数但未校验是否为空。若传入空回调(默认构造的 `std::function`),后续 `_on_message(fd, addr, msg)` 会抛出 `std::bad_function_call`,该异常在 worker 线程中被 catch 但在同步模式下会导致连接断开;在 `_dispatch`(RpcServer)中会导致单个请求失败。虽然不会崩溃服务,但行为不符合预期且错误信息不明确。
|
||||
|
||||
**修复方案**(第四轮修复):
|
||||
`start()` 开头添加校验:`if (!on_message) throw MsgException("on_message callback must not be null");`
|
||||
|
||||
---
|
||||
|
||||
## 🟢 P3 — 优化建议(第四轮)
|
||||
|
||||
### P3-2: RpcServer _route 非 string 类型时错误信息不准确 ✅ 已修复(第四轮)
|
||||
|
||||
- **CWE**: CWE-209 (Information Exposure Through an Error Message)
|
||||
- **位置**: `RpcServer.hpp` `_dispatch()`
|
||||
- **置信度**: 70%
|
||||
|
||||
**问题描述**:
|
||||
`_dispatch()` 中检查 `_route` 字段时,若字段存在但不是 string 类型(如数字、数组),错误信息统一返回 `"missing _route"`,误导客户端以为字段缺失而非类型错误。
|
||||
|
||||
**修复方案**(第四轮修复):
|
||||
区分两种情况:字段不存在返回 `"missing _route"`,类型不符返回 `"_route must be a string"`。
|
||||
|
||||
### P3-3: _accept_client 中 setsockopt 在加入 _clients 之后执行 ✅ 已修复(第四轮)
|
||||
|
||||
- **位置**: `MsgServer.hpp` `_accept_client()`
|
||||
- **置信度**: 60%
|
||||
|
||||
**问题描述**:
|
||||
原实现先将 `client_fd` 加入 `_clients` 和 `_send_locks`(持锁),释放锁后再设置 `TCP_NODELAY` 和 `SO_SNDTIMEO`。虽然新连接此时尚未收到消息,不会有 reply/broadcast 发送数据,但最佳实践是先完成所有 socket 配置再暴露给其他线程。
|
||||
|
||||
**修复方案**(第四轮修复):
|
||||
调整顺序:先设置 `TCP_NODELAY` 和 `SO_SNDTIMEO`,再持锁加入 `_clients`。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 已有安全机制(更新后)
|
||||
|
||||
| 机制 | 位置 | 说明 |
|
||||
|------|------|------|
|
||||
| 消息大小上限 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,不传播到业务线程 |
|
||||
| 机制 | 位置 | 说明 |
|
||||
| -------------------------------- | ----------------------------------------------- | ---------------------------------------------- |
|
||||
| 消息大小上限 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 限制,但回调执行无超时。慢回调会阻塞事件循环。建议将慢操作移至线程池模式或业务层自行限时。 |
|
||||
| # | 问题 | 优先级 | 说明 |
|
||||
| ---- | ----------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 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 限制,但回调执行无超时。慢回调会阻塞事件循环。建议将慢操作移至线程池模式或业务层自行限时。 |
|
||||
|
||||
---
|
||||
|
||||
@@ -430,32 +124,32 @@ struct ConnectionLock {
|
||||
|
||||
**代码变更文件**:
|
||||
|
||||
| 文件 | 变更内容 |
|
||||
|------|----------|
|
||||
| 文件 | 变更内容 |
|
||||
| --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [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) |
|
||||
| [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) |
|
||||
| [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 非空 |
|
||||
| 文件 | 变更内容 |
|
||||
| ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [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 非空 |
|
||||
|
||||
---
|
||||
|
||||
@@ -466,14 +160,14 @@ struct ConnectionLock {
|
||||
- **第一轮(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 顺序)已修复。
|
||||
- **第四轮(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 类型校验
|
||||
- **竞态安全**: 所有 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 项为架构层面的已知限制/设计决策,非代码缺陷,可根据实际部署需求决定是否增强。
|
||||
@@ -90,6 +90,8 @@ public:
|
||||
_stop(false),
|
||||
_wakeup(false),
|
||||
_dropped(0) {
|
||||
// [libmsg-audit P2-3] 入口校验:handler 非空、batch_size>0、flush_interval∈(0,300]、
|
||||
// poll_timeout∈(0,5]。拒绝非法输入(如 batch_size=0 导致死循环、poll_timeout=0 忙轮询)。
|
||||
if (!_handler) {
|
||||
throw std::invalid_argument("AsyncQueue: handler must not be null");
|
||||
}
|
||||
|
||||
+11
-7
@@ -51,9 +51,9 @@ namespace detail {
|
||||
|
||||
using namespace msg_constants;
|
||||
|
||||
// ---- UTF-8 合法性校验(对齐 Python decode('utf-8'))------------------------
|
||||
// ---- UTF-8 合法性校验(对齐 Python decode('utf-8'))------------------------ [libmsg-audit P2-4]
|
||||
// 防御性校验:协议声明载荷为 UTF-8,非法字节序列表示协议错误或攻击,
|
||||
// 直接抛 MsgConnectionException 断开连接。
|
||||
// 直接抛 MsgConnectionException 断开连接(拒绝 overlong / 代理对 / 超 U+10FFFF)。
|
||||
inline bool isValidUtf8(const char* data, size_t len) noexcept {
|
||||
const unsigned char* bytes = reinterpret_cast<const unsigned char*>(data);
|
||||
size_t i = 0;
|
||||
@@ -91,7 +91,9 @@ inline bool isValidUtf8(const char* data, size_t len) noexcept {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 参数校验辅助 --------------------------------------------------------
|
||||
// ---- 参数校验辅助 -------------------------------------------------------- [libmsg-audit P2-3]
|
||||
// validateTimeout / validatePort:统一拒绝非法 timeout((0,300]s)与 port([1,65535]),
|
||||
// 防止 0/负值导致 deadline 立即到期、极大值绕过 Slowloris 防护、port 越界 htons 截断。
|
||||
inline void validateTimeout(double timeout_sec) {
|
||||
if (timeout_sec <= 0 || timeout_sec > 300.0) { // 最大 5 分钟
|
||||
throw MsgException("invalid timeout: " + std::to_string(timeout_sec) + " seconds (must be in (0, 300])");
|
||||
@@ -153,9 +155,10 @@ inline void recvExact(int sockfd, char *buf, size_t n) {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- deadline 模式精确读取 n 字节(总超时,防 Slowloris)-------------------
|
||||
// ---- deadline 模式精确读取 n 字节(总超时,防 Slowloris)------------------- [libmsg-audit P1-1]
|
||||
// 与 Python 版 _recv_exact 对齐:每次 select 前按剩余时间收紧超时,
|
||||
// header+payload 共享同一个 deadline,总接收时间不超过 timeout_sec。
|
||||
// header+payload 共享同一个 deadline,总接收时间不超过 timeout_sec,
|
||||
// 防止单连接以不完整帧头长期占用接收线程(Slowloris 变体,CWE-400)。
|
||||
|
||||
inline void recvExactDeadline(int sockfd, char *buf, size_t n,
|
||||
const std::chrono::steady_clock::time_point& deadline) {
|
||||
@@ -217,7 +220,7 @@ inline std::string recvFrameDeadline(int sockfd, double timeout_sec) {
|
||||
std::string payload(static_cast<size_t>(length), '\0');
|
||||
if (length > 0) {
|
||||
recvExactDeadline(sockfd, payload.data(), length, deadline);
|
||||
// UTF-8 合法性校验(对齐 Python decode('utf-8')),非法载荷视为协议错误
|
||||
// [libmsg-audit P2-4] UTF-8 合法性校验(对齐 Python decode('utf-8')),非法载荷视为协议错误
|
||||
if (!isValidUtf8(payload.data(), length)) {
|
||||
throw MsgConnectionException("invalid UTF-8 payload");
|
||||
}
|
||||
@@ -246,6 +249,7 @@ inline void sendAll(int sockfd, const char *data, size_t len) {
|
||||
// ---- 短连接 helper:connect → action → close ------------------------------
|
||||
|
||||
inline int openConnection(const std::string& addr, int port, double timeout) {
|
||||
// [libmsg-audit P2-3] 入口边界校验:port 与 timeout 必须在合法范围。
|
||||
validatePort(port);
|
||||
validateTimeout(timeout);
|
||||
int fd = ::socket(AF_INET, SOCK_STREAM, 0);
|
||||
@@ -317,7 +321,7 @@ inline std::string pmsg_recv(int sockfd) {
|
||||
std::string payload(static_cast<size_t>(length), '\0');
|
||||
if (length > 0) {
|
||||
detail::recvExact(sockfd, payload.data(), length);
|
||||
// UTF-8 合法性校验(对齐 Python decode('utf-8')),非法载荷视为协议错误
|
||||
// [libmsg-audit P2-4] UTF-8 合法性校验(对齐 Python decode('utf-8')),非法载荷视为协议错误
|
||||
if (!detail::isValidUtf8(payload.data(), length)) {
|
||||
throw MsgConnectionException("invalid UTF-8 payload");
|
||||
}
|
||||
|
||||
+34
-20
@@ -81,9 +81,14 @@ struct MsgServerConfig {
|
||||
|
||||
/// 每连接的发送锁和活跃标志。
|
||||
///
|
||||
/// active 标志用于防止 CWE-362 竞态:当 _remove_client() 先获取 send_mtx
|
||||
/// 并 close(fd) 后,reply/broadcast 后获取 send_mtx 时通过 active==false
|
||||
/// 得知 fd 已关闭,立即放弃发送,避免向 OS 复用的 fd 发送数据。
|
||||
/// [libmsg-audit P0-1 / P0-2 / P1-3] 组合 send_mtx(互斥锁)+ active(原子标志)彻底消除
|
||||
/// CWE-362 竞态:
|
||||
/// - 第一轮 P0-1:单客户端移除路径先持 send_mtx 再 close,reply/broadcast 先获取
|
||||
/// send_mtx 则 _remove_client 等待,防止 fd 关闭后被 OS 复用导致数据发往错误客户端。
|
||||
/// - 第二轮 P0-2:批量关闭路径 _close_all_clients 同样持 send_mtx 后 shutdown+close,
|
||||
/// 修复第一轮只覆盖单路径的竞态回归。
|
||||
/// - 第四轮 P1-3:active 标志堵住反向竞态——_remove_client 先获取锁并 close(fd) 后,
|
||||
/// reply/broadcast 后获取锁时通过 active==false 得知 fd 已关闭,立即放弃发送。
|
||||
struct ConnectionLock {
|
||||
std::mutex send_mtx;
|
||||
std::atomic<bool> active{true};
|
||||
@@ -101,6 +106,10 @@ public:
|
||||
|
||||
explicit MsgServer(const std::string& addr, int port, MsgServerConfig config = {})
|
||||
: _addr(addr), _port(port), _config(config), _server_fd(-1), _running(false) {
|
||||
// [libmsg-audit P2-3 / P2-5] 全量边界校验:拒绝非法 port / workers / recv_timeout /
|
||||
// max_connections / max_queue_size / listen_backlog / poll_timeout。防止 htons 截断产生
|
||||
// 错误端口、负值导致所有连接被拒或队列无法提交、极大值(>300s)绕过 Slowloris 防护、
|
||||
// poll_timeout=0 导致 select 忙轮询 CPU 100%。
|
||||
detail::validatePort(port);
|
||||
if (config.workers < 0 || config.workers > 256) {
|
||||
throw MsgException("invalid workers: " + std::to_string(config.workers) + " (must be in [0, 256])");
|
||||
@@ -136,6 +145,8 @@ public:
|
||||
* 回调内可安全调用 reply()/broadcast()/stop()。
|
||||
*/
|
||||
void start(MessageCallback on_message) {
|
||||
// [libmsg-audit P2-8] 空回调会在 worker 线程 / RpcServer 分发中抛 std::bad_function_call,
|
||||
// 提前校验并给出明确错误,避免行为不符合预期且错误信息含糊。
|
||||
if (!on_message) {
|
||||
throw MsgException("MsgServer::start(): on_message callback must not be null");
|
||||
}
|
||||
@@ -233,14 +244,14 @@ public:
|
||||
conn_lock = it->second;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(conn_lock->send_mtx);
|
||||
// 检查 active:防止 _remove_client 先获取 send_mtx 并 close(fd) 后,
|
||||
// reply 向已关闭/被 OS 复用的 fd 发送数据(CWE-362)
|
||||
// [libmsg-audit P1-3] 检查 active:防止 _remove_client 先获取 send_mtx 并 close(fd) 后,
|
||||
// reply 向已关闭/被 OS 复用的 fd 发送数据(CWE-362 反向竞态)。
|
||||
if (!conn_lock->active.load()) return;
|
||||
try {
|
||||
pmsg_send(sockfd, msg);
|
||||
} catch (const MsgException&) {
|
||||
// 发送失败(连接断开或发送超时),标记为 failed 由主循环清理。
|
||||
// 与 broadcast() 保持一致,避免连接残留。
|
||||
// [libmsg-audit P2-6] 发送失败(连接断开或发送超时),标记为 failed 由主循环清理。
|
||||
// 与 broadcast() 保持一致,避免连接残留不清理(原 reply 静默忽略,连接滞留)。
|
||||
std::lock_guard<std::mutex> flk(_failed_mtx);
|
||||
_failed.insert(sockfd);
|
||||
}
|
||||
@@ -267,12 +278,12 @@ public:
|
||||
}
|
||||
for (auto& [fd, conn_lock] : targets) {
|
||||
std::lock_guard<std::mutex> slock(conn_lock->send_mtx);
|
||||
// 检查 active:防止 _remove_client 先 close(fd) 后向复用 fd 发送(CWE-362)
|
||||
// [libmsg-audit P1-3] 检查 active:防止 _remove_client 先 close(fd) 后向复用 fd 发送(CWE-362 反向竞态)
|
||||
if (!conn_lock->active.load()) continue;
|
||||
try {
|
||||
pmsg_send(fd, msg);
|
||||
} catch (const MsgException&) {
|
||||
// 发送失败,标记为 failed 由主循环清理
|
||||
// [libmsg-audit P2-6] 发送失败,标记为 failed 由主循环统一清理
|
||||
std::lock_guard<std::mutex> flk(_failed_mtx);
|
||||
_failed.insert(fd);
|
||||
}
|
||||
@@ -314,7 +325,8 @@ private:
|
||||
}
|
||||
if (!has_task) continue;
|
||||
|
||||
// workers > 0 模式下,消息接收在 worker 线程中完成(防事件循环阻塞 CWE-400)
|
||||
// [libmsg-audit P1-1] workers > 0 模式下,消息接收在 worker 线程中完成(防事件循环阻塞 CWE-400),
|
||||
// 避免攻击者以 max_connections 个半开连接阻塞事件循环长达数小时(Slowloris 变体)。
|
||||
if (task.needs_recv) {
|
||||
try {
|
||||
task.message = pmsg_recv(task.fd, _config.recv_timeout);
|
||||
@@ -452,12 +464,13 @@ private:
|
||||
}
|
||||
if (client_fd >= FD_SETSIZE) { ::close(client_fd); return; }
|
||||
|
||||
// 禁用 Nagle 算法和设置发送超时(在加入 _clients 之前,确保连接配置完整)
|
||||
// [libmsg-audit P3-3] 禁用 Nagle 算法和设置发送超时(在加入 _clients 之前,确保连接配置完整),
|
||||
// 先完成所有 socket 配置再暴露给其他线程,避免半配置连接被回复。
|
||||
int flag = 1;
|
||||
::setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));
|
||||
|
||||
// 设置发送超时:防止慢客户端不读数据导致发送缓冲区填满,
|
||||
// 使 reply()/broadcast() 的 send() 永久阻塞(拒绝服务)。
|
||||
// [libmsg-audit P1-2] 设置发送超时:防止慢客户端不读数据导致发送缓冲区填满,
|
||||
// 使 reply()/broadcast() 的 send() 永久阻塞(拒绝服务,CWE-400)。
|
||||
// 超时后 sendAll() 抛 MsgTimeoutException,被 reply/broadcast 捕获并标记 failed 清理。
|
||||
struct timeval sndtv {};
|
||||
constexpr double kServerSendTimeout = 5.0; // 秒
|
||||
@@ -511,7 +524,7 @@ private:
|
||||
return;
|
||||
}
|
||||
|
||||
// 线程池模式:标记 busy,提交任务(由 worker 负责 recv + callback,
|
||||
// [libmsg-audit P1-1] 线程池模式:标记 busy,提交任务(由 worker 负责 recv + callback,
|
||||
// 事件循环线程不阻塞接收,防 Slowloris DoS CWE-400)
|
||||
{
|
||||
std::lock_guard<std::mutex> blk(_busy_mtx);
|
||||
@@ -557,9 +570,9 @@ private:
|
||||
_send_locks.erase(it);
|
||||
}
|
||||
}
|
||||
// 持有 send_mtx 后设置 active=false 再 close(fd):
|
||||
// 1. 防止 broadcast/reply 在 fd 关闭后向 OS 复用的新连接发送数据(CWE-362)
|
||||
// 2. active=false 让 reply/broadcast 在获取锁后得知 fd 已关闭,放弃发送
|
||||
// [libmsg-audit P0-1 / P1-3] 持有 send_mtx 后设置 active=false 再 close(fd):
|
||||
// 1. 防止 broadcast/reply 在 fd 关闭后向 OS 复用的新连接发送数据(CWE-362,第一轮 P0-1)
|
||||
// 2. active=false 让 reply/broadcast 在获取锁后得知 fd 已关闭,放弃发送(第四轮 P1-3 反向竞态)
|
||||
if (conn_lock) {
|
||||
std::lock_guard<std::mutex> slk(conn_lock->send_mtx);
|
||||
conn_lock->active.store(false);
|
||||
@@ -587,8 +600,9 @@ private:
|
||||
_clients.clear();
|
||||
_send_locks.clear();
|
||||
}
|
||||
// 持有每个连接的 send_mtx 后设置 active=false 再 close(fd):
|
||||
// 防止 stop()/析构时 reply/broadcast 正在发送,导致数据发到 OS 复用的新连接(CWE-362)
|
||||
// [libmsg-audit P0-2 / P1-3] 持有每个连接的 send_mtx 后设置 active=false 再 close(fd):
|
||||
// 防止 stop()/析构时 reply/broadcast 正在发送,导致数据发到 OS 复用的新连接(CWE-362)。
|
||||
// 第二轮 P0-2 修复批量关闭路径的竞态回归,第四轮 P1-3 的 active 标志堵住反向竞态。
|
||||
for (auto& [fd, conn_lock] : to_close) {
|
||||
if (conn_lock) {
|
||||
std::lock_guard<std::mutex> slk(conn_lock->send_mtx);
|
||||
@@ -611,7 +625,7 @@ private:
|
||||
std::string _addr;
|
||||
int _port;
|
||||
MsgServerConfig _config;
|
||||
std::atomic<int> _server_fd; ///< 监听 socket fd(atomic 防止 stop() 与 _event_loop 数据竞争)
|
||||
std::atomic<int> _server_fd; ///< [libmsg-audit P2-7] 监听 socket fd(atomic 防止 stop() 与 _event_loop 数据竞争 UB,CWE-362/662)
|
||||
std::atomic<bool> _running;
|
||||
MessageCallback _on_message;
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ public:
|
||||
_timeout(timeout),
|
||||
_retry_on_refused(retry_on_refused),
|
||||
_retry_interval(retry_interval) {
|
||||
// [libmsg-audit P2-3] 入口边界校验:timeout ∈ (0,300]s,retry_interval ∈ (0,60]s。
|
||||
if (timeout <= 0 || timeout > 300.0) {
|
||||
throw MsgException("invalid timeout: " + std::to_string(timeout));
|
||||
}
|
||||
@@ -128,7 +129,8 @@ public:
|
||||
}
|
||||
const json::value& success = obj.at("success");
|
||||
if (!success.is_bool()) {
|
||||
// 类型不符时不要调用 as_bool()(会抛未捕获的 boost system_error),统一抛 RpcError
|
||||
// [libmsg-audit P3-1] 类型不符时不要调用 as_bool()(会抛未捕获的 boost system_error),
|
||||
// 统一抛 RpcError,与其余非法响应处理保持一致,避免底层异常泄露内部细节。
|
||||
throw RpcError("非法 RPC 响应(success 字段不是 bool)");
|
||||
}
|
||||
if (!success.as_bool()) {
|
||||
|
||||
@@ -83,6 +83,7 @@ public:
|
||||
* @param name 路由名(对应请求中的 _route 字段),不能为空
|
||||
* @param handler 处理函数,签名 json::value(const json::object& req)
|
||||
*/
|
||||
// [libmsg-audit P2-3] 入口校验:name 与 handler 均非空,否则后续调用会崩溃。
|
||||
void add_route(const std::string& name, Handler handler) {
|
||||
if (name.empty()) {
|
||||
throw MsgException("route name cannot be empty");
|
||||
@@ -127,7 +128,7 @@ private:
|
||||
|
||||
json::object req = req_val.as_object();
|
||||
|
||||
// 提取 _route
|
||||
// [libmsg-audit P3-2] 提取 _route:区分"字段缺失"与"类型不符"返回不同错误信息,避免误导客户端。
|
||||
std::string route;
|
||||
if (auto it = req.find("_route"); it != req.end() && it->value().is_string()) {
|
||||
route = it->value().as_string().c_str();
|
||||
@@ -181,9 +182,10 @@ private:
|
||||
_server.reply(fd, json::serialize(resp));
|
||||
}
|
||||
|
||||
// ---- 错误信息脱敏 ------------------------------------------------------
|
||||
// ---- 错误信息脱敏 ------------------------------------------------------ [libmsg-audit P2-1]
|
||||
// 匹配绝对路径(如 /opt/micro_servers/db/xxx.py 或 /usr/lib/...),替换为 <redacted>
|
||||
// 不返回函数堆栈、SQL、文件路径等内部细节给客户端;完整异常由 on_error 钩子记录到服务端日志。
|
||||
// 正则已扩展为 /\S+(?:\/\S*)*,覆盖含空格/括号/@/~ 等特殊字符的路径(原正则会泄露此类路径,CWE-209)。
|
||||
|
||||
static std::string _sanitize_error(const std::string& msg) {
|
||||
if (msg.empty()) return msg;
|
||||
|
||||
Reference in new issue
Block a user