Files
blackbird/docs/security/libsqlite3_SECURITY_AUDIT_20260806.md
T
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

8.4 KiB
Raw Blame History

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 风险。

    修复前代码问题:

    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,现在可以传入字符串字面量和临时对象:

    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

  • 当前状态: ❌ 未修复

  • 详情:

    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小时)

  • P0-1: 为 Sqlite3 和 Sqlite3_stmt 显式 delete 拷贝、实现移动构造/赋值
  • P0-2: ~Sqlite3() 改用 sqlite3_close_v2 或检查返回值
  • P0-3: 为 exec 增加参数化绑定,限制字符串版本仅用于硬编码 SQL

短期(72小时)

  • P1-1: stmt() 参数改为 const std::string&
  • P1-2: 增加 bind_int64
  • P1-3: 补充 bind_null、bind_double、bind_blob
  • P1-4: 异常信息携带 sqlite3_errmsg 和错误码

中期

  • P2-1: 成员指针初始化为 nullptr
  • P2-2: read_text 生命周期文档化
  • P2-3: 补充读取接口(int64/double)
  • 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. 评估接口完整性(参数化绑定、类型绑定、读取接口)