# W13.5 Session + Tools Plugin 联合审计 **审计人**: 曹武 (security-cao) **日期**: 2026-05-27 **文件**: plugins/session/src/session_plugin.cpp (264 行) + plugins/tools/src/tools_plugin.cpp (251 行) **标尺**: plugin-abi.md §2 §5 §8 §9; security-logging.md **前置工作**: W2.2 (api_key secure_zero, 仅 deepseek/anthropic) — 已注明合规; W9.3 (日志凭证审计, session 已审日志路径) — 已注明合规 --- ## 1. Session 文件总览 session_plugin.cpp 实现 dstalk_session_service_t vtable (6 个函数): add / clear / save / load / history / token_count。 内部状态: - `g_history`: std::vector — 完整消息历史 (role + content + tool_call_id + tool_calls_json) - `g_cached_history`: std::vector — history() 返回的 C 数组缓存 (g_host->strdup 分配) - `g_host` / `g_file_io`: 服务指针 依赖: file_io (save/load 持久化), Boost.JSON (序列化)。 --- ## 2. Tools 文件总览 tools_plugin.cpp 实现 dstalk_tools_service_t vtable (4 个函数): register_tool / unregister_tool / get_tools_json / execute。 内置两个工具: - `file_read`: 读取任意路径文件内容, 返回 JSON `{"content":"..."}`. - `file_write`: 写入任意路径, 返回 JSON `{"success":true}`. 内部状态: - `g_tools`: std::vector — 已注册工具列表 - `g_host` / `g_file_io`: 服务指针 依赖: file_io, Boost.JSON。 --- ## 3. 按维度发现 ### 维度 1: Session 敏感数据持有与清零 [session] **W2.2 覆盖范围**: api_key 的 secure_zero 仅实现在 deepseek_plugin.cpp 和 anthropic_plugin.cpp 的 anonymous namespace 内。session 插件**不在** W2.2 范围内 — 已注明合规。 **发现 1.1 — [高] 会话历史明文持有可能含 token 的数据** (session L33-40, L127-135) - g_history 存储完整的 InternalMessage (含 tool_calls_json)。AI 模型回复中的 tool_calls 可包含 API 返回的敏感参数。 - on_shutdown (L242-249) 仅调用 g_history.clear() → std::string 析构 → 内存归还堆, **未 secure_zero 擦除**。 - g_cached_history 中的字符串通过 g_host->free 释放 (L104-107), 同样未擦除。 - 与 W2.2 的 secure_zero 处置形成落差: api_key 被安全擦除, 但可能包含等效敏感度的会话数据未受同等保护。 **发现 1.2 — [中] 会话落盘明文无保护** (session L141-157) - session_save 将完整消息 JSON 逐行写入 path 指定文件。content 字段包含用户输入 + AI 回复原文, tool_calls_json 包含工具调用参数。 - 无加密、无访问控制、无审计日志。 - path 参数无校验 — 依赖 file_io 服务实现 (若 file_io 无 sandbox, 可写任意路径)。 - 评级: 若 g_history 中包含 token 或隐私数据, 则构成明文存储敏感信息。 ### 维度 2: Session 历史持久化安全 [session] - 见维度 1 发现 1.2。补充: session_load (L159-202) 同样无路径校验, 可加载攻击者构造的 JSON 文件注入恶意历史记录 (如伪造 tool_call_id 导致下游误操作)。 - 加载时的 JSON 解析异常仅返回 -1 未记日志 (L194-195), 不利于入侵检测。 ### 维度 3: Tools 路径校验与注入防护 [tools] **发现 3.1 — [严重] 路径遍历: file_read / file_write 无路径消毒** (tools L50, L85) - builtin_file_read (L39-67): `g_file_io->read(path.c_str(), &content)` — path 直接来自用户 JSON 输入, 未经任何规范化/白名单/拒绝遍历序列 (../) 检查。 - builtin_file_write (L69-98): 同。 - 攻击场景: `{"path":"../../../etc/passwd"}` 或 `{"path":"C:/Windows/System32/drivers/etc/hosts"}` — 取决于 file_io 底层实现。 - CVSS 初步评分: 7.5 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N 需确认调用方可控性) — 若 AI 模型被 prompt injection 诱导调用 file_read/file_write, 攻击者可读写任意文件。 - **未发现命令注入**: 全文搜索无 system() / popen() / exec() / ShellExecute() / CreateProcess() 调用。仅通过 file_io 服务间接操作文件系统, 非 shell 执行。 ### 维度 4: Tools dispatch 权限校验 [tools] **发现 4.1 — [中] tools_execute 无调用方身份/权限检查** (tools L160-190) - 任何持有 tools service vtable 指针的代码均可调用 execute(name, args) 执行任意已注册工具。 - 无 capabilities / ACL / rate limiting / audit logging。 - 如果未来注册 shell_exec 或 network 工具, 风险急剧升级。当前仅 file_read/write 但仍可被恶意插件滥用。 ### 维度 5: §5 + §8 异常安全 (全部 C ABI 入口) [session + tools 共有] **W11.1 审计已知模式**: context_plugin 因同样问题被评为 B 级 (trim_impl 无 try/catch)。 **发现 5.1 — [高] Session: 5/8 个 C ABI 函数无异常保护** | 函数 | 行号 | 风险操作 | try/catch | |------|------|----------|-----------| | session_add | L127-135 | std::string 构造, vector::push_back | **无** | | session_save | L141-157 | std::string, json::object, json::serialize | **无** | | session_load | L159-202 | std::string, std::vector, string::substr | 仅 L178-196 (json::parse 部分) | | session_history → rebuild_cached_history | L101-121 | std::vector, g_host->strdup | **无** | | on_shutdown | L242-249 | → rebuild_cached_history | **无** | | session_clear | L137 | g_history.clear() | noexcept — **安全** | | session_token_count | L210 | 纯计算 | **低风险** | | on_init | L227-240 | host->log, register_service | host 侧有保护, 插件侧**无** | **发现 5.2 — [高] Tools: 5/8 个 C ABI 函数无异常保护** | 函数 | 行号 | 风险操作 | try/catch | |------|------|----------|-----------| | tools_register_tool | L106-121 | std::string, vector::push_back | **无** | | tools_unregister_tool | L123-130 | std::string, vector::erase | **无** | | tools_get_tools_json | L132-157 | json::parse, json::serialize, std::string | **无** | | on_init | L203-230 | → tools_register_tool x2 | **间接暴露** | | tools_execute | L160-190 | handler 调用 | **部分** (L180-189) | | builtin_file_read | L39-67 | json::parse, std::string | **有** (L63-66) | | builtin_file_write | L69-98 | json::parse, std::string | **有** (L94-97) | | on_shutdown | L232-236 | clear + nullptr | noexcept — **安全** | **违反条款**: plugin-abi §8.1 (适用范围第2项: service vtable 函数) + §8.2 (实施要求)。 **后果**: OOM 或 json::parse 异常时 → std::terminate() → 进程崩溃, 无恢复机会。 **发现 5.3 — [低] 异常消息信息泄露** (tools L64, L95) - builtin_file_read/write 的 catch 块将 `e.what()` 直接拼入返回给调用方的 JSON 字符串。 - `e.what()` 可包含文件路径、系统错误描述等内部信息。 - 虽算不上安全漏洞 (调用方本就控制 path 参数), 但违反最小信息暴露原则。 ### 维度 6: §9 字符串返回 [session + tools 共有] **发现 6.1 — [中] session_history 返回指针在下一次调用时悬垂** (session L101-121, L204-208) - `session_history()` 调用 `rebuild_cached_history()`, 后者先 `g_host->free` 所有旧 g_cached_history 字符串 (L104-107) 再重建。 - 调用方若持有上一次 history() 返回的 `const dstalk_message_t*`, 在下一次 history() 调用后其中的 role/content 指针已悬垂 (已 free)。 - 模式类似 dstalk_chat_result_t (§2.1), 但 **无文档警告** "must copy before next history()"。 - 违反 §9.2 模式 A 文档要求 ("caller must free" 等效说明缺失)。 **发现 6.2 — [低] Tools 返回字符串模式正确但缺文档** (tools L62, L93, L157) - builtin_file_read/write, tools_get_tools_json, tools_execute 错误路径全部使用 g_host->strdup 返回 — Mode A, 正确。 - 但函数无注释说明 "caller must free with host->free", 违反 §9.2 文档要求。 ### 维度 7: §2 跨 DLL 堆纪律 [session + tools 共有] **两个文件均合规** ✅ (同 W11.1 审计结论 — 编码者即使未读 plugin-abi 也遵循了正确模式): - 所有跨边界分配: g_host->strdup (session L115-118; tools L62, L93, L157) - 所有跨边界释放: g_host->free (session L104-107, L167) - 内部类型: std::string / std::vector 仅插件 DLL 内使用, 不出边界 - 无裸 malloc / free / strdup / new / delete 处理 host 传入/传出数据 **W2.1**: 两个文件均未搜到裸 free 调用。任何 W2.1 的 free 迁移已在上层重构中消化 (旧 deepseek_api.cpp 等已删除)。已注明合规。 ### 维度 8: 并发安全 [session + tools 共有] **发现 8.1 — [严重] 两个插件所有全局状态均无同步保护** Session 竞态表: | 变量 | 写入方 | 读取方 | 同步 | |------|--------|--------|------| | g_history | add, clear, load | save, token_count, rebuild_cached_history | **无** | | g_cached_history | rebuild_cached_history (via history) | history | **无** | | g_host | on_init, on_shutdown | rebuild_cached_history, save, load | **无** | | g_file_io | on_init, on_shutdown | save, load | **无** | Tools 竞态表: | 变量 | 写入方 | 读取方 | 同步 | |------|--------|--------|------| | g_tools | register_tool, unregister_tool | get_tools_json, execute | **无** | | g_host | on_init, on_shutdown | get_tools_json, execute, file_read, file_write | **无** | | g_file_io | on_init, on_shutdown | file_read, file_write | **无** | **典型竞态场景**: - Race A (session): T1 add() push_back to g_history, T2 save() iterates → 迭代器失效 / 数据损坏。 - Race B (session): T1 history() reads g_cached_history, T2 history() frees + rebuilds → UAF。 - Race C (tools): T1 execute() iterates g_tools, T2 unregister_tool() erases → 迭代器失效。 - Race D (共有): T1 any-service reads g_host/g_file_io, T2 on_shutdown() writes nullptr → NPD (空指针解引用)。 Race D 已于 W11.1 审计确认 (context 的同模式问题评为 C 级)。session + tools 的竞态面更大 (更多全局可变状态)。 --- ## 4. TOP 3 严重问题 ### TOP 1 — [严重] 路径遍历: file_read/file_write 无路径消毒 (tools L50, L85) - 攻击者控制 args_json → 读写任意文件系统路径。无 sandbox、无白名单、无规范化。 - 若 AI 模型被 prompt injection 诱导调用工具, 可泄露系统配置/源码/凭证文件。 - CVSS: 7.5 (High) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N (假设调用方可控) - 修复方向: 在 builtin_file_read/write 中添加路径规范化 (realpath/Canonicalize), 限制在预设工作目录内, 拒绝 ".." 段。 ### TOP 2 — [严重] 全局状态无同步 → 多线程竞态 (session + tools, 全文) - g_history / g_tools / g_host / g_file_io 读写均无 mutex。 - 后果: 迭代器失效 (crash)、use-after-free (crash/任意代码)、空指针解引用 (crash)。 - 影响面: 两个插件共 8 个 vtable 函数 + 2 个 on_shutdown = 10 个竞态入口。 - CVSS: 7.5 (High) — 多线程使用时 crash 概率高, 可能远程触发 (通过 AI chat 间接调用 session/tools)。 - 修复方向: 每个插件引入 std::shared_mutex (或 std::mutex), 写操作持 unique_lock, 读操作持 shared_lock。on_shutdown 需额外原子/锁确保服务调用排空。 ### TOP 3 — [高] C++ 异常穿越 ABI 边界 (session L127/L141/L204/L242 + tools L106/L132/L203) - session 5 个函数 + tools 4 个函数使用 std::string/std::vector/json::parse 但无 try/catch 包裹。 - 违反 plugin-abi §8 硬性规则 → OOM 或解析异常时 std::terminate() → 进程崩溃。 - W11.1 已在 context 中识别同模式问题 (评为 B 级, trim_impl 无保护)。session/tools 影响面更广 (9 个 vtable 函数 vs context 的 3 个)。 - CVSS: 5.9 (Medium) — 触发需 OOM 或恶意 JSON 输入, 后果为 DoS (进程崩溃)。 - 修复方向: 按 §8.2 标准模式, 每个使用 C++ 类型的 C ABI 函数体包裹 `try { ... } catch (const std::exception& e) { log; return -1/nullptr; } catch (...) { log; return -1/nullptr; }`。 --- ## 5. 关键问题回答 | 问题 | 结论 | |------|------| | 是否发现凭证泄露? | **潜在风险**: session_save 明文落盘含 tool_calls_json, 若工具调用参数含 token 则泄露。当前未确认可利用实例, 但防御缺失。 | | 是否发现命令注入? | **未发现**: 两文件均无 system()/popen()/exec() 调用。tools 仅通过 file_io 服务间接操作文件, 非 shell 执行。 | | 是否发现路径遍历? | **确认**: tools builtin_file_read/write 无路径消毒 (L50, L85), 攻击者可读写任意文件。 | --- ## 6. 整体评级 ### Session: D+ (62/100) | 维度 | 评级 | 说明 | |------|------|------| | §2 跨 DLL 堆 | A | 完全合规, host->strdup/free | | §5/§8 异常安全 | D | 5/8 C ABI 函数无 try/catch | | §9 字符串返回 | C | 模式正确但缺文档; history() 悬垂风险 | | 敏感数据处理 | C | 明文落盘 + 无 secure_zero | | 并发安全 | D | 零同步, 4 个静态全局无保护 | | **综合** | **D+** | 堆纪律干净但异常安全/并发/数据保护三项不及格 | ### Tools: D (60/100) | 维度 | 评级 | 说明 | |------|------|------| | §2 跨 DLL 堆 | A | 完全合规, host->strdup/free | | §5/§8 异常安全 | C | 内置工具有保护, 注册/查询/on_init 无 | | §9 字符串返回 | B | Mode A 正确, 缺文档 | | 路径校验 | F | file_read/write 任意路径, 无消毒 | | 访问控制 | F | execute 无调用方身份/权限检查 | | 并发安全 | D | 零同步, 3 个静态全局无保护 | | **综合** | **D** | 路径遍历为硬伤; 并发/异常安全与 session 同病 | --- ## 7. 低优先级发现 - **session_load 异常吞没无日志** (session L194-195): 解析失败仅返回 -1 不记日志。 - **tools 异常消息泄露** (tools L64, L95): e.what() 返回给调用方, 含内部路径/系统错误。 - **两个插件的 on_init 均未检查 register_service 返回值**: 若服务名冲突返回 -2 被静默忽略。 - **W9.3 已审结论维持**: session 仅 on_init L233 调用 host->log(ERROR, 静态字符串) — 无凭证日志泄露。tools 无任何 host->log 调用。两个文件在日志维度上安全。