- P0-3 参数化 exec 已修复 - P1-2/P1-3/P1-4 绑定类型与异常信息已修复 - P2-2/P2-3 读取接口与文档已修复 - 修正 P2 问题数量统计(6 项而非 5 项)
219 lines
8.4 KiB
Markdown
219 lines
8.4 KiB
Markdown
# 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. 评估接口完整性(参数化绑定、类型绑定、读取接口)
|