将 Swift 项目接入 OSS-Fuzz:Swift fuzz target 编写与构建配置全指南
【免费下载链接】oss-fuzzOSS-Fuzz - continuous fuzzing for open source software.项目地址: https://gitcode.com/gh_mirrors/os/oss-fuzz
本篇指南基于 OSS-Fuzz 官方文档《Integrating a Swift project》展开,系统讲解如何将一个 Swift 语言项目接入 OSS-Fuzz 持续模糊测试平台。读完本文,你将掌握 Swift fuzz target 的编写规范、project.yaml与Dockerfile的 Swift 专属配置要点,以及precompile_swift辅助脚本与SWIFTFLAGS的底层构建原理,并可直接参照仓库中 swift-nio、swift-protobuf 两个真实项目落地实践。
概述:Swift 集成与通用流程的关系
Swift 项目接入 OSS-Fuzz 的整体流程与新项目接入通用指南高度一致:仍然需要在一个项目目录下放置project.yaml、Dockerfile、build.sh及 fuzz target 源文件,仍由 OSS-Fuzz 基础设施负责拉取代码、构建镜像、编译 fuzz target 并投喂测试用例。二者的差异集中在与 Swift 语言工具链相关的几个具体环节上:
- Swift 有独立的构建基础镜像(
base-builder-swift); - fuzz target 需要用 Swift 编写并导出
LLVMFuzzerTestOneInput符号; - 构建脚本需要通过
precompile_swift脚本生成一组专用的SWIFTFLAGS编译参数; - 引擎与 sanitizer 的选择范围较窄。
下文逐项展开这些 Swift 特有细节。
编写 Swift fuzz target
与 C/C++/Go 项目相同,Swift 项目接入 OSS-Fuzz 的第一步,是编写一个接受「字节流输入」并调用被测程序 API 的 fuzz target。该 fuzz target 应存放在你的项目仓库中(而不是 OSS-Fuzz 仓库),在构建阶段通过 Dockerfile 拷贝或克隆进构建环境。
Swift fuzz target 的核心要求是:通过@_cdecl导出LLVMFuzzerTestOneInput符号,参数签名为(UnsafeRawPointer, Int) -> CInt,即接收 libFuzzer 传入的原始内存指针与长度。仓库中 swift-nio 项目的 HTTP 解析器 fuzz target fuzz_http1.swift 是一个完整可参考的范例:
import NIOHTTP1 import NIO @_cdecl("LLVMFuzzerTestOneInput") public func test(_ start: UnsafeRawPointer, _ count: Int) -> CInt { let bytes = UnsafeRawBufferPointer(start: start, count: count) let channel = EmbeddedChannel() var buffer = channel.allocator.buffer(capacity: count) buffer.writeBytes(bytes) do { try channel.pipeline.addHandler(ByteToMessageHandler(HTTPRequestDecoder())).wait() try channel.writeInbound(buffer) channel.embeddedEventLoop.run() } catch { } do { try channel.finish(acceptAlreadyClosed: true) } catch { } return 0 }这个例子展示了 Swift fuzz target 的几个典型模式:
@_cdecl导出:@_cdecl("LLVMFuzzerTestOneInput")将 Swift 函数导出为 C 符号,libFuzzer 才能以 C ABI 调用它;- 字节流接入:将
UnsafeRawPointer与长度包装为UnsafeRawBufferPointer,再写入被测库(如 NIO 的ByteBuffer); - 异常兜底:用
do/catch吞掉业务异常(如解析错误),避免异常导致进程异常退出干扰模糊测试; - 固定返回值:函数统一返回
0,崩溃与否完全交由 sanitizer 判定。
在搭建你自己的 fuzz target 时,将import与处理逻辑替换为被测 Swift 库的 API 即可。
项目目录结构与 project.yaml 配置
Swift 项目在 OSS-Fuzz 仓库中的目录结构与其他语言项目并无差别,即projects/<project-name>/下放置project.yaml、Dockerfile、build.sh等文件。差异主要体现在project.yaml的字段取值上。
language 字段必须指定
project.yaml中language属性必须显式声明为swift:
language: swift引擎与 sanitizer 的限定范围
Swift 项目目前唯一支持的 fuzzing 引擎是libfuzzer,支持的 sanitizer 为address(AddressSanitizer)与thread(ThreadSanitizer)。以仓库中 swift-nio/project.yaml 的真实配置为例:
homepage: "https://github.com/apple/swift-nio" language: swift primary_contact: "lukasa@apple.com" auto_ccs : - "johannesweiss@apple.com" - "pp_adams@apple.com" - "p.antoine@catenacyber.fr" fuzzing_engines: - libfuzzer sanitizers: - address - thread main_repo: 'https://github.com/apple/swift-nio.git' base_os_version: ubuntu-24-04需要注意:Swift 项目不支持coverage之外的额外引擎(如afl、honggfuzz),也不支持undefined、memory等 sanitizer。这一限制来自底层 Swift 工具链与 libFuzzer 的集成方式,配置超出范围会导致构建/运行阶段失败。
此外,仓库中的 swift-protobuf/project.yaml 还展示了 Swift 项目可用的coverage_extra_args配置——用于在覆盖率统计中排除自动生成的.pb.swift文件与构建产物目录:
coverage_extra_args: > -ignore-filename-regex=.*\.pb\.swift -ignore-filename-regex=.*/\.build/.*Dockerfile:基于 base-builder-swift 基础镜像
Swift 项目的 Dockerfile 必须从gcr.io/oss-fuzz-base/base-builder-swift开始,而不是通用基础镜像base-builder。该镜像已预装 Swift 工具链与precompile_swift脚本。
以 swift-nio/Dockerfile 为参照:
FROM gcr.io/oss-fuzz-base/base-builder-swift:ubuntu-24-04 # specific swift-nio RUN git clone --depth 1 https://github.com/google/fuzzing RUN git clone --depth 1 https://github.com/apple/swift-nio.git COPY build.sh $SRC COPY *.swift $SRC/ WORKDIR $SRC/swift-nio值得注意的细节:
- 显式指定 tag:仓库中 Swift 项目的 Dockerfile 均使用带版本 tag 的镜像,如
:ubuntu-24-04,并在project.yaml中同步声明base_os_version: ubuntu-24-04; - fuzz target 的拷贝方式:通过
COPY *.swift $SRC/将项目仓库中的 Swift fuzz target 拷贝到构建环境(对应前文「fuzz target 存放在项目仓库」的要求); $SRC环境变量:与其他语言项目一致,$SRC是 OSS-Fuzz 约定的源码工作目录。
该基础镜像的构成可以在仓库中追溯:base-builder-swift/Dockerfile 在base-builder之上执行install_swift.sh,并将precompile_swift脚本安装到/usr/local/bin/:
FROM gcr.io/oss-fuzz-base/base-builder RUN install_swift.sh COPY precompile_swift /usr/local/bin/而 install_swift.sh 则完成了 Swift 工具链的安装:安装libc6-dev、libstdc++-9-dev、pkg-config、uuid-dev、zlib1g-dev等依赖包,下载并解压 Swift 6.1.3 release 工具链到/usr/,并从 LLVM 源码编译出专供 Swift 使用的llvm-symbolizer-swift(用于崩溃栈符号化,对应precompile_swift中将其复制到$OUT的动作)。
build.sh 与 precompile_swift:SWIFTFLAGS 的生成与使用
核心用法
build.sh的第一步应当 source(执行)precompile_swift脚本,该脚本会生成环境变量SWIFTFLAGS,随后即可将$SWIFTFLAGS直接拼接到 Swift 构建命令中,例如:
swift build -c release $SWIFTFLAGSswift-protobuf 的完整范例
仓库中 swift-protobuf 项目的构建脚本(文档原文引用)展示了完整的用法,其中还包含 fuzz target 产物的收集与重命名逻辑:
. precompile_swift # build project cd FuzzTesting swift build -c debug $SWIFTFLAGS ( cd .build/debug/ find . -maxdepth 1 -type f -name "*Fuzzer" -executable | while read i; do cp $i $OUT/"$i"-debug; done )逐步解读这段脚本:
. precompile_swift:以 source 方式执行脚本,使SWIFTFLAGS等环境变量在当前 shell 生效;swift build -c debug $SWIFTFLAGS:进入 fuzz 测试工程目录(本例为FuzzTesting),携带$SWIFTFLAGS执行构建。使用-c debug而非 release,可以保留更多调试信息、提升 sanitizer 报错的可读性;- 收集产物:
find在.build/debug/下查找名称以Fuzzer结尾、且带可执行权限的文件,逐个拷贝到$OUT并追加-debug后缀。由于 swiftpm 会生成多个可执行目标,这种批量收集方式可以避免遗漏; $OUT是 OSS-Fuzz 约定的 fuzz target 输出目录,构建产物最终会被打包并运行于集群。
precompile_swift 的底层实现原理
SWIFTFLAGS的内容由 precompile_swift 脚本根据当前构建上下文动态生成,其核心逻辑如下(摘录关键行):
cp /usr/local/bin/llvm-symbolizer-swift $OUT/llvm-symbolizer export SWIFTFLAGS="-Xswiftc -parse-as-library -Xswiftc -static-stdlib --static-swift-stdlib" if [ "$SANITIZER" = "coverage" ] then export SWIFTFLAGS="$SWIFTFLAGS -Xswiftc -profile-generate -Xswiftc -profile-coverage-mapping -Xswiftc -sanitize=fuzzer" else export SWIFTFLAGS="$SWIFTFLAGS -Xswiftc -sanitize=fuzzer,$SANITIZER --sanitize=$SANITIZER" for f in $CFLAGS; do export SWIFTFLAGS="$SWIFTFLAGS -Xcc=$f" done for f in $CXXFLAGS; do export SWIFTFLAGS="$SWIFTFLAGS -Xcxx=$f" done fi逐项拆解这些标志的含义:
| 标志 | 作用 |
|---|---|
-Xswiftc -parse-as-library | 将顶层代码按库(library)而非可执行文件解析,这是 fuzz target 以函数入口(而非main)组织的必要条件 |
-Xswiftc -static-stdlib/--static-swift-stdlib | 静态链接 Swift 标准库,避免运行时依赖动态库导致 fuzz target 在 OSS-Fuzz 运行环境中无法加载 |
-Xswiftc -sanitize=fuzzer,$SANITIZER/--sanitize=$SANITIZER | 同时启用 libFuzzer 与当前 sanitizer(address/thread),其中$SANITIZER由 OSS-Fuzz 构建系统按project.yaml中sanitizers字段注入 |
-Xswiftc -profile-generate/-profile-coverage-mapping | 仅 coverage 构建时启用,生成覆盖率映射信息(配合-sanitize=fuzzer实现带覆盖引导的模糊测试) |
-Xcc=$f/-Xcxx=$f | 将 OSS-Fuzz 为 C/C++ 设置的$CFLAGS/$CXXFLAGS(如-fsanitize、-O1等)逐条透传给底层 Clang 编译器,保证 Swift 与 C/C++ 混合代码的插桩与 sanitizer 行为一致 |
此外,脚本第一步将llvm-symbolizer-swift复制为$OUT/llvm-symbolizer,使崩溃报告能够正确符号化 Swift 栈帧。这与project.yaml中仅声明libfuzzer引擎、address/threadsanitizer 的限制遥相呼应:precompile_swift是在 Swift 工具链之上补齐 libFuzzer 集成的关键胶水层。
端到端集成清单
将以上内容整合,一个 Swift 项目接入 OSS-Fuzz 的完整落地步骤为:
- 编写 fuzz target(置于你的项目仓库):用
@_cdecl("LLVMFuzzerTestOneInput")导出入口,签名(UnsafeRawPointer, Int) -> CInt; - 创建项目目录:在 OSS-Fuzz 仓库的
projects/<name>/下准备三个文件; - 配置 project.yaml:声明
language: swift,fuzzing_engines仅填libfuzzer,sanitizers填address/thread,建议同步声明base_os_version: ubuntu-24-04; - 编写 Dockerfile:
FROM gcr.io/oss-fuzz-base/base-builder-swift:ubuntu-24-04,克隆源码并COPYfuzz target 与build.sh; - 编写 build.sh:以
. precompile_swift开头,用swift build -c debug $SWIFTFLAGS构建,再按需将产物复制到$OUT(可参考 swift-protobuf 的批量收集写法)。
完成上述步骤后,即可按照新项目接入通用指南的后续流程提交项目、通过本地构建验证并接入 OSS-Fuzz 的持续模糊测试。仓库中的 swift-nio 与 swift-protobuf 两个目录是完整的 Swift 项目参考实现,涵盖project.yaml、Dockerfile、build.sh与 Swift fuzz target 源文件,可作为新项目模板对照学习。
【免费下载链接】oss-fuzzOSS-Fuzz - continuous fuzzing for open source software.项目地址: https://gitcode.com/gh_mirrors/os/oss-fuzz
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考