ARTICLE DETAIL

资讯详情

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

OSS-Fuzz 集成 Rust 项目实战:基于 cargo-fuzz 的完整接入指南

OSS-Fuzz 集成 Rust 项目实战:基于 cargo-fuzz 的完整接入指南 OSS-Fuzz 集成 Rust 项目实战基于 cargo-fuzz 的完整接入指南【免费下载链接】oss-fuzzOSS-Fuzz - continuous fuzzing for open source software.项目地址: https://gitcode.com/gh_mirrors/os/oss-fuzz本文以 OSS-Fuzz 官方新项目接入指南中的 Rust 专篇为骨架系统讲解如何把一个用 Rust 编写的开源项目接入 OSS-Fuzz 持续模糊测试体系。你将从零开始掌握 Rust 项目接入所需的项目文件布局、project.yaml与 Dockerfile 配置、build.sh构建脚本编写以及利用cfg(fuzzing)实现测试与模糊测试复用的 Rust 特有技巧文中所有配置与脚本均可在当前仓库的 projects 目录中找到真实可运行的项目实例作为对照。概览Rust 项目接入 OSS-Fuzz 的整体流程把 Rust 项目集成到 OSS-Fuzz与通用的新建项目流程见 docs/getting-started/new-project-guide/ 下的各语言指南高度相似在 projects/ 下新建一个以项目名命名的目录提供project.yaml、Dockerfile、build.sh三个核心文件由 OSS-Fuzz 的基础镜像完成环境准备最终在云端持续运行模糊测试并自动归档崩溃用例。Rust 特有的差异点集中在四个方面构建工具必须使用cargo fuzz来编译 fuzz target它会自动附加编译器插桩参数并链接正确的 libFuzzer语言声明project.yaml中必须显式声明language: rust引擎与消毒器目前仅支持libfuzzer引擎与address消毒器基础镜像Dockerfile 需基于gcr.io/oss-fuzz-base/base-builder-rust构建该镜像预装了最新 nightly Rust 与cargo fuzz。截至当前仓库已有 89 个项目的 project.yaml 声明了language: rust覆盖 JSONserde_json、TOMLtoml_edit、日期解析chrono、TLSrustls、WebAssembly 运行时wasmtime、服务网格代理linkerd2-proxy等各类生态为本文提供了大量真实参照。cargo-fuzzRust 集成的核心构建工具Rust 项目接入 OSS-Fuzz 时官方期望使用cargo fuzz来构建 fuzzers。cargo fuzz的价值在于自动注入编译插桩以正确的编译器标志如 sanitizer coverage构建代码正确链接 libFuzzer在 OSS-Fuzz 环境内自动链接对应版本的 libFuzzer本地可复现使用cargo fuzz后即便在本地拿到失败用例crash input也可以轻松重现并调试无需依赖 OSS-Fuzz 云端环境。在 OSS-Fuzz 的 Rust 基础镜像中cargo fuzz已经预装并位于PATH中。从 infra/base-images/base-builder/install_rust.sh 的源码可以看到其安装方式curl https://sh.rustup.rs | sh -s -- -y --default-toolchain$RUSTUP_TOOLCHAIN --profileminimal cargo install cargo-fuzz --locked rm -rf /rust/registry即镜像构建时通过 rustup 安装指定 nightly 工具链见下方 Dockerfile 章节 中RUSTUP_TOOLCHAIN环境变量再cargo install cargo-fuzz完成安装。项目文件布局fuzz 目录的三件套在把 Rust 项目接入 OSS-Fuzz 之前首先需要按照cargo fuzz的规范组织项目。参考cargo fuzz的官方安装与初始化流程对应 rust-fuzz 官方文档项目完成后应具备顶层fuzz目录集中存放 fuzz 相关代码与清单fuzz/Cargo.tomlfuzz 清单文件引入模糊测试所需的依赖如libfuzzer-sys、arbitrary若干fuzz/fuzz_targets/*.rs文件真正的 fuzz target即会被编译并在 OSS-Fuzz 上运行的入口程序。当前仓库中 projects/serde_urlencoded/ 是这一布局的完整范例其 fuzz/Cargo.toml 展示了 fuzz 清单的关键要素[package] name serde_urlencoded-fuzz version 0.0.0 publish false edition 2018 [package.metadata] cargo-fuzz true [dependencies] arbitrary { version 1.2.3, features [derive] } libfuzzer-sys 0.4 serde { version 1.0.100, features [derive] } [dependencies.serde_urlencoded] path .. # Prevent this from interfering with workspaces [workspace] members [.] [[bin]] name roundtrip path fuzz_targets/roundtrip.rs test false doc false其中值得注意的细节[package.metadata] cargo-fuzz true标记该包为 fuzz 包libfuzzer-sys提供fuzz_target!宏与 libFuzzer 绑定是所有 fuzz target 的必备依赖[[bin]]显式声明每个 fuzz target 的二进制名称与源文件路径test false、doc false避免它们被当作普通测试/文档目标处理[workspace] members [.]防止该 fuzz 包干扰上层 workspace 的依赖解析。对应的 fuzz target 实现fuzz/fuzz_targets/roundtrip.rs展示了标准写法——#![no_main]加libfuzzer_sys::fuzz_target!宏包裹闭包#![no_main] use libfuzzer_sys::fuzz_target; #[derive(Debug, Serialize, Deserialize, PartialEq)] enum PlainEnum { A, B, C, D } // ... 更多被测类型定义 ... fuzz_target!(|data: Vec[u8]| { // 对各类数据执行 序列化 - 反序列化 - 断言相等 的 round-trip 校验 round_trip!(i32, data, true); // ... });fuzz_target!宏接受的闭包参数类型即 libFuzzer 喂入的输入类型本例为Vec[u8]函数体内部对被测库进行反复编解码并断言结果一致从而发现序列化/反序列化中的不一致或 panic。此外你也可以自定义这一布局例如把 fuzz 目录放在子 crate 中如 projects/toml_edit/build.sh 通过--manifest-path crates/toml_edit_fuzz/Cargo.toml指向嵌套清单但需要同步修改下文 build.sh 章节 中的脚本以匹配实际路径。project.yaml声明语言、引擎与消毒器Rust 项目的 project.yaml 中language属性必须显式指定为rustlanguage: rust同时Rust 项目目前仅支持libfuzzer作为模糊测试引擎、address作为消毒器二者必须在sanitizers与fuzzing_engines中明确列出sanitizers: - address fuzzing_engines: - libfuzzer对照仓库中的真实项目 projects/serde_json/project.yaml 与 projects/linkerd2-proxy/project.yaml可以看到除了上述三项还通常会补充homepage、main_repo、primary_contact主联系人邮箱与auto_ccs自动抄送的安全研究人员homepage: https://github.com/serde-rs/json primary_contact: dtolnaygmail.com main_repo: https://github.com/serde-rs/json sanitizers: - address fuzzing_engines: - libfuzzer language: rust auto_ccs: - davidadalogics.com关于消毒器组合的补充说明addressAddressSanitizer即 ASan是当前 Rust 集成唯一官方支持的消毒器。虽然 infra/base-images/base-builder/install_rust.sh 中注释提到需要重新编译 Rust 标准库以支持 MSAN但这属于基础镜像层面的预留能力普通项目的project.yaml仍以addresslibfuzzer为准。Dockerfile基于 base-builder-rust 基础镜像Rust 项目的 Dockerfile 必须以FROM gcr.io/oss-fuzz-base/base-builder-rust开头。该基础镜像定义见 infra/base-images/base-builder-rust/Dockerfile已为你准备好了最新的 nightly 版本 Rust 工具链通过RUSTUP_TOOLCHAIN环境变量锁定当前镜像固定为nightly-2025-09-05见 base-builder-rust/Dockerfile预装的cargo fuzz已在PATH中开箱即用关键的构建环境变量CARGO_HOME/rust、RUSTUP_HOME/rust/rustup统一 Rust 工具链的存放路径CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKERclang将 x86_64 Linux 下 rustc 默认链接器cc替换为clang便于找到libc等自定义库OSSFUZZ_RUSTPATH /rust用于覆盖率报告中的源码路径映射。因此在你项目的 Dockerfile 里通常只需要做两件事拉取最新代码安装构建所需的系统依赖。projects/serde_json/Dockerfile 是最简洁的示范FROM gcr.io/oss-fuzz-base/base-builder-rust RUN apt-get update apt-get install -y make autoconf automake libtool curl cmake python llvm-dev libclang-dev clang RUN git clone --depth 1 https://github.com/serde-rs/json json WORKDIR $SRC COPY build.sh $SRC/若项目需要额外系统库则在RUN中一并安装例如 projects/linkerd2-proxy/Dockerfile 安装了libssl-dev、pkg-config、pythonFROM gcr.io/oss-fuzz-base/base-builder-rust RUN apt-get --yes update \ apt-get install --no-install-recommends --yes \ libssl-dev \ pkg-config \ python \ apt-get clean \ rm --recursive --force /var/lib/apt/lists/* RUN git clone --depth 1 https://github.com/linkerd/linkerd2-proxy COPY build.sh $SRC/ WORKDIR $SRC COPY rustc.py $SRC/注意几个环境约定$SRC是 OSS-Fuzz 规定的源码目录克隆代码时通常--depth 1浅克隆以加速镜像构建build.sh需通过COPY复制进$SRC构建时 OSS-Fuzz 会在$SRC下执行它。build.sh构建并复制 fuzz targetbuild.sh是 OSS-Fuzz 在容器内执行的构建脚本职责是构建项目的 fuzz targets并把最终生成的二进制复制到输出目录$OUT。Rust 项目的典型写法如下对应 projects/serde_json/build.sh 的思路cd $SRC/json cargo fuzz build -O cp fuzz/target/x86_64-unknown-linux-gnu/release/from_slice $OUT/常用编译标志-O推荐以 release 模式构建 fuzzer性能显著优于 debug 模式从而在同样的时间预算内覆盖更多输入--debug-assertions按需启用更多运行时检查如越界、溢出断言帮助捕获 release 模式下被优化掉的逻辑错误上例中from_slice即 fuzz target 对应的二进制名与fuzz_targets/from_slice.rs的文件名一致。自动复制全部 fuzz target手工逐个cp不够优雅——当你新增一个 fuzz target 时希望它自动被纳入 OSS-Fuzz。可以用一段 bash 循环遍历fuzz/fuzz_targets/*.rs按文件名推导二进制名并批量复制FUZZ_TARGET_OUTPUT_DIRtarget/x86_64-unknown-linux-gnu/release for f in fuzz/fuzz_targets/*.rs do FUZZ_TARGET_NAME$(basename ${f%.*}) cp $FUZZ_TARGET_OUTPUT_DIR/$FUZZ_TARGET_NAME $OUT/ done这段脚本在当前仓库有完整落地实例projects/serde_urlencoded/build.sh 即采用完全相同的写法其FUZZ_TARGET_OUTPUT_DIR为fuzz/target/x86_64-unknown-linux-gnu/release因为该项目的 fuzz 目录就是顶层fuzz/。而 projects/chrono/build.sh 则展示了显式逐个复制的做法cd $SRC/chrono cargo fuzz build -O cp fuzz/target/x86_64-unknown-linux-gnu/release/fuzz_reader $OUT/ cp fuzz/target/x86_64-unknown-linux-gnu/release/fuzz_format $OUT/非 cargo-fuzz 的替代路径若项目 fuzz 清单无法直接用cargo fuzz驱动例如 fuzz 包被嵌套在子目录、或对构建流程有特殊要求也可以退回到cargo build --release --manifest-path加手工复制projects/toml_edit/build.sh 即为一例cd $SRC/toml_edit cargo build --release --manifest-path crates/toml_edit_fuzz/Cargo.toml cp target/release/parse_document $OUT/注意这种方式需要自行保证构建参数插桩、链接 libFuzzer正确是cargo fuzz之外的可选路径并不改变project.yaml中libfuzzeraddress的声明。覆盖率模式的特殊处理在 OSS-Fuzz 的覆盖率构建模式下一些对插桩敏感的大型依赖如异步运行时可能引发问题。projects/linkerd2-proxy/build.sh 展示了处理方式当$SANITIZER为coverage时通过rustc.py包装 rustc在编译tokio_util、hyper等 crate 时移除-Zinstrument-coverage标志对应实现见 projects/linkerd2-proxy/rustc.pyif [ $SANITIZER coverage ] then export RUSTFLAGS$RUSTFLAGS -C debug-assertionsno chmod x $SRC/rustc.py export RUSTC$SRC/rustc.py export CFLAGS fi若你的项目在覆盖率构建阶段遇到特定 crate 的插桩兼容问题可参考这一模式按需裁剪。此外linkerd2-proxy 的构建脚本还示范了为 fuzz target 附带.options文件如写入detect_leaks0用于按 target 覆盖默认运行参数。编写 fuzzer 的测试风格策略cfg(fuzzing)这是 Rust 接入 OSS-Fuzz 最独特的技巧。Rust 项目中测试代码通常用#[cfg(test)]包裹只在 test 模式下编译进产物#[cfg(test)] mod tests { use super::*; // ... 测试逻辑 ... }而cargo fuzz在构建时会自动启用名为fuzzing的 feature。这意味着你可以把 fuzz 逻辑写得和测试一样——用#[cfg(fuzzing)]包裹的模块只会在 fuzz 构建时编译普通构建与测试构建均不受影响#[cfg(fuzzing)] pub mod fuzz_logic { use super::*; // ... 仅供 fuzzer 调用的逻辑 ... }随后在 fuzz target 中调用fuzz_logic内的逻辑即可。这种做法的收益是fuzz 专用代码与测试代码遵循同一套组织习惯且不会泄漏到发布产物中。fuzz 专用依赖的声明与测试依赖[dev-dependencies]类似fuzz 专用依赖可以在Cargo.toml中通过目标条件target cfg声明[target.cfg(fuzzing).dependencies]而普通测试依赖仍按惯例写在[dev-dependencies]两者互不干扰cfg(fuzzing)依赖仅在cargo fuzz构建时拉入dev-dependencies仅在cargo test时拉入。测试逻辑与 fuzz 逻辑的复用更进一步你完全可以把已有的测试逻辑与新增的 fuzz 逻辑合并复用只需把条件写成二者的并集#[cfg(any(test, fuzzing))]这样同一段代码既能作为单元测试运行也能作为 fuzzer 的输入处理逻辑避免重复维护两套实现。仓库实例projects/linkerd2-proxy 正是采用这一结构的项目——其build.sh分别在app/inbound、addr、dns、proxy/http、tls、transport-header等多个子 crate 中调用cargo fuzz build并为每个模块产出独立的 fuzz 二进制fuzz_inbound、fuzz_addr、fuzz_dns、fuzz_http、fuzz_tls、fuzz_transport_raw、fuzz_transport_structured其 fuzz 代码即通过cfg(fuzzing)系列条件与业务代码共库共存是学习该策略的最佳参照。本地复现与调试cargo fuzz带来的另一个实用好处是本地可调试。接入 OSS-Fuzz 后若云端跑出了崩溃用例你可以在本地项目目录下执行cargo fuzz run target_name crash_input文件直接以该输入复现崩溃配合 debugger如rust-gdb或RUST_BACKTRACE1定位问题根因修复后重新cargo fuzz build -O本地验证不再崩溃再通过 PR 提交修复。这与测试风格策略相辅相成——因为 fuzz 代码与测试代码共用同一套cfg机制本地迭代的体验与写单元测试几乎一致显著降低了维护成本。小结将 Rust 项目接入 OSS-Fuzz 的关键要点可归纳为环节核心要求仓库参照项目布局顶层fuzz/fuzz/Cargo.tomlfuzz/fuzz_targets/*.rsprojects/serde_urlencoded/fuzz/project.yamllanguage: rust仅addresslibfuzzerprojects/serde_json/project.yamlDockerfileFROM gcr.io/oss-fuzz-base/base-builder-rustprojects/serde_json/Dockerfilebuild.shcargo fuzz build -O 复制产物到$OUTprojects/chrono/build.sh代码组织#[cfg(fuzzing)]/#[cfg(any(test, fuzzing))]/[target.cfg(fuzzing).dependencies]projects/linkerd2-proxy/基础镜像层面的实现细节nightly 工具链版本、cargo fuzz安装、链接器配置可进一步阅读 infra/base-images/base-builder-rust/Dockerfile 与 infra/base-images/base-builder/install_rust.sh。掌握以上流程后你便可以为自己的 Rust 库快速建立持续模糊测试并把运行方式、崩溃复现与修复验证完整纳入日常开发循环。【免费下载链接】oss-fuzzOSS-Fuzz - continuous fuzzing for open source software.项目地址: https://gitcode.com/gh_mirrors/os/oss-fuzz创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表