libfuzzer初学者(一):mjson--slowinput的模糊测试

libfuzzer初学者(一):mjson–slowinput的模糊测试

以下内容实际上缺失了对源码的插桩,使得cov非常小,但是因为漏洞很浅,所以也能挖出来。
建议结合libfuzzer初学者(三)COV–在源码编译时插桩并链接,修正(一)—mjson为例一起食用本文。

实验步骤

首先,从github上下载mjson库到本地。
git clone https://github.com/cesanta/mjson.git mjson
然后在mjson的文件夹里创建fuzzer目录
在其中,创建target.cc
注意把代码里面所有的yourpath改成你自己正确的路径。

  • target.cc
#include "/yourpath/workspace/mjson/src/mjson.h"
#include <cstdint>
#include <cstdlib>
#include <stddef.h>    
#include <stdint.h>

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
  char *json = (char *)malloc(size + 1);
  if (json == nullptr) {
    return 0;
  }
  memcpy(json, data, size);
  json[size] = '\0';

  mjson_find(json, size, "$", NULL, NULL);

  free(json);
  return 0;
}

接下来,编写一个 CMakeLists.txt 文件来构建模糊测试目标。将以下内容添加到 workspace/mjson/fuzzer/CMakeLists.txt 文件中:

  • CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(mjson_fuzzing)    

set(CMAKE_C_COMPILER clang)
set(CMAKE_CXX_COMPILER clang++)

set(CMAKE_CXX_STANDARD 11)

# 添加 mjson 库
add_library(mjson STATIC /home/xjy/fuzz/workspace/mjson/src/mjson.c)

# 添加 fuzz_target 可执行文件
add_executable(fuzz_target target.cc)

# 链接 mjson 库和 libFuzzer
target_link_libraries(fuzz_target PRIVATE mjson /lib/llvm-14/lib/clang/14.0.0/lib/linux/libclang_rt.fuzzer-x86_64.a)

# 启用编译选项
target_compile_options(fuzz_target PRIVATE -g -std=c++11 -fsanitize=address -fsanitize=fuzzer)
target_link_options(fuzz_target PRIVATE -fsanitize=address -fsanitize=fuzzer)

然后,使用以下命令来构建和运行模糊测试:

cd workspace/mjson
mkdir build
cd build
cmake ..
make
./fuzz_target

执行结果如下图所示
在这里插入图片描述

INFO: Seed: 4078298920下我们成功地触发了mjson的slowinput。
解读终端输出信息含义:
这个输出是 libFuzzer 在运行模糊测试时生成的日志信息。以下是对每个部分的解释:

日志信息解释
  1. #2 INITED cov: 2 ft: 2 corp: 1/1b exec/s: 0 rss: 28Mb

    • #2:这是模糊测试的第二次迭代。
    • INITED:表示模糊测试已经初始化。
    • cov: 2:覆盖了 2 个代码块(coverage)。
    • ft: 2:发现了 2 个新的代码路径(features)。
    • corp: 1/1b:当前语料库中有 1 个输入,总大小为 1 字节。
    • exec/s: 0:每秒执行的次数(由于输入非常慢,执行次数为 0)。
    • rss: 28Mb:当前进程的常驻内存集大小(RSS,Resident Set Size)为 28 MB。
  2. Slowest unit: 13 s

    • 表示最慢的输入单元花费了 13 秒的时间来处理。
  3. artifact_prefix='./'; Test unit written to ./slow-unit-992a94056add041c29abe37cdddf0da7a7be791e

    • artifact_prefix='./':表示生成的测试单元(artifact)将被写入当前目录。
    • Test unit written to ./slow-unit-992a94056add041c29abe37cdddf0da7a7be791e:表示最慢的输入单元已经被写入到文件 ./slow-unit-992a94056add041c29abe37cdddf0da7a7be791e 中。
  4. Base64: ODg4ODg4ODhFODg4ODg4ODg4ODg4ODg4ODg4ODg4Cg==

    • 这是导致 slow-input 的输入数据的 Base64 编码形式。
    • 解码后的数据是 88888888E888888888888888888888
原因解读

mjson 库中的 mystrtod 函数用于将字符串转换为双精度浮点数。输入 88888888E888888888888888888888 可能会导致 mystrtod 函数在处理指数部分时变得非常慢,从而导致 slow-input 问题。

mystrtod 函数的实现如下:

static double mystrtod(const char *str, const char **end) {
  double d = 0.0;
  int sign = 1, n = 0;
  const char *p = str, *a = str;

  /* decimal part */
  if (*p == '-') {
    sign = -1;
    ++p;
  } else if (*p == '+') {
    ++p;
  }
  if (is_digit(*p)) {
    d = (double) (*p++ - '0');
    while (*p && is_digit(*p)) {
      d = d * 10.0 + (double) (*p - '0');
      ++p;
      ++n;
    }
    a = p;
  } else if (*p != '.') {
    goto done;
  }
  d *= sign;

  /* fraction part */
  if (*p == '.') {
    double f = 0.0;
    double base = 0.1;
    ++p;

    if (is_digit(*p)) {
      while (*p && is_digit(*p)) {
        f += base * (*p - '0');
        base /= 10.0;
        ++p;
        ++n;
      }
    }
    d += f * sign;
    a = p;
  }

  /* exponential part */
  if ((*p == 'E') || (*p == 'e')) {
    int i, e = 0, neg = 0;
    p++;
    if (*p == '-') p++, neg++;
    if (*p == '+') p++;
    while (is_digit(*p)) e = e * 10 + *p++ - '0';
    if (neg) e = -e;
    for (i = 0; i < e; i++) d *= 10;
    for (i = 0; i < -e; i++) d /= 10;
    a = p;
  } else if (p > str && !is_digit(*(p - 1))) {
    a = str;
    goto done;
  }

done:
  if (end) *end = a;
  return d;
}

问题分析

  1. 指数部分处理

    if ((*p == 'E') || (*p == 'e')) {
      int i, e = 0, neg = 0;
      p++;
      if (*p == '-') p++, neg++;
      if (*p == '+') p++;
      while (is_digit(*p)) e = e * 10 + *p++ - '0';
      if (neg) e = -e;
      for (i = 0; i < e; i++) d *= 10;
      for (i = 0; i < -e; i++) d /= 10;
      a = p;
    }
    
    • 当解析到 Ee 时,函数会进入指数部分处理。
    • 它会解析指数值,并根据指数值调整 d 的大小。
    • 如果指数值非常大(例如 888888888888888888888888),则会导致大量的乘法或除法操作,从而导致函数变得非常慢。
  2. 输入 88888888E888888888888888888888

    • 该输入的指数部分 888888888888888888888888 是一个非常大的数。
    • 在处理这个指数时,函数会执行大量的乘法操作(for (i = 0; i < e; i++) d *= 10;),这会导致函数变得非常慢。
  • 解决方案

    为了避免这种 slow-input 问题,可以在处理指数部分时添加一个限制,防止指数值过大。例如,可以限制指数值的绝对值不超过某个阈值:

if ((*p == 'E') || (*p == 'e')) {
  int i, e = 0, neg = 0;
  p++;
  if (*p == '-') p++, neg++;
  if (*p == '+') p++;
  while (is_digit(*p)) e = e * 10 + *p++ - '0';
  if (neg) e = -e;
  if (e > 308) e = 308;  // 限制最大指数值
  if (e < -308) e = -308;  // 限制最小指数值
  for (i = 0; i < e; i++) d *= 10;
  for (i = 0; i < -e; i++) d /= 10;
  a = p;
}

通过这种方式,可以避免指数值过大导致的 slow-input 问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值