Files
2024-09-27 19:21:56 +08:00

10 KiB
Raw Permalink Blame History

测试框架Fuzzing安全测试指导

Fuzzing简介

模糊测试fuzzing test是一种软件测试技术其核心思想是将自动或半自动生成的随机数据输入到一个程序中并监视程序异常如崩溃断言assertion失败以发现可能的程序错误比如内存泄漏访问越界等。

Fuzzing测试框架使用了LLVM编译器框架中的libFuzzer作为Fuzzing引擎进行构建libFuzzer是一个基于LLVM编译时路径插桩可以对被测库API进行路径引导测试的Fuzzing引擎。

使用Fuzzing测试框架需要完成fuzzer测试用例初始化、fuzzer用例编写、fuzzer用例编译和fuzzer用例执行几步。

Fuzzing测试关注的风险接口

开发者应该了解自身模块的接口数据输入是基于不可信的来源输入,特别是针对

  • 解析处理远程发送来的TCP/UDP或者蓝牙等协议数据
  • 通过复杂的文件解码处理等,包括音视频、图片解码、解压缩等
  • IPC跨进程的数据输入处理

通过Fuzzing的覆盖引导能力可以有效的探测和消减外部输入造成的内存安全问题也可以极大的增强系统稳定性。

使用测试框架开展Fuzzing

配置启动测试框架

参考 开发者测试组件中的描述完成测试框架安装、设备连接配置并在linux环境下通过

./start.sh

启动测试框架。

单个Fuzz用例初始化

  1. Fuzz测试用例生成

    执行gen命令用于fuzzer源文件生成会自动生成fuzzer源文件、fuzzer配置文件和corpus语料目录结构如下

    calculator_fuzzer/
    ├── corpus                        # Fuzz语料目录
    │   ├── init                      # Fuzz语料
    ├── BUILD.gn                      # Fuzz用例编译配置
    ├── calculator_fuzzer.cpp              # Fuzz用例源文件
    ├── calculator_fuzzer.h                # Fuzz用例头文件
    ├── project.xml                   # Fuzz选项配置文件
    
  2. 命令参数说明参数可以指定fuzzer名称和fuzzer路径

    gen -t TESTTYPE -fn FUZZERNAME -dp DIRECTORYPATH
    
    参数 描述 说明 备注
    -t testtype 测试类型 目前仅支持"FUZZ"
    -fn fuzzername fuzzer名称 为显式区分Fuzz用例名称必须以测试套前缀小写 + _fuzzer形式命名
    -dp dirpath fuzzer生成路径 路径不存在则自动创建目录
  3. gen命令示例-t、-fn和-dp均为必选项

    gen -t FUZZ -fn calculator_fuzzer -dp test/developertest/example/calculator/test/fuzztest/common
    

    执行完毕后会在test/developertest/example/calculator/test/fuzztest/common目录下生成一个Fuzz用例demo。

Fuzz用例编写

  1. 源文件编写

    Fuzz用例主要在**${fuzzer名称}.cpp**源文件中一个Fuzz用例仅支持一个接口进行fuzz测试。

    源文件包含两个接口:

    接口 说明
    LLVMFuzzerTestOneInput Fuzz入口函数由Fuzz框架调用
    DoSomethingInterestingWithMyAPI 被测试接口,实现各业务被测试逻辑

    img 说明: DoSomethingInterestingWithMyAPI接口名称允许依据业务逻辑修改。两接口参数data和size为fuzz测试标准化参数不可修改。

    #include "calculator_fuzzer.h"
    
    #include <stddef.h>
    #include <stdint.h>
    
    const int FUZZ_DATA_LEN = 3;
    const int FUZZ_FST_DATA = 0;
    const int FUZZ_SND_DATA = 1;
    const int FUZZ_TRD_DATA = 2;
    const int FUZZ_FTH_DATA = 3;
    
    namespace OHOS {
        bool DoSomethingInterestingWithMyAPI(const uint8_t* data, size_t size)
        {
            bool result = false;
            if (size >= FUZZ_DATA_LEN) {
                result = data[FUZZ_FST_DATA] == 'F' &&
                    data[FUZZ_SND_DATA] == 'U' &&
                    data[FUZZ_TRD_DATA] == 'Z' &&
                    data[FUZZ_FTH_DATA] == 'Z';
            }
            return result;
        }
    }
    
    /* Fuzzer entry point */
    extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
    {
        /* Run your code on data */
        OHOS::DoSomethingInterestingWithMyAPI(data, size);
        return 0;
    }
    

    注意当data需强制转换为字符串类型时需要携带size例如:

    std::string result((const char*) data, size);
    
  2. BUILD.gn编写

    基于[ohos_fuzztest]配置Fuzz模板例如

    需要注意的是fuzz_config_file, 使用gen命令生成的BUILD.GN文件中没有指明需要写完测试套后加在BUILD.gn中

    ohos_fuzztest("CalculatorFuzzTest") {     #定义测试套名称CalculatorFuzzTest
      module_out_path = module_output_path
      fuzz_config_file = "//test/developertest/examples/calculator/test/fuzztest/common/calculator_fuzzer"
      include_dirs = []
      cflags = [
        "-g",
        "-O0",
        "-Wno-unused-variable",
        "-fno-omit-frame-pointer",
      ]
      sources = [ "calculator_fuzzer.cpp" ]
    }
    

    [group]引用测试套,例如:

    group("fuzztest") {
      testonly = true
      deps = []
    
      deps += [
        # deps file
        ":CalculatorFuzzTest",     #引用测试套
      ]
    }
    

    注意:

    • 测试套名称必须采用大驼峰风格并且必须以FuzzTest结尾测试套前缀与fuzzer目录名相对应例如calculator_fuzzer只能有一个下划线

    • module_out_path为测试套编译输出目录内容为部件+模块名。
  3. Fuzz配置编写

    project.xml为DTFuzz参数配置文件提供基于libFuzzer参数的动态配置更多参数配置可参考libFuzzer参数配置

    <!-- maximum length of a test input -->
    <max_len>1000</max_len>
    <!-- maximum total time in seconds to run the DTFuzz -->
    <max_total_time>300</max_total_time>
    <!-- memory usage limit in Mb -->
    <rss_limit_mb>4096</rss_limit_mb>
    

Fuzz用例编译

添加Fuzz用例编译到模块的test_list中

  1. 在需要DTFuzz测试的对应模块bundle.json中添加Fuzz用例路径如在bundle.json添加

    "tests": [
      "//test/developertest/examples/calculator/test:unittest",
      "//test/developertest/examples/calculator/test:fuzztest", #添加DTFuzz用例路径
      "//test/developertest/examples/detector/test:unittest",
      "//test/developertest/examples/sleep/test:performance",
      "//test/developertest/examples/distributedb/test:distributedtest"
    ]
    
  2. 在用例路径下的BUILD.gn添加group如examples/calculator/test的BUILD.gn

    group("fuzztest") {
      testonly = true
      deps = []
    
      deps += [ "fuzztest/common/calculator_fuzzer:fuzztest" ]
    }
    

Fuzz用例执行

Fuzz能力集成在测试类型-t中新增FUZZ类型执行Fuzz测试指令示例其中-t为必选-ss和-tm为可选

run -t FUZZ -ss developertest -tm calculator
参数 描述 说明 备注
-t TESTTYPE 测试类型
-ss SUBSYSTEM 子系统 被测试子系统
-tm TESTMODULE 模块 被测试模块如calculator
  • Windows环境脱离源码执行

    Windows环境可通过归档DTFuzz用例配置文件project.xml、语料corpus和可执行文件执行DTFuzz。

    1. 归档用例配置文件、语料以及用例可执行文件

      新建目录,如:

      注意:必须是\tests目录

      D:\test\tests
      

      用例可执行文件为DTFuzz源文件编译产出文件以二进制形式存储在out/release/tests/fuzztest下。测试用例的配置文件均编译输出在out/release/tests/res目录下对应的xxxx_fuzzer目录中。 将fuzztest目录以及res目录直接拷贝到该路径下即可。

    2. 配置用例路径

      config\user_config.xml中配置用例归档路径

      <!-- configure test cases path -->
      <test_cases>
        <dir>D:\test\tests</dir>     #用例可执行文件归档路径
      </test_cases>
      
    3. 执行用例

      执行DTFuzz命令示例

      run -t FUZZ -ts CalculatorFuzzTest
      

测试结果与日志

  • 通过在测试框架中执行测试指令,即可以生成测试日志和测试报告。

  • 测试结果

    测试用例的结果会直接显示在控制台上,执行一次的测试结果根路径如下:

    reports/xxxx-xx-xx-xx-xx-xx
    

    测试用例格式化结果

    result/
    

    测试用例日志

    log/plan_log_xxxx-xx-xx-xx-xx-xx.log
    

    测试报告汇总

    summary_report.html
    

    测试报告详情

    details_report.html
    

    最新测试报告

    reports/latest