-
Notifications
You must be signed in to change notification settings - Fork 0
Mutator 方面
Libfuzzer 允许用户自定义 LLVMFuzzerCustomMutator
/ LLVMFuzzerMutate
。
在 src/main/native/com/code_intelligence/jazzer/driver/fuzz_target_runner.cpp
中,定义了 LLVMFuzzerCustomMutator
和 LLVMFuzzerCustomCrossOver
。Mutate 和 CrossOver 都属于高中生物的概念,这里只是一个借用,大概意思也差不多。因此可以注意到 Mutate 总是对一个 uint8_t *Data
操作,而 CrossOver 需要两条 DNA:
extern "C" size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size,
size_t MaxSize, unsigned int Seed);
extern "C" size_t LLVMFuzzerCustomCrossOver(const uint8_t *Data1, size_t Size1,
const uint8_t *Data2, size_t Size2,
uint8_t *Out, size_t MaxOutSize,
unsigned int Seed);
// We always define LLVMFuzzerCustomMutator, but only use it when --experimental_mutator is
// specified. libFuzzer contains logic that disables --len_control when it finds the custom
// mutator symbol:
// https://github.com/llvm/llvm-project/blob/da3623de2411dd931913eb510e94fe846c929c24/compiler-rt/lib/fuzzer/FuzzerDriver.cpp#L202-L207
// We thus have to explicitly set --len_control to its default value when not using the new
// mutator.
只有 --experimental_mutator
参数指定了之后才会使用 LLVMFuzzerCustomMutator
,否则一直都是使用 LLVMFuzzerMutate
。但是这块逻辑并不在 jazzer 的 java 代码中,而是在 c++ 部分。我们继续看 LLVMFuzzerCustomMutator
代码,这次贴出全部:
extern "C" size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size,
size_t MaxSize, unsigned int Seed) {
if (gUseExperimentalMutator) {
JNIEnv &env = *gEnv;
jint jsize =
std::min(Size, static_cast<size_t>(std::numeric_limits<jint>::max()));
jint jmaxSize = std::min(
MaxSize, static_cast<size_t>(std::numeric_limits<jint>::max()));
jint jseed = static_cast<jint>(Seed);
jint newSize = env.CallStaticLongMethod(gRunner, gMutateOneId, Data, jsize,
jmaxSize, jseed);
if (env.ExceptionCheck()) {
env.ExceptionDescribe();
_Exit(1);
}
return static_cast<uint32_t>(newSize);
} else {
return LLVMFuzzerMutate(Data, Size, MaxSize);
}
}
可见,是否执行默认的 LLVMFuzzerMutate
是由 gUseExperimentalMutator
这个全局变量决定的,而这个全局变量的赋值也在同一个文件下:
[[maybe_unused]] jint
Java_com_code_1intelligence_jazzer_runtime_FuzzTargetRunnerNatives_startLibFuzzer(
JNIEnv *env, jclass, jobjectArray args, jclass runner,
jboolean useExperimentalMutator) {
gUseExperimentalMutator = useExperimentalMutator;
gEnv = env;
env->GetJavaVM(&gJavaVm);
gRunner = reinterpret_cast<jclass>(env->NewGlobalRef(runner));
gRunOneId = env->GetStaticMethodID(runner, "runOne", "(JI)I");
gMutateOneId = env->GetStaticMethodID(runner, "mutateOne", "(JIII)I");
gCrossOverId = env->GetStaticMethodID(runner, "crossOver", "(JIJIJII)I");
// ignore some code
return LLVMFuzzerRunDriver(&argc, const_cast<char ***>(&argv), testOneInput);
}
可见这里的 useExperimentalMutator
是从 java 那边传过来的。我们可以追踪到调用这个函数的 java 代码位置:
/**
* Starts libFuzzer via LLVMFuzzerRunDriver.
*
* @param args command-line arguments encoded in UTF-8 (not null-terminated)
* @return the return value of LLVMFuzzerRunDriver
*/
private static int startLibFuzzer(byte[][] args) {
return FuzzTargetRunnerNatives.startLibFuzzer(
args, FuzzTargetRunner.class, useExperimentalMutator);
}
/*
* Starts libFuzzer via LLVMFuzzerRunDriver.
*/
public static int startLibFuzzer(List<String> args) {
// We always define LLVMFuzzerCustomMutator, but only use it when --experimental_mutator is
// specified. libFuzzer contains logic that disables --len_control when it finds the custom
// mutator symbol:
// https://github.com/llvm/llvm-project/blob/da3623de2411dd931913eb510e94fe846c929c24/compiler-rt/lib/fuzzer/FuzzerDriver.cpp#L202-L207
// We thus have to explicitly set --len_control to its default value when not using the new
// mutator.
if (!useExperimentalMutator) {
// args may not be mutable.
args = new ArrayList<>(args);
// https://github.com/llvm/llvm-project/blob/da3623de2411dd931913eb510e94fe846c929c24/compiler-rt/lib/fuzzer/FuzzerFlags.def#L19
args.add("-len_control=100");
}
for (String arg : args.subList(1, args.size())) {
if (!arg.startsWith("-")) {
Log.info("using inputs from: " + arg);
}
}
if (!IS_ANDROID) {
SignalHandler.initialize();
}
return startLibFuzzer(
args.stream().map(str -> str.getBytes(StandardCharsets.UTF_8)).toArray(byte[][] ::new));
}
笑死,这不正是写上面注释的地方吗。
上面只是说 LLVMFuzzerCustomMutator
被执行到且 useExperimentalMutator == true
的时候,会执行 custom 的部分。但也有可能不会执行到这个 LLVMFuzzerCustomMutator
里,这是因为 libfuzzer 的 MutatationDispatcher
逻辑:
MutationDispatcher::MutationDispatcher(Random &Rand,
const FuzzingOptions &Options)
: Rand(Rand), Options(Options) {
DefaultMutators.insert(
DefaultMutators.begin(),
{
{&MutationDispatcher::Mutate_EraseBytes, "EraseBytes"},
{&MutationDispatcher::Mutate_InsertByte, "InsertByte"},
{&MutationDispatcher::Mutate_InsertRepeatedBytes,
"InsertRepeatedBytes"},
{&MutationDispatcher::Mutate_ChangeByte, "ChangeByte"},
{&MutationDispatcher::Mutate_ChangeBit, "ChangeBit"},
{&MutationDispatcher::Mutate_ShuffleBytes, "ShuffleBytes"},
{&MutationDispatcher::Mutate_ChangeASCIIInteger, "ChangeASCIIInt"},
{&MutationDispatcher::Mutate_ChangeBinaryInteger, "ChangeBinInt"},
{&MutationDispatcher::Mutate_CopyPart, "CopyPart"},
{&MutationDispatcher::Mutate_CrossOver, "CrossOver"},
{&MutationDispatcher::Mutate_AddWordFromManualDictionary,
"ManualDict"},
{&MutationDispatcher::Mutate_AddWordFromPersistentAutoDictionary,
"PersAutoDict"},
});
if(Options.UseCmp)
DefaultMutators.push_back(
{&MutationDispatcher::Mutate_AddWordFromTORC, "CMP"});
if (EF->LLVMFuzzerCustomMutator)
Mutators.push_back({&MutationDispatcher::Mutate_Custom, "Custom"});
else
Mutators = DefaultMutators;
if (EF->LLVMFuzzerCustomCrossOver)
Mutators.push_back(
{&MutationDispatcher::Mutate_CustomCrossOver, "CustomCrossOver"});
}
首先上面这段代码是 libfuzzer 中的,现在 libfuzzer
已经不在 llvm-project 的 lib 目录下了,而是在 compiler-rt/lib/fuzzer
下面。这里给出的是 MutationDispatcher
的构造函数。我们可以看到:无论是 Mutate 还是 CrossOver,在 libfuzzer 中都还是视作 Mutator。另外,如果外部定义了 LLVMFuzzerCustomMutator,按道理说就不会加入上面那些 libfuzzer 中定义的 mutator,比如 Mutate_EraseBytes。
而在 jazzer 中,LLVMFuzzerCustomMutator
可以说是默认定义的,只是用不用要看 experimental_mutator
这个参数而已。
Jazzer 中有两种模式,分别是 autofuzz
和 specified target
。字面意思来讲,jazzer 倾向于指定具体的 “类” 来完成 fuzzing,这段对应的描述可见项目 README:
Run the jazzer
binary (jazzer.exe
on Windows), specifying the classpath and fuzz test class:
./jazzer --cp=<classpath> --target_class=<fuzz test class>
请注意这里的 target_class
的说明是:“fuzz test class”,也就是说这里指定的 class 其实是一个 Driver
,这也是 “jazzer 指定的是 target_class
而非 target_method
” 的原因。因为这里指定的就是 Driver
。
而反观 autofuzz
的要求是给出一个 “方法”,所以我们自然可以得知,jazzer
其实是可以根据方法签名来自己生成 Driver
的。于是我们可以总结为:
-
autofuzz
:只需要指定方法名,jazzer 会根据方法名自动生成Driver
; -
specified target
:只需要指定驱动类,jazzer 会去执行这个驱动类。
那么在知道这个信息的基础上,我们接下来以 specified target
为例,看下 jazzer 是如何对 fuzz test class 中的方法参数变异的。
既然是给定了一个类,那难道 jazzer 把这个类中所有方法都 fuzzing 一个遍吗?这肯定不是,都说了是 Driver
,jazzer 默认只取一个方法:
private static final String FUZZER_TEST_ONE_INPUT = "fuzzerTestOneInput";
private static FuzzTarget findFuzzTargetByMethodName(Class<?> clazz) {
Method fuzzTargetMethod;
if (Opt.experimentalMutator.get()) {
List<Method> fuzzTargetMethods =
Arrays.stream(clazz.getMethods())
.filter(method -> "fuzzerTestOneInput".equals(method.getName()))
.filter(method -> Modifier.isStatic(method.getModifiers()))
.collect(Collectors.toList());
if (fuzzTargetMethods.size() != 1) {
throw new IllegalArgumentException(
String.format("%s must define exactly one function of this form:%n"
+ "public static void fuzzerTestOneInput(...)%n",
clazz.getName()));
}
fuzzTargetMethod = fuzzTargetMethods.get(0);
} else {
Optional<Method> bytesFuzzTarget =
targetPublicStaticMethod(clazz, FUZZER_TEST_ONE_INPUT, byte[].class);
Optional<Method> dataFuzzTarget =
targetPublicStaticMethod(clazz, FUZZER_TEST_ONE_INPUT, FuzzedDataProvider.class);
if (bytesFuzzTarget.isPresent() == dataFuzzTarget.isPresent()) {
throw new IllegalArgumentException(String.format(
"%s must define exactly one of the following two functions:%n"
+ "public static void fuzzerTestOneInput(byte[] ...)%n"
+ "public static void fuzzerTestOneInput(FuzzedDataProvider ...)%n"
+ "Note: Fuzz targets returning boolean are no longer supported; exceptions should be thrown instead of returning true.",
clazz.getName()));
}
fuzzTargetMethod = dataFuzzTarget.orElseGet(bytesFuzzTarget::get);
}
// ignore some code
}
总的来说,二者都是只取一个叫做 fuzzerTestOneInput
的 public static
方法。findFuzzTargetByMethodName
这个方法会返回一个 FuzzTarget
,这个结果现在确实就包含了 Class / Method / Params
等等信息了。
即便是用户指定的,也有两种情况,就是这个 Driver
可能用 byte[]
或 FuzzedDataProvider
两种参数,这也代表了 FuzzTargetRunner.java
中的两种执行方式:
private static int runOne(long dataPtr, int dataLength) {
// ignore some code
byte[] data;
Object argument;
if (useExperimentalMutator) {
byte[] buf = copyToArray(dataPtr, dataLength);
boolean readExactly = mutator.read(new ByteArrayInputStream(buf));
// All inputs constructed by the mutator framework can be read exactly, existing corpus files
// may not be valid for the current fuzz target anymore, though. In this case, print a warning
// once.
if (!(invalidCorpusFileWarningShown || readExactly || isFixedLibFuzzerInput(buf))) {
invalidCorpusFileWarningShown = true;
Log.warn("Some files in the seed corpus do not match the fuzz target signature. "
+ "This indicates that they were generated with a different signature and may cause issues reproducing previous findings.");
}
data = null;
argument = null;
} else if (useFuzzedDataProvider) {
fuzzedDataProvider.setNativeData(dataPtr, dataLength);
data = null;
argument = fuzzedDataProvider;
} else {
data = copyToArray(dataPtr, dataLength);
argument = data;
}
try {
if (useExperimentalMutator) {
// No need to detach as we are currently reading in the mutator state from bytes in every
// iteration.
mutator.invoke(false);
} else if (fuzzTargetInstance == null) {
fuzzTargetMethod.invoke(argument);
} else {
fuzzTargetMethod.invoke(fuzzTargetInstance, argument);
}
} catch (Throwable uncaughtFinding) {
finding = uncaughtFinding;
}
// ignore some code
}
至于怎么使用 Data
和 DataProvider
,那就是 Driver
自己要考虑的内容了。
需要额外说明的是,这个 runOne
方法同样并非在 java 中调用,而是在 fuzz_target_runner.cpp
中调用:
int testOneInput(const uint8_t *data, const std::size_t size) {
JNIEnv &env = *gEnv;
jint jsize =
std::min(size, static_cast<size_t>(std::numeric_limits<jint>::max()));
int res = env.CallStaticIntMethod(gRunner, gRunOneId, data, jsize);
if (env.ExceptionCheck()) {
env.ExceptionDescribe();
_Exit(1);
}
return res;
}
这里的 gRunner
是 FuzzTargetRunner
;gRunOneId
是 runOne
。而这个 testOneInput
函数是作为 LLVMFuzzerRunDriver
的 callback 传入的。
于是,startLibFuzzer
间接调用了 LLVMFuzzerRunDriver
,这相当于开启了 fuzz loop,而它又调用了 testOneInput
,这个 CB 会调用 runOne
,每次都会执行一次用户自定义的 Driver
。而 CB 的 data 是谁给的?那自然是 fuzz loop 中调用 LLVMFuzzerCustomMutator
时给的了。
我们知道在 fuzz loop 中会调用到 LLVMFuzzerCustomMutator
,而上面已经介绍了其内部代码:
gMutateOneId = env->GetStaticMethodID(runner, "mutateOne", "(JIII)I");
extern "C" size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size,
size_t MaxSize, unsigned int Seed) {
if (gUseExperimentalMutator) {
JNIEnv &env = *gEnv;
jint jsize =
std::min(Size, static_cast<size_t>(std::numeric_limits<jint>::max()));
jint jmaxSize = std::min(
MaxSize, static_cast<size_t>(std::numeric_limits<jint>::max()));
jint jseed = static_cast<jint>(Seed);
jint newSize = env.CallStaticLongMethod(gRunner, gMutateOneId, Data, jsize,
jmaxSize, jseed);
if (env.ExceptionCheck()) {
env.ExceptionDescribe();
_Exit(1);
}
return static_cast<uint32_t>(newSize);
} else {
return LLVMFuzzerMutate(Data, Size, MaxSize);
}
}
根据这段代码,可以非常清楚地理解 “jazzer 为什么是 libfuzzer 套壳”,毕竟默认就是用的全套 libfuzzer。但当 UseExperimentalMutator 时,将返回去调用 FuzzTargetRunner
的 mutateOne
。这里需要首先来到 FuzzTargetrRunner
的 static 块中,块中不仅有上文 “对 FuzzTarget
的指定”,还有 mutator 的初始化:
if (useExperimentalMutator) {
if (Modifier.isStatic(fuzzTarget.method.getModifiers())) {
mutator = ArgumentsMutator.forStaticMethodOrThrow(fuzzTarget.method);
} else {
mutator = ArgumentsMutator.forInstanceMethodOrThrow(fuzzTargetInstance, fuzzTarget.method);
}
Log.info("Using experimental mutator: " + mutator);
} else {
mutator = null;
}
可见,如果是 useExperimentalMutator
下,代码会根据 fuzzTarget
中的 method signature
得到一系列的 mutators。具体可在 ArgumentsMutator
中看到。
这里要多说一句,在 jazzer 当中,有 SerializingMutator
/ ProductMutator
/ ArgumentsMutator
这三层抽象,它们的意思分别是:
名称 | 含义 |
---|---|
SerializingMutator | 是一个 abstract class,需要去实现 ValueMutator 这个接口,该接口包含 init / mutate / crossOver。Jazzer 中所有 mutator 都是这个 abstract class 的子类 |
ProductMutator | 这里的 Product 可能取自 “积” |
ArgumentsMutator | 算是一层外包装,提供了一系列 static 方法来创建自己。里面有一个 ProductMutator。这几乎可以说是对 FuzzTargetRunner 提供的最外层抽象 |
上面提到的 LLVMFuzzerCustomMutator
调用的就是下面的 mutateOne
,而上面通过 ArgumentsMutator
的 static method 生成的 mutator
也在下面使用:
@SuppressWarnings("unused")
private static int mutateOne(long data, int size, int maxSize, int seed) {
mutate(data, size, seed);
return writeToMemory(mutator, data, maxSize);
}
private static void mutate(long data, int size, int seed) {
// libFuzzer sends the input "\n" when there are no corpus entries. We use that as a signal to
// initialize the mutator instead of just reading that trivial input to produce a more
// interesting value.
if (size == 1 && UNSAFE.getByte(data) == '\n') {
// 这里的 mutator 就是上面的 ArgumentsMutator.forStaticMethodOrThrow / forInstanceMethodOrThrow
mutator.init(seed);
} else {
mutator.read(new ByteArrayInputStream(copyToArray(data, size)));
mutator.mutate(seed);
}
}
如果我们跟着 ArgumentsMutator
的 mutate
一层层进去,就可以追溯到在确切执行 mutate
方法的 “SerializingMutator 的实现类” 们,比如:
- BooleanMutator
- ByteArrayMutator
- FloatMutator
- DoubleMutator
- ...
这些 MutatorFactory
生成的 mutator 要么是定义了类型,要么是匿名(反正只要一个 instance)。
也就是说,jazzer 在 --experimental_mutator
没开之前确实是一个 libfuzzer 的完全套壳。用的是 libfuzzer 的 LLVMFuzzerMutate
。
但加上这个之后,jazzer 会去使用定义在 java 代码里面的 mutator 们,这通过 LLVMFuzzerCustomMutate
反向调用 java 中 FuzzTargetRunner
中的 mutate
提供了一种比较方便的定义 mutator 的方法。