Compare commits

...

6 Commits
v4.31c ... cg

Author SHA1 Message Date
b6cfe5ceb0 change 2021-07-06 12:28:59 +02:00
15fbbce5c3 no opt 2021-02-08 22:29:20 +01:00
691795a16a my first python script 2021-02-07 16:14:56 +01:00
9d22fad50c no opt for cg 2021-02-07 11:29:26 +01:00
a0e400d08b add description of todo python script 2021-02-07 09:21:16 +01:00
8805483d0c add AFL_LLVM_LTO_CALLGRAPH 2021-02-07 09:10:15 +01:00
4 changed files with 194 additions and 7 deletions

View File

@ -156,6 +156,7 @@ 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_CALLGRAPH` will not generate a binary but a callgraph instead
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_CALLGRAPH",
"AFL_LLVM_LTO_DONTWRITEID",
"AFL_LLVM_LTO_STARTID",
"AFL_NO_ARITH",
"AFL_NO_AUTODICT",
"AFL_NO_BUILTIN",

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
@ -57,6 +59,7 @@ static u8 * lto_flag = AFL_CLANG_FLTO, *argvnull;
static u8 debug;
static u8 cwd[4096];
static u8 cmplog_mode;
static u8 * ll_file;
u8 use_stdin; /* dummy */
// static u8 *march_opt = CFLAGS_OPT;
@ -315,6 +318,7 @@ static void edit_params(u32 argc, char **argv, char **envp) {
u8 fortify_set = 0, asan_set = 0, x_set = 0, bit_mode = 0, shared_linking = 0,
preprocessor_only = 0, have_unroll = 0, have_o = 0, have_pic = 0,
have_c = 0;
u32 out_idx = 0;
cc_params = ck_alloc((argc + 128) * sizeof(u8 *));
@ -741,6 +745,8 @@ static void edit_params(u32 argc, char **argv, char **envp) {
cc_params[cc_par_cnt++] = cur;
if (!strcmp(cur, "-o")) { out_idx = cc_par_cnt; }
}
if (getenv("AFL_HARDEN")) {
@ -801,7 +807,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_CALLGRAPH")) {
cc_params[cc_par_cnt++] = "-g";
if (!have_o) cc_params[cc_par_cnt++] = "-O3";
@ -947,7 +953,7 @@ static void edit_params(u32 argc, char **argv, char **envp) {
#ifndef __ANDROID__
if (compiler_mode != GCC && compiler_mode != CLANG) {
if (compiler_mode != GCC && compiler_mode != CLANG && !getenv("AFL_LLVM_LTO_CALLGRAPH")) {
switch (bit_mode) {
@ -1010,6 +1016,23 @@ static void edit_params(u32 argc, char **argv, char **envp) {
#endif
if (getenv("AFL_LLVM_LTO_CALLGRAPH")) {
if (!lto_mode) { FATAL("AFL_LLVM_LTO_CALLGRAPH requires LTO mode"); }
cc_params[cc_par_cnt++] = "-O0";
cc_params[cc_par_cnt++] = "-w";
if (!have_c) {
ll_file = alloc_printf("%s.ll", cc_params[out_idx]);
cc_params[out_idx] = ll_file;
cc_params[cc_par_cnt++] = "-S";
cc_params[cc_par_cnt++] = "-emit-llvm";
}
}
cc_params[cc_par_cnt] = NULL;
}
@ -1655,8 +1678,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_CALLGRAPH: will not generate a binary but a "
"callgraph instead\n"
" 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");
@ -1880,9 +1904,79 @@ int main(int argc, char **argv, char **envp) {
}
execvp(cc_params[0], (char **)cc_params);
s32 pid = 0;
if (ll_file) { pid = fork(); } // CG run
FATAL("Oops, failed to execute '%s' - check your PATH", cc_params[0]);
if (!pid) {
execvp(cc_params[0], (char **)cc_params);
FATAL("Oops, failed to execute '%s' - check your PATH", cc_params[0]);
}
// only for AFL_LLVM_LTO_CALLGRAPH
s32 status;
if (waitpid(pid, &status, 0) <= 0) { PFATAL("waitpid() failed"); }
u8 *cg_file = (u8 *)ck_strdup(ll_file);
cg_file[strlen(cg_file) - 2] = 0;
strcat(cg_file, "cg");
if (!be_quiet) SAYF("Generating callgraph into %s ...\n", cg_file);
u8 *opt_cmd = (u8 *)ck_alloc(strlen(cc_params[0]) + 16);
strcpy(opt_cmd, cc_params[0]);
if ((ptr = (u8 *)strrchr(opt_cmd, '/'))) {
++ptr;
} else {
ptr = opt_cmd;
}
u8 *dash = (u8 *)strchr(opt_cmd, '-');
strcpy((char *)opt_cmd, "opt");
if (dash) {
dash = (u8 *)strrchr(cc_params[0], '/');
dash = (u8 *)strchr(dash, '-');
strcat((char *)opt_cmd, (char *)dash);
}
u8 *opt_params[8];
u32 opt_par_cnt = 0;
opt_params[opt_par_cnt++] = opt_cmd;
opt_params[opt_par_cnt++] = "-O0";
opt_params[opt_par_cnt++] = "--analyze";
opt_params[opt_par_cnt++] = "--dot-callgraph";
opt_params[opt_par_cnt++] = "-o";
opt_params[opt_par_cnt++] = cg_file;
opt_params[opt_par_cnt++] = ll_file;
opt_params[opt_par_cnt] = NULL;
if (debug) {
DEBUGF("cd '%s';", getthecwd());
for (i = 0; i < (s32)opt_par_cnt; i++)
SAYF(" '%s'", opt_params[i]);
SAYF("\n");
fflush(stdout);
fflush(stderr);
}
pid = fork();
if (!pid) {
execvp(opt_params[0], (char **)opt_params);
FATAL("Oops, failed to execute '%s' - check your PATH", opt_params[0]);
}
if (waitpid(pid, &status, 0) <= 0) { PFATAL("waitpid() failed"); }
if (!be_quiet) SAYF("Done, delete '%s' now.\n", ll_file);
return 0;

View File

@ -0,0 +1,91 @@
#!/usr/bin/env python3
#
# american fuzzy lop++ - callgraph analysis script
# ------------------------------------------------
#
# (c) 2021 Marc Heuse <mh@mh-sec.de>
#
# python script a single parameter: fuzz.cg
# fuzz.cg is generated with with afl-clang-lto + AFL_LLVM_LTO_CALLGRAPH=1
#
# 1. run c++filt on both .cg inputs
# 2. parse fuzz-harness.cg, remove all functions that just have on external call
# keep an array of it's functions and the one it is calling
# 3. parse fuzz.cg, remove all functions that just have an external call.
# 4. loop on fuzz-harness.cg array, look for these functions in fuzz.cg,
# remove them from both fuzz-harness.cg and fuzz.cg array when found and
# add the callers into fuzz-harness.cg array instead.
# 5. resulting functions in fuzz-harness.cg array are unreachable
#
import subprocess
import sys
import os
import re
new_function = re.compile('^Call graph node for function:')
is_external = re.compile('calls external')
is_call = re.compile('calls function')
is_empty = re.compile('^\s*$')
all_functions = []
entries = []
calls = []
entry = { 'caller': '', 'calls': [''] }
func = ''
i = 0
print()
print('Analysing ' + sys.argv[1] + " for unreachable functions...")
with open(sys.argv[1]) as f:
for line in f:
#print(line)
if new_function.match(line):
if not len(func) == 0:
print('Error: invalid input: ' + line)
exit(1)
func = line.split("'")[1]
if len(func) > 0:
all_functions.append(func)
calls.clear()
elif is_call.search(line):
if len(func) > 0:
call = line.split("'")[1]
calls.append(call);
elif is_empty.match(line):
if len(calls) > 0 and len(func) > 0:
calls_u = list(dict.fromkeys(calls))
entry = { 'func' : func, 'calls' : calls_u }
entries.append(entry)
func = ''
calls.clear()
elif not is_external.search(line):
#print('Ignoring line: ' + line)
i += 1 # ignored
found = 0
for entry in entries:
if entry['func'] == 'main':
found = 1
break
if found:
follow = ['main']
else:
follow = ['LLVMFuzzerInitialize', 'LLVMFuzzerTestOneInput']
for f in follow:
for entry in entries:
if entry['func'] == f:
#print('follow: ' + f + ' => ' + str(entry['calls']))
for call in entry['calls']:
follow.append(call)
entries.remove(entry)
break
print('Analysis done, printing results.')
print('Note that CTORs/DTORs/callbacks-only functions will be in the unreachable list.')
print()
for entry in entries:
print('Unreachable: ' + entry['func'])