OpenSSL 1.1 Fuzzing 实战指南:用 LibFuzzer 与 AFL 对 SRS 内置 OpenSSL 进行健壮性测试
2026/9/10 3:07:54 网站建设 项目流程

OpenSSL 1.1 Fuzzing 实战指南:用 LibFuzzer 与 AFL 对 SRS 内置 OpenSSL 进行健壮性测试

【免费下载链接】srsSRS is a simple, high-performance, AI-driven real-time media server supporting RTMP, WebRTC, HLS, HTTP-FLV, HTTP-TS, SRT, MPEG-DASH, and GB28181, with codec support for H.264, H.265, AV1, VP9, AAC, Opus, and G.711.项目地址: https://gitcode.com/GitHub_Trending/sr/srs

SRS 媒体服务器在其 trunk/3rdparty/openssl-1.1-fit 目录中内置了裁剪过的 OpenSSL 1.1 源码,用于支撑 HTTPS、WebRTC/DTLS 等加密链路。本文以该内置 OpenSSL 源码树中的 fuzz/README.md 为核心,完整讲解如何用 LLVM LibFuzzer 与 AFL 对 OpenSSL 的 TLS 握手、ASN.1 解析、大整数运算等高风险入口做模糊测试,读完你将掌握从编译环境搭建、Fuzzer 构建、语料(corpus)管理到崩溃复现的完整工作流。

为什么需要 Fuzz OpenSSL

密码学库是安全攻防的第一线:TLS 握手解析、ASN.1/DER 编码、X.509 证书解析、BIGNUM 大整数运算都是历史上漏洞高发的区域。OpenSSL 官方正是通过在源码树内提供fuzz/目录,把模糊测试作为日常开发的一部分。在本仓库的 SRS 场景下,OpenSSL 被用于 RTMP over HTTPS、WebRTC DTLS 等实时媒体链路,一旦解析代码存在内存破坏类缺陷,后果会直接波及媒体服务的可用性与安全性。

OpenSSL 1.1 的 fuzz 体系同时支持两条主流工具链:

工具链特点触发配置开关
LibFuzzer(clang 自带)进程内覆盖率引导,速度极快enable-fuzz-libfuzzer
AFL(afl-clang-fast)独立进程 + 持久模式,生态成熟enable-fuzz-afl

两种方式共享同一套fuzz/*.c模糊测试目标与语料目录,只是驱动与编译插桩不同。下面从零开始依次演示。

准备 LibFuzzer 编译环境

LibFuzzer 依赖较新的 clang。官方文档给出的路径是从零搭建:

  1. 安装 git 并克隆 Chromium 维护的 clang 构建脚本,执行更新脚本拉取预编译的 clang:
$ sudo apt-get install git $ mkdir git-work $ git clone https://chromium.googlesource.com/chromium/src/tools/clang $ clang/scripts/update.py

提示:可以定期git pull并重跑update.py以保持 clang 版本较新,老版本可能无法正常工作。

  1. 把编译好的 clang 加入PATH
$ PATH=~/third_party/llvm-build/Release+Asserts/bin/:$PATH
  1. 获取并编译 LibFuzzer 运行时库(以 svn 方式拉取 compiler-rt 中的 fuzzer 源码,然后手工编译成静态库):
$ cd $ sudo apt-get install subversion $ mkdir svn-work $ cd svn-work $ svn co https://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/fuzzer Fuzzer $ cd Fuzzer $ clang++ -c -g -O2 -std=c++11 *.cpp $ ar r libFuzzer.a *.o $ ranlib libFuzzer.a

这一步得到的libFuzzer.a将在下一步配置 OpenSSL 时通过--with-fuzzer-lib传入。

配置并用 LibFuzzer 构建 OpenSSL

进入内置的 OpenSSL 源码树,用 clang 作为编译器,开启 fuzz 模式进行配置:

$ CC=clang ./config enable-fuzz-libfuzzer \ --with-fuzzer-include=../../svn-work/Fuzzer \ --with-fuzzer-lib=../../svn-work/Fuzzer/libFuzzer.a \ -DPEDANTIC enable-asan enable-ubsan no-shared \ -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION \ -fsanitize-coverage=trace-pc-guard,indirect-calls,trace-cmp \ enable-ec_nistp_64_gcc_128 -fno-sanitize=alignment enable-tls1_3 \ enable-weak-ssl-ciphers enable-rc5 enable-md2 \ enable-ssl3 enable-ssl3-method enable-nextprotoneg \ --debug

逐项拆解这一长串参数的作用:

参数作用
enable-fuzz-libfuzzer启用 LibFuzzer 驱动(对应 Configure 中的 fuzz 配置逻辑)
--with-fuzzer-include指向上面编译的 Fuzzer 头文件目录
--with-fuzzer-lib指向libFuzzer.a静态库
enable-asan enable-ubsan开启 AddressSanitizer 与 UndefinedBehaviorSanitizer,用于捕获内存越界、未定义行为
no-shared静态编译,避免动态库符号与插桩冲突
-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION关键宏:让 client/server fuzzer 使用可预测的随机数,保证语料覆盖稳定(详见后文"随机数与可复现性")
-fsanitize-coverage=trace-pc-guard,indirect-calls,trace-cmp覆盖率插桩类型,LibFuzzer 据此引导变异
enable-tls1_3 enable-weak-ssl-ciphers enable-rc5 enable-md2 enable-ssl3 enable-ssl3-method enable-nextprotoneg打开更多协议与弱密码族,扩大可 fuzz 的代码面
--debug开启调试符号与断言

接着编译,并指定链接器为clang++(因为 LibFuzzer 运行时是 C++):

$ sudo apt-get install make $ LDCMD=clang++ make -j

启动 Fuzzer 并处理崩溃

编译完成后,fuzz/目录下会生成若干可执行文件(如asn1x509clientserverbignumbndivcmsconfcrlctasn1parse等),每个对应一个模糊测试目标。官方推荐通过辅助脚本启动:

$ fuzz/helper.py $FUZZER

其中$FUZZERfuzz/下的某个可执行文件名。以仓库中提供的 fuzz/helper.py 源码来看,它会自动完成三件事:

  1. fuzz/corpora/$FUZZER/下创建(或复用)语料目录;
  2. 创建fuzz/corpora/$FUZZER-crash/崩溃目录;
  3. 若存在fuzz/corpora/$FUZZER-seed/种子目录则一并加入输入集合,最后以-artifact_prefix=$FUZZER-crash/参数启动 fuzzer。

因此一旦发生崩溃,对应的输入文件会自动落入:

fuzz/corpora/$FUZZER-crash/

这个目录中的每个文件就是一条可复现崩溃的极小输入,可以直接用于后续回归测试与缺陷定位。

使用 AFL 进行模糊测试

如果你偏好 AFL 工作流,配置同样简洁:

$ sudo apt-get install afl-clang $ CC=afl-clang-fast ./config enable-fuzz-afl no-shared -DPEDANTIC \ enable-tls1_3 enable-weak-ssl-ciphers enable-rc5 enable-md2 \ enable-ssl3 enable-ssl3-method enable-nextprotoneg \ enable-ec_nistp_64_gcc_128 -fno-sanitize=alignment \ --debug $ make

这里不再需要-fsanitize-coverage,插桩由afl-clang-fast完成;enable-fuzz-afl会让 fuzz/driver.c 编译为 AFL 专用入口:主循环调用__AFL_LOOP(10000)走持久化模式,从 stdin 读取不超过 64KB(BUF_SIZE 65536)的输入并喂给FuzzerTestOneInput,从而避免反复 fork 的开销。此外文档还提示以下 sanitizer 可按需叠加:

enable-asan, enable-ubsan, enable-msan

运行某个 fuzzer 的命令为:

$ afl-fuzz -i fuzz/corpora/$FUZZER -o fuzz/corpora/$FUZZER/out fuzz/$FUZZER

-i指定输入语料目录,-o指定输出目录(AFL 会把发现的新路径、崩溃、挂起分别放入out/queueout/crashesout/hangs)。

复现 fuzzer 发现的缺陷

当 fuzzer 产出崩溃文件后,需要用*-test二进制做最小化复现与调试。每个 fuzzer 目标都配套生成一个$FUZZER-test可执行程序,其主体编译自 fuzz/test-corpus.c:它把命令行给出的每个文件(也支持目录递归)逐条读入并调用FuzzerTestOneInput,任何崩溃都会如实暴露。

复现命令:

$ fuzz/$FUZZER-test $file

需要注意的要点:

  • *-test二进制不需要用 fuzz 配置编译,不必设置CC、不必传enable-fuzz-*-fsanitize-coverage
  • 但部分选项仍然建议保留:例如enable-asan/enable-ubsan能精准指示崩溃发生的时机与位置;
  • clientserver这两个 TLS fuzzer,复现时通常仍需要-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION,以复现当初生成该输入时的随机数序列。

随机数与可复现性:FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION

这是 OpenSSL fuzz 体系中最精妙的设计之一。TLS 握手过程中 client 和 server 会生成随机数参与密钥协商,这会导致一个副作用:语料覆盖随随机数变化而漂移,即使代码一行未改,每次运行 fuzzer 的覆盖曲线也在变,甚至整个测试套件的覆盖率统计都会随 commit 抖动。

为了让覆盖结果可复现、可比较,clientserverfuzzer 在定义了FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION时改用可预测的伪随机数。其实现就在 fuzz/rand.inc:

static int fuzz_bytes(unsigned char *buf, int num) { unsigned char val = 1; while (--num >= 0) *buf++ = val++; return 1; }

它通过FuzzerSetRand()RAND_set_rand_method()注册一个固定填充序列的RAND_METHOD,从而保证:client fuzzer 每次生成完全相同的 ClientHello,对应的 server 响应也可以离线预生成并固化进语料。同时文档强调:这个宏不会绕过任何哈希校验——语料中存放的是与固定随机数配套的正确哈希值,因此测试依然具备真实性。

另一个可复现性细节同样藏在源码里:client.cserver.c都重定义了time()函数,返回固定值FUZZTIME(1485898104),避免证书有效期、时间戳校验影响握手路径的可复现性(Windows 平台因链接机制差异不保证生效,仅会导致覆盖略有不同)。

覆盖率漂移与语料维护

正因为语料覆盖依赖 client/server 的默认行为(默认发送的 ClientHello 内容、默认的密码套件顺序等),一旦上游修改了默认行为,语料覆盖就会变化,此时必须同步更新语料。这是维护者需要持续跟进的工作。

官方在更新语料时,会针对多种配置分别生成 client/server 语料,再用 libfuzzer 的 merge 能力将各配置的增量覆盖合并到最小集合:

  • 上文文档记录的默认配置组合;
  • 去掉enable-ec_nistp_64_gcc_128且不带--debug的配置;
  • no-asm配置(关闭汇编实现,覆盖 C 代码路径);
  • 32 位构建;
  • 默认配置外加生成 fuzzer 所需的必要选项。

这种"多配置生成 + merge 归并"的语料管理方式,可以理解为对同一组模糊目标做交叉验证,显著提升覆盖的完备性。

仓库中的各 Fuzzer 目标源码解析

fuzz/目录下的每个.c文件都实现统一的FuzzerInitialize/FuzzerTestOneInput/FuzzerCleanup接口(声明见 fuzz/fuzzer.h),按测试面可归为三类:

TLS 握手类:client 与 server

  • fuzz/client.c:创建SSLv23_method()上下文,设置ALL:eNULL:@SECLEVEL=0密码套件、SNI=localhost,把模糊输入直接写入内存 BIO 作为服务端发来的握手数据,驱动SSL_do_handshake,握手成功后继续SSL_read消费应用数据直到 EOF 或出错。
  • fuzz/server.c:更复杂一些。它内嵌了内置的 RSA 证书/私钥、ECDSA 与 DSA 的 PEM 证书/私钥(以 DER/PEM 字节数组形式硬编码在源码中),加载进SSL_CTX后把模糊输入作为客户端握手指向的数据。输入的最后一个字节被当作选项位opt & 0x01时先尝试SSL_read_early_data处理 TLS 1.3 early data,再走完整握手。这使单个 fuzzer 能同时覆盖普通握手与 0-RTT 两条路径。

解析类:asn1、x509、cms、crl、conf、ct、asn1parse

  • fuzz/asn1.c:维护一张巨大的ASN1_ITEM表(涵盖X509PKCS7/PKCS12OCSP全系、RSA/DSA/EC参数、INT64/UINT64等数百种结构),对同一份输入逐一遍历,依次执行ASN1_item_d2iASN1_item_printASN1_item_i2dASN1_item_free的完整生命周期,并额外用d2i_*/i2d_*直接测试 DH、DSA、RSA、EC、SSL_SESSION 等类型的编解码。
  • fuzz/x509.c:d2i_X509解析后调用X509_print(会连带加载并打印公钥与扩展)、X509_issuer_and_serial_hash、再i2d_X509回写,覆盖证书解析、打印、哈希与重编码路径。
  • 其余如cms.ccrl.cconf.cct.casn1parse.c分别针对 CMS 消息、CRL 吊销列表、OpenSSL 配置文件、证书透明度、ASN.1 通用解析器等入口,机制一致。

大整数运算类:bignum 与 bndiv

  • fuzz/bignum.c:用输入前两个字节切分三段长度、第三个字节的低位决定符号,构造出三个大整数b1b2b3,然后对比BN_mod_exp(优化实现)与BN_mod_exp_simple(朴素实现)的结果是否一致——这是一种差分测试:两个实现结果不一致即视为失败,用于捕获模幂运算的隐藏缺陷。
  • fuzz/bndiv.c:对输入切分为被除数与除数,验证除法恒等式b3*b2 + b4 == b1以及商/余数的符号规则(sign(d) == sign(a)0 <= r <= b),并限制输入上限 256KB 以避免超时。当被除数为 0 时校验商与余数均为 0。

三种驱动的协作关系小结

最后用一张逻辑图梳理 OpenSSL fuzz 体系的运行架构(不需要截图,理解即可):

模糊输入 ──▶ FuzzerTestOneInput(buf, len) │ ├─ LibFuzzer 模式(driver.c):LLVMFuzzerTestOneInput 包装,覆盖率引导 + 进程内变异 ├─ AFL 模式(driver.c):__AFL_LOOP 持久循环 + stdin 喂入 └─ test 模式(test-corpus.c):逐文件回放,用于复现 fuzz/corpora/$FUZZER-crash/
  • 同一份模糊目标代码fuzz/*.c)通过 fuzz/driver.c 中的条件编译适配三种运行方式:定义OPENSSL_NO_FUZZ_LIBFUZZER与否分别走 LibFuzzer 入口或 AFL 入口;
  • 语料与崩溃产物统一落在fuzz/corpora/下,helper.py负责目录编排与参数注入;
  • 确定性由FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION+ 固定随机数 + 固定时间共同保证,覆盖数据才具有跨 commit 的可比性。

对 SRS 这类把 OpenSSL 1.1 作为第三方依赖内置的媒体服务器项目而言,理解这套 fuzz 工作流的价值在于:你可以随时在内置的 fuzz/README.md 指导下,针对自己实际启用的协议组合(TLS、DTLS 相关配置)重新生成语料、运行 fuzzer,并将发现的崩溃回灌到*-test复现与修复,从而在把 OpenSSL 引入生产媒体服务之前,先把它"摩擦"得足够健壮。

【免费下载链接】srsSRS is a simple, high-performance, AI-driven real-time media server supporting RTMP, WebRTC, HLS, HTTP-FLV, HTTP-TS, SRT, MPEG-DASH, and GB28181, with codec support for H.264, H.265, AV1, VP9, AAC, Opus, and G.711.项目地址: https://gitcode.com/GitHub_Trending/sr/srs

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询