OSS-Fuzz 接入 JavaScript / Node.js 项目实战指南:从 project.yaml 到 Jazzer.js Fuzz Target 的完整落地
OSS-Fuzz 接入 JavaScript / Node.js 项目实战指南从 project.yaml 到 Jazzer.js Fuzz Target 的完整落地【免费下载链接】oss-fuzzOSS-Fuzz - continuous fuzzing for open source software.项目地址: https://gitcode.com/gh_mirrors/os/oss-fuzz本文以 OSS-Fuzz 官方文档 docs/getting-started/new-project-guide/javascript_lang.md 为主线系统讲解如何在 OSS-Fuzz 中为 JavaScriptNode.js及可转译为 JavaScript 的项目如 TypeScript搭建持续模糊测试fuzzing工程。你将掌握project.yaml、Dockerfile、build.sh、fuzz target 编写与FuzzedDataProvider使用的完整流程并了解 Jazzer.js 在底层如何驱动 libFuzzer 执行模糊测试。读完即可参照 projects/javascript-example 与 projects/typescript-example 两个官方示例项目把自有 JS 项目接入 OSS-Fuzz。JavaScript 项目接入 OSS-Fuzz 的整体流程将 JavaScriptNode.js项目集成进 OSS-Fuzz 的过程与通用的 Setting up a new project 流程高度相似同样需要提供project.yaml项目元数据、Dockerfile构建环境、build.sh构建脚本以及若干 fuzz target模糊测试入口。JavaScript 项目的关键差异集中体现在三处语言与引擎选择language必须声明为javascriptfuzzing engine 仅支持libFuzzersanitizer 使用none基础镜像Dockerfile 以gcr.io/oss-fuzz-base/base-builder-javascript为起点镜像内预装 Node.js 与npm模糊测试引擎JS 模糊测试由 Jazzer.js 驱动它工作在 JavaScript 源码层面因此对任何可转译为 JavaScript 的语言如 TypeScript都适用。下文将按照 环境 → 配置 → 编写 fuzz target → 构建 → 数据提供器 的顺序逐步展开。Jazzer.jsJavaScript 模糊测试引擎OSS-Fuzz 中的 JavaScript 模糊测试由 Jazzer.js 提供能力该引擎在构建阶段由基础镜像自动安装。与基于 JVM 的 JazzerJava不同Jazzer.js 直接作用于JavaScript 源码层面source-code level这意味着它不需要把被测代码编译成特殊格式普通 Node.js 模块即可被 fuzz任何能转译为 JavaScript 的语言典型如 TypeScript都能复用同一套 fuzz 流程fuzz target 以普通 Node.js 模块形式存在导出名为fuzz的函数即可fuzz target 的具体形态可参考 Jazzer.js 官方的 Usage 文档。Jazzer.js 在底层依赖 libFuzzer 的 native addon 完成覆盖率引导coverage-guided的变异与反馈同时负责将命令行参数转发给该 addon——这正是compile_javascript_fuzzer生成的包装脚本能无缝替换 libFuzzer的原因详见下文 build.sh 一节。官方示例项目从零理解文件组织仓库内提供了两个可直接对照学习的示例项目示例路径说明JavaScript 示例projects/javascript-example最简单的 JS 模糊测试工程包含 3 个 fuzz target 与完整构建脚本TypeScript 示例projects/typescript-example展示 TS 项目的接入方式并演示了FuzzedDataProvider的用法javascript-example的完整文件清单如下project.yaml项目元数据语言、引擎、sanitizerDockerfile基于base-builder-javascript镜像把 fuzz target 拷贝进$SRC/examplepackage.jsonnpm 依赖描述fuzz_string_compare.js、fuzz_promise.js、fuzz_value_profiling.js三个不同风格的 fuzz targetbuild.sh依赖安装与 fuzzer 构建脚本。typescript-example则额外包含tsconfig.json、target.ts与fuzz_explore_me.ts展示 TypeScript 源码经编译后进入 fuzz 流程的标准做法。project.yaml语言、引擎与 Sanitizer 声明JavaScript 项目的project.yaml中language属性必须声明为language: javascript引擎与 sanitizer 的约束如下fuzzing engine 仅支持 libFuzzerlibfuzzer目前没有其他可选项原生 sanitizer如 AddressSanitizeraddress、UndefinedBehaviorSanitizerundefined暂不支持。这类 sanitizer 只在项目包含原生插件native addons时才需要而这对 JavaScript 项目来说属于较少见的情况。如果你确实需要 ASan 或 UBSan官方建议在 Jazzer.js 仓库提交 issue 提出需求none是 JavaScript 项目的默认 sanitizer因此在project.yaml中显式书写是可选的但建议显式声明以增强可读性。一个完整的project.yaml配置如下取自 projects/javascript-example/project.yamltypescript-example 与之相同homepage: https://github.com/CodeIntelligenceTesting/jazzer.js language: javascript main_repo: https://github.com/CodeIntelligenceTesting/jazzer.js fuzzing_engines: - libfuzzer sanitizers: - none vendor_ccs: - yakdancode-intelligence.com - norbert.schneidercode-intelligence.com - peter.samarincode-intelligence.com其中vendor_ccs用于声明该项目模糊测试的维护联系人本项目为 Jazzer.js 团队实际接入你自己的项目时应替换为对应的维护者邮箱。Dockerfile基于 base-builder-javascript 搭建构建环境JavaScript 项目的Dockerfile必须以基础镜像开头FROM gcr.io/oss-fuzz-base/base-builder-javascript该 OSS-Fuzz 基础镜像已预装 Node.js 19 与npm因此通常无需再安装 Node 运行时。镜像内还预装了compile_javascript_fuzzer构建脚本详见下文。Dockerfile 中通常只需要克隆目标项目源码或像示例那样把 fuzz target 直接拷贝进镜像设置WORKDIR按需安装项目特定依赖、拷贝必要文件。以 projects/javascript-example/Dockerfile 为模板FROM gcr.io/oss-fuzz-base/base-builder-javascript COPY build.sh $SRC/ # For real projects, you would clone your repo in the next step. RUN mkdir -p $SRC/example # Ideally, you have already configured fuzz tests in your repo so that they # run (in Jazzer.js regression mode) as part of unit testing. Keeping the fuzz # tests in sync with the source code ensures that they are adjusted continue # to work after code changes. Here, we copy them into the example project directory. COPY fuzz_string_compare.js fuzz_promise.js fuzz_value_profiling.js package.json $SRC/example/ WORKDIR $SRC/example注意示例注释中强调的最佳实践建议把 fuzz test 与源码放在同一仓库中并让它们在单元测试阶段以 Jazzer.js 回归模式regression mode运行这样当源码变更导致 fuzz target 失效时能被及时发现保持 fuzz 测试与代码同步演进。Fuzz Target最简单的形态是导出一个 fuzz 函数在最简单的情况下每个 fuzzer 就是一个导出了名为fuzz的函数的单个 JavaScript 文件该函数接收一个参数类型为 Node.js 的 Buffer。以下是一个名为fuzz_string_compare.js的示例 fuzz target与 projects/javascript-example/fuzz_string_compare.js 内容一致/** * param { Buffer } data */ module.exports.fuzz function (data) { const s data.toString(); if (s.length ! 16) { return; } if ( s.slice(0, 8) Awesome s.slice(8, 15) Fuzzing s[15] ! ) { throw Error(Welcome to Awesome Fuzzing!); } };这个 target 演示了模糊测试的核心机制fuzzer 不断生成随机输入并调用fuzz(data)一旦输入恰好满足s Awesome Fuzzing!这一串条件代码就会抛出Error。Jazzer.js 基于 libFuzzer 的覆盖率反馈会逐步引导变异方向最终发现这条路径从而触发一个可被报告的崩溃crash。异步 fuzz target基于 Promise 的写法JavaScript 生态大量使用异步 API因此 fuzz target 也可以返回 Promise。示例 projects/javascript-example/fuzz_promise.js 展示了这一点它在setTimeout回调中读取三个字节若满足one two three 42则 reject 一个 Error并额外校验了异步调用的执行顺序let lastInvocationCount 0; let invocationCount lastInvocationCount 1; /** * param { Buffer } data */ module.exports.fuzz function (data) { return new Promise((resolve, reject) { if (data.length 3) { resolve(invocationCount); return; } setTimeout(() { let one data.readInt8(0); let two data.readInt8(1); let three data.readInt8(2); if (one two three 42) { reject( new Error( ${one} ${two} ${three} 42 (invocation ${invocationCount}) ) ); } else { resolve(invocationCount); } }, 10); }).then((value) { if (value ! lastInvocationCount 1) { throw new Error( Invalid invocation order, received ${value} but last invocation was ${lastInvocationCount}. ); } lastInvocationCount value; }); };使用 Buffer 原生方法的数值解析 targetprojects/javascript-example/fuzz_value_profiling.js 展示了直接用data.readInt32BE()从 Buffer 中读取大端 32 位整数并对结果做 XOR 校验后抛错的模式/** * param {number} n */ function encrypt(n) { return n ^ 0x11223344; } /** * param { Buffer } data */ module.exports.fuzz function (data) { if (data.length 16) { return; } if ( encrypt(data.readInt32BE(0)) 0x50555637 encrypt(data.readInt32BE(4)) 0x7e4f5664 encrypt(data.readInt32BE(8)) 0x5757493e encrypt(data.readInt32BE(12)) 0x784c5465 ) { throw Error(XOR with a constant is not a secure encryption method ;-)); } };该 target 还带有--sync构建参数的使用场景同步模式详见下文 build.sh。build.sh借助 compile_javascript_fuzzer 一键构建OSS-Fuzz 的 JavaScript 基础镜像预装了compile_javascript_fuzzer脚本。在build.sh中你需要安装项目依赖如有必要把 TypeScript 等语言编译为 JavaScript调用compile_javascript_fuzzer构建各个 fuzzer。compile_javascript_fuzzer脚本承担两项关键职责确保jazzer.js/core已安装从而可以使用其 CLI 执行 fuzz 测试生成一个 libFuzzer 的即插即用包装脚本——生成的脚本接受与 libFuzzer 相同的命令行参数底层只是把这些参数转发给 Jazzer.js 所用的 libFuzzer native addon。以 javascript-example 的 projects/javascript-example/build.sh 为例#!/bin/bash -eu # Install dependencies. npm install # Install Jazzer.js npm install --save-dev jazzer.js/core # Build Fuzzers. compile_javascript_fuzzer example fuzz_promise.js compile_javascript_fuzzer example fuzz_string_compare.js --sync compile_javascript_fuzzer example fuzz_value_profiling.js --sync其中compile_javascript_fuzzer的参数含义如下参数含义第 1 个参数项目在$SRC目录下的相对路径如example第 2 个参数项目内 fuzz test 文件的相对路径如fuzz_string_compare.js其余参数原样转发给 Jazzer.js CLI如--sync表示同步模式执行--sync标志指示 Jazzer.js 以同步方式执行 fuzz target适用于不依赖异步操作的简单 target对于返回 Promise 的异步 target如fuzz_promise.js则不加该标志让 Jazzer.js 处理异步完成。构建完成后OSS-Fuzz 的回归regression与持续模糊测试阶段会直接运行这些生成的 libFuzzer 包装脚本。FuzzedDataProvider把原始字节翻译成 JS 原生类型Jazzer.js 提供了FuzzedDataProvider它的作用是把 fuzzer 输入的原始字节流翻译成便于使用的 JavaScript 原始类型从而显著简化 fuzz target 的编写。其功能与 Java、C 等其他语言中的FuzzedDataProvider类似C 侧的通用思路可参考 Google 的 split-inputs 文档。使用FuzzedDataProvider的 fuzz target 写法如下const { FuzzedDataProvider } require(jazzer.js/core); /** * param { Buffer } fuzzerInputData */ module.exports.fuzz function (fuzzerInputData) { const data new FuzzedDataProvider(fuzzerInputData); const i data.consumeIntegral(4); const s data.consumeRemainingAsString(); exploreMe(i, s); };代码要点从jazzer.js/core中解构引入FuzzedDataProvider用 fuzzer 传入的 Buffer 构造 provider 实例consumeIntegral(4)从输入中消费一个 4 字节整数consumeRemainingAsString()把剩余输入整体消费为字符串随后用这两个有意义的值调用被测函数exploreMe(i, s)。这正是 projects/typescript-example 中fuzz_explore_me.ts所演示的模式TypeScript 项目把 fuzz target 写成.ts文件构建时编译为 JavaScript 后同样通过compile_javascript_fuzzer构建。该示例也证明了所有可转译为 JavaScript 的语言都能无缝复用这套 fuzz 流程。实战自查清单完成 JavaScript 项目接入后可用以下清单快速自查project.yaml中language: javascript、fuzzing_engines: [libfuzzer]、sanitizers: [none]是否齐全Dockerfile是否以FROM gcr.io/oss-fuzz-base/base-builder-javascript开头fuzz target 是否导出了module.exports.fuzz function (data) { ... }且参数类型为 Bufferbuild.sh是否依次完成npm install、安装jazzer.js/core、调用compile_javascript_fuzzer是否按 target 的同步/异步特性正确使用--sync标志复杂输入解析是否优先考虑FuzzedDataProviderconsumeIntegral、consumeRemainingAsString等以提升命中率。将上述文件放入projects/your-project/目录并提交后即可按 OSS-Fuzz 的通用项目提交流程申请接入由 OSS-Fuzz 基础设施持续运行模糊测试并报告崩溃。【免费下载链接】oss-fuzzOSS-Fuzz - continuous fuzzing for open source software.项目地址: https://gitcode.com/gh_mirrors/os/oss-fuzz创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考