# RPM 包安全特性大规模扫描系统 ## 架构概览 ``` [包列表获取] → [并行下载] → [ELF提取] → [安全扫描] → [结果入库] → [报告生成] ``` ## 快速开始 ### 1. 安装依赖 ```bash # RHEL/Fedora/OpenEuler sudo dnf install -y parallel checksec sqlite # 验证 checksec --version parallel --version ``` ### 2. 初始化 ```bash chmod +x *.sh ./01_init.sh ``` ### 3. 执行扫描(无锁设计) ```bash # 全量扫描(8 并发) ./02_scan.sh # 自定义并发数 PARALLEL_JOBS=16 ./02_scan.sh ``` **输出文件**: - `results/scanned.txt` - 已扫描包列表 - `results/success.txt` - 扫描成功的包 - `results/failed.txt` - 扫描失败的包 - `results/no_binary.txt` - 无二进制文件的包 - `results/*.json` - checksec 原始结果 ### 4. 导入数据库 ```bash python3 04_import_results.py scan_workspace/results/scan_results.db scan_workspace/results ``` **为什么分两步?** - 避免多进程并发写入 SQLite 导致的数据库锁定问题 - 扫描阶段只写文件,完全无锁 - 导入阶段串行处理,安全可靠 ### 5. 生成报告 ```bash # 生成统计报告和 CSV python3 03_report.py scan_workspace/results/scan_results.db --export-csv report.csv ``` ## 核心设计 ### 1. 预筛选策略 **问题**: `dnf repoquery` 默认包含 noarch 包,浪费资源 **解决方案**: ```bash # 只查询 x86_64 架构(注意格式字符串末尾的 \n) dnf repoquery --arch x86_64 --qf '%{name}-%{version}-%{release}.%{arch}\n' # 进一步过滤:检查是否包含目标路径 dnf repoquery --arch x86_64 --list PACKAGE | grep -E '^/(usr/)?(bin|sbin|lib64)' ``` ### 2. 软链接去重 **问题**: `/bin` → `/usr/bin` 导致重复扫描 **解决方案**: ```bash # 使用 inode 去重 find . -type f -exec stat -c '%i %n' {} \; | sort -u -k1,1 | cut -d' ' -f2- ``` ### 3. 并行优化 **三级并行**: - **包级**: GNU Parallel 并行处理包 (`-j 8`) - **下载级**: DNF 并行下载配置 - **扫描级**: checksec 批量调用 ### 4. 无锁设计(重要) **问题**: SQLite 不支持多进程并发写入 **解决方案**: 扫描与入库分离 ```bash # 扫描阶段:多进程并发写文件(无锁) ./02_scan.sh → results/scanned.txt → results/success.txt → results/failed.txt → results/*.json # 导入阶段:单进程串行入库(安全) python3 04_import_results.py scan_results.db results/ ``` **关键改进**: - ✅ 使用 `flock` 文件锁代替数据库查询 - ✅ 状态文件代替数据库状态查询 - ✅ JSON 文件存储中间结果 - ✅ 批量导入代替逐条写入 ### 5. 增量扫描 文件记录扫描状态,跳过已扫描的包: ```bash # 使用 flock 检查是否已扫描 if grep -qx "$pkg_name" "$status_file"; then echo "跳过已扫描: $pkg_name" exit 0 fi ``` ### 6. 错误处理 - 下载超时: `timeout 300s` - 解压失败: 记录状态到数据库 - checksec 超时: 单包超时 60s - 重试机制: `parallel --retries 2` ## 数据库 Schema ```sql packages (id, name, version, status, scan_time) ↓ binaries (id, package_id, file_path, inode) ↓ security_checks (id, binary_id, pie, nx, canary, fortify, relro) ``` ## 配置文件 编辑 `config.sh`: ```bash PARALLEL_JOBS=8 # 并发数 REPO_ARCH="x86_64" # 架构 DOWNLOAD_TIMEOUT=300 # 下载超时 CHECKSEC_TIMEOUT=60 # 扫描超时 ``` ## 输出文件 ``` scan_workspace/ ├── rpm_cache/ # RPM 缓存 ├── extracted/ # 临时解压目录 ├── results/ │ ├── scan_results.db # SQLite 数据库 │ ├── *.json # checksec 原始结果 │ └── report.csv # CSV 报告 ├── scan.log # 运行日志 ├── error.log # 错误日志 └── parallel.log # GNU Parallel 日志 ``` ## 性能估算 - 单包平均耗时: 10-30s (下载 + 解压 + 扫描) - 8 并发处理 1000 个包: ~30-60 分钟 - 磁盘空间: 每包 10-100MB (临时) ## 故障排查 ### 1. checksec 找不到 ```bash # 从源码安装 git clone https://github.com/slimm609/checksec.sh sudo cp checksec /usr/local/bin/ ``` ### 2. 并行任务失败 查看日志: ```bash tail -f scan_workspace/error.log cat scan_workspace/parallel.log | grep -v "^0" ``` ### 3. 数据库锁定(已解决) **旧版本问题**: ``` Error: in prepare, database is locked (5) ``` **新版本解决方案**: - ✅ 采用无锁设计,扫描阶段不写数据库 - ✅ 使用文件锁 `flock` 代替数据库锁 - ✅ 扫描完成后批量导入 如果仍然遇到数据库锁定: ```bash # 删除锁文件 rm scan_workspace/results/scanned.txt.lock # 或使用新的工作目录重新扫描 WORK_DIR="./scan_workspace_new" ./02_scan.sh ``` ## 扩展功能 ### 1. 分布式扫描 ```bash # 机器 A: 扫描前 500 个包 head -500 packages.list | parallel -j 8 ./scan_package.sh # 机器 B: 扫描后 500 个包 tail -500 packages.list | parallel -j 8 ./scan_package.sh # 合并结果文件(不需要合并数据库) cat machine_a/results/*.json > all_results.json cat machine_a/results/success.txt machine_b/results/success.txt > all_success.txt # 导入到统一数据库 python3 04_import_results.py merged.db results/ ``` **优势**: - 每台机器独立工作,无数据库冲突 - 只需合并文本文件和 JSON - 最后统一导入数据库 ### 2. 定时增量扫描 ```bash # crontab 0 2 * * * cd /path/to/scan && ./01_init.sh && ./02_scan.sh && python3 04_import_results.py scan_results.db results/ ``` ### 3. Web Dashboard 使用 Grafana + SQLite 数据源可视化结果 ## 潜在问题与解决方案 | 问题 | 影响 | 解决方案 | |------|------|----------| | noarch 包误扫描 | 浪费资源 | `--arch x86_64` 预筛选 | | 软链接重复 | 结果重复 | inode 去重 | | 内核模块误报 | PIE 检查不适用 | 排除 `*.ko` | | 网络不稳定 | 下载失败 | 超时 + 重试 | | 磁盘空间不足 | 解压失败 | 及时清理临时文件 | | checksec 慢 | 扫描耗时长 | 批量调用 + 并行 | ## 最佳实践 1. **首次运行**: 先用小样本测试 (`head -10 packages.list`) 2. **并发调优**: 根据 CPU 核心数和网络带宽调整 `PARALLEL_JOBS` 3. **磁盘管理**: 定期清理 `rpm_cache/` 和 `extracted/` 4. **日志监控**: 实时查看 `tail -f scan.log` 5. **增量更新**: 定期运行获取新包 ## 参考资料 - [checksec 文档](https://github.com/slimm609/checksec.sh) - [GNU Parallel 教程](https://www.gnu.org/software/parallel/parallel_tutorial.html) - [RPM 包管理](https://rpm.org/)