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
在运行模糊测试时生成的日志信息。以下是对每个部分的解释:
日志信息解释
-
#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。
-
Slowest unit: 13 s
:- 表示最慢的输入单元花费了 13 秒的时间来处理。
-
artifact_prefix='./'; Test unit written to ./slow-unit-992a94056add041c29abe37cdddf0da7a7be791e
:artifact_prefix='./'
:表示生成的测试单元(artifact)将被写入当前目录。Test unit written to ./slow-unit-992a94056add041c29abe37cdddf0da7a7be791e
:表示最慢的输入单元已经被写入到文件./slow-unit-992a94056add041c29abe37cdddf0da7a7be791e
中。
-
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;
}
问题分析
-
指数部分处理:
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; }
- 当解析到
E
或e
时,函数会进入指数部分处理。 - 它会解析指数值,并根据指数值调整
d
的大小。 - 如果指数值非常大(例如
888888888888888888888888
),则会导致大量的乘法或除法操作,从而导致函数变得非常慢。
- 当解析到
-
输入
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
问题。