51 个版本 (30 个稳定版)

新版本 6.0.0 2024年8月21日
5.2.2 2024年7月12日
5.2.0 2024年5月10日
5.0.0 2024年1月22日
0.1.0-alpha.62020年11月19日

#86编码

Download history 8073/week @ 2024-05-03 10575/week @ 2024-05-10 9884/week @ 2024-05-17 9847/week @ 2024-05-24 10280/week @ 2024-05-31 11435/week @ 2024-06-07 11283/week @ 2024-06-14 12683/week @ 2024-06-21 9438/week @ 2024-06-28 9423/week @ 2024-07-05 11561/week @ 2024-07-12 8979/week @ 2024-07-19 9087/week @ 2024-07-26 7408/week @ 2024-08-02 8287/week @ 2024-08-09 8496/week @ 2024-08-16

每月下载量 34,896
用于 72 包(4 个直接使用)

AGPL-3.0 WITH mif-exception

56KB
557

test-fuzz

test-fuzz 是一个Cargo子命令和一组Rust宏,用于自动化与 afl.rs 相关的模糊测试任务,包括

  • 生成模糊测试语料库
  • 实现模糊测试框架

test-fuzz 通过Rust的测试功能完成这些任务(部分)。例如,要生成模糊测试语料库,test-fuzz 在每次调用时记录目标参数,这发生在 cargo test 调用期间。同样,test-fuzz 将模糊测试框架作为在 cargo-test 生成的二进制文件中的附加测试实现。这种与Rust测试功能的紧密集成是命名 test-fuzz 的原因。

内容

  1. 安装
  2. 概述
  3. 组件
  4. test-fuzz 包功能
  5. 自动生成的语料库文件
  6. 环境变量
  7. 限制
  8. 技巧和窍门
  9. 许可证

安装

使用以下命令安装 cargo-test-fuzzafl.rs

cargo install cargo-test-fuzz cargo-afl

概述

使用 test-fuzz 进行模糊测试基本上是三个步骤:*

  1. 确定模糊测试目标:

    • 将以下 dependencies 添加到目标crate的 Cargo.toml 文件中
      serde = "*"
      test-fuzz = "*"
      
    • 在目标函数之前使用 test_fuzz
      #[test_fuzz::test_fuzz]
      fn foo(...) {
          ...
      }
      
  2. 通过运行 cargo test 生成语料库

    cargo test
    
  3. 模糊测试目标 通过运行 cargo test-fuzz

    cargo test-fuzz foo
    

重启后可能需要额外的初步步骤。

cargo afl system-config

注意,上述命令在内部运行 sudo。因此,您可能需要输入密码。

组件

test_fuzz

在函数前加上 test_fuzz 宏表示该函数是模糊测试的目标。

test_fuzz 宏的主要作用包括

  • 为目标添加工具,以序列化其参数并将它们写入每个目标被调用时的语料库文件。此工具受 #[cfg(test)] 保护,以便仅在运行测试时生成语料库文件(然而,参见下文的 enable_in_production)。
  • 添加一个测试来从标准输入读取和反序列化参数,并将目标应用于它们。测试检查一个环境变量,该变量由 cargo test-fuzz 设置,以便测试不会在正常调用 cargo test 时阻塞尝试从标准输入读取。测试被包含在一个模块中,以降低名称冲突的可能性。当前模块的名称是 target_fuzz,其中 target 是目标的名称(然而,参见下文的 rename)。

参数

bounds= "where_predicates"

对用于序列化和反序列化参数的结构体施加 where_predicates(例如,特质界限)。这可能是有必要的,例如,如果目标的参数类型是一个关联类型。有关示例,请参阅此存储库中的 associated_type.rs

generic_args= "parameters"

在模糊测试时,使用 parameters 作为目标的类型参数。示例

#[test_fuzz(generic_args = "String")]
fn foo<T: Clone + Debug + Serialize>(x: &T) {
    ...
}

注意:目标的参数必须对其实例化的每个类型参数都是可序列化的。但是,当目标使用 parameters 实例化时,其参数只需要可反序列化。

impl_generic_args= "parameters"

在模糊测试时,使用 parameters 作为目标的 Self 类型参数。示例

#[test_fuzz_impl]
impl<T: Clone + Debug + Serialize> for Foo {
    #[test_fuzz(impl_generic_args = "String")]
    fn bar(&self, x: &T) {
        ...
    }
}

注意:目标的参数必须对其实例化的每个 Self 类型参数都是可序列化的。但是,当目标的 Self 使用 parameters 实例化时,其参数只需要可反序列化。

convert= "X, Y"

在序列化目标参数时,使用类型 Y 的实现将类型 X 的值转换为类型 Y,或者使用类型 &X 的非标准特质 test_fuzz::FromRef<X> 将类型 Y 转换为。反序列化时,使用类型 Y 的非标准特质 test_fuzz::Into<X> 将这些值转换回类型 X

这意味着使用 convert = "X, Y" 时必须伴随某些实现。如果 X 实现 Clone,则 Y 可以实现以下内容

impl From<X> for Y {
    fn from(x: X) -> Self {
        ...
    }
}

如果 X 未实现 Clone,则 Y 必须实现以下内容

impl test_fuzz::FromRef<X> for Y {
    fn from_ref(x: &X) -> Self {
        ...
    }
}

此外,Y 必须实现以下内容(无论 X 是否实现 Clone

impl test_fuzz::Into<X> for Y {
    fn into(self) -> X {
        ...
    }
}

test_fuzz::Into 的定义与 std::convert::Into 相同。使用非标准特质的原因是为了避免标准特质的泛型实现可能引起的冲突。

enable_in_production

在不运行测试的情况下生成语料库文件,前提是已设置环境变量 TEST_FUZZ_WRITE。默认情况下,仅在运行测试时生成语料库文件,无论是否设置了 TEST_FUZZ_WRITE。当从包目录外部运行目标时,将 TEST_FUZZ_MANIFEST_PATH 设置为包的 Cargo.toml 文件路径。

警告:设置 enable_in_production 可能会引入拒绝服务攻击向量。例如,为频繁以不同参数调用的函数设置此选项可能会导致磁盘空间耗尽。设置 TEST_FUZZ_WRITE 的检查旨在提供一些防御措施。尽管如此,在使用之前请仔细考虑此选项。

execute_with= "function"

而不是直接调用目标

  • 构建一个类型为 FnOnce() -> R 的闭包,其中 R 是目标的返回类型,这样调用闭包就会调用目标;
  • 使用闭包调用 function

以这种方式调用目标允许 function 设置调用环境。这在例如模糊测试 Substrate 外部性 时非常有用。

no_auto_generate

不要尝试为目标自动生成语料库文件。

only_generic_args

在运行测试时记录目标的泛型参数,但不生成语料库文件和实现模糊测试框架。当目标是一个泛型函数,但不确定应该使用哪些类型参数进行模糊测试时,这非常有用。

期望的工作流程是:启用 only_generic_args,然后运行 cargo test,然后运行 cargo test-fuzz --display generic-args。其中一个生成的泛型参数可能可以用作 generic_argsparameters。类似地,由 cargo test-fuzz --display impl-generic-args 生成的泛型参数可能可以用作 impl_generic_argsparameters

然而,需要注意的是,即使在测试期间以某些参数调用了目标,这也并不意味着当使用这些参数时目标的参数是可序列化和可反序列化的。--display generic-args/--display impl-generic-args 的结果仅仅是提示性的。

rename= "name"

在将模块添加到包围作用域时,将目标视为具有 name 名称。在展开 test_fuzz 宏时,将模块定义添加到包围作用域。默认情况下,模块名称如下

  • 如果目标不在 impl 块中,模块名称为 target_fuzz,其中 target 是目标的名称。
  • 如果目标出现在 impl 块中,模块名称为 path_target_fuzz,其中 pathimplSelf 类型转换为蛇形命名并连接 _

但是,使用此选项会导致模块名称为 name_fuzz。示例

#[test_fuzz(rename = "bar")]
fn foo() {}

// Without the use of `rename`, a name collision and compile error would result.
mod foo_fuzz {}

test_fuzz_impl

每当在 impl 块中使用 test_fuzz 宏时,必须使用 test_fuzz_impl 宏来先于 impl。示例

#[test_fuzz_impl]
impl Foo {
    #[test_fuzz]
    fn bar(&self, x: &str) {
        ...
    }
}

此要求的原因如下。展开 test_fuzz 宏将在包围作用域中添加一个模块定义。然而,模块定义不能出现在 impl 块中。在 impl 前面使用 test_fuzz_impl 宏会导致模块添加到 impl 块之外。

如果您看到以下错误,这很可能意味着缺少 test_fuzz_impl 宏的使用

error: module is not supported in `trait`s or `impl`s

test_fuzz_impl 目前没有选项。

cargo test-fuzz 命令

cargo test-fuzz 命令用于与模糊测试目标交互,并操作它们的语料库、崩溃、挂起和工作队列。示例调用包括

  1. 列出模糊测试目标

    cargo test-fuzz --list
    
  2. 显示目标 foo 的语料库

    cargo test-fuzz foo --display corpus
    
  3. 模糊测试目标 foo

    cargo test-fuzz foo
    
  4. 重新播放针对目标 foo 找到的崩溃

    cargo test-fuzz foo --replay crashes
    

用法

Usage: cargo test-fuzz [OPTIONS] [TARGETNAME] [-- <ARGS>...]

Arguments:
  [TARGETNAME]  String that fuzz target's name must contain
  [ARGS]...     Arguments for the fuzzer

Options:
      --backtrace                 Display backtraces
      --consolidate               Move one target's crashes, hangs, and work queue to its corpus; to
                                  consolidate all targets, use --consolidate-all
      --cpus <N>                  Fuzz using at most <N> cpus; default is all but one
      --display <OBJECT>          Display corpus, crashes, generic args, `impl` generic args, hangs,
                                  or work queue. By default, an uninstrumented fuzz target is used.
                                  To display with instrumentation, append `-instrumented` to
                                  <OBJECT>, e.g., --display corpus-instrumented.
      --exact                     Target name is an exact name rather than a substring
      --exit-code                 Exit with 0 if the time limit was reached, 1 for other
                                  programmatic aborts, and 2 if an error occurred; implies --no-ui,
                                  does not imply --run-until-crash or --max-total-time <SECONDS>
      --features <FEATURES>       Space or comma separated list of features to activate
      --list                      List fuzz targets
      --manifest-path <PATH>      Path to Cargo.toml
      --max-total-time <SECONDS>  Fuzz at most <SECONDS> of time (equivalent to -- -V <SECONDS>)
      --no-default-features       Do not activate the `default` feature
      --no-run                    Compile, but don't fuzz
      --no-ui                     Disable user interface
  -p, --package <PACKAGE>         Package containing fuzz target
      --persistent                Enable persistent mode fuzzing
      --pretty                    Pretty-print debug output when displaying/replaying
      --replay <OBJECT>           Replay corpus, crashes, hangs, or work queue. By default, an
                                  uninstrumented fuzz target is used. To replay with instrumentation
                                  append `-instrumented` to <OBJECT>, e.g., --replay
                                  corpus-instrumented.
      --reset                     Clear fuzzing data for one target, but leave corpus intact; to
                                  reset all targets, use --reset-all
      --resume                    Resume target's last fuzzing session
      --run-until-crash           Stop fuzzing once a crash is found
      --slice <SECONDS>           If there are not sufficiently many cpus to fuzz all targets
                                  simultaneously, fuzz them in intervals of <SECONDS> [default:
                                  1200]
      --test <NAME>               Integration test containing fuzz target
      --timeout <TIMEOUT>         Number of seconds to consider a hang when fuzzing or replaying
                                  (equivalent to -- -t <TIMEOUT * 1000> when fuzzing)
      --verbose                   Show build output when displaying/replaying
  -h, --help                      Print help
  -V, --version                   Print version

Try `cargo afl fuzz --help` to see additional fuzzer options.

便利函数和宏

警告:这些实用程序不包括在语义版本控制中,并且可能在 test-fuzz 的未来版本中删除。

dont_care!

dont_care! 宏可以用于实现类型 serde::Serialize/serde::Deserialize 的实现,这些类型易于构造,并且你不需要记录其值。直观地说,dont_care!($ty, $expr) 表示

  • 序列化时跳过 $ty 类型的值。
  • 反序列化时用 $expr 初始化 $ty 类型的值。

更具体地说,dont_care!($ty, $expr) 展开为以下内容

impl serde::Serialize for $ty {
    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        ().serialize(serializer)
    }
}

impl<'de> serde::Deserialize<'de> for $ty {
    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        <()>::deserialize(deserializer).map(|_| $expr)
    }
}

如果 $ty 是一个单元结构体,那么可以省略 $expr。也就是说,dont_care!($ty) 等同于 dont_care!($ty, $ty)

leak!

leak! 宏可以帮助序列化引用类型的目标参数,其类型实现了 ToOwned 特性。它打算与 convert 选项一起使用。

具体来说,以下形式的调用声明了一个类型 LeakedX,并为它实现了 Fromtest_fuzz::Into 特性

leak!(X, LeakedX);

然后可以使用 LeakedXconvert 选项如下

#[test_fuzz::test_fuzz(convert = "&X, LeakedX")

conversion.rs 中,出现了一个 X 的示例,其中 Path

更普遍地,形式为 leak!($ty, $ident) 的调用展开为以下内容

#[derive(Clone, std::fmt::Debug, serde::Deserialize, serde::Serialize)]
struct $ident(<$ty as ToOwned>::Owned);

impl From<&$ty> for $ident {
    fn from(ty: &$ty) -> Self {
        Self(ty.to_owned())
    }
}

impl test_fuzz::Into<&$ty> for $ident {
    fn into(self) -> &'static $ty {
        Box::leak(Box::new(self.0))
    }
}

serialize_ref / deserialize_ref

serialize_refdeserialize_ref 函数类似于 leak!,但它们打算与 Serde 的 serialize_withdeserialize_with 字段属性(分别)一起使用。

fn serialize_ref<S, T>(x: &&T, serializer: S) -> Result<S::Ok, S::Error>
where
    S: serde::Serializer,
    T: serde::Serialize,
{
    <T as serde::Serialize>::serialize(*x, serializer)
}

fn deserialize_ref<'de, D, T>(deserializer: D) -> Result<&'static T, D::Error>
where
    D: serde::Deserializer<'de>,
    T: serde::de::DeserializeOwned + std::fmt::Debug,
{
    let x = <T as serde::de::Deserialize>::deserialize(deserializer)?;
    Ok(Box::leak(Box::new(x)))
}

serialize_ref_mut / deserialize_ref_mut

serialize_ref_mutdeserialize_ref_mutserialize_refdeserialize_ref 类似(分别),只是它们在可变引用上操作,而不是在不可变引用上。

test-fuzz 包功能

本节中的功能适用于整个 test-fuzz 包。按照 The Cargo Book 中的说明,在 test-fuzz 的依赖规范中启用它们。例如,要启用 cast_checks 功能,使用

test-fuzz = { version = "*", features = ["cast_checks"] }

test-fuzz 包当前支持以下功能

cast_checks

使用 cast_checks 来自动检查目标函数中的无效类型转换。

Serde 格式

test-fuzz 可以使用多种 Serde 格式序列化目标参数。以下用于选择格式的功能。

自动生成的语料库文件

cargo-test-fuzz 可以自动生成实现特定特质的类型的值。如果目标的所有参数类型都实现了这样的特质,cargo-test-fuzz 可以自动为目标生成语料库文件。

cargo-test-fuzz 目前支持的特性及其生成的值如下

特质
有界 T::min_value()T::max_value()
有界+添加+一个 T::min_value() + T::一个()
有界+添加++两个 T::min_value() / T::两个() + T::max_value() / T::两个()
有界+添加++两个+一个 T::min_value() / T::两个() + T::max_value() / T::两个() + T::一个()
有界++一个 T::max_value() - T::一个()
默认 T::默认()

环境变量

TEST_FUZZ_LOG

在宏展开期间

  • 如果将 TEST_FUZZ_LOG 设置为 1,则将所有仪器化的模糊目标模块定义写入标准输出。
  • 如果 TEST_FUZZ_LOG 设置为 crate 名称,则将那个 crate 的仪器化模糊测试目标和模块定义写入标准输出。

这可以用于调试。

TEST_FUZZ_MANIFEST_PATH

当从包目录外部运行目标时,在此位置找到包的 Cargo.toml 文件。当使用 enable_in_production 时可能需要设置此环境变量。

TEST_FUZZ_WRITE

当不运行测试这些目标的测试时,生成语料库文件。

限制

可克隆的参数

目标参数必须实现 Clone 特性。此要求的原因是参数需要在两个地方使用:在写入语料库文件的 test-fuzz 内部函数中,以及在目标函数体中。为了解决这个冲突,在传递给前者之前参数会被克隆。

可序列化/反序列化的参数

通常,目标的参数必须实现 serde::Serializeserde::Deserialize 特性,例如通过 派生它们。我们说“通常”,因为 test-fuzz 知道如何处理某些特殊的情况,这些情况通常不可序列化/反序列化。例如,类型为 &str 的参数在序列化时转换为 String,在反序列化时转换回 &str。另请参阅上述的 generic_argsimpl_generic_args

全局变量

test-fuzz 实现的模糊测试工具不初始化全局变量。虽然 execute_with 提供了一些补救措施,但这不是完整的解决方案。通常,模糊测试依赖于全局变量的函数需要专门的方法。

convertgeneric_args / impl_generic_args

这些选项以下列方式互不兼容。如果一个模糊测试目标的参数类型是类型参数,convert 将尝试匹配类型参数,而不是参数设置的类型。支持后者似乎需要像编译器执行的那样模拟类型替换。但是,这目前尚未实现。

技巧和窍门

  • #[cfg(test)] 未启用集成测试。如果您的目标仅通过集成测试进行测试,则考虑使用 enable_in_productionTEST_FUZZ_WRITE 生成语料库。(注意,enable_in_production 伴随的警告。)

  • 如果您知道目标所在包的名称,将-p <package>传递给cargo test/cargo test-fuzz可以显著减少构建时间。同样,如果您知道目标仅从一个集成测试中被调用,传递--test <name>也可以减少构建时间。

  • Rust不允许您为其他存储库的类型实现serde::Serialize。但您可能能够修补其他存储库以使其类型可序列化。此外,cargo-clone可以用于获取依赖项的存储库。

  • Serde属性在实现serde::Serialize/serde::Deserialize对于难以处理的数据类型时非常有用。

许可证

test-fuzz根据AGPLv3许可协议许可并分发,带有宏和内联函数异常。简单来说,在您的软件中使用test_fuzztest_fuzz_impltest-fuzz便捷函数和宏不需要它受AGPLv3许可证的约束。

依赖项

~2.7–5MB
~84K SLoC