Files
blackbird/docs/security/libsqlite3_SECURITY_AUDIT_20260806.md
qing cb31db1078 docs(security): 更新 libsqlite3 审计报告修复状态
- P0-3 参数化 exec 已修复
- P1-2/P1-3/P1-4 绑定类型与异常信息已修复
- P2-2/P2-3 读取接口与文档已修复
- 修正 P2 问题数量统计(6 项而非 5 项)
2026-08-06 18:14:01 +00:00

219 lines
8.4 KiB
Markdown
Raw Permalink 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.
# libsqlite3 模块安全审计报告
**审计日期**: 2026-08-06
**审计范围**: `libsqlite3/inc/us/Sqlite3.hpp`、`Makefile`、`_set.sh`、`README.md`
**代码版本**: main 分支
---
## 修复进度
| 优先级 | 总数 | 已修复 | 未修复 |
|--------|------|--------|--------|
| P0 | 3 | 3 | 0 |
| P1 | 4 | 4 | 0 |
| P2 | 6 | 4 | 2 |
| **合计** | **13** | **11** | **2** |
---
## 模块现状
`libsqlite3` 当前实际是一个**单头文件 C++ RAII 封装库**,只提供 `qing::Sqlite3`(数据库连接)和 `qing::Sqlite3_stmt`(预编译语句)。
- 整个模块**没有任何单元测试或示例**
- `Makefile` 已经**完全注释掉编译规则**,只保留 `install/uninstall`
- `_set.sh` 只调用 `make install`,不编译任何产物
- 全项目**唯一引用方**是 `gateway/src/main.cpp`(`#include <us/Sqlite3.hpp>`),但对应的使用代码已被注释掉
- `db2` 直接使用原生 `sqlite3.h` API,**没有使用本封装**
结论:**该模块目前处于事实上的废弃/未使用状态**,但头文件仍被 `_inst.sh` 安装到系统目录,存在被误用的风险。
---
## P0 — 严重问题
### P0-1: 未定义拷贝/移动语义,存在 double free / use-after-free
- **位置**: `Sqlite3.hpp` L7-60(`Sqlite3_stmt`)、L62-97(`Sqlite3`)
- **当前状态**: ✅ 已修复(2026-08-06)
- **修复**: 为 `Sqlite3` 和 `Sqlite3_stmt` 显式 `= delete` 拷贝构造和拷贝赋值,并实现 `noexcept` 移动构造/移动赋值,移动后把源对象指针置 `nullptr`。析构函数先判空再释放,彻底消除 double free 和 use-after-free 风险。
修复前代码问题:
```cpp
Sqlite3_stmt stmt(std::string& sql) { // L91
return Sqlite3_stmt(db, sql.c_str()); // L92
}
```
`return` 依赖 NRVO/copy elision。若编译器未成功省略,会调用拷贝构造,触发 double free。即使现代编译器通常能省略,代码仍**不是良构且安全的**。
同理,`Sqlite3` 类也持有 `sqlite3* db` 原始指针,未禁用拷贝/移动,也存在 double free 风险。
---
### P0-2: `Sqlite3::~Sqlite3()` 忽略 `sqlite3_close` 返回值
- **位置**: `Sqlite3.hpp` L74-L76
- **当前状态**: ✅ 已修复(2026-08-06)
- **修复**: `~Sqlite3()` 改用 `sqlite3_close_v2(db)`,它会在所有 prepared statements 释放后才真正关闭数据库,避免 `SQLITE_BUSY` 导致的数据库未关闭问题。同时析构前判空,防止重复关闭。
---
### P0-3: `Sqlite3::exec` 使用字符串 SQL,无参数化绑定接口
- **位置**: `Sqlite3.hpp` L116-L160
- **当前状态**: ✅ 已修复(2026-08-06)
- **修复**:
- 原 `exec(const std::string& sql)` 增加注释,明确声明**仅用于硬编码 SQL,禁止拼接用户输入**。
- 新增模板参数化版本 `exec(const std::string& sql, Args&&... args)`,内部创建 `Sqlite3_stmt` 并按位置绑定参数,彻底避免 SQL 注入。
- 支持绑定类型:`int` / `int64_t` / `double` / `std::string` / `const char*` / `nullptr` / `std::vector<unsigned char>`。
---
## P1 — 高危问题
### P1-1: `stmt()` 参数应为 `const std::string&`
- **位置**: `Sqlite3.hpp` L91
- **当前状态**: ✅ 已修复(2026-08-06)
- **修复**: `stmt()` 参数改为 `const std::string& sql`,现在可以传入字符串字面量和临时对象:
```cpp
auto s = db.stmt("SELECT 1"); // 现在可以编译
```
---
### P1-2: `bind_int` 只支持 32 位 int
- **位置**: `Sqlite3.hpp` L40-L58
- **当前状态**: ✅ 已修复(2026-08-06)
- **修复**: 新增 `bind_int64(int idx, int64_t data)` 使用 `sqlite3_bind_int64`,参数化 `exec` 模板对 `int64_t` / `long` / `long long` 自动路由到该绑定。
---
### P1-3: 缺少 `bind_null`、`bind_double`、`bind_blob` 等接口
- **位置**: `Sqlite3.hpp` L40-L86
- **当前状态**: ✅ 已修复(2026-08-06)
- **修复**: 补充了 `bind_int64`、`bind_double`、`bind_text`、`bind_blob`、`bind_null`,参数化 `exec` 模板自动根据类型分发到对应绑定函数。
---
### P1-4: 异常信息丢失 SQLite 错误详情
- **位置**: `Sqlite3.hpp` L9-L15、L84-L90
- **当前状态**: ✅ 已修复(2026-08-06)
- **修复**: 构造/打开数据库的异常现在把 `sqlite3_errmsg()` 内容拼入 `std::runtime_error`;绑定失败也保留了具体错误描述,便于调用者定位问题。
---
## P2 — 中危问题
### P2-1: `Sqlite3_stmt` 成员 `stmt` 未显式初始化
- **位置**: `Sqlite3.hpp` L58-L59
- **当前状态**: ✅ 已修复(2026-08-06)
- **修复**: `Sqlite3` 和 `Sqlite3_stmt` 的成员指针均显式初始化为 `nullptr`,配合移动语义中的置空操作,避免野指针和重复释放。
---
### P2-2: `read_text` 返回指针生命周期未文档化
- **位置**: `Sqlite3.hpp` L103-L107
- **当前状态**: ✅ 已修复(2026-08-06)
- **修复**: 在 `read_text` 上方增加注释,明确说明返回指针指向 statement 内部缓冲区,statement finalize 或 step 后可能失效,长期保存需复制到 `std::string`。
---
### P2-3: 缺少类型安全读取接口
- **位置**: `Sqlite3.hpp` L96-L107
- **当前状态**: ✅ 已修复(2026-08-06)
- **修复**: 补充了 `read_int64`、`read_double`,保留 `read_int` 和 `read_text`。`column_type` / `column_count` 仍未暴露,如需可继续补充。
---
### P2-4: `exec` 中 `errMsg` 未初始化且存在危险注释
- **位置**: `Sqlite3.hpp` L78-L85
- **当前状态**: ✅ 已修复(2026-08-06)
- **修复**: `errMsg` 显式初始化为 `nullptr`,并删除危险注释 `//sqlite3_close(db)`,避免未来被误恢复导致 SQL 执行错误时关闭数据库连接。
---
### P2-5: `Makefile` install 目标文案错误
- **位置**: `libsqlite3/Makefile` L27
- **当前状态**: ❌ 未修复
- **详情**:
```makefile
install:
@echo "install UniServe_libth..."
```
文案是 `libth`,明显是从 `libth/Makefile` 复制过来的,应改为 `UniServe_libsqlite3`。
- **修复建议**: 修正 echo 文本。
---
### P2-6: `Makefile` 与 `_set.sh` 完全无构建/测试步骤
- **位置**: `libsqlite3/Makefile`、`libsqlite3/_set.sh`
- **当前状态**: ❌ 未修复
- **详情**:
- 全部编译规则被注释
- `_set.sh` 只执行 `make install`
- 头文件库不编译 `.so` 尚可理解,但连编译时语法检查都没有
- **修复建议**: 至少保留一个 `check` 或 `test` 目标,编译一个最小测试文件验证头文件能正常包含和实例化。
---
## 已有安全机制(正确实现)
1. **RAII 思路正确** — 用析构函数自动 `sqlite3_finalize` / `sqlite3_close`
2. **`SQLITE_TRANSIENT` 使用正确** — `bind_text` 传 `-1` 和 `SQLITE_TRANSIENT`,让 SQLite 复制数据,避免悬垂指针
3. **`step()` 返回值语义清晰** — 返回 `bool` 表示是否还有数据行
4. **异常路径不吞异常** — 构造/绑定失败直接抛异常,不返回错误码让调用者忽略
5. **头文件仅暴露最小接口** — 没有暴露原始 `sqlite3*`/`sqlite3_stmt*`,封装完整
---
## 修复路线图
### 立即(24小时)
- [x] **P0-1**: 为 `Sqlite3` 和 `Sqlite3_stmt` 显式 delete 拷贝、实现移动构造/赋值
- [x] **P0-2**: `~Sqlite3()` 改用 `sqlite3_close_v2` 或检查返回值
- [x] **P0-3**: 为 `exec` 增加参数化绑定,限制字符串版本仅用于硬编码 SQL
### 短期(72小时)
- [x] **P1-1**: `stmt()` 参数改为 `const std::string&`
- [x] **P1-2**: 增加 `bind_int64`
- [x] **P1-3**: 补充 `bind_null`、`bind_double`、`bind_blob`
- [x] **P1-4**: 异常信息携带 `sqlite3_errmsg` 和错误码
### 中期
- [x] **P2-1**: 成员指针初始化为 `nullptr`
- [x] **P2-2**: `read_text` 生命周期文档化
- [x] **P2-3**: 补充读取接口(int64/double)
- [x] **P2-4**: 初始化 `errMsg` 并删除危险注释
- [ ] **P2-5**: 修正 `Makefile` echo 文案
- [ ] **P2-6**: 增加最小编译检查目标
---
## 审计方法
1. 通读 `libsqlite3/inc/us/Sqlite3.hpp` 全部 98 行实现
2. 审查 `libsqlite3/Makefile`、`_set.sh`、`README.md` 的安装与构建流程
3. 使用 `grep` 全项目查找 `Sqlite3.hpp`、`Sqlite3`、`Sqlite3_stmt`、`libus_sqlite3` 的引用情况
4. 对照 C++ 资源管理规则检查拷贝/移动/析构语义
5. 对照 SQLite3 C API 文档检查 API 使用正确性(绑定、关闭、finalize)
6. 评估接口完整性(参数化绑定、类型绑定、读取接口)