ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

H2O 模糊测试指南:基于 LibFuzzer 的 HTTP/1、HTTP/2 与 HTTP/3 驱动构建与语料库维护

H2O 模糊测试指南:基于 LibFuzzer 的 HTTP/1、HTTP/2 与 HTTP/3 驱动构建与语料库维护 H2O 模糊测试指南基于 LibFuzzer 的 HTTP/1、HTTP/2 与 HTTP/3 驱动构建与语料库维护【免费下载链接】h2oH2O - the optimized HTTP/1, HTTP/2, HTTP/3 server项目地址: https://gitcode.com/gh_mirrors/h2/h2o本篇技术指南围绕 H2O 仓库中的 fuzz/README.md 展开介绍如何利用 LLVM LibFuzzer 对 H2O 这一同时支持 HTTP/1、HTTP/2 与 HTTP/3 的服务器进行持续模糊测试。你将掌握 fuzz 目录的组织结构、fuzz driver 的 CMake 构建方法、HTTP/1 与 HTTP/2 驱动的启动参数、H2O_FUZZER_CLIENT_TIMEOUT等运行时环境变量以及使用-merge提交新种子与定期重最小化语料库的完整工作流并能对照仓库源码理解 fuzzer 的内部数据通路。一、fuzz 目录一览驱动、语料库与字典fuzz/ 目录存放了 H2O 模糊测试的全部代码与测试数据包括Fuzz driver 源码fuzz/driver.ccHTTP/1 与 HTTP/2 共用、fuzz/driver_h3.ccHTTP/3、fuzz/driver_url.ccURL 解析、fuzz/driver_common.cc 与 fuzz/driver_common.h公共辅助逻辑QUIC 模拟层fuzz/quicly_mock.c 与 fuzz/quicly_mock.h用于 HTTP/3 驱动替换部分 quicly 实现语料库http1-corpus、http2-corpus、http3-corpus、url-corpus以及 HTTP/3 测试输入http3-test-inputs输入字典fuzz/http.dict数据采集补丁fuzz/gather-data.patch用于从单元测试运行中抓取真实请求数据辅助工具fuzz/h3_header_generator.c生成 HTTP/3 头部测试输入。从 CMakeLists.txt 可以看出BUILD_FUZZER开关下共生成四个可执行 driverh2o-fuzzer-http1、h2o-fuzzer-http2、h2o-fuzzer-http3与h2o-fuzzer-url。前两者由同一份 fuzz/driver.cc 源码编译仅通过-DHTTP1与-DHTTP2编译宏区分见 CMakeLists.txt L969-L970并依赖#error预处理指令保证两者互斥见 fuzz/driver.cc。二、构建 Fuzz driver要构建模糊测试驱动需要在 cmake 配置阶段传入-DBUILD_FUZZERON。该开关会启用对libh2o的插桩并生成上述四个 driver 程序cmake -DBUILD_FUZZERON /path/to/h2o cmake --build . --target h2o-fuzzer-http1 h2o-fuzzer-http2 h2o-fuzzer-http3 h2o-fuzzer-url构建时有几个硬性前提全部可以在 CMakeLists.txt 中找到依据必须使用支持 LibFuzzer 的 LLVM Clang。CMake 会直接检查CMAKE_CXX_COMPILER_ID若非 Clang 则报错The fuzzer needs clang as a compiler终止构建L961-L963Clang 5.0 之前的老版本没有内置 libFuzzer此时 CMake 会通过 misc/build_libFuzzer.sh 拉取并编译libFuzzer.a同时显式追加-fsanitizeaddress -fsanitize-coverageedge,indirect-calls,8bit-counters等插桩与 ASan 选项L988-L997Clang 7.0 之后则直接使用-fsanitizefuzzer,addressL1001-L1010面向 OSS-Fuzz 环境时可再传-DOSS_FUZZON此时链接FuzzingEngine并统一追加-fno-omit-frame-pointerL975-L979。各 driver 与 libh2o 的链接关系见 CMakeLists.txt目标链接对象h2o-fuzzer-http1/h2o-fuzzer-http2libh2o-evlooph2o-fuzzer-http3直接链接源码与quicly_mock注释明确说明它不使用 libh2o以便用 mock 替换 quiclyh2o-fuzzer-urllibh2o-evlooph3-header-generatorlibh2o-evloop语料辅助生成工具三、运行 fuzzerHTTP/1 与 HTTP/2 基础命令文档给出的两个基础运行示例可以直接在构建产物目录下执行建议根据执行环境调整 fuzzer 选项HTTP/1ASAN_OPTIONSdetect_leaks0 ./h2o-fuzzer-http1 -max_len$((16 * 1024)) -dictfuzz/http.dict fuzz/http1-corpusHTTP/2ASAN_OPTIONSdetect_leaks0 ./h2o-fuzzer-http2 -max_len$((16 * 1024)) -dictfuzz/http.dict fuzz/http2-corpus命令中的参数含义-max_len$((16 * 1024))限制单条输入的最大长度为 16 KiB防止单个输入导致运行时间失控-dictfuzz/http.dict加载 HTTP 领域字典引导变异器优先尝试协议相关的关键 tokenfuzz/http1-corpus或http2-corpus语料库目录fuzzer 会读取其中种子并在运行期间持续扩充新增的用例默认写入该目录ASAN_OPTIONSdetect_leaks0关闭 LeakSanitizer因为 driver 采用多线程架构线程退出时点不保证所有堆对象可被精确回收开启 leak 检测会产生大量误报。除上述两个驱动外同一套流程也适用于 HTTP/3 与 URL 解析驱动仓库对应的集成测试位于 t/99fuzzer-http1.t、t/99fuzzer-http2.t、t/99fuzzer-http3.t 与 t/99fuzzer-url.t它们通过t::Util的exec_fuzzer辅助函数驱动运行。四、运行时环境变量H2O_FUZZER_CLIENT_TIMEOUT是驱动唯一公开的客户端调优项它配置客户端线程事件循环的超时时间单位为毫秒默认值为10 ms。设置方式H2O_FUZZER_CLIENT_TIMEOUT50 ./h2o-fuzzer-http1 ...其读取逻辑在 fuzz/driver.cc 中通过getenv读取后以atoi转换若未设置或转换结果为 0则回退到 10。该值同时驱动客户端线程的select轮询超时fuzz/driver.cc以及主线程h2o_evloop_run的单轮运行上限fuzz/driver.cc因此调大它意味着每个输入用例允许更长的处理时间但会线性抬高总吞吐的耗时。此外HTTP/3 驱动还额外支持H2O_FUZZER_LOG_ACCESS设为非零值时会将访问日志输出到/dev/stdout便于在 fuzzing 时观察请求处理路径见 fuzz/driver_h3.cc。五、fuzzer 内部工作原理源码级剖析fuzz/driver.cc 的LLVMFuzzerTestOneInput是 LibFuzzer 的回调入口L258。其设计要点如下一次性初始化L269-L311首条输入到达时驱动会完成以下工作屏蔽SIGPIPEL275避免写入已关闭 socket 导致进程意外退出通过mkdtemp创建临时目录并基于该目录构造一个http://[unix://tmp/_.sock]/proxy形式的 Unix socket 上游地址L277-L278注册一个 H2O host挂载三个处理路径/chunked-test对应一个仅接受 GET、返回 hello world 的测试 handlerL69-L84 的chunked_test/reproxy-test通过 fuzz/driver_common.cc 的register_proxy注册到 Unix socket 上游的反向代理/则注册 examples/doc_root 静态文件服务L289-L291从而让同一份 fuzz 输入可以覆盖 handler、反向代理与静态文件三条处理路径创建 evloop 上下文与 accept 上下文L293-L297启动两个后台线程writer_thread扮演 HTTP 客户端L300-L305upstream_thread扮演上游源站L306-L308两者通过h2o_barrier与主线程同步初始化完成L309。每个输入用例的执行流程L313-L332create_acceptedL220-L242创建一对 Unix socketpair一端作为 h2o 的已接受连接交给h2o_evloop_socket_create与h2o_http1_accept/h2o_http2_accept由HTTP1/HTTP2宏决定L235-L239另一端连同输入数据交给feederL196-L213投递给客户端线程writer_threadL130-L190读取输入后以特殊标记\n--MARK--\n将输入切分为多个 TCP 包依次写入 socketL152-L167并在数据发送完毕后shutdown(SHUT_WR)模拟真实网络中的分片、半关闭与持久连接场景主线程随后循环调用h2o_evloop_run(ctx.loop, client_timeout_ms)直到连接被任一方关闭is_valid_fd检查L247-L250再通过 barrier 等待客户端线程完成收尾L328。HTTP/3 驱动 fuzz/driver_h3.cc 采用了完全不同的模型它在内存中直接构造 QUIC 连接h2o_http3_server_acceptL170-L171打开一条双向客户端流把输入数据通过quicly_recvstate_update与on_receive注入L179-L180随后驱动事件循环并模拟 ACK 发送事件L194-L204直至收到完整响应sent_response或连接因输入错误被关闭。URL 驱动 fuzz/driver_url.cc 则最简单直接把输入交给h2o_url_parse解析并通过assert校验解析结果各字段长度总和不超过输入长度的两倍L39-L52。六、语料库的来源与维护6.1 语料库如何产生仓库内的四个语料库并非凭空生成。根据 fuzz/README.md 的说明其初始内容是通过两个步骤建立的先用随仓库提供的 fuzz/gather-data.patch 给 h2o 打补丁然后运行单元测试由补丁把测试过程中真实产生的网络数据落盘。fuzz/gather-data.patch 的实现机制很直观它分别在 lib/common/socket.c 的h2o_socket_close、lib/common/socket/evloop.c.h 的on_read_core以及decode_ssl_input中注入log_for_fuzzer/close_for_fuzzer调用将所有读入的字节流按out.线程.fd.序号.随机数命名写成文件并在每段数据后追加\n--MARK--\n分隔标记——这与 fuzz/driver.cc 中客户端线程用于切分输入包的标记完全对应保证了采集数据与 fuzzer 输入格式的一致。采集到的原始请求再经过一轮 fuzzing 扩展与 LibFuzzer 最小化minimization处理最终形成当前语料库。6.2 提交新种子文件项目欢迎能覆盖目标程序新执行路径的种子文件。提交前请务必先用 driver 的-merge标志确认新种子确实能为现有语料库增加覆盖率例如./h2o-fuzzer-http2 -max_len$((16 * 1024)) -merge1 ./fuzz/http2-corpus ./fuzz/my-new-seeds-merge1会把./fuzz/my-new-seeds中的用例与./fuzz/http2-corpus合并只保留能触达新代码路径的输入从而避免引入冗余种子。6.3 定期重新最小化随着时间推移、代码变更单个种子文件所能覆盖的路径可能发生变化因此文档建议周期性对语料库重新最小化控制语料库体积、维持运行效率./h2o-fuzzer-http2 -max_len$((16 * 1024)) -merge1 ./fuzz/http2-corpus.fresh ./fuzz/http2-corpus cp ./fuzz/http2-corpus.fresh/* ./fuzz/http2-corpus执行后http2-corpus.fresh即包含被最小化的、彼此互不冗余的种子集合再将其内容覆盖回正式语料库目录即可。七、HTTP 字典http.dict的作用fuzz/http.dict 采用 LibFuzzer 字典格式每行一个名字值条目帮助变异器生成更像真实协议流量的输入。字典内容可分为几类均可从文件内容直接验证标点与字节值如colon:、crlf\x0D\x0A、nul\x00、hi\x80等用于构造边界字节序列HTTP 方法与版本覆盖GET、POST、PRI、CONNECT等全部标准及扩展方法以及HTTP/0.9到HTTP/3的版本串HTTP 头部数百个标准与非标准头部名Host、Transfer-Encoding、Sec-WebSocket-Key、X-Forwarded-For等以及 HTTP/2 伪头:authority、:method、:path、:scheme、:status媒体类型大量application/*、text/*等 MIME 类型串用于覆盖 Content-Type 解析与压缩协商路径。八、与测试体系的集成fuzz driver 构建完成后会自动纳入 H2O 的测试依赖CMake 中h2o-fuzzer-http1、h2o-fuzzer-http2、h2o-fuzzer-http3、h2o-fuzzer-url均被加入checkdependsCMakeLists.txt而对应的回归测试脚本 t/99fuzzer-http1.t、t/99fuzzer-http2.t、t/99fuzzer-http3.t 与 t/99fuzzer-url.t 会在make check时通过exec_fuzzer以预置语料库对每个驱动执行快速验证确保新增代码不会破坏既有 fuzzing 输入的处理稳定性。九、实践建议与注意事项综合文档与源码在实际使用中有以下几点值得注意按环境定制选项-max_len、-rss_limit_mb、-timeout等 LibFuzzer 参数应依据 CI 机器内存与时间预算调整H2O_FUZZER_CLIENT_TIMEOUT默认 10 ms 通常足够若遇到超时误报可适当调大始终携带字典运行HTTP 协议解析路径高度依赖特定 token不加载 fuzz/http.dict 会让变异效率显著下降多协议联合覆盖HTTP/1、HTTP/2、HTTP/3 驱动共享 handler/代理/静态文件三条处理路径HTTP/3 驱动另有独立注册提交种子时建议分别对四个语料库执行-merge检查ASan 配置保持ASAN_OPTIONSdetect_leaks0以避免多线程驱动下的泄漏误报如需检测泄漏可改用 Valgrind 或关闭驱动线程化路径单独验证周期性重最小化将 6.3 节的合并-复制流程纳入例行维护避免语料库随运行时间无限膨胀拖慢每次 fuzz 的启动与调度。通过以上流程即可在 H2O 上建立一套可持续运行的 LibFuzzer 模糊测试体系持续为 HTTP/1、HTTP/2、HTTP/3 与 URL 解析代码路径补充覆盖并挖掘潜在缺陷。【免费下载链接】h2oH2O - the optimized HTTP/1, HTTP/2, HTTP/3 server项目地址: https://gitcode.com/gh_mirrors/h2/h2o创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表