Fanjun Kong 72f2e1a897 just for testing
Signed-off-by: Fanjun Kong <kongfanjun@iscas.ac.cn>
2026-01-30 16:04:18 +08:00
2026-01-30 16:04:18 +08:00
2026-01-30 16:04:18 +08:00
2026-01-30 16:04:18 +08:00
2026-01-30 16:04:18 +08:00
2026-01-30 16:04:18 +08:00
2026-01-30 16:04:18 +08:00
2026-01-30 16:04:18 +08:00
2026-01-30 16:04:18 +08:00

RPM 包安全特性大规模扫描系统

架构概览

[包列表获取] → [并行下载] → [ELF提取] → [安全扫描] → [结果入库] → [报告生成]

快速开始

1. 安装依赖

# RHEL/Fedora/OpenEuler
sudo dnf install -y parallel checksec sqlite

# 验证
checksec --version
parallel --version

2. 初始化

chmod +x *.sh
./01_init.sh

3. 执行扫描(无锁设计)

# 全量扫描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. 导入数据库

python3 04_import_results.py scan_workspace/results/scan_results.db scan_workspace/results

为什么分两步?

  • 避免多进程并发写入 SQLite 导致的数据库锁定问题
  • 扫描阶段只写文件,完全无锁
  • 导入阶段串行处理,安全可靠

5. 生成报告

# 生成统计报告和 CSV
python3 03_report.py scan_workspace/results/scan_results.db --export-csv report.csv

核心设计

1. 预筛选策略

问题: dnf repoquery 默认包含 noarch 包,浪费资源

解决方案:

# 只查询 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 导致重复扫描

解决方案:

# 使用 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 不支持多进程并发写入

解决方案: 扫描与入库分离

# 扫描阶段:多进程并发写文件(无锁)
./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. 增量扫描

文件记录扫描状态,跳过已扫描的包:

# 使用 flock 检查是否已扫描
if grep -qx "$pkg_name" "$status_file"; then
    echo "跳过已扫描: $pkg_name"
    exit 0
fi

6. 错误处理

  • 下载超时: timeout 300s
  • 解压失败: 记录状态到数据库
  • checksec 超时: 单包超时 60s
  • 重试机制: parallel --retries 2

数据库 Schema

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:

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 找不到

# 从源码安装
git clone https://github.com/slimm609/checksec.sh
sudo cp checksec /usr/local/bin/

2. 并行任务失败

查看日志:

tail -f scan_workspace/error.log
cat scan_workspace/parallel.log | grep -v "^0"

3. 数据库锁定(已解决)

旧版本问题

Error: in prepare, database is locked (5)

新版本解决方案

  • 采用无锁设计,扫描阶段不写数据库
  • 使用文件锁 flock 代替数据库锁
  • 扫描完成后批量导入

如果仍然遇到数据库锁定:

# 删除锁文件
rm scan_workspace/results/scanned.txt.lock

# 或使用新的工作目录重新扫描
WORK_DIR="./scan_workspace_new" ./02_scan.sh

扩展功能

1. 分布式扫描

# 机器 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. 定时增量扫描

# 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. 增量更新: 定期运行获取新包

参考资料

Description
No description provided
Readme 42 KiB
Languages
Shell 51.4%
Python 48.6%