Compare commits

...

14 Commits
v4.30c ... cg2

Author SHA1 Message Date
c89946fc19 test.c 2021-02-25 11:36:25 +01:00
3136ad5bca fix unreachable 2021-02-24 20:53:45 +01:00
50311d5a6a generic ignore 2021-02-21 16:36:40 +01:00
748da4ee16 debug output fix 2021-02-11 15:27:54 +01:00
6d5e5484b6 fixes 2021-02-11 15:04:47 +01:00
37de3ba212 Merge pull request #738 from AFLplusplus/dev
Dev
2021-02-11 13:21:46 +01:00
2a13c8b497 need fflush 2021-02-11 09:50:07 +01:00
87be884221 print to stdout 2021-02-11 08:48:17 +01:00
ff168fe3eb renamed callgraph -> unreachable, fine-tuning done 2021-02-10 18:07:38 +01:00
dce29b1e7d c++ class/virt support 2021-02-10 13:37:09 +01:00
518b13827b less opt 2021-02-09 14:33:27 +01:00
b7a2999c03 fixes 2021-02-09 12:59:43 +01:00
a86659f834 follow ctors/dtors and functions 2021-02-09 12:02:26 +01:00
917c0bf1d7 first tests for function pointer analysis 2021-02-09 01:48:37 +01:00
10 changed files with 634 additions and 27 deletions

View File

@ -299,7 +299,7 @@ ifeq "$(TEST_MMAP)" "1"
endif
PROGS_ALWAYS = ./afl-cc ./afl-compiler-rt.o ./afl-compiler-rt-32.o ./afl-compiler-rt-64.o
PROGS = $(PROGS_ALWAYS) ./afl-llvm-pass.so ./SanitizerCoveragePCGUARD.so ./split-compares-pass.so ./split-switches-pass.so ./cmplog-routines-pass.so ./cmplog-instructions-pass.so ./afl-llvm-dict2file.so ./compare-transform-pass.so ./libLLVMInsTrim.so ./afl-ld-lto ./afl-llvm-lto-instrumentlist.so ./afl-llvm-lto-instrumentation.so ./SanitizerCoverageLTO.so
PROGS = $(PROGS_ALWAYS) ./afl-llvm-pass.so ./SanitizerCoveragePCGUARD.so ./split-compares-pass.so ./split-switches-pass.so ./cmplog-routines-pass.so ./cmplog-instructions-pass.so ./afl-llvm-dict2file.so ./compare-transform-pass.so ./libLLVMInsTrim.so ./afl-ld-lto ./afl-llvm-lto-instrumentlist.so ./afl-llvm-lto-instrumentation.so ./SanitizerCoverageLTO.so ./afl-llvm-unreachable.so
# If prerequisites are not given, warn, do not build anything, and exit with code 0
ifeq "$(LLVMVER)" ""
@ -429,7 +429,10 @@ endif
./cmplog-instructions-pass.so: instrumentation/cmplog-instructions-pass.cc instrumentation/afl-llvm-common.o | test_deps
$(CXX) $(CLANG_CPPFL) -shared $< -o $@ $(CLANG_LFL) instrumentation/afl-llvm-common.o
afl-llvm-dict2file.so: instrumentation/afl-llvm-dict2file.so.cc instrumentation/afl-llvm-common.o | test_deps
./afl-llvm-unreachable.so: instrumentation/afl-llvm-unreachable.cc instrumentation/afl-llvm-common.o | test_deps
$(CXX) $(CLANG_CPPFL) -shared $< -o $@ $(CLANG_LFL) instrumentation/afl-llvm-common.o
./afl-llvm-dict2file.so: instrumentation/afl-llvm-dict2file.so.cc instrumentation/afl-llvm-common.o | test_deps
$(CXX) $(CLANG_CPPFL) -shared $< -o $@ $(CLANG_LFL) instrumentation/afl-llvm-common.o
.PHONY: document

View File

@ -156,6 +156,8 @@ Then there are a few specific features that are only available in instrumentatio
This defaults to 1
- `AFL_LLVM_LTO_DONTWRITEID` prevents that the highest location ID written
into the instrumentation is set in a global variable
- `AFL_LLVM_LTO_UNREACHABLE` will *not* instrument the binary for fuzzing but
instead perform an analysis on unreachable functions
See [instrumentation/README.lto.md](../instrumentation/README.lto.md) for more information.

View File

@ -102,8 +102,9 @@ static char *afl_environment_variables[] = {
"AFL_LLVM_INSTRUMENT_FILE",
"AFL_LLVM_SKIP_NEVERZERO",
"AFL_NO_AFFINITY",
"AFL_LLVM_LTO_STARTID",
"AFL_LLVM_LTO_UNREACHABLE",
"AFL_LLVM_LTO_DONTWRITEID",
"AFL_LLVM_LTO_STARTID",
"AFL_NO_ARITH",
"AFL_NO_AUTODICT",
"AFL_NO_BUILTIN",

View File

@ -57,23 +57,28 @@ bool isIgnoreFunction(const llvm::Function *F) {
static const char *ignoreList[] = {
"asan.",
"llvm.",
"sancov.",
"__ubsan_",
"ign.",
".module_",
"__sanitizer", // maybe we should drop anything starting with "__"?
"__local",
"__ubsan",
"__afl_",
"_fini",
"__libc_csu",
"__libc_",
"__asan",
"__msan",
"__cmplog",
"__sancov",
"__clang",
"__gnu",
"__decide_deferred",
"_fini",
"ign.", // compiler specifics
"llvm.",
"asan.",
"sancov.",
"msan.",
"LLVMFuzzerM",
"LLVMFuzzerM", // fuzzing framework specifics
"LLVMFuzzerC",
"LLVMFuzzerI",
"__decide_deferred",
"maybe_duplicate_stderr",
"discard_output",
"close_stdout",

View File

@ -0,0 +1,512 @@
/*
american fuzzy lop++ - LLVM Dead Function Analysis
--------------------------------------------------
Written by Marc Heuse <mh@mh-sec.de>
Copyright 2019-2020 AFLplusplus Project. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
http://www.apache.org/licenses/LICENSE-2.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <list>
#include <string>
#include <fstream>
#include <regex>
#include "llvm/Config/llvm-config.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/IR/Module.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
#include "llvm/Pass.h"
#include "llvm/Analysis/ValueTracking.h"
#if LLVM_VERSION_MAJOR > 3 || \
(LLVM_VERSION_MAJOR == 3 && LLVM_VERSION_MINOR > 4)
#include "llvm/IR/Verifier.h"
#include "llvm/IR/DebugInfo.h"
#else
#include "llvm/Analysis/Verifier.h"
#include "llvm/DebugInfo.h"
#define nullptr 0
#endif
#include <set>
#include "config.h"
#include "debug.h"
#include "types.h"
#include "afl-llvm-common.h"
using namespace llvm;
namespace {
class Unreachable : public ModulePass {
public:
static char ID;
Unreachable() : ModulePass(ID) {
initInstrumentList();
}
bool runOnModule(Module &M) override;
#if LLVM_VERSION_MAJOR < 4
const char *getPassName() const override {
#else
StringRef getPassName() const override {
#endif
return "cmplog instructions";
}
private:
bool hookInstrs(Module &M);
bool is_in_function_list(std::string);
Function *get_next_follow_function(Module &M);
void add_to_follow_list(Module &M, std::string func);
void remove_from_function_list(std::string fname);
void extract_all_plain_constants(std::vector<Value *> *all_constants,
Value * V);
std::vector<std::string> all_functions;
std::vector<std::string> follow;
int debug = 0;
};
} // namespace
char Unreachable::ID = 0;
bool isIgnoreFunction(const llvm::Function *F);
/* Check if a function is our total function list */
bool Unreachable::is_in_function_list(std::string fname) {
std::vector<std::string>::iterator it =
std::find(all_functions.begin(), all_functions.end(), fname);
if (it != all_functions.end()) {
return true;
} else {
return false;
}
}
/* Return the next entry in the function list we follow */
Function *Unreachable::get_next_follow_function(Module &M) {
std::string fname = follow.front();
follow.erase(follow.begin());
return M.getFunction(fname);
}
/* Remove an entry from the list of all functions */
void Unreachable::remove_from_function_list(std::string fname) {
std::vector<std::string>::iterator it =
std::find(all_functions.begin(), all_functions.end(), fname);
if (it != all_functions.end()) { all_functions.erase(it); }
}
/* A function that is called in a function we follow - so follow it too */
void Unreachable::add_to_follow_list(Module &M, std::string fname) {
Function *F = M.getFunction(fname);
std::vector<std::string>::iterator it =
std::find(all_functions.begin(), all_functions.end(), fname);
if (it != all_functions.end()) {
all_functions.erase(it);
if (!F || !F->size()) return;
if (!isInInstrumentList(F)) return;
follow.push_back(fname);
}
}
/* GlobalVariables can be structs that contain arrays that contain ...
This is all broken up here until we only have raw Constant parameters */
void Unreachable::extract_all_plain_constants(
std::vector<Value *> *all_constants, Value *V) {
auto CS = dyn_cast<ConstantStruct>(V);
auto CA = dyn_cast<ConstantArray>(V);
auto CV = dyn_cast<ConstantVector>(V);
unsigned int i = 0;
Constant * C;
if (CS) {
while ((C = CS->getAggregateElement(i++))) {
extract_all_plain_constants(all_constants,
C->stripPointerCastsAndAliases());
}
} else if (CA) {
while ((C = CA->getAggregateElement(i++))) {
extract_all_plain_constants(all_constants,
C->stripPointerCastsAndAliases());
}
} else if (CV) {
while ((C = CV->getAggregateElement(i++))) {
extract_all_plain_constants(all_constants,
C->stripPointerCastsAndAliases());
}
} else {
all_constants->push_back(V);
}
}
bool Unreachable::hookInstrs(Module &M) {
/*
if (debug) { // needs an llvm debug build!
int i = 0;
for (auto &F : M)
// if (F.getName().compare("foo") == 0)
for (auto &BB : F)
for (auto &IN : BB) {
fprintf(stderr, "%d: ", ++i);
IN.dump();
}
}
*/
/* Grab all functions */
for (auto &F : M) {
if (F.size() && !isIgnoreFunction(&F)) {
if (debug) fprintf(stderr, "ADD: %s\n", F.getName().str().c_str());
all_functions.push_back(F.getName().str());
}
}
/* Add CTORs and DTORs - if they should be followed */
GlobalVariable *GV = M.getNamedGlobal("llvm.global_ctors");
if (GV && !GV->isDeclaration() && !GV->hasLocalLinkage()) {
ConstantArray *InitList = dyn_cast<ConstantArray>(GV->getInitializer());
if (InitList) {
for (unsigned i = 0, e = InitList->getNumOperands(); i != e; ++i) {
if (ConstantStruct *CS =
dyn_cast<ConstantStruct>(InitList->getOperand(i))) {
if (CS->getNumOperands() >= 2) {
if (CS->getOperand(1)->isNullValue())
break; // Found a null terminator, stop here.
Constant *FP = CS->getOperand(1);
if (ConstantExpr *CE = dyn_cast<ConstantExpr>(FP))
if (CE->isCast()) FP = CE->getOperand(0);
if (Function *F = dyn_cast<Function>(FP)) {
if (!F->isDeclaration()) {
if (debug)
fprintf(stderr, "Adding CTOR: %s\n",
F->getName().str().c_str());
add_to_follow_list(M, F->getName().str());
}
}
}
}
}
}
}
GV = M.getNamedGlobal("llvm.global_dtors");
if (GV && !GV->isDeclaration() && !GV->hasLocalLinkage()) {
ConstantArray *InitList = dyn_cast<ConstantArray>(GV->getInitializer());
if (InitList) {
for (unsigned i = 0, e = InitList->getNumOperands(); i != e; ++i) {
if (ConstantStruct *CS =
dyn_cast<ConstantStruct>(InitList->getOperand(i))) {
if (CS->getNumOperands() >= 2) {
if (CS->getOperand(1)->isNullValue())
break; // Found a null terminator, stop here.
Constant *FP = CS->getOperand(1);
if (ConstantExpr *CE = dyn_cast<ConstantExpr>(FP))
if (CE->isCast()) FP = CE->getOperand(0);
if (Function *F = dyn_cast<Function>(FP)) {
if (!F->isDeclaration()) {
if (debug)
fprintf(stderr, "Adding DTOR: %s\n",
F->getName().str().c_str());
add_to_follow_list(M, F->getName().str());
}
}
}
}
}
}
}
/* Search and add starter functions */
if (is_in_function_list("LLVMFuzzerTestOneInput")) {
add_to_follow_list(M, "LLVMFuzzerTestOneInput");
if (is_in_function_list("LLVMFuzzerInitialize"))
add_to_follow_list(M, "LLVMFuzzerInitialize");
} else if (is_in_function_list("main")) {
add_to_follow_list(M, "main");
} else {
fprintf(stderr, "Error: no main() or LLVMFuzzerTestOneInput() found!\n");
return 0;
}
/* Here happens the magic -> static analysis of all functions to follow */
while (follow.size()) {
Function *F = get_next_follow_function(M);
if (!F || !F->size()) { continue; }
if (debug) fprintf(stderr, "Following: %s\n", F->getName().str().c_str());
for (auto &BB : *F) {
for (auto &IN : BB) {
auto SI = dyn_cast<StoreInst>(&IN);
if (SI) {
auto V = SI->getValueOperand()->stripPointerCastsAndAliases();
if (V) {
auto T = V->getType();
if (T && T->isPointerTy()) {
// This is C++ class + virtual function support
auto VV = V->stripInBoundsOffsets();
auto *G = dyn_cast<GlobalVariable>(VV);
if (G && G->hasInitializer()) {
Constant * GV = G->getInitializer();
std::vector<Value *> all_constants;
Value * VV = dyn_cast<Value>(GV);
extract_all_plain_constants(&all_constants, VV);
for (auto C : all_constants) {
Function *f = dyn_cast<Function>(C);
if (f) {
if (debug)
fprintf(stderr, "F:%s Store isFunction %s\n",
F->getName().str().c_str(),
f->getName().str().c_str());
// this is wrong here, needs static analysis
add_to_follow_list(M, f->getName().str());
}
}
}
if (isa<FunctionType>(T->getPointerElementType())) {
if (debug)
fprintf(stderr, "F:%s Store isFunction",
F->getName().str().c_str());
Function *f = dyn_cast<Function>(V);
if (f) {
if (debug)
fprintf(stderr, " \"%s\"\n", f->getName().str().c_str());
// this is wrong here, needs static analysis
add_to_follow_list(M, f->getName().str());
} else {
if (debug) fprintf(stderr, " <unknown>\n");
}
}
}
}
}
auto CI = dyn_cast<CallInst>(&IN);
if (CI) {
Function *Callee = CI->getCalledFunction();
if (Callee) add_to_follow_list(M, Callee->getName().str());
for (int i = 0; i < CI->getNumArgOperands(); i++) {
auto O = CI->getArgOperand(i);
auto T = O->getType();
if (T && T->isPointerTy()) {
if (isa<FunctionType>(T->getPointerElementType())) {
if (debug)
fprintf(stderr, "F:%s call %s ", F->getName().str().c_str(),
Callee->getName().str().c_str());
if (debug) fprintf(stderr, "isFunctionPtr[%d]", i);
Function *f =
dyn_cast<Function>(O->stripPointerCastsAndAliases());
if (f) {
if (debug)
fprintf(stderr, " \"%s\"\n", f->getName().str().c_str());
add_to_follow_list(M, f->getName().str());
} else {
if (debug) fprintf(stderr, " <unknown>\n");
}
}
}
}
}
}
}
}
// print all functions not visited, however drop __*, std:: and gnu::
std::regex re1("(^__*[A-Z0-9][A-Z0-9]*_*)([a-z]*)(.*)");
for (auto func : all_functions) {
std::string rest = std::regex_replace(func, re1, "$2");
if (rest.empty() || (rest.compare("t") && rest.compare(0, 3, "gnu") &&
func.compare(0, 2, "__")))
printf("UNREACHABLE FUNCTION: %s\n", func.c_str());
}
return true;
}
bool Unreachable::runOnModule(Module &M) {
if (getenv("AFL_DEBUG")) { debug = 1; }
if (!getenv("AFL_QUIET") || debug)
printf("Running afl-llvm-unreachable by Marc Heuse, mh@mh-sec.de\n");
else
be_quiet = 1;
hookInstrs(M);
if (!be_quiet) printf("Unreachable analysis finished.\n");
fflush(stdout);
verifyModule(M);
return true;
}
static void registerUnreachablePass(const PassManagerBuilder &,
legacy::PassManagerBase &PM) {
auto p = new Unreachable();
PM.add(p);
}
static RegisterStandardPasses RegisterUnreachablePass(
PassManagerBuilder::EP_OptimizerLast, registerUnreachablePass);
static RegisterStandardPasses RegisterUnreachablePass0(
PassManagerBuilder::EP_EnabledOnOptLevel0, registerUnreachablePass);
#if LLVM_VERSION_MAJOR >= 11
static RegisterStandardPasses RegisterUnreachablePassLTO(
PassManagerBuilder::EP_FullLinkTimeOptimizationEarly,
registerUnreachablePass);
#endif

View File

@ -31,6 +31,8 @@
#include <strings.h>
#include <limits.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/wait.h>
#if (LLVM_MAJOR - 0 == 0)
#undef LLVM_MAJOR
@ -566,12 +568,14 @@ static void edit_params(u32 argc, char **argv, char **envp) {
cc_params[cc_par_cnt++] = "-Wl,--allow-multiple-definition";
if (instrument_mode == INSTRUMENT_CFG ||
instrument_mode == INSTRUMENT_PCGUARD)
if (getenv("AFL_LLVM_LTO_UNREACHABLE"))
cc_params[cc_par_cnt++] = alloc_printf(
"-Wl,-mllvm=-load=%s/afl-llvm-unreachable.so", obj_path);
else if (instrument_mode == INSTRUMENT_CFG ||
instrument_mode == INSTRUMENT_PCGUARD)
cc_params[cc_par_cnt++] = alloc_printf(
"-Wl,-mllvm=-load=%s/SanitizerCoverageLTO.so", obj_path);
else
cc_params[cc_par_cnt++] = alloc_printf(
"-Wl,-mllvm=-load=%s/afl-llvm-lto-instrumentation.so", obj_path);
cc_params[cc_par_cnt++] = lto_flag;
@ -801,7 +805,7 @@ static void edit_params(u32 argc, char **argv, char **envp) {
}
if (!getenv("AFL_DONT_OPTIMIZE")) {
if (!getenv("AFL_DONT_OPTIMIZE") && !getenv("AFL_LLVM_LTO_UNREACHABLE")) {
cc_params[cc_par_cnt++] = "-g";
if (!have_o) cc_params[cc_par_cnt++] = "-O3";
@ -1010,6 +1014,29 @@ static void edit_params(u32 argc, char **argv, char **envp) {
#endif
if (getenv("AFL_LLVM_LTO_UNREACHABLE")) {
if (!lto_mode) { FATAL("AFL_LLVM_LTO_UNREACHABLE requires LTO mode"); }
cc_params[cc_par_cnt++] = "-O0";
cc_params[cc_par_cnt++] = "-w";
cc_params[cc_par_cnt++] = "-Wl,-mllvm,-compute-dead=false";
cc_params[cc_par_cnt++] = "-fno-inline";
cc_params[cc_par_cnt++] = "-fno-inline-functions";
cc_params[cc_par_cnt++] = "-Wl,--discard-none";
cc_params[cc_par_cnt++] = "-Wl,--lto-O0";
cc_params[cc_par_cnt++] = "-femit-all-decls";
cc_params[cc_par_cnt++] = "-fno-virtual-function-elimination";
cc_params[cc_par_cnt++] = "-Wl,--no-gc-sections";
if (!be_quiet) {
printf(
"Note: UNREACHABLE analysis will not create an instrumented binary "
"for fuzzing.\n");
}
}
cc_params[cc_par_cnt] = NULL;
}
@ -1655,8 +1682,9 @@ int main(int argc, char **argv, char **envp) {
" AFL_LLVM_LTO_DONTWRITEID: don't write the highest ID used to a "
"global var\n"
" AFL_LLVM_LTO_STARTID: from which ID to start counting from for "
"a "
"bb\n"
"a bb\n"
" AFL_LLVM_LTO_UNREACHABLE: will analyze for unreachable "
"functions, not instrument for fuzzing"
" AFL_REAL_LD: use this lld linker instead of the compiled in "
"path\n"
"If anything fails - be sure to read README.lto.md!\n");
@ -1831,8 +1859,8 @@ int main(int argc, char **argv, char **envp) {
DEBUGF("cd '%s';", getthecwd());
for (i = 0; i < argc; i++)
SAYF(" '%s'", argv[i]);
SAYF("\n");
DEBUGF(" '%s'", argv[i]);
DEBUGF("\n");
fflush(stdout);
fflush(stderr);
@ -1873,17 +1901,15 @@ int main(int argc, char **argv, char **envp) {
DEBUGF("cd '%s';", getthecwd());
for (i = 0; i < (s32)cc_par_cnt; i++)
SAYF(" '%s'", cc_params[i]);
SAYF("\n");
DEBUGF(" '%s'", cc_params[i]);
DEBUGF("\n");
fflush(stdout);
fflush(stderr);
}
execvp(cc_params[0], (char **)cc_params);
FATAL("Oops, failed to execute '%s' - check your PATH", cc_params[0]);
return 0;
}

View File

@ -1303,7 +1303,7 @@ static u8 cmp_extend_encoding(afl_state_t *afl, struct cmp_header *h,
}
#endif /* CMPLOG_SOLVE_ARITHMETIC */
#endif /* CMPLOG_SOLVE_ARITHMETIC */
return 0;
@ -2670,3 +2670,4 @@ exit_its:
return r;
}

57
test.c Normal file
View File

@ -0,0 +1,57 @@
#include <stdio.h>
#include <stdlib.h>
void barY() {
printf("barY\n");
}
void barZ() {
printf("barZ\n");
}
void bar0() {
printf("bar0\n");
}
void bar1() {
printf("bar1\n");
}
void bar2() {
printf("bar2\n");
}
void bard() {
printf("bar3\n");
}
int foo(int a, int b, int c) {
switch(a) {
case 0: bar0(); break;
case 1: bar1(); break;
case 2: bar2(); break;
default: bard(); break;
}
switch(b) {
case 0: bar0(); break;
case 1: bar1(); break;
case 2: bar2(); break;
default: bard(); break;
}
switch(c) {
case 1: barY(); break;
case 2: barZ(); break;
default: barZ(); break;
}
return 0;
}
int main(int argc, char **argv) {
int a = 1, b = 2, c;
if (argc == 1) c = 3; else if (argc == 2) c = atoi(argv[1]);
// else: uninitialized
return foo(a, b, c);
}