libFuzzer模糊测试引擎调研与自定义魔改
Brinmon 发表于 湖南 安全工具 597浏览 · 2024-09-14 07:02

libFuzzer类库模糊测试引擎调研

一、简介

项目地址:llvm-project/compiler-rt/lib/fuzzer at main · llvm/llvm-project (github.com)
libFuzzer 是由 LLVM 项目开发的覆盖率引导模糊测试引擎,广泛用于自动发现软件中的漏洞和错误。它通过不断生成和测试输入数据,提高代码覆盖率,找到潜在的缺陷。libFuzzer 尤其适合测试用 C 和 C++ 编写的代码。

二、libFuzzer 的模块结构

libFuzzer 是一个面向库的模糊测试引擎,广泛用于发现软件中的漏洞。了解其模块结构有助于深入理解其工作原理和扩展能力。以下是 libFuzzer 的主要模块结构:

  1. FuzzerDriver
    • 功能:这是 libFuzzer 的核心入口点,负责初始化模糊测试环境、读取输入文件、启动模糊测试循环。
    • 关键函数
      • main: 入口函数,负责解析命令行参数并调用 FuzzerDriver
      • FuzzerDriver: 核心循环,包含模糊测试的主逻辑。
  2. Mutation
    • 功能:负责生成新的测试用例,通过变异(Mutation)已有的输入数据来发现更多潜在的漏洞。
    • 关键函数
      • Mutate: 对输入数据进行变异。
      • CrossOver: 将两个输入数据交叉生成新的输入。
    • 相关文件FuzzerMutate.cpp, FuzzerMutate.h
  3. Corpus
    • 功能:管理测试用例集合(Corpus),包括收集和存储有效的测试用例。
    • 关键函数
      • AddToCorpus: 将新的测试用例添加到集合中。
      • LoadCorpus: 从磁盘加载测试用例集合。
    • 相关文件FuzzerCorpus.cpp, FuzzerCorpus.h
  4. Coverage
    • 功能:收集代码覆盖率信息,以便评估测试用例的有效性和指导变异策略。
    • 关键函数
      • UpdateCoverage: 更新覆盖率信息。
      • GetCoverage: 获取当前的覆盖率。
    • 相关文件FuzzerTracePC.cpp, FuzzerTracePC.h
  5. Crash Detection
    • 功能:检测程序崩溃,并记录相关的崩溃信息以便后续分析。
    • 关键函数
      • HandleCrash: 处理崩溃事件。
      • ReportCrash: 报告崩溃信息。
    • 相关文件FuzzerFork.cpp, FuzzerFork.h
  6. FuzzerInterface
    • 功能:定义模糊测试引擎的接口,包括用户需要实现的目标函数。
    • 关键函数
      • LLVMFuzzerTestOneInput: 用户提供的函数,libFuzzer 将变异后的输入传递给该函数进行测试。
    • 相关文件FuzzerInterface.h
  7. Options
    • 功能:管理 libFuzzer 的配置选项,允许用户通过命令行参数或配置文件来调整模糊测试行为。
    • 关键函数
      • ParseFlags: 解析命令行参数。
      • PrintHelp: 打印帮助信息。
    • 相关文件FuzzerOptions.cpp, FuzzerOptions.h
  8. Utils
    • 功能:提供各种实用工具函数,用于日志记录、内存管理等。
    • 关键函数
      • Print: 打印日志信息。
      • AllocateMemory: 分配内存。
    • 相关文件FuzzerUtil.cpp, FuzzerUtil.h
  9. MainLoop
    • 功能:控制模糊测试的主循环,协调各模块的工作。
    • 关键函数
      • RunOne: 运行一个测试用例。
      • Loop: 主循环,反复调用 RunOne 进行模糊测试。
    • 相关文件FuzzerLoop.cpp, FuzzerLoop.h

三、libFuzzer 功能详解

libFuzzer 的主要功能包括:

  • 覆盖率引导测试:通过覆盖率信息指导输入生成,使测试尽可能覆盖更多代码路径。
  • 最小化崩溃输入:自动最小化导致崩溃的输入,便于调试。
  • 多样化输入生成:支持多种输入生成策略,包括基于变异的输入生成。
  • 词典支持:支持用户提供的词典文件,以增强输入生成的效果。
  • 内存错误检测:结合 AddressSanitizer 使用,可以检测到各种内存错误,如缓冲区溢出和使用未初始化内存等。
  • 并行模糊测试:libFuzzer可以指定行运行加速模糊测试、提高覆盖率。
  • fork模式:它通过创建多个子进程来同时进行模糊测试,从而提高测试效率。

四、libFuzzer源码解析

Libfuzzer的主类构成

源码位置:llvm-project/compiler-rt/lib/fuzzer at main · llvm/llvm-project (github.com)
这个 Fuzzer 类的构成可以分为以下几个部分:

1. 构造函数与析构函数

  • Fuzzer(UserCallback CB, InputCorpus &Corpus, MutationDispatcher &MD, FuzzingOptions Options);
  • ~Fuzzer();
    #### 2. 公有成员函数(Public Member Functions)
  • 主要功能函数:

    • void Loop(Vector<SizedFile> &CorporaFiles); // 主循环,执行模糊测试过程
    • void ReadAndExecuteSeedCorpora(Vector<SizedFile> &CorporaFiles); // 读取并执行种子语料
    • void MinimizeCrashLoop(const Unit &U); // 最小化崩溃处理
    • void RereadOutputCorpus(size_t MaxSize); // 重新读取输出语料库
  • 时间与状态函数:

    • size_t secondsSinceProcessStartUp(); // 获取从进程启动到现在的秒数
    • bool TimedOut(); // 检查是否超时
    • size_t execPerSec(); // 计算每秒执行的次数
    • size_t getTotalNumberOfRuns(); // 获取总执行次数
  • 静态回调函数(Static Callback Functions):

    • static void StaticAlarmCallback();
    • static void StaticCrashSignalCallback();
    • static void StaticExitCallback();
    • static void StaticInterruptCallback();
    • static void StaticFileSizeExceedCallback();
    • static void StaticGracefulExitCallback();
  • 其他功能函数:

    • void ExecuteCallback(const uint8_t *Data, size_t Size); // 执行用户回调
    • bool RunOne(const uint8_t *Data, size_t Size, bool MayDeleteFile = false, InputInfo *II = nullptr, bool *FoundUniqFeatures = nullptr); // 运行一次模糊测试
    • void Merge(const Vector<std::string> &Corpora); // 合并语料库
    • void CrashResistantMergeInternalStep(const std::string &ControlFilePath); // 崩溃情况下的内部合并步骤
    • MutationDispatcher &GetMD(); // 获取变异调度器
    • void PrintFinalStats(); // 打印最终统计信息
    • void SetMaxInputLen(size_t MaxInputLen); // 设置最大输入长度
    • void SetMaxMutationLen(size_t MaxMutationLen); // 设置最大变异长度
    • void RssLimitCallback(); // 内存使用限制的回调
    • bool InFuzzingThread() const; // 检查是否在模糊测试线程中
    • size_t GetCurrentUnitInFuzzingThead(const uint8_t **Data) const; // 获取模糊测试线程中的当前单元数据
    • void TryDetectingAMemoryLeak(const uint8_t *Data, size_t Size, bool DuringInitialCorpusExecution); // 尝试检测内存泄漏
    • void HandleMalloc(size_t Size); // 处理内存分配操作
    • static void MaybeExitGracefully(); // 可能会优雅退出程序
    • std::string WriteToOutputCorpus(const Unit &U); // 将单元写入输出语料库

3. 私有成员函数(Private Member Functions)

  • void AlarmCallback(); // 闹钟信号的回调
  • void CrashCallback(); // 崩溃信号的回调
  • void ExitCallback(); // 程序退出的回调
  • void CrashOnOverwrittenData(); // 数据被覆盖时崩溃的回调
  • void InterruptCallback(); // 中断信号的回调
  • void MutateAndTestOne(); // 变异并测试一个单元
  • void PurgeAllocator(); // 清理内存分配器
  • void ReportNewCoverage(InputInfo *II, const Unit &U); // 报告新覆盖率
  • void PrintPulseAndReportSlowInput(const uint8_t *Data, size_t Size); // 打印状态并报告慢速输入
  • void WriteUnitToFileWithPrefix(const Unit &U, const char *Prefix); // 使用前缀将单元写入文件
  • void PrintStats(const char *Where, const char *End = "\n", size_t Units = 0, size_t Features = 0); // 打印统计信息
  • void PrintStatusForNewUnit(const Unit &U, const char *Text); // 打印新单元的状态信息
  • void CheckExitOnSrcPosOrItem(); // 检查是否需要退出
  • static void StaticDeathCallback(); // 处理死亡信号的回调
  • void DumpCurrentUnit(const char *Prefix); // 转储当前单元数据
  • void DeathCallback(); // 死亡信号的回调
  • void AllocateCurrentUnitData(); // 分配当前单元数据

4. 私有成员变量(Private Member Variables)

  • uint8_t *CurrentUnitData = nullptr; // 当前单元数据指针
  • std::atomic<size_t> CurrentUnitSize; // 当前单元大小(原子变量)
  • uint8_t BaseSha1[kSHA1NumBytes]; // 基本单元的SHA1校验和
  • bool GracefulExitRequested = false; // 是否请求优雅退出
  • size_t TotalNumberOfRuns = 0; // 总执行次数
  • size_t NumberOfNewUnitsAdded = 0; // 新单元的数量
  • size_t LastCorpusUpdateRun = 0; // 上次语料库更新的运行次数
  • bool HasMoreMallocsThanFrees = false; // 是否有比释放更多的内存分配操作
  • size_t NumberOfLeakDetectionAttempts = 0; // 检测内存泄漏的尝试次数
  • system_clock::time_point LastAllocatorPurgeAttemptTime = system_clock::now(); // 上次内存分配器清理的时间点
  • UserCallback CB; // 用户回调函数
  • InputCorpus &Corpus; // 输入语料库的引用
  • MutationDispatcher &MD; // 变异调度器的引用
  • FuzzingOptions Options; // 模糊测试选项
  • DataFlowTrace DFT; // 数据流跟踪对象
  • system_clock::time_point ProcessStartTime = system_clock::now(); // 进程启动时间
  • system_clock::time_point UnitStartTime; // 当前单元开始时间
  • system_clock::time_point UnitStopTime; // 当前单元结束时间
  • long TimeOfLongestUnitInSeconds = 0; // 最长单元执行时间(秒)
  • long EpochOfLastReadOfOutputCorpus = 0; // 最后读取输出语料库的时间
  • size_t MaxInputLen = 0; // 最大输入长度
  • size_t MaxMutationLen = 0; // 最大变异长度
  • size_t TmpMaxMutationLen = 0; // 临时最大变异长度
  • Vector<uint32_t> UniqFeatureSetTmp; // 唯一特征集的临时存储
  • static thread_local bool IsMyThread; // 用于标识当前线程是否是模糊测试线程

这个类主要涉及模糊测试的核心功能,包括种子语料库的处理、崩溃管理、变异调度、统计信息打印等多个方面。

libFuzzer的核心运行流程

Libfuzzer源码学习项目:Dor1s/libfuzzer-workshop: Repository for materials of "Modern fuzzing of C/C++ Projects" workshop. (github.com)
libfuzzer最新源码位置:llvm-project/compiler-rt/lib/fuzzer at main · llvm/llvm-project (github.com)
源码讲解:https://www.bilibili.com/video/BV1RH4y1r74p/?spm_id_from=333.788

libFuzzer 是 LLVM 项目中的一部分,专门用于通过模糊测试来自动化测试 C/C++ 项目中的漏洞。其核心流程大致分为以下几个步骤:

1. 主函数入口 (FuzzerMain.cpp)

主函数入口代码如下:

#include "FuzzerDefs.h"

extern "C" {
// 该函数由用户定义,用于测试目标函数
int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size);
}  // extern "C"

ATTRIBUTE_INTERFACE int main(int argc, char **argv) {
  return fuzzer::FuzzerDriver(&argc, &argv, LLVMFuzzerTestOneInput);
}

在这个主函数中,调用了 fuzzer::FuzzerDriver 函数,并传入了用户定义的 LLVMFuzzerTestOneInput 函数。LLVMFuzzerTestOneInput 是由用户编写的,用于定义模糊测试的目标函数,它接收输入数据进行测试并返回测试结果。FuzzerDriver 函数则负责模糊测试的核心逻辑。

2. 进入核心函数 FuzzerDriver (FuzzerDriver.cpp)

FuzzerDriver 是 libFuzzer 的核心函数,它的主要作用是设置和启动模糊测试。函数签名如下:

int FuzzerDriver(int *argc, char ***argv, UserCallback Callback) {
// 进行对象初始化
  auto *MD = new MutationDispatcher(Rand, Options);
  auto *Corpus = new InputCorpus(Options.OutputCorpus);
  auto *F = new Fuzzer(Callback, *Corpus, *MD, Options);

// 解析命令选项
...

// 读取并处理输入的语料库文件,传入模糊测试主循环
  auto CorporaFiles = ReadCorpora(*Inputs, ParseSeedInuts(Flags.seed_inputs));
  F->Loop(CorporaFiles);
}
  • 核心步骤包括:

    • 对象初始化:创建并初始化负责输入变异的 MutationDispatcher、输入语料库 InputCorpus 以及核心 Fuzzer 对象。
      • MutationDispatcher:负责生成新的输入变异,目的是探索更多可能的代码路径。
      • InputCorpus:管理模糊测试所使用的输入数据集合(语料库)。
      • Fuzzer:负责执行具体的模糊测试流程,调用用户定义的 LLVMFuzzerTestOneInput
    • 解析命令行选项:处理用户通过命令行传递的选项,包括模糊测试的各种配置参数。
    • 处理语料库:通过 ReadCorpora 函数从输入文件中读取语料库文件,并调用 Fuzzer::Loop 函数启动模糊测试的主循环。
  • 主循环 (Fuzzer::Loop)
    Loop 是 libFuzzer 中用于执行模糊测试的核心循环。它会不断从语料库中提取输入数据,进行输入变异,并将变异后的输入传递给 LLVMFuzzerTestOneInput 进行测试。循环的目的是通过广泛探索输入空间,发现导致程序崩溃或行为异常的输入,从而发现潜在的漏洞。

  • 变异引擎
    MutationDispatcher 是 libFuzzer 的变异引擎,它会生成各种输入数据变种,覆盖尽可能多的代码路径。变异策略包括:
    • 位变异、字节翻转
    • 随机插入或删除数据
    • 拼接不同输入的部分
      通过不断生成新的变种,libFuzzer 可以更深入地挖掘目标程序中的代码路径,进而发现隐藏的错误或漏洞。
  • 测试结果收集
    每次输入变异后,libFuzzer 会将结果反馈给 Fuzzer 对象,判断程序是否崩溃或异常。出现崩溃时,libFuzzer 会记录触发崩溃的输入,并终止循环进行进一步分析。

libFuzzer源码编译环境与使用

源码安装 Clang

更多版本:Release LLVM 18.1.8 · llvm/llvm-project (github.com)
libFuzzer的源码根据需要下载对应版本进行源码编译。

一键配置clang脚本:,该脚本在Ubuntu20上成功测试运行过:

#!/bin/bash

# 安装 Clang 的依赖
sudo apt-get --yes install curl subversion screen gcc g++ cmake ninja-build golang autoconf libtool apache2 python-dev pkg-config zlib1g-dev libgcrypt20-dev libgss-dev libssl-dev libxml2-dev ragel nasm libarchive-dev make automake libdbus-1-dev libboost-dev autoconf-archive libtinfo5

# 定义 Clang 的版本和下载链接
CLANG_VERSION="18.1.8"
CLANG_TAR="clang+llvm-${CLANG_VERSION}-x86_64-linux-gnu-ubuntu-18.04.tar.xz"
CLANG_URL="https://github.com/llvm/llvm-project/releases/download/llvmorg-${CLANG_VERSION}/${CLANG_TAR}"
CLANG_INSTALL_DIR="/usr/local/clang-${CLANG_VERSION}"

# 检查 Clang 是否已下载
if [ -f "${CLANG_TAR}" ]; then
    echo "Clang 已经下载 (${CLANG_TAR})"
else
    echo "正在下载 Clang..."
    wget "${CLANG_URL}"
fi

# 检查 Clang 是否已安装
if [ -d "${CLANG_INSTALL_DIR}" ]; then
    echo "Clang 已经安装在 ${CLANG_INSTALL_DIR}"
else
    echo "正在安装 Clang..."

    # 创建目录
    sudo mkdir -p "${CLANG_INSTALL_DIR}"

    echo "正在安装 解压时间比较久..."
    # 解压 Clang
    sudo tar -xf "${CLANG_TAR}" -C "${CLANG_INSTALL_DIR}" --strip-components=1
fi

# 检查环境变量是否已经设置
if ! grep -q "${CLANG_INSTALL_DIR}/bin" ~/.bashrc; then
    echo "添加环境变量..."
    echo "export PATH=${CLANG_INSTALL_DIR}/bin:\$PATH" >> ~/.bashrc
    echo "export LD_LIBRARY_PATH=${CLANG_INSTALL_DIR}/lib:\$LD_LIBRARY_PATH" >> ~/.bashrc

    # 使环境变量生效
    source ~/.bashrc
else
    echo "环境变量已经配置"
fi

# 运行 Clang 验证安装
echo "验证 Clang 安装..."
clang --version
echo "如果验证失败再次运行该命令: source ~/.bashrc"

libFuzzer与覆盖率有关的命令行使用

libfuzzer与覆盖率有关的命令行

与覆盖率相关的选项如下:

// 是否打印新覆盖的 PC 地址
Options.PrintNewCovPcs = Flags.print_pcs;

// 是否打印新覆盖的函数
Options.PrintNewCovFuncs = Flags.print_funcs;

// 是否打印覆盖率信息
Options.PrintCoverage = Flags.print_coverage;

// 是否打印完整的覆盖率信息
Options.PrintFullCoverage = Flags.print_full_coverage;

以下是与覆盖率相关的选项的使用方法和案例说明:

1. Options.PrintNewCovPcs = Flags.print_pcs

  • 功能: 打印新的覆盖 PC(程序计数器)地址。
  • 使用方法: 当你希望在测试过程中显示每次新发现的代码路径(对应的 PC 地址)时,可以启用此选项。
  • 命令行示例:
./woff2-2016-05-06-fsanitize_fuzzer -print_pcs=1
  • 案例输出:
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 2544448443
INFO: Loaded 1 modules   (10708 inline 8-bit counters): 10708 [0x562f5c3345c0, 0x562f5c336f94),
INFO: Loaded 1 PC tables (10708 PCs): 10708 [0x562f5c336f98,0x562f5c360cd8),
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 cov: 15 ft: 16 corp: 1/1b exec/s: 0 rss: 32Mb
    NEW_PC: 0x562f5c220771 in ReadWOFF2Header /home/ub20/FTS/woff/SRC/src/woff2_dec.cc:987:7
#21 NEW    cov: 16 ft: 17 corp: 2/5b lim: 4 exec/s: 0 rss: 33Mb L: 4/4 MS: 4 CrossOver-InsertByte-ChangeBit-CopyPart-
    NEW_PC: 0x562f5c2161a8 in ReadU32 /home/ub20/FTS/woff/SRC/src/./buffer.h:127:9
#999    NEW    cov: 17 ft: 18 corp: 3/9b lim: 11 exec/s: 0 rss: 34Mb L: 4/4 MS: 3 ChangeByte-ShuffleBytes-CMP- DE: "wOF2"-
    NEW_PC: 0x562f5c22077c in ReadU32 /home/ub20/FTS/woff/SRC/src/./buffer.h:127:9
#1300   NEW    cov: 18 ft: 19 corp: 4/17b lim: 11 exec/s: 0 rss: 34Mb L: 8/8 MS: 1 PersAutoDict- DE: "wOF2"-
    NEW_PC: 0x562f5c22103f in ReadWOFF2Header /home/ub20/FTS/woff/SRC/src/woff2_dec.cc:995:7
#1618   NEW    cov: 19 ft: 20 corp: 5/29b lim: 14 exec/s: 0 rss: 34Mb L: 12/12 MS: 3 InsertRepeatedBytes-PersAutoDict-InsertRepeatedBytes- DE: "wOF2"-

2. Options.PrintNewCovFuncs = Flags.print_funcs

  • 功能: 打印新覆盖的函数。
  • 使用方法: 当你想知道每次新发现的函数覆盖时,可以启用此选项。
  • 命令行示例:
    ./woff2-2016-05-06-fsanitize_fuzzer -print_funcs=1
    
  • 案例输出:
    ub20@ub20:~/FTS/woff$ ./woff2-2016-05-06-fsanitize_fuzzer -print_funcs=1
    INFO: Running with entropic power schedule (0xFF, 100).
    INFO: Seed: 2688967800
    INFO: Loaded 1 modules   (10708 inline 8-bit counters): 10708 [0x55b819abf5c0, 0x55b819ac1f94), 
    INFO: Loaded 1 PC tables (10708 PCs): 10708 [0x55b819ac1f98,0x55b819aebcd8), 
    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 cov: 15 ft: 16 corp: 1/1b exec/s: 0 rss: 32Mb
    #10 NEW    cov: 16 ft: 17 corp: 2/5b lim: 4 exec/s: 0 rss: 32Mb L: 4/4 MS: 3 CrossOver-InsertByte-CrossOver-
    ...
    #144927 NEW    cov: 29 ft: 30 corp: 14/437b lim: 1390 exec/s: 48309 rss: 47Mb L: 119/119 MS: 1 PersAutoDict- DE: "\012\000\000\000\000\000\000\000"-
    NEW_FUNC[1/1]: 0x55b8199a0050 in woff2::ReadBase128(woff2::Buffer*, unsigned int*) /home/ub20/FTS/woff/SRC/src/variable_length.cc:94
    ...

3. Options.PrintCoverage = Flags.print_coverage

  • 功能: 打印覆盖率信息。
  • 使用方法: 启用此选项可以在 fuzzing 过程中定期打印当前的覆盖率统计信息。
  • 命令行示例:
    ./openssl-1.0.1f-fsanitize_fuzzer -print_coverage=1
    
  • 案例输出:
    UNCOVERED_FUNC: hits: 0 edges: 0/5 Init() /home/ub20/FTS/openssl-1.0.1f/target.cc:9
    COVERED_FUNC: hits: 42 edges: 2/5 LLVMFuzzerTestOneInput /home/ub20/FTS/openssl-1.0.1f/target.cc:26
    UNCOVERED_PC: /home/ub20/FTS/openssl-1.0.1f/target.cc:27
    UNCOVERED_PC: /home/ub20/FTS/openssl-1.0.1f/target.cc:27
    UNCOVERED_PC: /home/ub20/FTS/openssl-1.0.1f/target.cc:0
    COVERED_FUNC: hits: 42 edges: 19/34 ssleay_rand_add /home/ub20/FTS/openssl-1.0.1f_crash/BUILD/crypto/rand/md_rand.c:194
    UNCOVERED_PC: /home/ub20/FTS/openssl-1.0.1f_crash/BUILD/crypto/rand/md_rand.c:228
    UNCOVERED_PC: /home/ub20/FTS/openssl-1.0.1f_crash/BUILD/crypto/rand/md_rand.c:228
    UNCOVERED_PC: /home/ub20/FTS/openssl-1.0.1f_crash/BUILD/crypto/rand/md_rand.c:249
    ...
    UNCOVERED_PC: /home/ub20/FTS/openssl-1.0.1f_crash/BUILD/crypto/rand/md_rand.c:322

4. Options.PrintFullCoverage = Flags.print_full_coverage

  • 功能: 打印完整的覆盖率信息。
  • 使用方法: 启用此选项时,程序会在结束时输出完整的覆盖率报告,详细列出所有覆盖到的代码。
  • 命令行示例:

    ./woff2-2016-05-06-fsanitize_fuzzer -print_full_coverage=1
    
  • 案例输出:

    ub20@ub20:~/FTS/woff$ ./woff2-2016-05-06-fsanitize_fuzzer -print_coverage=1
    INFO: Running with entropic power schedule (0xFF, 100).
    INFO: Seed: 3116423339
    INFO: Loaded 1 modules   (10708 inline 8-bit counters): 10708 [0x5652816785c0, 0x56528167af94), 
    INFO: Loaded 1 PC tables (10708 PCs): 10708 [0x56528167af98,0x5652816a4cd8), 
    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 cov: 15 ft: 16 corp: 1/1b exec/s: 0 rss: 32Mb
    #27 NEW    cov: 16 ft: 17 corp: 2/5b lim: 4 exec/s: 0 rss: 33Mb L: 4/4 MS: 5 ChangeBit-ChangeBit-ChangeBinInt-CopyPart-CopyPart-
    #433    NEW    cov: 17 ft: 18 corp: 3/10b lim: 6 exec/s: 0 rss: 33Mb L: 5/5 MS: 1 CMP- DE: "wOF2"-
    #636    REDUCE cov: 17 ft: 18 corp: 3/9b lim: 6 exec/s: 0 rss: 33Mb L: 4/4 MS: 3 ShuffleBytes-ChangeBit-EraseBytes-
    #884    REDUCE cov: 18 ft: 19 corp: 4/17b lim: 8 exec/s: 0 rss: 34Mb L: 8/8 MS: 3 InsertRepeatedBytes-ChangeByte-PersAutoDict- DE: "wOF2"-
    #1635   NEW    cov: 19 ft: 20 corp: 5/29b lim: 14 exec/s: 0 rss: 34Mb L: 12/12 MS: 1 CrossOver-
    #3505   NEW    cov: 20 ft: 21 corp: 6/58b lim: 29 exec/s: 0 rss: 35Mb L: 29/29 MS: 5 PersAutoDict-CrossOver-InsertRepeatedBytes-ChangeByte-ChangeBinInt- DE: "wOF2"-
    #4629   NEW    cov: 21 ft: 22 corp: 7/87b lim: 38 exec/s: 0 rss: 35Mb L: 29/29 MS: 4 ChangeBit-CopyPart-ShuffleBytes-ChangeBit-
    #9279   REDUCE cov: 21 ft: 22 corp: 7/72b lim: 80 exec/s: 0 rss: 35Mb L: 14/29 MS: 5 EraseBytes-EraseBytes-ChangeBit-EraseBytes-ChangeBinInt-
    #9535   REDUCE cov: 22 ft: 23 corp: 8/86b lim: 80 exec/s: 0 rss: 35Mb L: 14/29 MS: 1 ChangeByte-
    #27928  REDUCE cov: 23 ft: 24 corp: 9/99b lim: 261 exec/s: 0 rss: 37Mb L: 13/29 MS: 3 ChangeBit-EraseBytes-ChangeBinInt-
    #31817  NEW    cov: 24 ft: 25 corp: 10/120b lim: 293 exec/s: 0 rss: 37Mb L: 21/29 MS: 4 ChangeBit-EraseBytes-ChangeBit-ChangeBinInt-
    #40024  REDUCE cov: 24 ft: 25 corp: 10/119b lim: 373 exec/s: 0 rss: 38Mb L: 12/29 MS: 2 EraseBytes-CMP- DE: "\001\000\000\000\000\000\000\014"-
    #42588  NEW    cov: 25 ft: 26 corp: 11/155b lim: 397 exec/s: 0 rss: 38Mb L: 36/36 MS: 4 CrossOver-CopyPart-InsertByte-ChangeBinInt-
    ^C==12029== libFuzzer: run interrupted; exiting
    COVERAGE:
    UNCOVERED_FUNC: hits: 0 edges: 0/19 brotli::ZopfliCreateCommands(unsigned long, unsigned long, unsigned long, std::vector<unsigned int, std::allocator<unsigned int>> const&, brotli::ZopfliNode const*, int*, unsigned long*, brotli::Command*, unsigned long*) /home/ub20/FTS/woff/BROTLI/enc/backward_references.cc:437
    UNCOVERED_FUNC: hits: 0 edges: 0/18 brotli::Command::Command(unsigned long, unsigned long, unsigned long, unsigned long) /home/ub20/FTS/woff/BROTLI/enc/././command.h:102
    UNCOVERED_FUNC: hits: 0 edges: 0/31 brotli::ZopfliComputeShortestPath(unsigned long, unsigned long, unsigned char const*, unsigned long, unsigned long, int const*, brotli::HashToBinaryTree*, brotli::ZopfliNode*, std::vector<unsigned int, std::allocator<unsigned int>>*) /home/ub20/FTS/woff/BROTLI/enc/backward_references.cc:510
    UNCOVERED_FUNC: hits: 0 edges: 0/27 brotli::ZopfliCostModel::SetFromLiteralCosts(unsigned long, unsigned long, unsigned char const*, unsigned long) /home/ub20/FTS/woff/BROTLI/enc/backward_references.cc:77
    UNCOVERED_FUNC: hits: 0 edges: 0/44 brotli::HashToBinaryTree::FindAllMatches(unsigned char const*, unsigned long, unsigned long, unsigned long, unsigned long, brotli::BackwardMatch*) /home/ub20/FTS/woff/BROTLI/enc/././hash.h:681
    ...
    

总结

  • -print_pcs=1: 适用于监控每次 fuzzing 发现的新代码路径。
  • -print_funcs=1: 适用于了解每次 fuzzing 发现的新函数覆盖。
  • -print_coverage=1: 适用于定期检查当前覆盖率的进展情况。
  • -print_full_coverage=1: 适用于获取 fuzzing 结束后的完整覆盖率报告。
    这些选项可以帮助你更好地理解 fuzzing 的进展和覆盖的代码区域。

libFuzzer所有可用的命令行参数:

在libfuzzer中有很多命令并未实现可以自行寻找,这是在vscode匹配命令选项的正则表达式:Options\.\w+\s*=\s*Flags\.\w+;

// 设置详细程度的级别
Options.Verbosity = Flags.verbosity;

// 设置最大输入长度
Options.MaxLen = Flags.max_len;
...
// 是否处理 SIGINT 信号
Options.HandleInt = Flags.handle_int;

// 是否处理 SIGSEGV 信号
Options.HandleSegv = Flags.handle_segv;

// 是否处理 SIGTERM 信号
Options.HandleTerm = Flags.handle_term;

// 是否处理 SIGXFSZ 信号
Options.HandleXfsz = Flags.handle_xfsz;

// 是否处理 SIGUSR1 信号
Options.HandleUsr1 = Flags.handle_usr1;

// 是否处理 SIGUSR2 信号
Options.HandleUsr2 = Flags.handle_usr2;

// 是否处理 Windows 异常
Options.HandleWinExcept = Flags.handle_winexcept;

// 是否启用分叉的种子组
Options.ForkCorpusGroups = Flags.fork_corpus_groups;

libFuzzer自定义开发与编译

为了满足以下二次开发需求:

  1. 将模糊测试的数据导出并编写接口传递到前端页面。
  2. 解决 libFuzzer 无法持续性 fuzz 的问题,如遇到 crash 就停止模糊测试。
    可以通过以下方式进行源码修改与编译。
    ## 1. 模糊测试的覆盖率接口编写
    首先,修改 libFuzzer/FuzzerLoop.cpp,增加用于导出模糊测试过程中数据的接口。我们可以通过直接修改 libFuzzer 的覆盖率部分,创建接口来收集并输出信息。下面是覆盖率和其他统计信息的部分代码片段:
// 返回总的程序计数器覆盖率(PC Coverage)
size_t GetTotalPCCoverage() const {
    return TPC.GetTotalPCCoverage();
}

// 返回发现的特征数量
size_t GetNumFeatures() const {
    return Corpus.NumFeatures();
}

// 返回活跃输入的数量
size_t GetNumActiveUnits() const {
    return Corpus.NumActiveUnits();
}

// 返回语料库大小(以字节为单位)
size_t GetSizeInBytes() const {
    return Corpus.SizeInBytes();
}

// 返回触发重点功能(Focus Function)的输入数量
size_t GetNumInputsThatTouchFocusFunction() const {
    return Corpus.NumInputsThatTouchFocusFunction();
}

// 返回每秒执行次数
size_t GetExecPerSec() const {
    return execPerSec();
}

// 返回临时最大变异长度
size_t GetTmpMaxMutationLen() const {
    return TmpMaxMutationLen;
}

// 返回峰值内存使用量(以MB为单位)
size_t GetPeakRSSMb() const {
    return GetPeakRSSMb();
}

// 打印模糊测试的统计信息并导出数据
void Fuzzer::PrintStats(const char *Where, const char *End, size_t Units,
                        size_t Features) {
    size_t ExecPerSec = GetExecPerSec();

    if (!Options.Verbosity)
        return;

    Printf("#%zd\t%s", TotalNumberOfRuns, Where);

    if (size_t N = GetTotalPCCoverage())
        Printf(" cov: %zd", N);

    if (size_t N = Features ? Features : GetNumFeatures())
        Printf(" ft: %zd", N);

    if (!Corpus.empty()) {
        Printf(" corp: %zd", GetNumActiveUnits());

        if (size_t N = GetSizeInBytes()) {
            if (N < (1 << 14))
                Printf("/%zdb", N);
            else if (N < (1 << 24))
                Printf("/%zdKb", N >> 10);
            else
                Printf("/%zdMb", N >> 20);
        }

        if (size_t FF = GetNumInputsThatTouchFocusFunction())
            Printf(" focus: %zd", FF);
    }

    if (size_t Lim = GetTmpMaxMutationLen())
        Printf(" lim: %zd", Lim);

    if (Units)
        Printf(" units: %zd", Units);

    Printf(" exec/s: %zd", ExecPerSec);
    Printf(" rss: %zdMb", GetPeakRSSMb());

    // 添加魔改标识
    Printf(" [MODIFIED] Custom version of Fuzzer::PrintStats is running!\n");

    // 输出结束符
    Printf("%s", End);

    // 可以在这里添加接口将数据传递到前端
    ExportDataToFrontend({
        {"TotalNumberOfRuns", TotalNumberOfRuns},
        {"PCCoverage", GetTotalPCCoverage()},
        {"NumFeatures", GetNumFeatures()},
        {"NumActiveUnits", GetNumActiveUnits()},
        {"SizeInBytes", GetSizeInBytes()},
        {"ExecPerSec", GetExecPerSec()},
        {"PeakRSSMb", GetPeakRSSMb()}
    });
}

// 示例数据导出函数,可将数据通过接口传递给前端
void ExportDataToFrontend(const std::map<std::string, size_t>& data) {
    // 通过网络接口、文件、或者其他方式将数据传递到前端页面
    // 这里可以通过 JSON 格式将数据发送到 HTTP 服务器或 WebSocket
}

Fuzzer::PrintStats 函数中,我们已经将覆盖率、执行次数、内存使用情况等统计信息打印,并通过 ExportDataToFrontend 函数导出到外部系统,可以进一步传递到前端页面。

2. 解决 libFuzzer 无法持续性 fuzz 的问题

要实现 libFuzzer 的持久模糊测试,并且避免在发现 crash 后终止,可以通过修改 libFuzzer 源码来调整其行为。libFuzzer 默认行为是当检测到崩溃(如非法内存访问、堆溢出等)时终止模糊测试。为了避免这种情况,目标是允许 fuzzing 继续进行而不退出。

修改思路

  1. 定位崩溃处理逻辑
    libFuzzer 在检测到 crash 后会调用 LLVMFuzzerTestOneInput,这段代码可能会引发未处理异常,导致 fuzzing 过程终止。我们可以通过修改 libFuzzer 的 crash 处理逻辑,使其在崩溃时记录问题但继续模糊测试。

  2. 关键代码修改点
    需要查找 libFuzzer 源码中的崩溃处理位置,一般在 FuzzerLoop.cppFuzzerDriver.cpp 文件中。

    • FuzzerLoop.cpp: 此文件控制模糊测试的主循环。在这里可以捕获崩溃异常并继续模糊测试。
    • FuzzerDriver.cpp: 此文件负责驱动整个 libFuzzer 运行,崩溃检测和异常处理可能在这里发生。
  3. 持久化模糊测试的入口
    如果你想让模糊测试保持“持久化”,即反复利用同一测试环境进行测试,可以在 FuzzerLoop.cpp 中引入一个持久化模式(Persistent Mode)。这种模式会允许测试继续,而不是每次退出。

示例修改步骤

  1. 实现持久化模式
    FuzzerLoop.cpp 中找到模糊测试的主要循环。将其改成持久化循环模式,像这样:

    while (true) {
        int result = LLVMFuzzerTestOneInput(Data, Size);
        if (result == 0) {
            // 记录崩溃信息,但不终止
            // 继续模糊测试循环
            continue;
        } else {
            // 如果检测到崩溃,记录日志或者保存问题
            LogCrash();
            // 但不要退出程序,继续模糊测试
            continue;
        }
    }
    
  2. 捕获崩溃并继续测试
    修改 libFuzzer 中的 FuzzerDriver.cpp 文件,让崩溃不会终止程序。可以在捕获崩溃的地方加入 try-catch 或平台相关的异常处理机制,确保在崩溃时记录并恢复。

    try {
        LLVMFuzzerTestOneInput(Data, Size);
    } catch (...) {
        // 捕获异常,记录崩溃,不退出
        ReportCrash();
        continue;  // 继续模糊测试
    }
    
  3. 处理信号(Signal Handling)
    在某些情况下,libFuzzer 可能通过信号(如 SIGSEGV, SIGABRT)来处理崩溃。你可以修改信号处理函数,确保信号不会导致进程终止。

    signal(SIGSEGV, HandleSignal);
    signal(SIGABRT, HandleSignal);
    
    void HandleSignal(int signal) {
        // 捕获信号并记录信息
        LogSignalCrash(signal);
        // 继续执行
    }
    

具体修改位置:

  1. libFuzzer/FuzzerLoop.cpp: 主模糊测试循环部分。
  2. libFuzzer/FuzzerDriver.cpp: 驱动整个模糊测试的入口。
  3. 信号处理部分:如果有需要,检查是否需要在 libFuzzer 中添加或修改信号处理机制。
    通过以上修改,可以避免在检测到崩溃时 libFuzzer 终止程序,并且实现持久模糊测试模式。如果你有特定的需求或问题,还可以进一步定制相关代码。

3. 编译自定义的 libFuzzer

编译脚本文件路径:llvm-project/compiler-rt/lib/fuzzer/build.sh at main · llvm/llvm-project (github.com)
在完成代码修改后,需要重新编译 libFuzzer 生成 libFuzzer.a 静态库。可以使用如下编译脚本:

#!/bin/sh
LIBFUZZER_SRC_DIR=$(dirname $0)
CXX="${CXX:-clang}"
for f in $LIBFUZZER_SRC_DIR/*.cpp; do
  $CXX -g -O2 -fno-omit-frame-pointer -std=c++17 $f -c &
done
wait
rm -f libFuzzer.a
ar r libFuzzer.a Fuzzer*.o
rm -f Fuzzer*.o

将修改后的源码放置到 libFuzzer 源码目录,运行该脚本编译,生成一个 libFuzzer.a 静态库,用于后续的模糊测试项目链接使用。

自定义的libFuzzer的使用方式:

clang++ -g -O1 fuzz_target.cc libFuzzer.a -o mytarget_fuzzer

这里的libFuzzer.a是自己使用项目提供的build.sh编译出来的版本,还可以添加其他编译选项自行添加!

libFuzzer模糊测试运行案例

教程地址:fuzzing/tutorial/libFuzzerTutorial.md 在 master ·谷歌/模糊测试 (github.com)

案例一:长时间无crash的案例

步骤 1:下载测试目标
#使用官方案例进行测试
ub20@ub20:~$git clone https://github.com/google/fuzzer-test-suite.git FTS
ub20@ub20:~$cd FTS; mkdir -p woff; cd woff;
ub20@ub20:~$~/FTS/woff2-2016-05-06/build.sh
步骤 2:创建模糊测试目标

文件位置:FTS/woff2-2016-05-06/target.cc

// Copyright 2016 Google Inc. All Rights Reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
#include <stddef.h>
#include <stdint.h>

#include "woff2_dec.h"

// Entry point for LibFuzzer.
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
  std::string buf;
  woff2::WOFF2StringOut out(&buf);
  out.SetMaxSize(30 * 1024 * 1024);
  woff2::ConvertWOFF2ToTTF(data, size, &out);
  return 0;
}
步骤 3:编译模糊测试目标
ub20@ub20:~/woff$ ~/Fuzz/FTS/woff2-2016-05-06/build.sh
ub20@ub20:~/woff$ ~/Fuzz/FTS/woff2-2016-05-06/build.sh
正克隆到 'SRC'...
remote: Enumerating objects: 1150, done.
remote: Counting objects: 100% (24/24), done.
remote: Compressing objects: 100% (23/23), done.
remote: Total 1150 (delta 2), reused 20 (delta 0), pack-reused 1126 (from 1)
接收对象中: 100% (1150/1150), 3.48 MiB | 6.03 MiB/s, 完成.
处理 delta : 100% (680/680), 完成.
...
#编译成功后
ub20@ub20:~/woff$ ls
backward_references.o  compress_fragment_two_pass.o  font.o          normalize.o    table_tags.o                       woff2_dec.o
bit_reader.o           decode.o                      glyph.o         seeds          transform.o                        woff2_enc.o
block_splitter.o       dictionary.o                  histogram.o     SRC            utf8_util.o                        woff2_out.o
BROTLI                 encode.o                      huffman.o       state.o        variable_length.o
brotli_bit_stream.o    encode_parallel.o             literal_cost.o  static_dict.o  woff2-2016-05-06-fsanitize_fuzzer
compress_fragment.o    entropy_encode.o              metablock.o     streams.o      woff2_common.o
步骤 4:运行模糊测试
ub20@ub20:~/woff$ ./woff2-2016-05-06-fsanitize_fuzzer
INFO: Seed: 707405402
INFO: Loaded 1 modules   (11231 inline 8-bit counters): 11231 [0x799080, 0x79bc5f), 
INFO: Loaded 1 PC tables (11231 PCs): 11231 [0x726be0,0x7529d0), 
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 cov: 15 ft: 16 corp: 1/1b exec/s: 0 rss: 28Mb
#9      NEW    cov: 16 ft: 17 corp: 2/5b lim: 4 exec/s: 0 rss: 28Mb L: 4/4 MS: 2 CopyPart-CrossOver-
#3134   NEW    cov: 17 ft: 18 corp: 3/10b lim: 33 exec/s: 0 rss: 28Mb L: 5/5 MS: 5 InsertByte-ShuffleBytes-ChangeBinInt-ChangeByte-CMP- DE: "wOF2"-
....

案例二:快速有crash的案例

步骤 1:下载测试目标

准备对:CVE-2014-0160 进行复现
漏洞原理:OpenSSL 心脏滴血漏洞(CVE-2014-0160)漏洞讲解(小白可懂,简单详细)-CSDN博客
使用github项目已经准备好的编译脚本:

#下载对应目标漏洞的fuzzer项目
[AFL++ 570d9dc7fa7e] /downloads # git clone https://github.com/google/fuzzer-test-suite.git
Cloning into 'fuzzer-test-suite'...
remote: Enumerating objects: 3521, done.
remote: Counting objects: 100% (26/26), done.
remote: Compressing objects: 100% (18/18), done.
remote: Total 3521 (delta 10), reused 19 (delta 8), pack-reused 3495
Receiving objects: 100% (3521/3521), 2.78 MiB | 3.69 MiB/s, done.
Resolving deltas: 100% (2267/2267), done.
步骤 2:创建模糊测试目标

创建一个包含 LLVMFuzzerTestOneInput 函数的模糊测试目标:
文件所在路径:fuzzer-test-suite/openssl-1.0.1f

// Copyright 2016 Google Inc. All Rights Reserved.
// Licensed under the Apache License, Version 2.0 (the "License");

#include <openssl/ssl.h>    // OpenSSL 库,用于 SSL/TLS 功能
#include <openssl/err.h>    // OpenSSL 错误处理
#include <assert.h>         // 断言
#include <stdint.h>         // 定义标准整数类型
#include <stddef.h>         // 定义标准数据类型

// 初始化 OpenSSL 库并设置 SSL 上下文
SSL_CTX *Init() {
  SSL_library_init();       // 初始化 OpenSSL 库
  SSL_load_error_strings(); // 加载错误字符串
  ERR_load_BIO_strings();   // 加载 BIO 错误字符串
  OpenSSL_add_all_algorithms(); // 加载所有加密算法

  SSL_CTX *sctx;
  // 创建一个新的 SSL_CTX 对象,使用 TLSv1 协议
  assert(sctx = SSL_CTX_new(TLSv1_method()));
  /* 使用以下命令创建这两个文件:
      openssl req -x509 -newkey rsa:512 -keyout server.key \
      -out server.pem -days 9999 -nodes -subj /CN=a/
  */
  // 加载服务器证书
  assert(SSL_CTX_use_certificate_file(sctx, "runtime/server.pem",
                                      SSL_FILETYPE_PEM));
  // 加载服务器私钥
  assert(SSL_CTX_use_PrivateKey_file(sctx, "runtime/server.key",
                                     SSL_FILETYPE_PEM));
  return sctx; // 返回 SSL_CTX 对象
}

// fuzzer 的入口函数
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
  static SSL_CTX *sctx = Init(); // 初始化 SSL_CTX,仅在第一次调用时执行
  SSL *server = SSL_new(sctx);   // 创建一个新的 SSL 对象

  // BIO(Basic Input/Output)对象用于处理输入和输出操作
  // 内存 BIO (Memory BIO):使用内存缓冲区进行读写操作
  BIO *sinbio = BIO_new(BIO_s_mem());
  BIO *soutbio = BIO_new(BIO_s_mem());

  // 将 BIO 对象与 SSL 对象关联,使 SSL 对象可以通过 BIO 进行 I/O 操作
  SSL_set_bio(server, sinbio, soutbio);
  SSL_set_accept_state(server); // 将 SSL 对象设置为接受状态,准备进行 SSL/TLS 握手

  // 将模糊测试输入数据写入 sinbio,以便模拟从客户端接收数据
  BIO_write(sinbio, Data, Size);

  // 执行 SSL/TLS 握手过程
  SSL_do_handshake(server);

  // 释放 SSL 对象,清理资源
  SSL_free(server);
  return 0; // 返回 0 表示成功
}
步骤 3:编译模糊测试目标

在项目根目录中,运行以下命令编译模糊测试目标,详细的编译选项可以查看build.sh :

#对目标项目进行clang插桩
[AFL++ 570d9dc7fa7e] /downloads/fuzzer-test-suite # ./openssl-1.0.1f/build.sh 
Cloning into 'SRC'...
remote: Enumerating objects: 480247, done.
步骤 4:运行模糊测试

在项目根目录中,运行以下命令开始模糊测试,成功跑出堆溢出漏洞:

[AFL++ 570d9dc7fa7e] /downloads/fuzzer-test-suite # ./openssl-1.0.1f-fsanitize_fuzzer 
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 528530075
INFO: Loaded 1 modules   (35481 inline 8-bit counters): 35481 [0x5da29cfd2410, 0x5da29cfdaea9), 
INFO: Loaded 1 PC tables (35481 PCs): 35481 [0x5da29cfdaeb0,0x5da29d065840), 
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 cov: 402 ft: 403 corp: 1/1b exec/s: 0 rss: 41Mb
#3  NEW    cov: 402 ft: 404 corp: 2/3b lim: 4 exec/s: 0 rss: 41Mb L: 2/2 MS: 1 CrossOver-
#45 REDUCE cov: 402 ft: 404 corp: 2/2b lim: 4 exec/s: 0 rss: 43Mb L: 1/1 MS: 2 ChangeBinInt-....
                                             suite/BUILD/crypto/rand/rand_lib.c:169
#23239  NEW    cov: 532 ft: 775 corp: 35/784b lim: 110 exec/s: 23239 rss: 387Mb L: 11/88 MS: 1 CMP- DE: "\001\000\003R"-
#23380  NEW    cov: 535 ft: 780 corp: 36/800b lim: 110 exec/s: 23380 rss: 387Mb L: 16/88 MS: 1 CrossOver-
#23802  REDUCE cov: 535 ft: 780 corp: 36/797b lim: 110 exec/s: 23802 rss: 387Mb L: 28/88 MS: 2 ChangeBinInt-EraseBytes-
=================================================================
==2346==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x629000009748 at pc 0x5da29cb00a7d bp 0x7ffd011caf10 sp 0x7ffd011ca6d8
READ of size 17731 at 0x629000009748 thread T0
    #0 0x5da29cb00a7c in __asan_memcpy (/downloads/fuzzer-test-suite/openssl-1.0.1f-fsanitize_fuzzer+0x272a7c) (BuildId: b91eb95b107c49cb91fb52d711a75150508e5122)
    #1 0x5da29cb4a42d in tls1_process_heartbeat /downloads/fuzzer-test-suite/BUILD/ssl/t1_lib.c:2586:3
    #2 0x5da29cbb35c9 in ssl3_read_bytes /downloads/fuzzer-test-suite/BUILD/ssl/s3_pkt.c:1092:4
    #3 0x5da29cbb7990 in ssl3_get_message /downloads/fuzzer-test-suite/BUILD/ssl/s3_both.c:457:7
....fsanitize_fuzzer+0x1c25d3) (BuildId: b91eb95b107c49cb91fb52d711a75150508e5122)
    #10 0x5da29ca3e760 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/downloads/fuzzer-test-suite/openssl-1.0.1f-fsanitize_fuzzer+0x1b0760) (BuildId: b91eb95b107c49cb91fb52d711a75150508e5122)
    #11 0x5da29ca67aa2 in main (/downloads/fuzzer-test-suite/openssl-1.0.1f-fsanitize_fuzzer+0x1d9aa2) (BuildId: b91eb95b107c49cb91fb52d711a75150508e5122)
    #12 0x724301664d8f  (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 490fef8403240c91833978d494d39e537409b92e)

SUMMARY: AddressSanitizer: heap-buffer-overflow (/downloads/fuzzer-test-suite/openssl-1.0.1f-fsanitize_fuzzer+0x272a7c) (BuildId: b91eb95b107c49cb91fb52d711a75150508e5122) in __asan_memcpy
Shadow bytes around the buggy address:
  0x629000009480: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x629000009500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x629000009580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x629000009600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x629000009680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x629000009700: 00 00 00 00 00 00 00 00 00[fa]fa fa fa fa fa fa
  0x629000009780: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x629000009800: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x629000009880: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x629000009900: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x629000009980: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==2346==ABORTING
MS: 2 EraseBytes-PersAutoDict- DE: "ECDSA"-; base unit: 1fd5cf2beed57b0a1a2a477ff6e33cbf1a23c38e
0x18,0x3,0x5,0x0,0x5,0x1,0x45,0x43,0x44,0x53,0x41,
\030\003\005\000\005\001ECDSA
artifact_prefix='./'; Test unit written to ./crash-96ba5b3de75e70a2400b07afcf813e645282baa2
Base64: GAMFAAUBRUNEU0E=
0 条评论
某人
表情
可输入 255