测试猜测字符功能

  • 编写被测 API,当且仅当输入数组长度是 8、内容是 "Cangjie!" 对应的 ASCII 时抛出异常,纯随机的情况下最差需要 264 次猜测才会触发异常。
  • 创建 Fuzzer 并且调用待测 API,进入主流程。
// 导入依赖的类
import fuzz.fuzz.Fuzzer

main() {
    // 创建Fuzzer并启动fuzz流程
    Fuzzer(api).startFuzz()
    return 0
}


// 被测函数,在满足特定条件会抛出异常,该异常会被 Fuzzer 捕获
public func api(data: Array<UInt8>): Int32 {
    if (data.size == 8 && data[0] == b'C' && data[1] == b'a' && data[2] == b'n' && data[3] == b'g' && data[4] == b'j' &&
        data[5] == b'i' && data[6] == b'e' && data[7] == b'!') {
        throw Exception("TRAP")
    }
    return 0
}

Linux 的编译命令是:cjc fuzz_main.cj --link-options="--whole-archive $CANGJIE_HOME/lib/linux_x86_64_llvm/libclang_rt.fuzzer_no_main.a -no-whole-archive -lstdc++" --sanitizer-coverage-inline-8bit-counters

macOS 的编译命令是:cjc fuzz_main.cj --link-options="$CANGJIE_HOME/lib/linux_x86_64_llvm/libclang_rt.fuzzer_no_main.a -lc++" --sanitizer-coverage-inline-8bit-counters

释义:

  • link-options 是链接时选项,fuzz 库本身依赖符号 LLVMFuzzerRunDriver,该符号需要开发者自行解决。
    • 仓颉语言在 $CANGJIE_HOME/lib/linux_x86_64_llvm/libclang_rt.fuzzer_no_main.a 存放一份 修改过 的 libfuzzer,对标准的 libfuzzer 进行了增强,见 实验性特性-覆盖率信息打印
    • 可以使用 find $(clang -print-runtime-dir) -name "libclang_rt.fuzzer_no_main*.a" 寻找本地安装好的静态库文件。
  • 向 Linux 编译需要使用 whole-archive libfuzzer.a 是因为 cjc 调用 ld 后端时,从左到右顺序是 libfuzzer.alibcangjie-fuzz-fuzz.a、 libc 等基础库,该顺序会导致 libcangjie-fuzz-fuzz.a 依赖的 LLVMFuzzerRunDriver 符号未被找到。解决方案有:
    • libfuzzer.a 放到 libcangjie-fuzz-fuzz.a 后面;
    • 使用 whole-archive libfuzzer.a 来规避符号找不到的问题。
  • -lstdc++ (Linux) / -lc++ (macOS) 用于链接 libfuzzer 依赖的 std 库。
  • --sanitizer-coverage-inline-8bit-counterscjc 的编译选项,它会对当前 package 执行覆盖率反馈插桩,详见 cjc 编译器使用手册。
    • 其他高级的参数有:--sanitizer-coverage-trace-compares(提高Fuzz变异的效率)、--sanitizer-coverage-pc-table(Fuzz结束后打印覆盖率信息)。

libfuzzer 体验类似,可以直接运行,数秒后(取决于 CPU 性能)可获得 crash,且输入的数据是 "Cangjie!"

运行结果

$ ./main
INFO: Seed: 246468919
INFO: Loaded 1 modules   (15 inline 8-bit counters): 15 [0x55bb7c76dcb0, 0x55bb7c76dcbf),
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
INFO: A corpus is not provided, starting from an empty corpus
#2      INITED ft: 4 corp: 1/1b exec/s: 0 rss: 28Mb
#420    NEW    ft: 5 corp: 2/9b lim: 8 exec/s: 0 rss: 28Mb L: 8/8 MS: 3 CrossOver-InsertByte-InsertRepeatedBytes-
#1323   NEW    ft: 6 corp: 3/17b lim: 14 exec/s: 0 rss: 28Mb L: 8/8 MS: 3 InsertByte-InsertByte-CrossOver-
#131072 pulse  ft: 6 corp: 3/17b lim: 1300 exec/s: 65536 rss: 35Mb
#262144 pulse  ft: 6 corp: 3/17b lim: 2600 exec/s: 65536 rss: 41Mb
#295225 NEW    ft: 7 corp: 4/25b lim: 2930 exec/s: 73806 rss: 43Mb L: 8/8 MS: 2 ShuffleBytes-ChangeByte-
#514006 NEW    ft: 8 corp: 5/33b lim: 4096 exec/s: 73429 rss: 53Mb L: 8/8 MS: 1 ChangeByte-
#524288 pulse  ft: 8 corp: 5/33b lim: 4096 exec/s: 74898 rss: 53Mb
#1048576        pulse  ft: 8 corp: 5/33b lim: 4096 exec/s: 61680 rss: 78Mb
#1064377        NEW    ft: 9 corp: 6/41b lim: 4096 exec/s: 62610 rss: 79Mb L: 8/8 MS: 1 ChangeByte-
#1287268        NEW    ft: 10 corp: 7/49b lim: 4096 exec/s: 61298 rss: 90Mb L: 8/8 MS: 1 ChangeByte-
#2097152        pulse  ft: 10 corp: 7/49b lim: 4096 exec/s: 59918 rss: 128Mb
#2875430        NEW    ft: 11 corp: 8/57b lim: 4096 exec/s: 61179 rss: 165Mb L: 8/8 MS: 2 ChangeBinInt-ChangeByte-
#4194304        pulse  ft: 11 corp: 8/57b lim: 4096 exec/s: 59918 rss: 227Mb
#4208258        NEW    ft: 12 corp: 9/65b lim: 4096 exec/s: 60117 rss: 228Mb L: 8/8 MS: 3 CrossOver-CrossOver-ChangeBit-
[WARNING]: Detect uncatched exception, maybe caused by bugs, exit now
An exception has occurred:
Exception: TRAP
         at default.api(std/core::Array<...>)(/data/Cangjie/fuzz_main.cj:14)
         at _ZN7default3apiER_ZN8std$core5ArrayIhE_cc_wrapper(/data/Cangjie/fuzz_main.cj:0)
         at libfuzzerCallback(fuzz/fuzz/callback.cj:20)
[INFO]: data is: [67, 97, 110, 103, 106, 105, 101, 33]
[INFO]: data base64: Q2FuZ2ppZSE=
crash file will stored with libfuzzer
==899957== ERROR: libFuzzer: fuzz target exited
SUMMARY: libFuzzer: fuzz target exited
MS: 1 ChangeByte-; base unit: 7d8b0108ce76a937161065eafcde95bbf3d47dbf
0x43,0x61,0x6e,0x67,0x6a,0x69,0x65,0x21,
Cangjie!
artifact_prefix='./'; Test unit written to ./crash-555e7af32a2ceb585cdd9ce810c4804e65d41cea
Base64: Q2FuZ2ppZSE=