Files
blackbird/docs/security/db2_SECURITY_AUDIT_20260806.md
T
qing 7467e7bd42 docs: 整理文档——修复全部 file:// 失效链接、补齐缺失模块 README
- 将 7 个文档中的 mac 绝对路径链接(file:///Users/qingsong/...)转换为仓库相对路径,
  并修正错误目标:aibox 入口 main.py → aibox.py、pymsg C++ 实现 → libmsg/inc/us/Msg.hpp、
  bot4cj cpp/cnn → cuda
- 主 README 补 liblogc 基础库条目与 confs2 归档说明,安装顺序补 ca
- 新增 liblogc / libtry / confs2 / 3d / memdb README
- 同步 db2 脚本迁移(db/create_sqlite3.sql → db2/scripts/)导致的失效引用
2026-08-10 15:48:29 +00:00

347 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# db2 模块安全审计报告
**初次审计**: 2026-08-06
**最近复审**: 2026-08-07(第六轮审计,全部问题已修复,编译验证通过)
**审计范围**: `db2/src/db2.cpp`、`Makefile`、`docker-entrypoint.sh`
**对照基准**: Python 版 `db/dbv2.py`(接口兼容性对比)
**代码版本**: main 分支 + 第六轮修复
---
## 修复进度
| 优先级 | 总数 | 已修复 | 未修复 |
|--------|------|--------|--------|
| P0 | 2 | 2 | 0 |
| P1 | 1 | 1 | 0 |
| P2 | 4 | 4 | 0 |
| P3 | 20 | 20 | 0 |
| **合计** | **26** | **26** | **0** |
---
## P0 — 严重问题
### P0-1: 列名 SQL 注入(列名未校验直接拼接 SQL)
- **位置**: 历史版本 L399-455 等位置
- **当前状态**: ✅ 已修复(2026-08-06)
- **修复**: 新增 `_validate_col` 静态方法 [db2.cpp L443-449](../../db2/src/db2.cpp#L443-L449),对所有列名(where keys、columns、order_by、INSERT/UPDATE values keys)校验其存在于表 schema 的 `col_map` 中,不存在则抛 `"unknown column"` 错误。5 个注入点全部修复。
`sqlite3_prepare_v2` 只编译单条语句(`; DROP TABLE` 不会执行),但**单语句内注入**在修复前仍然可行。修复后列名必须是真实存在的列名,注入不可能。
- **注入点覆盖**:
- `where` 的 key → `_build_where` [L473-475](../../db2/src/db2.cpp#L473-L475)
- `columns` 数组元素 → `_do_select` [L588-590](../../db2/src/db2.cpp#L588-L590)
- `order_by[].column` → `_do_select` [L611-612](../../db2/src/db2.cpp#L611-L612)
- INSERT `values` 的 key → `_do_insert` [L691-693](../../db2/src/db2.cpp#L691-L693)
- UPDATE SET `values` 的 key → `_do_update` [L734-736](../../db2/src/db2.cpp#L734-L736)
---
### P0-2: 空 `in` 数组 → 无 WHERE 子句 → 全表删除/更新
- **位置**: `_build_where` `in` 运算符分支
- **当前状态**: ✅ 已修复(2026-08-06)
- **修复**: [L503-506](../../db2/src/db2.cpp#L503-L506) 空 `in` 数组现在生成恒假条件 `parts.push_back("0")`,使 WHERE 子句匹配零行,与 Python 版 SQLAlchemy `column.in_([])` 行为一致。
---
## P1 — 高危问题
### P1-1: 所有值绑定为 TEXT,类型信息丢失
- **位置**: 历史版本 `_coerce_value` / `_bind_value`
- **当前状态**: ✅ 已修复(2026-08-06)
- **修复**:
- `_coerce_value` [L414-420](../../db2/src/db2.cpp#L414-L420) 现在返回 `boost::json::value`,仅对字符串做 `$now` 替换,其他类型保持原样。
- 新增 `_bind_value` [L422-440](../../db2/src/db2.cpp#L422-L440),按 JSON 类型分别调用 `sqlite3_bind_int64` / `sqlite3_bind_double` / `sqlite3_bind_text` / `sqlite3_bind_null` / `sqlite3_bind_int` (bool)。
- `WhereClause::params` 改为 `std::vector<json::value>`,保留原始类型。
修复后 INTEGER/REAL 等数值类型以原生 SQLite 存储类写入,避免 TEXT 列上的字符串比较问题,行为与 Python 版一致。
---
## P2 — 中危问题
### P2-1: 清理时间戳格式不一致(T vs 空格)导致保留期偏移约 1 天
- **位置**: 历史版本 `utc_now` + `_do_cleanup`
- **当前状态**: ✅ 已修复(2026-08-07)
- **修复**: [L863-865](../../db2/src/db2.cpp#L863-L865) 清理 SQL 改用 `strftime('%Y-%m-%dT%H:%M:%S','now','-N days')`,与 `utc_now()` 的 `T` 分隔格式完全对齐。
---
### P2-2: 缺少配置完整性校验
- **位置**: 历史版本 `load_config`
- **当前状态**: ✅ 已修复(2026-08-07)
- **修复**: [L159-175](../../db2/src/db2.cpp#L159-L175) 在 INI 解析后,对 7 个必需字段做必填校验,缺失任一则 `fprintf` 明确错误信息后 `exit(1)`。
---
### P2-3: `cleanup_interval` 无下限校验,配置为 0 或负数时导致清理线程 busy loop 占满 CPU
- **位置**: `_cleanup_loop`、`load_config`
- **当前状态**: ✅ 已修复(2026-08-07)
- **修复**: [db2.cpp L207-L227](../../db2/src/db2.cpp#L207-L227) 在 `load_config` 返回前(所有配置来源——INI、环境变量、命令行参数——都加载完毕后)统一增加数值范围校验,非法值立即 `fprintf` 明确错误信息后 `exit(1)`。
- **校验项**:
- `listen_port`: 1-65535
- `workers`: ≥ 1
- `cleanup_interval`: ≥ 1(busy loop 问题根源)
- `retention_days`: ≥ 0
- `max_records`: ≥ 1
---
## P3 — 低危问题
| # | 问题 | 位置 | 说明 |
|---|------|------|------|
| P3-1 | schema 缓存永不失效 | 历史版本 | ✅ 已修复:缓存项携带 `PRAGMA schema_version` [L270-273](../../db2/src/db2.cpp#L270-L273),每次取 schema 前比对版本号;DDL 变更后自动失效并重新反射 |
| P3-2 | `_coerce_value` 对 null 返回 `""` | 历史版本 | ✅ 已修复:`_coerce_value` 改为返回 `json::value`,null 保持为 null |
| P3-3 | `limit`/`offset` 未校验负值 | 历史版本 | ✅ 已修复:[L622-632](../../db2/src/db2.cpp#L622-L632) 非负校验 |
| P3-4 | WHERE 条件数 / IN 数组大小无上限 | 历史版本 | ✅ 已修复:[L463-467](../../db2/src/db2.cpp#L463-L467) 64 条件上限、[L507-511](../../db2/src/db2.cpp#L507-L511) IN 元素 1000 上限 |
| P3-5 | 空 `values` 对象导致 INSERT/UPDATE 语法错误 | 历史版本 | ✅ 已修复:[L680-682](../../db2/src/db2.cpp#L680-L682)、[L724-726](../../db2/src/db2.cpp#L724-L726) 前置校验 |
| P3-6 | 未知 WHERE 运算符被静默跳过 | 历史版本 | ✅ 已修复:[L519-521](../../db2/src/db2.cpp#L519-L521) 未知运算符抛错 |
| **P3-7** | **`utc_now()` 使用非线程安全的 `gmtime()`** | [L58-66](../../db2/src/db2.cpp#L58-L66) | ✅ 已修复(2026-08-07,第五轮):改用 `gmtime_r(&t, &tmbuf)` 线程安全版本,消除多线程并发调用时的静态缓冲区数据竞争 |
| **P3-8** | **其他数值配置无范围校验** | [L186-L191](../../db2/src/db2.cpp#L186-L191) | ✅ 已修复(2026-08-07,与 P2-3 一同):[L207-L227](../../db2/src/db2.cpp#L207-L227) 所有数值配置项范围校验覆盖:port 1-65535、workers ≥1、cleanup_interval ≥1、retention_days ≥0、max_records ≥1 |
| **P3-9** | **`columns` 数组中非字符串元素未检查类型** | [L633-636](../../db2/src/db2.cpp#L633-L636) | ✅ 已修复(2026-08-07,第五轮):添加 `if (!c.is_string()) throw "columns array must contain only strings"` 类型校验,非字符串元素抛清晰错误而非 boost 原始异常 |
| **P3-10** | **`DbConn` 析构使用 `sqlite3_close` 而非 `sqlite3_close_v2`** | [L254-256](../../db2/src/db2.cpp#L254-L256) | ✅ 已修复(2026-08-07,第五轮):改用 `sqlite3_close_v2`,异常路径存在未 finalize 的 stmt 时标记为 zombie 状态,等所有 stmt 释放后自动关闭,更鲁棒 |
| **P3-11** | **`std::stoi/stoll` 解析配置失败无友好错误提示** | [L72-87](../../db2/src/db2.cpp#L72-L87)、[L163](../../db2/src/db2.cpp#L163)、[L208-214](../../db2/src/db2.cpp#L208-L214)、[L218-219](../../db2/src/db2.cpp#L218-L219)、[L226-227](../../db2/src/db2.cpp#L226-L227) | ✅ 已修复(2026-08-07,第五轮):引入 `safe_stoi(s, field_name)` 辅助函数,所有 `std::stoi` 调用点(CONFS_PORT、INI 配置、环境变量、命令行参数共 9 处)替换为 `safe_stoi`,失败时抛带字段名的友好错误(如 `"invalid [server] port: 'abc' (stoi)"`) |
| **P3-12**(新) | **`_log_internal` 静默吞掉所有异常** | [L898-900](../../db2/src/db2.cpp#L898-L900) | ✅ 已修复(2026-08-07,第五轮):`catch (...)` 改为 `catch (const std::exception& e)`,并 `fprintf(stderr, "[db2] _log_internal failed: %s", e.what())`,便于排查日志写入失败原因,仍不阻塞业务 |
| **P3-13**(新) | **`_do_cleanup` COUNT 查询 prepare 失败静默跳过** | [L947-951](../../db2/src/db2.cpp#L947-L951) | ✅ 已修复(2026-08-07,第五轮):prepare 失败时 `fprintf(stderr, ...)` 记录 sqlite3_errmsg,同时简化 finalize 逻辑为单一出口 `if (stmt) sqlite3_finalize(stmt)`,避免重复 finalize 风险 |
| **P3-14**(新) | **`order_by` 非法元素静默跳过** | [L654-671](../../db2/src/db2.cpp#L654-L671) | ✅ 已修复(2026-08-07,第五轮):非 object 元素、缺少 `column` 字段、`column` 非 string、`desc` 非 bool 均改为抛 `runtime_error` 明确报错,不再静默跳过,避免掩盖客户端错误 |
| **P3-15**(新) | **`action` 字段非 string 静默默认为 "select"** | [L596-600](../../db2/src/db2.cpp#L596-L600) | ✅ 已修复(2026-08-07,第五轮):`action` 字段存在但非 string 类型时抛 `"'action' must be a string"`,不再静默回退为 "select" 导致意外执行查询 |
| **P3-16**(新) | **`order_by` 的 `column`/`desc` 字段类型未校验** | [L658-668](../../db2/src/db2.cpp#L658-L668) | ✅ 已修复(2026-08-07,第五轮,与 P3-14 一同):`column` 字段检查 `is_string()`,`desc` 字段检查 `is_bool()`,类型不符时抛清晰错误而非 boost 原始异常 |
---
## 第六轮审计新增问题(2026-08-07)
### ND-1(P2): `start()` 异常路径导致 `std::terminate()` 硬崩溃
- **位置**: `start()` [L341-355](../../db2/src/db2.cpp#L341-L355)
- **当前状态**: ✅ 已修复(2026-08-07,第六轮)
- **问题**: 清理线程 [L343](../../db2/src/db2.cpp#L343) 启动后,若 `rpc.start()` 抛出异常(socket/bind/listen 失败如端口被占用,或事件循环异常经 `MsgServer::start()` 重新抛出),原有的 `_cleanup_running=false; notify; join` 正常清理路径被跳过。栈展开时 `DB2Server` 析构,`std::thread` 成员仍 joinable → `std::terminate()` 进程硬崩溃,并掩盖真实错误(本应干净退出返回错误码)。
- **修复**: 引入 RAII 作用域守卫 `CleanupGuard` [L348-355](../../db2/src/db2.cpp#L348-L355),析构时停止并 join 清理线程。无论 `rpc.start()` 正常返回还是抛出,清理线程都被优雅回收,异常沿调用链传播到 `main` 的 catch 返回错误码。
### ND-4(P3): `limit`/`offset`/`where` 类型不符被静默忽略
- **位置**: `_do_select` limit/offset [L692-708](../../db2/src/db2.cpp#L692-L708)、`_handle_execute` where [L626-630](../../db2/src/db2.cpp#L626-L630)
- **当前状态**: ✅ 已修复(2026-08-07,第六轮)
- **问题**: `limit`/`offset` 仅当 `is_int64()` 才应用,`where` 仅当 `is_object()` 才解析;调用方传错类型(字符串/浮点/数组)时字段被静默丢弃,出现"看似生效实则未生效"的静默偏差,掩盖客户端错误。
- **修复**: `limit`/`offset` 存在但非整型、`where` 存在但非对象时抛清晰错误(与 P3-15 action 校验一致)。
### ND-5(P3): `_bind_value` uint64 超 INT64_MAX 截断溢出
- **位置**: `_bind_value` [L480-485](../../db2/src/db2.cpp#L480-L485)
- **当前状态**: ✅ 已修复(2026-08-07,第六轮)
- **问题**: `jv.as_uint64()` 经 `static_cast<sqlite3_int64>` 直接截断,超出 INT64_MAX 的 uint64 值发生回绕,数值静默失真。
- **修复**: [L480-483](../../db2/src/db2.cpp#L480-L483) 绑定前校验 `u > INT64_MAX` 则抛错,防止静默数值截断。
### ND-6(P3): `in` 运算符非数组值被静默当作空数组
- **位置**: `_build_where` in 分支 [L558-562](../../db2/src/db2.cpp#L558-L562)
- **当前状态**: ✅ 已修复(2026-08-07,第六轮)
- **问题**: `in` 值为字符串/数字等非数组时被当作空数组 → 恒假条件,静默掩盖客户端错误。
- **修复**: [L558-559](../../db2/src/db2.cpp#L558-L559) 非数组立即抛 `"'in' operator requires an array value"`;仅空数组保留恒假 `0` 语义(与 Python 版 `column.in_([])` 一致)。
### 已知限制(设计决策,非缺陷)
- **ND-2(L-13)**: **`select` 无结果集上限** — 无 WHERE + 无 `limit` 时 `SELECT *` 返回全表并整体装入内存,恶意/异常客户端可触发内存耗尽 DoS。为使调用方零改动切换而不加隐式 limit,记录为已知限制;建议 gateway 侧对响应做大小/行数限制,或后续引入可配置 `max_result_rows`(默认关闭以保兼容)。
- **ND-3(L-14)**: **`update`/`delete` 无 WHERE 时全表更新/删除,无内置防护** — 与 Python 版行为对齐,属兼容性权衡;调用方漏传 where 会静默作用于全表,属数据安全风险。建议调用方侧强制校验,或后续提供可选 `require_where` 开关。
---
## 已有安全机制(正确实现)
1. **表名校验** — [L558-562](../../db2/src/db2.cpp#L558-L562):仅允许 `[A-Za-z0-9_]`,防止表名级 SQL 注入
2. **列名白名单校验** — `_validate_col` [L443-449](../../db2/src/db2.cpp#L443-L449):5 个拼接点全部覆盖,列名必须存在于表 schema
3. **值参数化绑定** — `_bind_value` [L422-440](../../db2/src/db2.cpp#L422-L440):按 JSON 类型分别 `sqlite3_bind_*`,无值级 SQL 注入
4. **per-thread 连接** — [L337-341](../../db2/src/db2.cpp#L337-L341):`thread_local` 独占连接,避免跨线程共享 prepared statement
5. **异常脱敏** — `RpcServer::_sanitize_error`(libmsg):正则替换绝对路径为 `<redacted>`
6. **仅操作已有表** — `PRAGMA table_info` 反射,无法建表/删表/执行 DDL
7. **空 `in` 恒假条件** — [L503-506](../../db2/src/db2.cpp#L503-L506):空数组不忽略 WHERE,匹配零行
8. **SQLite PRAGMA 优化** — [L223-228](../../db2/src/db2.cpp#L223-L228):WAL + synchronous=NORMAL + foreign_keys=ON + busy_timeout=5000 + mmap_size=256MB
9. **schema 缓存加锁 + 版本感知失效** — `_schema_mtx` [L326](../../db2/src/db2.cpp#L326) 保护缓存;`PRAGMA schema_version` 自动感知 DDL 变更
10. **配置完整性校验** — [L159-175](../../db2/src/db2.cpp#L159-L175):7 个必需字段缺失则 `exit(1)`
11. **confs 引导重试** — `retry_on_refused=true`,不要求服务启动顺序
12. **日志内部直写** — `_log_internal` [L814-843](../../db2/src/db2.cpp#L814-L843) 绕过 RPC 防递归,异常静默不阻塞业务
13. **后台清理线程** — 条件变量优雅停止 [L847-856](../../db2/src/db2.cpp#L847-L856),时间+条数双策略清理,时间戳格式 T 对齐
14. **空 values 前置校验** — INSERT/UPDATE 空 values 立即报错
15. **WHERE 运算符白名单** — 未知运算符立即抛错,不静默跳过
16. **`limit`/`offset` 非负校验** — 负值直接报错
17. **WHERE / IN 输入规模上限** — 64 条件 / 1000 IN 元素,防资源耗尽
18. **日志截断** — `kMaxMessage=8192`、`kMaxExtra=4096`,防止超长日志耗尽内存
19. **不可拷贝连接** — `DbConn` 显式 `= delete` 拷贝构造/赋值,防止 double free
20. **数值配置范围校验** — [L207-L227](../../db2/src/db2.cpp#L207-L227):listen_port 1-65535、workers ≥1、cleanup_interval ≥1、retention_days ≥0、max_records ≥1,非法值立即 `exit(1)`,防止误配置导致 busy loop/绑定失败等异常行为
21. **线程安全时间函数** — `utc_now()` 使用 `gmtime_r` 而非 `gmtime`,消除多线程并发写日志时的静态缓冲区数据竞争(P3-7 修复)
22. **安全 sqlite3 关闭** — `DbConn` 析构使用 `sqlite3_close_v2`,异常路径未 finalize 的 stmt 不会导致连接泄漏(P3-10 修复)
23. **友好配置解析错误** — `safe_stoi(s, field_name)` 包装所有 `std::stoi` 调用点,失败时抛带字段名的错误信息(P3-11 修复)
24. **全量 JSON 字段类型校验** — `action`/`columns`/`order_by` 的 `column`+`desc` 字段均检查 JSON 类型,类型不符抛清晰错误而非 boost 原始异常(P3-9/P3-15/P3-16 修复)
25. **日志写入失败可观测** — `_log_internal` catch 块记录 stderr,不再静默吞异常(P3-12 修复)
26. **清理线程错误可观测** — `_do_cleanup` COUNT 查询 prepare 失败时记录 stderr(P3-13 修复)
27. **清理线程 RAII 守卫** — `start()` 使用 `CleanupGuard`,`rpc.start()` 异常路径下清理线程被优雅回收,不触发 `std::terminate`(ND-1 修复)
28. **select 输入类型全量校验** — `where`/`limit`/`offset` 类型不符抛清晰错误,不静默忽略(ND-4 修复)
29. **uint64 溢出防护** — `_bind_value` 绑定前校验超出 INT64_MAX 抛错,防止静默数值截断(ND-5 修复)
30. **`in` 运算符类型校验** — 非数组立即抛错,仅空数组保留恒假语义(ND-6 修复)
---
## 修复路线图
### 已完成
- [x] **P0-1**: 列名白名单校验
- [x] **P0-2**: 空 `in` 生成恒假条件
- [x] **P1-1**: 按 JSON 类型分别参数绑定
- [x] **P2-1**: 清理时间戳格式统一为 T 分隔
- [x] **P2-2**: 配置完整性必填校验
- [x] **P2-3 + P3-8**: 所有数值配置项范围校验(port 1-65535、workers ≥1、cleanup_interval ≥1、retention_days ≥0、max_records ≥1)
- [x] **P3-1**: schema 缓存基于 `schema_version` 自动失效
- [x] **P3-2**: `_coerce_value` null 正确处理
- [x] **P3-3**: limit/offset 非负校验
- [x] **P3-4**: WHERE/IN 输入规模上限
- [x] **P3-5**: 空 values 前置校验
- [x] **P3-6**: 未知 WHERE 运算符报错
- [x] **P3-7**: `utc_now()` 改用 `gmtime_r` 保证线程安全(第五轮)
- [x] **P3-9**: `columns` 数组元素类型检查,非字符串抛清晰错误(第五轮)
- [x] **P3-10**: `DbConn` 析构改用 `sqlite3_close_v2`(第五轮)
- [x] **P3-11**: `safe_stoi` 包装所有 `std::stoi` 调用,带字段名友好错误(第五轮)
- [x] **P3-12**: `_log_internal` catch 块记录 stderr(第五轮)
- [x] **P3-13**: `_do_cleanup` COUNT prepare 失败记录 stderr(第五轮)
- [x] **P3-14**: `order_by` 非法元素抛错而非静默跳过(第五轮)
- [x] **P3-15**: `action` 非 string 抛错而非默认 "select"(第五轮)
- [x] **P3-16**: `order_by` 的 `column`/`desc` 字段类型校验(第五轮)
- [x] **ND-1**: `start()` 清理线程 RAII 守卫,`rpc.start()` 异常路径不 `std::terminate`(第六轮)
- [x] **ND-4**: `where`/`limit`/`offset` 类型不符立即抛错,不静默忽略(第六轮)
- [x] **ND-5**: `_bind_value` uint64 超 INT64_MAX 抛错,防止截断溢出(第六轮)
- [x] **ND-6**: `in` 运算符非数组值抛错,仅空数组保留恒假语义(第六轮)
### 待修复
无 — 全部 26 个问题已修复(100%)。新增 2 项已知限制(ND-2/ND-3)属兼容性设计决策,记录于"第六轮审计新增问题"章节。
---
## 审计方法
### 初次审计(2026-08-06)
1. 通读 `db2/src/db2.cpp` 全部 851 行实现
2. 逐函数对照 Python 版 `db/dbv2.py` 验证接口兼容性
3. 审查 libmsg RpcServer 异常脱敏机制
4. 逐注入点构造 PoC 验证 SQL 注入可行性
### 第一次复审(2026-08-07)
1. 通读修复后代码 875 行
2. 验证 P2-1/P2-2 修复
3. 全量扫描发现 P3-5/P3-6
### 第二次复审(2026-08-07)
1. 通读当前代码(main 分支 761cfd4)
2. 逐函数验证所有 11 个已修复问题无回退
3. 检查所有 `std::stoi`/数值转换路径,发现配置无范围校验问题(P2-3、P3-8、P3-11)
4. 检查所有系统调用线程安全性,发现 `gmtime()` 问题(P3-7)
5. 检查所有 JSON 访问路径的类型安全,发现 `columns` 缺类型检查(P3-9)
6. 对照 libsqlite3 审计最佳实践,发现 `sqlite3_close` 可改进(P3-10)
7. 更新行号引用至当前代码
### P2-3 / P3-8 修复验证(2026-08-07,第四次复审)
1. 在 `load_config` 末尾(命令行参数解析之后)新增 5 项数值范围校验:port 1-65535、workers ≥1、cleanup_interval ≥1、retention_days ≥0、max_records ≥1
2. 非法配置立即 `fprintf` 带字段名的明确错误信息后 `exit(1)`,与已有配置必填校验风格一致
3. 修复 error handler lambda 未使用的 `this` 捕获,消除 `-Wunused-lambda-capture` 警告
4. `g++ -std=c++20 -O3 -Wall -Werror` 编译通过,零警告零错误
5. 更新审计文档:修复进度、问题状态、安全机制清单、修复路线图、复审结论
### 第五轮复审(2026-08-07,本次)
1. 通读当前代码全部 960 行,逐函数审查
2. 修复 4 个遗留 P3 问题:
- P3-7: `utc_now()` 改用 `gmtime_r` 线程安全版本
- P3-9: `columns` 数组元素添加 `is_string()` 类型校验
- P3-10: `DbConn` 析构改用 `sqlite3_close_v2`
- P3-11: 引入 `safe_stoi(s, field_name)` 辅助函数,替换全部 9 处 `std::stoi` 调用
3. 新发现并修复 5 个 P3 问题:
- P3-12: `_log_internal` 的 `catch (...)` 改为 `catch (const std::exception& e)` 并记录 stderr
- P3-13: `_do_cleanup` COUNT 查询 prepare 失败时记录 stderr,简化 finalize 逻辑为单一出口
- P3-14: `order_by` 非法元素(非 object、缺 column、类型不符)改为抛错而非静默跳过
- P3-15: `action` 非 string 类型时抛错而非默认 "select"
- P3-16: `order_by` 的 `column`/`desc` 字段添加 `is_string()`/`is_bool()` 类型校验
4. `clang++ -std=c++20 -O2 -Wall -Werror -c` 编译验证通过,零警告零错误
5. 更新审计文档:修复进度(22/22)、问题状态、安全机制清单(26 项)、修复路线图、复审结论
### 第六轮复审(2026-08-07,本次)
1. 通读当前代码全部约 1000 行,逐函数审查
2. 核查 libmsg `RpcServer`/`MsgServer` 依赖行为,确认 `start()` 在 socket/bind/listen 失败时抛出异常
3. 新发现并修复 4 个问题:
- ND-1 (P2): `start()` 异常路径清理线程未回收 → `std::terminate()` 硬崩溃,引入 `CleanupGuard` RAII 守卫
- ND-4 (P3): `where`/`limit`/`offset` 类型不符静默忽略
- ND-5 (P3): `_bind_value` uint64 超 INT64_MAX 截断溢出
- ND-6 (P3): `in` 运算符非数组值静默当作空数组
4. 记录 2 项已知限制 ND-2/ND-3(无结果集上限、无 WHERE 全表更新/删除,均属兼容性设计决策)
5. `clang++ -std=c++20 -O2 -Wall -Werror -fsyntax-only` 与 `g++ -std=c++20 -O3 -Wall -Werror -c` 双重编译验证通过,零警告零错误
6. 更新审计文档:修复进度(26/26)、问题状态、安全机制清单(30 项)、修复路线图、复审结论
---
## 并发性分析
### 并发模型
```
RpcServer (N workers, 默认 4)
├── thread 1 → thread_local DbConn (sqlite3*)
├── thread 2 → thread_local DbConn (sqlite3*)
├── thread 3 → thread_local DbConn (sqlite3*)
└── thread 4 → thread_local DbConn (sqlite3*)
Cleanup thread → thread_local DbConn (sqlite3*)
```
每个 worker 和清理线程各自持有独立的 `sqlite3*` 连接(`thread_local`),通过 WAL 模式实现并发读写。
### 优势
| 机制 | 位置 | 效果 |
|------|------|------|
| **WAL 模式** | [L223](../../db2/src/db2.cpp#L223) | 读不阻塞写、写不阻塞读 |
| **per-thread 连接** | [L337-341](../../db2/src/db2.cpp#L337-L341) | 无跨线程共享,零连接池锁 |
| **busy_timeout=5000** | [L226](../../db2/src/db2.cpp#L226) | 写-写冲突自动等锁最多 5 秒 |
| **无 GIL** | — | C++ 线程真并行 |
### 瓶颈与风险
1. **写-写串行化** — SQLite 同一时刻只允许一个写事务,典型写入 300-800 TPS,对当前规模足够
2. **无批量事务** — 每条请求独立 auto-commit,无原子批量操作(Python 版同)
3. **清理与业务写竞争** — 大批量 DELETE 持锁数百毫秒可能阻塞业务写入
4. **`_schema_mtx` 全局锁** — 仅首次反射表结构时持锁,缓存命中无锁,影响极小
### 与 Python 版对比
| 维度 | Python db (dbv2.py) | C++ db2 |
|------|---------------------|---------|
| 并发模型 | N worker + 共享 engine | N worker + per-thread 连接 |
| GIL 限制 | 有 | 无,真并行 |
| 连接管理 | SQLAlchemy 连接池 | thread_local 独占 |
| 写冲突处理 | SQLAlchemy 自动重试 | busy_timeout=5000 |
---
## 复审结论
**当前状态**:六轮审计累计发现 **26 个问题,全部已修复(100%)**。P0/P1/P2 级问题在第六轮后全部修复(2 P0 / 1 P1 / 4 P2),第五轮修复了 9 个 P3 问题,第六轮新发现修复 1 个 P2(ND-1)与 3 个 P3(ND-4/ND-5/ND-6)。SQL 注入防护机制完整,配置误操作风险已全部消除,所有 JSON 字段访问路径均有类型校验,异常路径均有错误日志,启动异常路径不再触发进程硬崩溃。新增 2 项已知限制(ND-2/ND-3)属兼容性设计决策。
**第六轮修复亮点**:
- **启动健壮性**:`CleanupGuard` RAII 守卫确保 `rpc.start()` 无论返回还是抛出都回收清理线程,消除异常路径 `std::terminate()` 硬崩溃
- **输入类型完备**:`where`/`limit`/`offset` 类型不符立即抛错,杜绝"看似生效实则未生效"的静默偏差
- **数值保真**:`_bind_value` 对超 INT64_MAX 的 uint64 抛错,防止静默截断溢出
- **运算符严格化**:`in` 运算符非数组值抛错,仅空数组保留恒假语义(对齐 Python 版)
**第五轮修复亮点**:
- **线程安全**:`utc_now()` 改用 `gmtime_r`,消除多线程并发写日志时的数据竞争
- **资源安全**:`sqlite3_close_v2` 替代 `sqlite3_close`,异常路径不泄漏连接
- **错误可观测**:`_log_internal` 和 `_do_cleanup` 不再静默吞异常,均有 stderr 输出
- **输入校验完整**:`action`/`columns`/`order_by` 全部 JSON 字段类型校验,类型不符抛清晰错误
- **配置友好**:`safe_stoi` 包装全部 9 处 `std::stoi` 调用,失败时带字段名定位
**与 Python 版对比**:安全基线已超越 Python 版——C++ 版新增了 schema 版本感知自动失效、空 values 校验、未知运算符报错、limit/offset 非负与类型校验、WHERE/IN 输入规模上限、表名格式校验、日志截断、配置范围校验、线程安全时间函数、安全 sqlite3 关闭、全量 JSON 字段类型校验、异常可观测、启动异常路径 RAII 守卫等防护,Python 版尚未实现这些。
**生产可用性**:✅ **可用于生产**。全部 26 个问题已修复,30 项安全机制就位,核心安全与健壮性均有保障。2 项已知限制(ND-2 无结果集上限、ND-3 无 WHERE 全表更新/删除)为兼容性权衡,建议在调用方(gateway)侧做相应防护。