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
@@ -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