#mutation #fuzzer #fuzzing #arbitrary #random-string

no-std mutatis

mutatis 是为 Rust 中的模糊器编写自定义、结构化测试用例突变体的库

3 个版本 (破坏性更新)

新功能 0.3.0 2024 年 8 月 19 日
0.2.0 2024 年 8 月 15 日
0.1.0 2024 年 8 月 15 日

#55测试

Download history 211/week @ 2024-08-10

每月 211 次下载

MIT/Apache 许可

145KB
1.5K SLoC

mutatis

轻松创建自定义、结构化的模糊器突变体。

crates.io docs.rs supported rustc stable

代码库 | 文档 | 指南 | 贡献

关于

最受欢迎的模糊器,包括 libfuzzerAFL — 都是 基于覆盖率突变 的。

基于覆盖率 意味着模糊器会观察在测试系统中运行输入时执行了哪些代码。当创建新输入时,它会尝试执行新的代码路径,以最大化探索的代码量。如果一个新输入触发了新的代码路径执行,则将其添加到语料库中。如果一个新输入仅执行了已发现的代码路径,则将其丢弃。

基于突变 意味着在创建新输入时,模糊器会修改语料库中的现有输入。其想法是,如果现有输入触发了测试系统的有趣行为,那么对该输入的修改也可能同样如此,但也可能触发一些新的行为。考虑这种场景,当我们模糊一个编译器时:如果某个输入通过了解析器、类型检查器和代码生成器 — 而不是因为无效的令牌而提前退出 — 那么,从这个输入衍生出的新输入也很可能深入编译器的管道。至少,与完全随机的字符串相比,可能性更大。

但是当我们不是在对文本或二进制接口进行模糊测试时会发生什么?当我们有一个模糊器内置变异策略不太擅长针对的自定义输入类型时会发生什么?许多模糊器会提供一个钩子来定制从其语料库中的现有输入变异的例程,以创建新的候选输入,例如 libfuzzerfuzz_mutator! 钩子。

mutatis 存在是为了让编写这些自定义变异器变得简单且高效。

使用默认变异器

使用默认、现成的变异器随机变异一个值

  • 创建一个 mutatis::Session
  • 调用 session.mutate,传入您希望变异的值。

这里有一个使用 mutatis 和其默认变异器随机变异值的简单示例

# fn foo() -> mutatis::Result<()> {
let mut point = (42, 36);

let mut session = mutatis::Session::new();
for _ in 0..3 {
    session.mutate(&mut point)?;
    println!("mutated point is {point:?}");
}

// Example output:
//
//     mutated point is (-565504428, 36)
//     mutated point is (-565504428, 49968845)
//     mutated point is (-1854163941, 49968845)
# Ok(())
# }
# foo().unwrap()

组合和自定义变异器

您可以使用 mutatis::mutators 模块中的变异器组合器从更简单的变异器构建更复杂的变异器,或者自定义变异策略,例如,维护类型的内部不变量或将结果值限制在特定范围内。通常将 mutatis::mutators 模块导入为别名 m

使用自定义变异器随机变异一个值

  • 使用 mutatis::mutators 组合器和 Mutate 特征适配器方法创建自定义变异器。
  • 创建一个 mutatis::Session
  • 调用 session.mutate_with,传入您希望变异的值和您希望用于变异的变异器。

这里有一个使用 mutatis 定义自定义变异器的示例,该变异器针对具有多个字段的自定义 struct 类型,并维护字段值之间的关系

# fn foo() -> mutatis::Result<()> {
use mutatis::{mutators as m, Mutate, Session};

/// A scary monster type.
#[derive(Debug)]
pub struct Monster {
    pos: [i32; 2],
    hp: u16,

    // Invariant: ghost's are already dead, so when `is_ghost = true` it must
    // always be the case that `hp = 0`.
    is_ghost: bool,
}

/// A mutator that mutates one of a monster's fields, while maintaining our
/// invariant that ghosts always have zero HP.
let mut mutator =
    // Mutate the `pos` field...
    m::array(m::i32()).proj(|x: &mut Monster| &mut x.pos)
        // ...or mutate the `hp` field...
        .or(
            m::u16()
                .proj(|x: &mut Monster| &mut x.hp)
                .map(|_ctx, monster| {
                    // If we mutated the `hp` such that it is non-zero, then the
                    // monster cannot be a ghost.
                    if monster.hp > 0 {
                        monster.is_ghost = false;
                    }
                    Ok(())
                }),
        )
        // ...or mutate the `is_ghost` field.
        .or(
            m::bool()
                .proj(|x: &mut Monster| &mut x.is_ghost)
                .map(|_ctx, monster| {
                    // If we turned this monster into a ghost, then its `hp`
                    // must be zero.
                    if monster.is_ghost {
                        monster.hp = 0;
                    }
                    Ok(())
                }),
        );

// Define a monster...
let mut monster = Monster {
    hp: 36,
    is_ghost: false,
    pos: [-8, 9000],
};

// ...and mutate it a bunch of times!
let mut session = Session::new();
for _ in 0..5 {
    session.mutate_with(&mut mutator, &mut monster)?;
    println!("mutated monster is {monster:?}");
}

// Example output:
//
//     mutated monster is Monster { pos: [-8, -1647191276], hp: 36, is_ghost: false }
//     mutated monster is Monster { pos: [-8, -1062708247], hp: 36, is_ghost: false }
//     mutated monster is Monster { pos: [-8, -1062708247], hp: 61401, is_ghost: false }
//     mutated monster is Monster { pos: [-8, -1062708247], hp: 0, is_ghost: true }
//     mutated monster is Monster { pos: [-8, 1487274938], hp: 0, is_ghost: true }
# Ok(())
# }
# foo().unwrap()

使用 #[derive(Mutate)] 自动推导变异器

首先,启用此 crate 的 derive 功能,然后在您的类型定义上贴上 #[derive(Mutate)]

# fn foo() -> mutatis::Result<()> {
#![cfg(feature = "derive")]
use mutatis::{Mutate, Session};

// An RGB color.
#[derive(Debug)]
#[derive(Mutate)] // Automatically derive a mutator for `Rgb`!
pub struct Rgb {
    r: u8,
    g: u8,
    b: u8,
}

// Create an RGB color: chartreuse.
let mut color = Rgb {
    r: 0x7f,
    g: 0xff,
    b: 0x00,
};

// ...and mutate it a bunch of times!
let mut session = Session::new();
for _ in 0..5 {
    session.mutate(&mut color)?;
    println!("mutated color is {color:?}");
}

// Example output:
//
//     mutated color is Rgb { r: 127, g: 45, b: 0 }
//     mutated color is Rgb { r: 127, g: 134, b: 0 }
//     mutated color is Rgb { r: 127, g: 10, b: 0 }
//     mutated color is Rgb { r: 127, g: 10, b: 29 }
//     mutated color is Rgb { r: 172, g: 10, b: 29 }
# Ok(())
# }
# #[cfg(feature = "derive")] foo().unwrap()

使用 mutatis::check 编写烟雾测试

当您在 Cargo.toml 中启用 check 功能时,the mutatis::check 模块 提供了一个小型的基于属性的测试框架,适用于编写用于本地开发和 CI 的烟雾测试。它不打算取代您用于深入、持续模糊测试的完整功能的、基于覆盖率的模糊测试引擎。

# #[cfg(feature = "check")]
#[cfg(test)]
mod tests {
    use mutatis::check::Check;

    #[test]
    fn test_that_addition_commutes() {
        Check::new()
            .iters(1000)
            .shrink_iters(1000)
            .run(|(a, b): &(i32, i32)| {
                if a + b == b + a {
                    Ok(())
                } else {
                    Err("addition is not commutative!")
                }
            })
            .unwrap();
    }
}

有关更多详细信息,请参阅 the check 模块的文档

文档

API 参考文档

API参考文档可在docs.rs上找到。

指南

查看指南,包括教程、讨论和食谱;所有不属于API参考类别的其他内容。

许可证

双许可MIT或Apache-2.0,由您选择。

除非您明确说明,否则您有意向包含在此项目中的任何贡献,根据Apache-2.0许可证的定义,将如上双许可,没有任何额外的条款或条件。

依赖项

~240–440KB