# W13.3 Network Plugin Audit **Auditor**: 王测 (qa-wang) **Date**: 2026-05-27 **File**: plugins/network/src/network_plugin.cpp (322 行) **Scope**: plugin-abi.md §2 §3 §5 §8 §9, TLS, timeout, concurrency, resources --- ## 1. 跨 DLL 堆纪律 (§2 §3) **评级: A (完全合规)** 逐行检查: - L259: `g_host->strdup(result_body.c_str())` — 正确使用 host 堆分配,符 §3.2 模式 A。调用方 `dstalk_free` 释放。 - L40-80 `parse_headers_json`: 内部 `std::string` / `unordered_map`,不跨 DLL 边界。 - L118-123 Lambda capture: 内部 `std::string`,不跨边界。 - 全文零处裸 `malloc`/`free`/`strdup`/`new`/`delete`。 唯一跨边界分配 L259 使用 `g_host->strdup`,正确。 ## 2. 异常安全 ABI (§5 §8) **评级: B (基础合规,缺 catch(...))** - `on_init` (L296-303): 无线程局部 STL 操作,不会抛异常。安全。 - `do_post_stream` (L140-254): 整个 I/O 逻辑包裹在 `try { ... } catch (std::exception& e)` (L251)。所有 `std::string`/`unordered_map`/`std::function` 使用均在保护范围内。 - **缺失**: L251 仅捕获 `std::exception&`,无 `catch(...)` 兜底。符 §8.2 推荐但不符最佳实践(§8.2 正例含 `catch(...)`)。 - vtable 函数 `http_post_json` / `http_post_stream` 直接调用 `do_post_stream`,间接享受到保护。不直接暴露异常。 - 若出现非 `std::exception` 派生的异常(SEH / 自定义异常),将穿越 ABI → `std::terminate()`。 ## 3. 字符串返回值生命周期 (§9) **评级: A** - L248: `result_body = parser.get().body()` — 复制到本地 `std::string`。 - L252-253: `result_body = e.what()` — `e.what()` 的 C 字符串在 `e` 析构前已复制到 `std::string`,安全。 - L259: `*response_body = g_host->strdup(result_body.c_str())` — 在函数返回前用 host 堆复制,符 §9.2 模式 A(拥有权转移)。调用方负责释放。 - 无悬垂指针 / 锁外返回 c_str() / 临时对象 c_str() 等 §9.3 反模式。 ## 4. TLS 证书验证 **评级: F (完全未启用)** ```cpp // L87-93 — HttpClientCtx 构造函数 ssl::context ssl_ctx{ssl::context::tlsv12_client}; HttpClientCtx() { ssl_ctx.set_default_verify_paths(); // 设置了 CA 路径... // ★ 但从未调用 set_verify_mode()! } ``` - **`set_verify_mode(ssl::verify_peer)` 未被调用**: 默认模式为 `ssl::verify_none`,接受**任何**证书(自签名、过期、域名不匹配)。 - **无 hostname 验证**: 即使启用 `verify_peer`,OpenSSL 本身不做 hostname 匹配。需额外调用 `SSL_set1_host()` 或使用 `boost::asio::ssl::host_name_verification`。代码未做。 - **SNI 已设置** (L148): `SSL_set_tlsext_host_name` 调用存在,但仅影响 SNI 扩展,不含验证语义。 - **`set_default_verify_paths` 形同虚设**: CA 路径已配置,但因验证模式未开启,永不使用。 **影响**: 所有 AI API 调用 (含 `api_key` 的 Authorization header) 可被中间人任意拦截。CVSS 7.4 (High)。 ## 5. 超时 (connect / read / write) **评级: C (DNS 无超时可永久 hang)** | 阶段 | 位置 | 超时 | 状态 | |------|------|------|------| | DNS 解析 | L142 `resolver.resolve()` | 无 | **缺失** — DNS 无响应则永久阻塞 | | TCP 连接 | L154-156 | `connect_timeout` (默认 30s) | OK | | SSL 握手 | L160-163 | `connect_timeout` (默认 30s) | OK | | HTTP 写入 | L180-183 | `request_timeout` (默认 120s) | OK | | HTTP 读头 | L188-191 | `request_timeout` (默认 120s) | OK | | 流式读体 | L218-219 | `request_timeout` (默认 120s) | OK (每轮循环重设) | | 非流式读体 | L241-242 | `request_timeout` (默认 120s) | OK (每轮循环重设) | | SSL 关闭 | L250 `stream.shutdown()` | 无 | 低风险(ec 参数,不抛异常) | **关键缺失**: L142 的同步 `resolver.resolve()` 无法设置 socket 级超时(stream 尚未创建)。需改用 `async_resolve` 或在独立线程中执行 resolver。 **可配置性**: L129-135 从 config service 读取 `http.connect_timeout` / `http.request_timeout`,有兜底默认值 (30s/120s)。 ## 6. 错误码语义与重试 **评级: B** - L261: `(result_code >= 200 && result_code < 300) ? 0 : -1` — 2xx 成功,其余返回 -1。 - 调用方可通过 `*status_code` 区分: -1 = 连接/超时/DNS 错误,4xx/5xx = HTTP 错误码。语义正确。 - **无重试逻辑**: 无任何 retry。对低层 HTTP 客户端这是合理设计,重试应由上层 (AI 插件) 实现。 - 异常路径 (L251-254): `result_code = -1`, `result_body = e.what()` — 异常消息通过 response_body 传递给调用方,设计合理。 - L221 / L244: `if (ec) break;` — 流/非流 read_some 错误静默退出循环,错误原因不记录日志(全文零 `g_host->log` 调用)。 ## 7. 并发安全 **评级: A** - 每次 `do_post_stream` 调用创建新 `HttpClientCtx` (L125),内含独立 `io_context`。零共享状态。 - `g_host` (L33): `on_init` 写入一次,后续只读。`on_shutdown` 不置空(L305-307 为空函数体)。 - `g_config_svc` (L34): 同上。 - 无全局 socket / stream / resolver 共享。每个请求自包含。 - 多线程并发调用 `http_post_json` 安全。 ## 8. 资源清理 **评级: A** - `HttpClientCtx` (L125): 栈上对象,内含 RAII `io_context` + `ssl::context`。析构自动清理。 - `ssl_stream` (L144), `resolver` (L141), `flat_buffer` (L145): 全部栈上 RAII。 - 早期退出路径: `goto done` (L150, L214) — 栈展开正确析构所有对象。 - 异常路径: `catch(std::exception&)` (L251) — 栈展开触发析构。 - L249 `cancel()` + L250 `stream.shutdown(ec)` — 正常路径优雅关闭。 **零泄漏路径**,包括异常和提前退出。无动态分配(除 `g_host->strdup` 的返回值由调用方管理)。 ## 9. 缓冲区限制 / DoS **评级: A** - L187: `parser.body_limit(16 * 1024 * 1024)` — 16MB 响应体硬上限。防止恶意服务器返回无限大数据导致 OOM。 - `parse_headers_json` 无长度限制 (L40-80): 理论上调用方可传入超大 JSON,但 headers_json 来源为同进程内其他插件(可信),非网络输入。低风险。 --- ## TOP 3 严重问题 ### 发现 1 — [严重] TLS 证书验证完全禁用 (L87-93) **问题**: 从未调用 `ssl_ctx.set_verify_mode(ssl::verify_peer)`,默认接受任何证书。无 hostname 验证。所有 API key 和通信内容可被 MITM 拦截。 **修复方向**: 添加 `ssl_ctx.set_verify_mode(ssl::verify_peer)` + `ssl_ctx.set_verify_callback(ssl::host_name_verification(host))` 或在 handshake 后调用 `SSL_get_peer_certificate` + `X509_check_host`。 ### 发现 2 — [高] DNS 解析无超时 (L142) **问题**: `resolver.resolve(host, port)` 同步调用,无法通过 `expires_after` 设置超时(socket 尚不存在)。若 DNS 服务器不响应 → 线程永久阻塞 → 调用方 hang。 **修复方向**: 使用 `async_resolve` 并将 io_context 的 `run()` 或 `run_for()` 与超时结合,或在独立 future/promise 线程中执行 resolution 并使用 wait_for。 ### 发现 3 — [中] 异常处理缺 catch(...) 兜底 (L251) **问题**: 仅捕获 `std::exception&`,无 `catch(...)`。非标准异常 → 穿越 C ABI → `std::terminate()`。虽然 Boost.Beast/Asio 的已知异常均派生自 `std::exception`,但 §8.2 要求双级 catch 全覆盖。 **修复方向**: 在 `catch (std::exception&)` 之后追加 `catch (...) { result_code = -1; result_body = "unknown exception"; }`。 --- ## TLS 验证状态 **未启用**。`ssl_ctx.set_verify_mode` 从未调用,默认 `verify_none` 接受一切证书。hostname 验证(`SSL_set1_host` / `boost::asio::ssl::host_name_verification`)也未实现。`set_default_verify_paths` 的调用形同虚设。 **影响评估**: 生产不可用。任何可访问网络路径的中间人可解密全部通信,包括 API 密钥、对话内容。在公共 WiFi 或不可信网络环境下,这是可直接利用的凭证泄露漏洞。 --- ## 整体评级: C | 维度 | 评级 | |------|------| | 跨 DLL 堆纪律 | A | | 异常安全 ABI | B | | 字符串返回生命周期 | A | | TLS 证书验证 | F | | 超时 | C | | 错误码语义 | B | | 并发安全 | A | | 资源清理 | A | | 缓冲区限制 | A | | **综合** | **C** | **总评**: RAII、堆纪律、字符串生命周期、并发安全均高质量。但 TLS 证书验证完全禁用 (F) 是致命安全缺陷,DNS 无超时可无限 hang。两个问题 (TLS + DNS) 使该插件在任何生产环境中不可用。修复后预期可达 A 级。