diff options
author | vanhauser-thc <vh@thc.org> | 2022-07-04 09:59:47 +0200 |
---|---|---|
committer | vanhauser-thc <vh@thc.org> | 2022-07-04 09:59:47 +0200 |
commit | 1334851e7c35bf7bf35db0dc2a974b2e0249d8f8 (patch) | |
tree | 934793839d3c939c12ed9202dae51cb06561c0d6 | |
parent | e21738a24852e0ed9b346c28aeb4132a34d5b7cc (diff) | |
download | afl++-1334851e7c35bf7bf35db0dc2a974b2e0249d8f8.tar.gz |
cmplog support for gcc_plugin by adacore
-rw-r--r-- | GNUmakefile.gcc_plugin | 14 | ||||
-rw-r--r-- | docs/Changelog.md | 5 | ||||
-rw-r--r-- | docs/env_variables.md | 2 | ||||
-rw-r--r-- | docs/features.md | 2 | ||||
-rw-r--r-- | instrumentation/README.gcc_plugin.md | 9 | ||||
-rw-r--r-- | instrumentation/afl-gcc-cmplog-pass.so.cc | 404 | ||||
-rw-r--r-- | instrumentation/afl-gcc-cmptrs-pass.so.cc | 366 | ||||
-rw-r--r-- | instrumentation/afl-gcc-common.h | 498 | ||||
-rw-r--r-- | instrumentation/afl-gcc-pass.so.cc | 518 | ||||
-rw-r--r-- | src/afl-cc.c | 25 | ||||
-rw-r--r-- | test-instr.c | 21 |
11 files changed, 1367 insertions, 497 deletions
diff --git a/GNUmakefile.gcc_plugin b/GNUmakefile.gcc_plugin index e21203ae..17bd825d 100644 --- a/GNUmakefile.gcc_plugin +++ b/GNUmakefile.gcc_plugin @@ -100,7 +100,9 @@ ifeq "$(SYS)" "SunOS" endif -PROGS = ./afl-gcc-pass.so ./afl-compiler-rt.o ./afl-compiler-rt-32.o ./afl-compiler-rt-64.o +PASSES = ./afl-gcc-pass.so ./afl-gcc-cmplog-pass.so ./afl-gcc-cmptrs-pass.so + +PROGS = $(PASSES) ./afl-compiler-rt.o ./afl-compiler-rt-32.o ./afl-compiler-rt-64.o .PHONY: all all: test_shm test_deps $(PROGS) test_build all_done @@ -141,6 +143,8 @@ afl-common.o: ./src/afl-common.c @printf "[*] Building 64-bit variant of the runtime (-m64)... " @$(CC) $(CFLAGS_SAFE) $(CPPFLAGS) -O3 -Wno-unused-result -m64 -fPIC -c $< -o $@ 2>/dev/null; if [ "$$?" = "0" ]; then echo "success!"; else echo "failed (that's fine)"; fi +$(PASSES): instrumentation/afl-gcc-common.h + ./afl-gcc-pass.so: instrumentation/afl-gcc-pass.so.cc | test_deps $(CXX) $(CXXEFLAGS) $(PLUGIN_FLAGS) -shared $< -o $@ ln -sf afl-cc afl-gcc-fast @@ -148,6 +152,12 @@ afl-common.o: ./src/afl-common.c ln -sf afl-cc.8 afl-gcc-fast.8 ln -sf afl-cc.8 afl-g++-fast.8 +./afl-gcc-cmplog-pass.so: instrumentation/afl-gcc-cmplog-pass.so.cc | test_deps + $(CXX) $(CXXEFLAGS) $(PLUGIN_FLAGS) -shared $< -o $@ + +./afl-gcc-cmptrs-pass.so: instrumentation/afl-gcc-cmptrs-pass.so.cc | test_deps + $(CXX) $(CXXEFLAGS) $(PLUGIN_FLAGS) -shared $< -o $@ + .PHONY: test_build test_build: $(PROGS) @echo "[*] Testing the CC wrapper and instrumentation output..." @@ -190,6 +200,8 @@ install: all ln -sf afl-c++ $${DESTDIR}$(BIN_PATH)/afl-g++-fast ln -sf afl-compiler-rt.o $${DESTDIR}$(HELPER_PATH)/afl-gcc-rt.o install -m 755 ./afl-gcc-pass.so $${DESTDIR}$(HELPER_PATH) + install -m 755 ./afl-gcc-cmplog-pass.so $${DESTDIR}$(HELPER_PATH) + install -m 755 ./afl-gcc-cmptrs-pass.so $${DESTDIR}$(HELPER_PATH) install -m 644 -T instrumentation/README.gcc_plugin.md $${DESTDIR}$(DOC_PATH)/README.gcc_plugin.md .PHONY: clean diff --git a/docs/Changelog.md b/docs/Changelog.md index 737df7fa..7284500e 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -8,6 +8,11 @@ Want to stay in the loop on major new features? Join our mailing list by sending a mail to <afl-users+subscribe@googlegroups.com>. +### Version ++4.02a (dev) + - gcc_plugin: + - Adacore submitted CMPLOG support to the gcc_plugin! :-) + + ### Version ++4.01c (release) - fixed */build_...sh scripts to work outside of git - new custom_mutator: libafl with token fuzzing :) diff --git a/docs/env_variables.md b/docs/env_variables.md index 0598a809..c836a929 100644 --- a/docs/env_variables.md +++ b/docs/env_variables.md @@ -160,6 +160,8 @@ Available options: Setting `AFL_LLVM_CMPLOG=1` during compilation will tell afl-clang-fast to produce a CmpLog binary. +For afl-gcc-fast set `AFL_GCC_CMPLOG=1` instead. + For more information, see [instrumentation/README.cmplog.md](../instrumentation/README.cmplog.md). diff --git a/docs/features.md b/docs/features.md index dd3d2bcb..212302f8 100644 --- a/docs/features.md +++ b/docs/features.md @@ -12,7 +12,7 @@ QEMU 5.1 with laf-intel and Redqueen, FRIDA mode, unicorn mode, gcc plugin, full | NeverZero [B] | x86[_64] | x(1) | x | x | x | x | | | | Persistent Mode [C] | | x | x | x86[_64]/arm64 | x86[_64]/arm[64] | x | | | | LAF-Intel / CompCov [D] | | x | | | x86[_64]/arm[64] | x86[_64]/arm[64] | x86[_64] | | -| CmpLog [E] | | x | | x86[_64]/arm64 | x86[_64]/arm[64] | | | | +| CmpLog [E] | | x | x | x86[_64]/arm64 | x86[_64]/arm[64] | | | | | Selective Instrumentation [F] | | x | x | x | x | | | | | Non-Colliding Coverage [G] | | x(4) | | | (x)(5) | | | | | Ngram prev_loc Coverage [H] | | x(6) | | | | | | | diff --git a/instrumentation/README.gcc_plugin.md b/instrumentation/README.gcc_plugin.md index ed39af9d..011a574a 100644 --- a/instrumentation/README.gcc_plugin.md +++ b/instrumentation/README.gcc_plugin.md @@ -99,4 +99,11 @@ See ## 6) Bonus feature #3: selective instrumentation It can be more effective to fuzzing to only instrument parts of the code. For -details, see [README.instrument_list.md](README.instrument_list.md). \ No newline at end of file +details, see [README.instrument_list.md](README.instrument_list.md). + +## 7) Bonus feature #4: CMPLOG + +The gcc_plugin also support CMPLOG/Redqueen, just set `AFL_GCC_CMPLOG` before +instrumenting the target. +Read more about this in the llvm document. + diff --git a/instrumentation/afl-gcc-cmplog-pass.so.cc b/instrumentation/afl-gcc-cmplog-pass.so.cc new file mode 100644 index 00000000..c2910498 --- /dev/null +++ b/instrumentation/afl-gcc-cmplog-pass.so.cc @@ -0,0 +1,404 @@ +/* GCC plugin for cmplog instrumentation of code for AFL++. + + Copyright 2014-2019 Free Software Foundation, Inc + Copyright 2015, 2016 Google Inc. All rights reserved. + Copyright 2019-2020 AFLplusplus Project. All rights reserved. + Copyright 2019-2022 AdaCore + + Written by Alexandre Oliva <oliva@adacore.com>, based on the AFL++ + LLVM CmpLog pass by Andrea Fioraldi <andreafioraldi@gmail.com>, and + on the AFL GCC pass. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + + */ + +#include "afl-gcc-common.h" + +/* This plugin, being under the same license as GCC, satisfies the + "GPL-compatible Software" definition in the GCC RUNTIME LIBRARY + EXCEPTION, so it can be part of an "Eligible" "Compilation + Process". */ +int plugin_is_GPL_compatible = 1; + +namespace { + +static const struct pass_data afl_cmplog_pass_data = { + + .type = GIMPLE_PASS, + .name = "aflcmplog", + .optinfo_flags = OPTGROUP_NONE, + .tv_id = TV_NONE, + .properties_required = 0, + .properties_provided = 0, + .properties_destroyed = 0, + .todo_flags_start = 0, + .todo_flags_finish = (TODO_update_ssa | TODO_cleanup_cfg | TODO_verify_il | + TODO_rebuild_cgraph_edges), + +}; + +struct afl_cmplog_pass : afl_base_pass { + + afl_cmplog_pass(bool quiet) + : afl_base_pass(quiet, /*debug=*/false, afl_cmplog_pass_data), + t8u(), + cmplog_hooks() { + + } + + /* An unsigned 8-bit integral type. */ + tree t8u; + + /* Declarations for the various cmplog hook functions, allocated on demand.. + [0] is for __cmplog_ins_hookN, that accepts non-power-of-2 sizes. + [n in 1..5] are for unsigned ints of 2^{n-1} bytes. */ + tree cmplog_hooks[6]; + + tree cmplog_hook(unsigned i) { + + tree t, fnt; + + if (!t8u) { + + if (BITS_PER_UNIT == 8) + t8u = unsigned_char_type_node; + else + t8u = build_nonstandard_integer_type(8, 1); + + } + + if (i <= ARRAY_SIZE(cmplog_hooks) && cmplog_hooks[i]) + return cmplog_hooks[i]; + + switch (i) { + + case 0: +#ifdef uint128_type_node + t = uint128_type_node; +#else + t = build_nonstandard_integer_type(128, 1); +#endif + fnt = + build_function_type_list(void_type_node, t, t, t8u, t8u, NULL_TREE); + t = cmplog_hooks[0] = build_fn_decl("__cmplog_ins_hookN", fnt); + break; + + case 1: + t = t8u; + fnt = build_function_type_list(void_type_node, t, t, t8u, NULL_TREE); + t = cmplog_hooks[1] = build_fn_decl("__cmplog_ins_hook1", fnt); + break; + + case 2: + t = uint16_type_node; + fnt = build_function_type_list(void_type_node, t, t, t8u, NULL_TREE); + t = cmplog_hooks[2] = build_fn_decl("__cmplog_ins_hook2", fnt); + break; + + case 3: + t = uint32_type_node; + fnt = build_function_type_list(void_type_node, t, t, t8u, NULL_TREE); + t = cmplog_hooks[3] = build_fn_decl("__cmplog_ins_hook4", fnt); + break; + + case 4: + t = uint64_type_node; + fnt = build_function_type_list(void_type_node, t, t, t8u, NULL_TREE); + t = cmplog_hooks[4] = build_fn_decl("__cmplog_ins_hook8", fnt); + break; + + case 5: +#ifdef uint128_type_node + t = uint128_type_node; +#else + t = build_nonstandard_integer_type(128, 1); +#endif + fnt = build_function_type_list(void_type_node, t, t, t8u, NULL_TREE); + t = cmplog_hooks[5] = build_fn_decl("__cmplog_ins_hook16", fnt); + break; + + default: + gcc_unreachable(); + + } + + /* Mark the newly-created decl as non-throwing, so that we can + insert call within basic blocks. */ + TREE_NOTHROW(t) = 1; + + return t; + + } + + /* Insert a cmplog hook call before GSI for a CODE compare between + LHS and RHS. */ + void insert_cmplog_call(gimple_stmt_iterator gsi, tree_code code, tree lhs, + tree rhs) { + + gcc_checking_assert(TYPE_MAIN_VARIANT(TREE_TYPE(lhs)) == + TYPE_MAIN_VARIANT(TREE_TYPE(rhs))); + + tree fn; + bool pass_n = false; + + /* Obtain the compare operand size as a constant. */ + tree st = TREE_TYPE(lhs); + tree szt = TYPE_SIZE(st); + + if (!tree_fits_uhwi_p(szt)) return; + + unsigned HOST_WIDE_INT sz = tree_to_uhwi(szt); + + /* Round it up. */ + if (sz % 8) sz = (((sz - 1) / 8) + 1) * 8; + + /* Select the hook function to call, based on the size. */ + switch (sz) { + + default: + fn = cmplog_hook(0); + pass_n = true; + break; + + case 8: + fn = cmplog_hook(1); + break; + + case 16: + fn = cmplog_hook(2); + break; + + case 32: + fn = cmplog_hook(3); + break; + + case 64: + fn = cmplog_hook(4); + break; + + case 128: + fn = cmplog_hook(5); + break; + + } + + /* Set attr according to the compare operation. */ + unsigned char attr = 0; + + switch (code) { + + case UNORDERED_EXPR: + case ORDERED_EXPR: + /* ??? */ + /* Fallthrough. */ + case NE_EXPR: + case LTGT_EXPR: + break; + + case EQ_EXPR: + case UNEQ_EXPR: + attr += 1; + break; + + case GT_EXPR: + case UNGT_EXPR: + attr += 2; + break; + + case GE_EXPR: + case UNGE_EXPR: + attr += 3; + break; + + case LT_EXPR: + case UNLT_EXPR: + attr += 4; + break; + + case LE_EXPR: + case UNLE_EXPR: + attr += 5; + break; + + default: + gcc_unreachable(); + + } + + if (FLOAT_TYPE_P(TREE_TYPE(lhs))) { + + attr += 8; + + tree t = build_nonstandard_integer_type(sz, 1); + + tree s = make_ssa_name(t); + gimple *g = gimple_build_assign(s, VIEW_CONVERT_EXPR, + build1(VIEW_CONVERT_EXPR, t, lhs)); + lhs = s; + gsi_insert_before(&gsi, g, GSI_SAME_STMT); + + s = make_ssa_name(t); + g = gimple_build_assign(s, VIEW_CONVERT_EXPR, + build1(VIEW_CONVERT_EXPR, t, rhs)); + rhs = s; + gsi_insert_before(&gsi, g, GSI_SAME_STMT); + + } + + /* Convert the operands to the hook arg type, if needed. */ + tree t = TREE_VALUE(TYPE_ARG_TYPES(TREE_TYPE(fn))); + + lhs = fold_convert_loc(UNKNOWN_LOCATION, t, lhs); + if (!is_gimple_val(lhs)) { + + tree s = make_ssa_name(t); + gimple *g = gimple_build_assign(s, lhs); + lhs = s; + gsi_insert_before(&gsi, g, GSI_SAME_STMT); + + } + + rhs = fold_convert_loc(UNKNOWN_LOCATION, t, rhs); + if (!is_gimple_val(rhs)) { + + tree s = make_ssa_name(t); + gimple *g = gimple_build_assign(s, rhs); + rhs = s; + gsi_insert_before(&gsi, g, GSI_SAME_STMT); + + } + + /* Insert the call. */ + tree att = build_int_cst(t8u, attr); + gimple *call; + if (pass_n) + call = gimple_build_call(fn, 4, lhs, rhs, att, + build_int_cst(t8u, sz / 8 - 1)); + else + call = gimple_build_call(fn, 3, lhs, rhs, att); + + gsi_insert_before(&gsi, call, GSI_SAME_STMT); + + } + + virtual unsigned int execute(function *fn) { + + if (!isInInstrumentList(fn)) return 0; + + basic_block bb; + FOR_EACH_BB_FN(bb, fn) { + + /* A GIMPLE_COND or GIMPLE_SWITCH will always be the last stmt + in a BB. */ + gimple_stmt_iterator gsi = gsi_last_bb(bb); + if (gsi_end_p(gsi)) continue; + + gimple *stmt = gsi_stmt(gsi); + + if (gimple_code(stmt) == GIMPLE_COND) { + + tree_code code = gimple_cond_code(stmt); + tree lhs = gimple_cond_lhs(stmt); + tree rhs = gimple_cond_rhs(stmt); + + insert_cmplog_call(gsi, code, lhs, rhs); + + } else if (gimple_code(stmt) == GIMPLE_SWITCH) { + + gswitch *sw = as_a<gswitch *>(stmt); + tree lhs = gimple_switch_index(sw); + + for (int i = 0, e = gimple_switch_num_labels(sw); i < e; i++) { + + tree clx = gimple_switch_label(sw, i); + tree rhsl = CASE_LOW(clx); + /* Default case labels exprs don't have a CASE_LOW. */ + if (!rhsl) continue; + tree rhsh = CASE_HIGH(clx); + /* If there is a CASE_HIGH, issue range compares. */ + if (rhsh) { + + insert_cmplog_call(gsi, GE_EXPR, lhs, rhsl); + insert_cmplog_call(gsi, LE_EXPR, lhs, rhsh); + + } + + /* Otherwise, use a single equality compare. */ + else + insert_cmplog_call(gsi, EQ_EXPR, lhs, rhsl); + + } + + } else + + continue; + + } + + return 0; + + } + +}; + +static struct plugin_info afl_cmplog_plugin = { + + .version = "20220420", + .help = G_("AFL gcc cmplog plugin\n\ +\n\ +Set AFL_QUIET in the environment to silence it.\n\ +"), + +}; + +} // namespace + +/* This is the function GCC calls when loading a plugin. Initialize + and register further callbacks. */ +int plugin_init(struct plugin_name_args * info, + struct plugin_gcc_version *version) { + + if (!plugin_default_version_check(version, &gcc_version)) + FATAL(G_("GCC and plugin have incompatible versions, expected GCC %s, " + "is %s"), + gcc_version.basever, version->basever); + + /* Show a banner. */ + bool quiet = false; + if (isatty(2) && !getenv("AFL_QUIET")) + SAYF(cCYA "afl-gcc-cmplog-pass " cBRI VERSION cRST + " by <oliva@adacore.com>\n"); + else + quiet = true; + + const char *name = info->base_name; + register_callback(name, PLUGIN_INFO, NULL, &afl_cmplog_plugin); + + afl_cmplog_pass * aflp = new afl_cmplog_pass(quiet); + struct register_pass_info pass_info = { + + .pass = aflp, + .reference_pass_name = "ssa", + .ref_pass_instance_number = 1, + .pos_op = PASS_POS_INSERT_AFTER, + + }; + + register_callback(name, PLUGIN_PASS_MANAGER_SETUP, NULL, &pass_info); + + return 0; + +} + diff --git a/instrumentation/afl-gcc-cmptrs-pass.so.cc b/instrumentation/afl-gcc-cmptrs-pass.so.cc new file mode 100644 index 00000000..31679b9b --- /dev/null +++ b/instrumentation/afl-gcc-cmptrs-pass.so.cc @@ -0,0 +1,366 @@ +/* GCC plugin for cmplog routines instrumentation of code for AFL++. + + Copyright 2014-2019 Free Software Foundation, Inc + Copyright 2015, 2016 Google Inc. All rights reserved. + Copyright 2019-2020 AFLplusplus Project. All rights reserved. + Copyright 2019-2022 AdaCore + + Written by Alexandre Oliva <oliva@adacore.com>, based on the AFL++ + LLVM CmpLog Routines pass by Andrea Fioraldi + <andreafioraldi@gmail.com>, and on the AFL GCC CmpLog pass. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + + */ + +#include "afl-gcc-common.h" + +/* This plugin, being under the same license as GCC, satisfies the + "GPL-compatible Software" definition in the GCC RUNTIME LIBRARY + EXCEPTION, so it can be part of an "Eligible" "Compilation + Process". */ +int plugin_is_GPL_compatible = 1; + +namespace { + +static const struct pass_data afl_cmptrs_pass_data = { + + .type = GIMPLE_PASS, + .name = "aflcmptrs", + .optinfo_flags = OPTGROUP_NONE, + .tv_id = TV_NONE, + .properties_required = 0, + .properties_provided = 0, + .properties_destroyed = 0, + .todo_flags_start = 0, + .todo_flags_finish = (TODO_update_ssa | TODO_cleanup_cfg | TODO_verify_il | + TODO_rebuild_cgraph_edges), + +}; + +struct afl_cmptrs_pass : afl_base_pass { + + afl_cmptrs_pass(bool quiet) + : afl_base_pass(quiet, /*debug=*/false, afl_cmptrs_pass_data), + tp8u(), + cmptrs_hooks() { + + } + + /* A pointer type to a unsigned 8-bit integral type. */ + tree tp8u; + + /* Declarations for the various cmptrs hook functions, allocated on + demand.. [0] is for compares between any pointers, [1] is for + compares between G++ std::string, [2] is for compares between G++ + std::string and GCC C strings, [3] and [4] are analogous to [1] + and [2] but for LLVM C++ strings. */ + tree cmptrs_hooks[5]; + + tree cmptrs_hook(unsigned i) { + + if (!tp8u) { + + tree t8u; + if (BITS_PER_UNIT == 8) + t8u = unsigned_char_type_node; + else + t8u = build_nonstandard_integer_type(8, 1); + tp8u = build_pointer_type(t8u); + + } + + if (i <= ARRAY_SIZE(cmptrs_hooks) && cmptrs_hooks[i]) + return cmptrs_hooks[i]; + + const char *n = NULL; + + switch (i) { + + case 0: + n = "__cmplog_rtn_hook"; + break; + + case 1: + n = "__cmplog_rtn_gcc_stdstring_stdstring"; + break; + + case 2: + n = "__cmplog_rtn_gcc_stdstring_cstring"; + break; + + case 3: + n = "__cmplog_rtn_llvm_stdstring_stdstring"; + break; + + case 4: + n = "__cmplog_rtn_llvm_stdstring_cstring"; + break; + + default: + gcc_unreachable(); + + } + + tree fnt = build_function_type_list(void_type_node, tp8u, tp8u, NULL_TREE); + tree t = cmptrs_hooks[i] = build_fn_decl(n, fnt); + + /* Mark the newly-created decl as non-throwing, so that we can + insert call within basic blocks. */ + TREE_NOTHROW(t) = 1; + + return t; + + } + + /* Return true if T is the char* type. */ + bool is_c_string(tree t) { + + return (POINTER_TYPE_P(t) && + TYPE_MAIN_VARIANT(TREE_TYPE(t)) == char_type_node); + + } + + /* Return true if T is an indirect std::string type. The LLVM pass + tests portions of the mangled name of the callee. We could do + that in GCC too, but computing the mangled name may cause + template instantiations and get symbols defined that could + otherwise be considered unused. We check for compatible layout, + and class, namespace, and field names. These have been unchanged + since at least GCC 7, probably longer, up to GCC 11. Odds are + that, if it were to change in significant ways, mangling would + also change to flag the incompatibility, and we'd have to use a + different hook anyway. */ + bool is_gxx_std_string(tree t) { + + /* We need a pointer or reference type. */ + if (!POINTER_TYPE_P(t)) return false; + + /* Get to the pointed-to type. */ + t = TREE_TYPE(t); + if (!t) return false; + + /* Select the main variant, so that can compare types with pointers. */ + t = TYPE_MAIN_VARIANT(t); + + /* We expect it to be a record type. */ + if (TREE_CODE(t) != RECORD_TYPE) return false; + + /* The type of the template is basic_string. */ + if (strcmp(IDENTIFIER_POINTER(TYPE_IDENTIFIER(t)), "basic_string") != 0) + return false; + + /* It's declared in an internal namespace named __cxx11. */ + tree c = DECL_CONTEXT(TYPE_NAME(t)); + if (!c || TREE_CODE(c) != NAMESPACE_DECL || + strcmp(IDENTIFIER_POINTER(DECL_NAME(c)), "__cxx11") != 0) + return false; + + /* The __cxx11 namespace is a member of namespace std. */ + c = DECL_CONTEXT(c); + if (!c || TREE_CODE(c) != NAMESPACE_DECL || + strcmp(IDENTIFIER_POINTER(DECL_NAME(c)), "std") != 0) + return false; + + /* And the std namespace is in the global namespace. */ + c = DECL_CONTEXT(c); + if (c && TREE_CODE(c) != TRANSLATION_UNIT_DECL) return false; + + /* Check that the first nonstatic data member of the record type + is named _M_dataplus. */ + for (c = TYPE_FIELDS(t); c; c = DECL_CHAIN(c)) + if (TREE_CODE(c) == FIELD_DECL) break; + if (!c || !integer_zerop(DECL_FIELD_BIT_OFFSET(c)) || + strcmp(IDENTIFIER_POINTER(DECL_NAME(c)), "_M_dataplus") != 0) + return false; + + /* Check that the second nonstatic data member of the record type + is named _M_string_length. */ + tree f2; + for (f2 = DECL_CHAIN(c); f2; f2 = DECL_CHAIN(f2)) + if (TREE_CODE(f2) == FIELD_DECL) break; + if (!f2 /* No need to check this field's offset. */ + || strcmp(IDENTIFIER_POINTER(DECL_NAME(f2)), "_M_string_length") != 0) + return false; + + /* The type of the second data member is size_t. */ + if (!TREE_TYPE(f2) || TYPE_MAIN_VARIANT(TREE_TYPE(f2)) != size_type_node) + return false; + + /* Now go back to the first data member. Its type should be a + record type named _Alloc_hider. */ + c = TREE_TYPE(c); + if (!c || TREE_CODE(c) != RECORD_TYPE || + strcmp(IDENTIFIER_POINTER(TYPE_IDENTIFIER(c)), "_Alloc_hider") != 0) + return false; + + /* And its first data member is named _M_p. */ + for (c = TYPE_FIELDS(c); c; c = DECL_CHAIN(c)) + if (TREE_CODE(c) == FIELD_DECL) break; + if (!c || !integer_zerop(DECL_FIELD_BIT_OFFSET(c)) || + strcmp(IDENTIFIER_POINTER(DECL_NAME(c)), "_M_p") != 0) + return false; + + /* For the basic_string<char> type we're interested in, the type + of the data member is the C string type. */ + if (!is_c_string(TREE_TYPE(c))) return false; + + /* This might not be the real thing, but the bits that matter for + the hook are there. */ + + return true; + + } + + /* ??? This is not implemented. What would the point be of + recognizing LLVM's string type in GCC? */ + bool is_llvm_std_string(tree t) { + + return false; + + } + + virtual unsigned int execute(function *fn) { + + if (!isInInstrumentList(fn)) return 0; + + basic_block bb; + FOR_EACH_BB_FN(bb, fn) { + + for (gimple_stmt_iterator gsi = gsi_after_labels(bb); !gsi_end_p(gsi); + gsi_next(&gsi)) { + + gimple *stmt = gsi_stmt(gsi); + + /* We're only interested in GIMPLE_CALLs. */ + if (gimple_code(stmt) != GIMPLE_CALL) continue; + + if (gimple_call_num_args(stmt) < 2) continue; + + gcall *c = as_a<gcall *>(stmt); + + tree callee_type = gimple_call_fntype(c); + + if (!callee_type || !TYPE_ARG_TYPES(callee_type) || + !TREE_CHAIN(TYPE_ARG_TYPES(callee_type))) + continue; + + tree arg_type[2] = { + + TYPE_MAIN_VARIANT(TREE_VALUE(TYPE_ARG_TYPES(callee_type))), + TYPE_MAIN_VARIANT( + TREE_VALUE(TREE_CHAIN(TYPE_ARG_TYPES(callee_type))))}; + + tree fn = NULL; + /* Callee arglist starts with two GCC std::string arguments. */ + if (arg_type[0] == arg_type[1] && is_gxx_std_string(arg_type[0])) + fn = cmptrs_hook(1); + /* Callee arglist starts with GCC std::string and C string. */ + else if (is_gxx_std_string(arg_type[0]) && is_c_string(arg_type[1])) + fn = cmptrs_hook(2); + /* Callee arglist starts with two LLVM std::string arguments. */ + else if (arg_type[0] == arg_type[1] && is_llvm_std_string(arg_type[0])) + fn = cmptrs_hook(3); + /* Callee arglist starts with LLVM std::string and C string. */ + else if (is_llvm_std_string(arg_type[0]) && is_c_string(arg_type[1])) + fn = cmptrs_hook(4); + /* Callee arglist starts with two pointers to the same type, + and callee returns a value. */ + else if (arg_type[0] == arg_type[1] && POINTER_TYPE_P(arg_type[0]) && + (TYPE_MAIN_VARIANT(gimple_call_return_type(c)) != + void_type_node)) + fn = cmptrs_hook(0); + else + continue; + + tree arg[2] = {gimple_call_arg(c, 0), gimple_call_arg(c, 1)}; + + for (unsigned i = 0; i < ARRAY_SIZE(arg); i++) { + + tree c = fold_convert_loc(UNKNOWN_LOCATION, tp8u, arg[i]); + if (!is_gimple_val(c)) { + + tree s = make_ssa_name(tp8u); + gimple *g = gimple_build_assign(s, c); + c = s; + gsi_insert_before(&gsi, g, GSI_SAME_STMT); + + } + + arg[i] = c; + + } + + gimple *call = gimple_build_call(fn, 2, arg[0], arg[1]); + gsi_insert_before(&gsi, call, GSI_SAME_STMT); + + } + + } + + return 0; + + } + +}; + +static struct plugin_info afl_cmptrs_plugin = { + + .version = "20220420", + .help = G_("AFL gcc cmptrs plugin\n\ +\n\ +Set AFL_QUIET in the environment to silence it.\n\ +"), + +}; + +} // namespace + +/* This is the function GCC calls when loading a plugin. Initialize + and register further callbacks. */ +int plugin_init(struct plugin_name_args * info, + struct plugin_gcc_version *version) { + + if (!plugin_default_version_check(version, &gcc_version)) + FATAL(G_("GCC and plugin have incompatible versions, expected GCC %s, " + "is %s"), + gcc_version.basever, version->basever); + + /* Show a banner. */ + bool quiet = false; + if (isatty(2) && !getenv("AFL_QUIET")) + SAYF(cCYA "afl-gcc-cmptrs-pass " cBRI VERSION cRST + " by <oliva@adacore.com>\n"); + else + quiet = true; + + const char *name = info->base_name; + register_callback(name, PLUGIN_INFO, NULL, &afl_cmptrs_plugin); + + afl_cmptrs_pass * aflp = new afl_cmptrs_pass(quiet); + struct register_pass_info pass_info = { + + .pass = aflp, + .reference_pass_name = "ssa", + .ref_pass_instance_number = 1, + .pos_op = PASS_POS_INSERT_AFTER, + + }; + + register_callback(name, PLUGIN_PASS_MANAGER_SETUP, NULL, &pass_info); + + return 0; + +} + diff --git a/instrumentation/afl-gcc-common.h b/instrumentation/afl-gcc-common.h new file mode 100644 index 00000000..806e7ac3 --- /dev/null +++ b/instrumentation/afl-gcc-common.h @@ -0,0 +1,498 @@ +/* GCC plugin common infrastructure for AFL++ instrumentation passes. + + Copyright 2014-2019 Free Software Foundation, Inc + Copyright 2015, 2016 Google Inc. All rights reserved. + Copyright 2019-2022 AdaCore + + Written by Alexandre Oliva <oliva@adacore.com>, based on the AFL++ + GCC plugin. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + + */ + +#include "../include/config.h" +#include "../include/debug.h" + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#ifdef likely + #undef likely +#endif +#ifdef unlikely + #undef unlikely +#endif + +#include <list> +#include <string> +#include <fstream> + +#include <algorithm> +#include <fnmatch.h> + +#include <gcc-plugin.h> +#include <plugin-version.h> +#include <toplev.h> +#include <tree-pass.h> +#include <context.h> +#include <tree.h> +#include <gimplify.h> +#include <basic-block.h> +#include <tree-ssa-alias.h> +#include <gimple-expr.h> +#include <gimple.h> +#include <gimple-iterator.h> +#include <stringpool.h> +#include <gimple-ssa.h> +#if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) >= \ + 60200 /* >= version 6.2.0 */ + #include <tree-vrp.h> +#endif +#include <tree-ssanames.h> +#include <tree-phinodes.h> +#include <ssa-iterators.h> + +#include <intl.h> + +namespace { + +struct afl_base_pass : gimple_opt_pass { + + afl_base_pass(bool quiet, bool debug, struct pass_data const &pd) + : gimple_opt_pass(pd, g), be_quiet(quiet), debug(debug) { + + initInstrumentList(); + + } + + /* Are we outputting to a non-terminal, or running with AFL_QUIET + set? */ + const bool be_quiet; + + /* Are we running with AFL_DEBUG set? */ + const bool debug; + +#define report_fatal_error(msg) BADF(msg) + + std::list<std::string> allowListFiles; + std::list<std::string> allowListFunctions; + std::list<std::string> denyListFiles; + std::list<std::string> denyListFunctions; + + /* Note: this ignore check is also called in isInInstrumentList() */ + bool isIgnoreFunction(function *F) { + + // Starting from "LLVMFuzzer" these are functions used in libfuzzer based + // fuzzing campaign installations, e.g. oss-fuzz + + static constexpr const char *ignoreList[] = { + + "asan.", + "llvm.", + "sancov.", + "__ubsan_", + "ign.", + "__afl_", + "_fini", + "__libc_csu", + "__asan", + "__msan", + "__cmplog", + "__sancov", + "msan.", + "LLVMFuzzerM", + "LLVMFuzzerC", + "LLVMFuzzerI", + "__decide_deferred", + "maybe_duplicate_stderr", + "discard_output", + "close_stdout", + "dup_and_close_stderr", + "maybe_close_fd_mask", + "ExecuteFilesOnyByOne" + + }; + + const char *name = IDENTIFIER_POINTER(DECL_NAME(F->decl)); + int len = IDENTIFIER_LENGTH(DECL_NAME(F->decl)); + + for (auto const &ignoreListFunc : ignoreList) { + + if (strncmp(name, ignoreListFunc, len) == 0) { return true; } + + } + + return false; + + } + + void initInstrumentList() { + + char *allowlist = getenv("AFL_GCC_ALLOWLIST"); + if (!allowlist) allowlist = getenv("AFL_GCC_INSTRUMENT_FILE"); + if (!allowlist) allowlist = getenv("AFL_GCC_WHITELIST"); + if (!allowlist) allowlist = getenv("AFL_LLVM_ALLOWLIST"); + if (!allowlist) allowlist = getenv("AFL_LLVM_INSTRUMENT_FILE"); + if (!allowlist) allowlist = getenv("AFL_LLVM_WHITELIST"); + char *denylist = getenv("AFL_GCC_DENYLIST"); + if (!denylist) denylist = getenv("AFL_GCC_BLOCKLIST"); + if (!denylist) denylist = getenv("AFL_LLVM_DENYLIST"); + if (!denylist) denylist = getenv("AFL_LLVM_BLOCKLIST"); + + if (allowlist && denylist) + FATAL( + "You can only specify either AFL_GCC_ALLOWLIST or AFL_GCC_DENYLIST " + "but not both!"); + + if (allowlist) { + + std::string line; + std::ifstream fileStream; + fileStream.open(allowlist); + if (!fileStream) report_fatal_error("Unable to open AFL_GCC_ALLOWLIST"); + getline(fileStream, line); + + while (fileStream) { + + int is_file = -1; + std::size_t npos; + std::string original_line = line; + + line.erase(std::remove_if(line.begin(), line.end(), ::isspace), + line.end()); + + // remove # and following + if ((npos = line.find("#")) != std::string::npos) + line = line.substr(0, npos); + + if (line.compare(0, 4, "fun:") == 0) { + + is_file = 0; + line = line.substr(4); + + } else if (line.compare(0, 9, "function:") == 0) { + + is_file = 0; + line = line.substr(9); + + } else if (line.compare(0, 4, "src:") == 0) { + + is_file = 1; + line = line.substr(4); + + } else if (line.compare(0, 7, "source:") == 0) { + + is_file = 1; + line = line.substr(7); + + } + + if (line.find(":") != std::string::npos) { + + FATAL("invalid line in AFL_GCC_ALLOWLIST: %s", original_line.c_str()); + + } + + if (line.length() > 0) { + + // if the entry contains / or . it must be a file + if (is_file == -1) + if (line.find("/") != std::string::npos || + line.find(".") != std::string::npos) + is_file = 1; + // otherwise it is a function + + if (is_file == 1) + allowListFiles.push_back(line); + else + allowListFunctions.push_back(line); + + } + + getline(fileStream, line); + + } + + if (debug) + DEBUGF("loaded allowlist with %zu file and %zu function entries\n", + allowListFiles.size(), allowListFunctions.size()); + + } + + if (denylist) { + + std::string line; + std::ifstream fileStream; + fileStream.open(denylist); + if (!fileStream) report_fatal_error("Unable to open AFL_GCC_DENYLIST"); + getline(fileStream, line); + + while (fileStream) { + + int is_file = -1; + std::size_t npos; + std::string original_line = line; + + line.erase(std::remove_if(line.begin(), line.end(), ::isspace), + line.end()); + + // remove # and following + if ((npos = line.find("#")) != std::string::npos) + line = line.substr(0, npos); + + if (line.compare(0, 4, "fun:") == 0) { + + is_file = 0; + line = line.substr(4); + + } else if (line.compare(0, 9, "function:") == 0) { + + is_file = 0; + line = line.substr(9); + + } else if (line.compare(0, 4, "src:") == 0) { + + is_file = 1; + line = line.substr(4); + + } else if (line.compare(0, 7, "source:") == 0) { + + is_file = 1; + line = line.substr(7); + + } + + if (line.find(":") != std::string::npos) { + + FATAL("invalid line in AFL_GCC_DENYLIST: %s", original_line.c_str()); + + } + + if (line.length() > 0) { + + // if the entry contains / or . it must be a file + if (is_file == -1) + if (line.find("/") != std::string::npos || + line.find(".") != std::string::npos) + is_file = 1; + // otherwise it is a function + + if (is_file == 1) + denyListFiles.push_back(line); + else + denyListFunctions.push_back(line); + + } + + getline(fileStream, line); + + } + + if (debug) + DEBUGF("loaded denylist with %zu file and %zu function entries\n", + denyListFiles.size(), denyListFunctions.size()); + + } + + } + + /* Returns the source file name attached to the function declaration F. If + there is no source location information, returns an empty string. */ + std::string getSourceName(function *F) { + + return DECL_SOURCE_FILE(F->decl) ? DECL_SOURCE_FILE(F->decl) : ""; + + } + + bool isInInstrumentList(function *F) { + + bool return_default = true; + + // is this a function with code? If it is external we don't instrument it + // anyway and it can't be in the instrument file list. Or if it is it is + // ignored. + if (isIgnoreFunction(F)) return false; + + if (!denyListFiles.empty() || !denyListFunctions.empty()) { + + if (!denyListFunctions.empty()) { + + std::string instFunction = IDENTIFIER_POINTER(DECL_NAME(F->decl)); + + for (std::list<std::string>::iterator it = denyListFunctions.begin(); + it != denyListFunctions.end(); ++it) { + + /* We don't check for filename equality here because + * filenames might actually be full paths. Instead we + * check that the actual filename ends in the filename + * specified in the list. We also allow UNIX-style pattern + * matching */ + + if (instFunction.length() >= it->length()) { + + if (fnmatch(("*" + *it).c_str(), instFunction.c_str(), 0) == 0) { + + if (debug) + DEBUGF( + "Function %s is in the deny function list, not " + "instrumenting ... \n", + instFunction.c_str()); + return false; + + } + + } + + } + + } + + if (!denyListFiles.empty()) { + + std::string source_file = getSourceName(F); + + if (!source_file.empty()) { + + for (std::list<std::string>::iterator it = denyListFiles.begin(); + it != denyListFiles.end(); ++it) { + + /* We don't check for filename equality here because + * filenames might actually be full paths. Instead we + * check that the actual filename ends in the filename + * specified in the list. We also allow UNIX-style pattern + * matching */ + + if (source_file.length() >= it->length()) { + + if (fnmatch(("*" + *it).c_str(), source_file.c_str(), 0) == 0) { + + return false; + + } + + } + + } + + } else { + + // we could not find out the location. in this case we say it is not + // in the instrument file list + if (!be_quiet) + WARNF( + "No debug information found for function %s, will be " + "instrumented (recompile with -g -O[1-3]).", + IDENTIFIER_POINTER(DECL_NAME(F->decl))); + + } + + } + + } + + // if we do not have a instrument file list return true + if (!allowListFiles.empty() || !allowListFunctions.empty()) { + + return_default = false; + + if (!allowListFunctions.empty()) { + + std::string instFunction = IDENTIFIER_POINTER(DECL_NAME(F->decl)); + + for (std::list<std::string>::iterator it = allowListFunctions.begin(); + it != allowListFunctions.end(); ++it) { + + /* We don't check for filename equality here because + * filenames might actually be full paths. Instead we + * check that the actual filename ends in the filename + * specified in the list. We also allow UNIX-style pattern + * matching */ + + if (instFunction.length() >= it->length()) { + + if (fnmatch(("*" + *it).c_str(), instFunction.c_str(), 0) == 0) { + + if (debug) + DEBUGF( + "Function %s is in the allow function list, instrumenting " + "... \n", + instFunction.c_str()); + return true; + + } + + } + + } + + } + + if (!allowListFiles.empty()) { + + std::string source_file = getSourceName(F); + + if (!source_file.empty()) { + + for (std::list<std::string>::iterator it = allowListFiles.begin(); + it != allowListFiles.end(); ++it) { + + /* We don't check for filename equality here because + * filenames might actually be full paths. Instead we + * check that the actual filename ends in the filename + * specified in the list. We also allow UNIX-style pattern + * matching */ + + if (source_file.length() >= it->length()) { + + if (fnmatch(("*" + *it).c_str(), source_file.c_str(), 0) == 0) { + + if (debug) + DEBUGF( + "Function %s is in the allowlist (%s), instrumenting ... " + "\n", + IDENTIFIER_POINTER(DECL_NAME(F->decl)), + source_file.c_str()); + return true; + + } + + } + + } + + } else { + + // we could not find out the location. In this case we say it is not + // in the instrument file list + if (!be_quiet) + WARNF( + "No debug information found for function %s, will not be " + "instrumented (recompile with -g -O[1-3]).", + IDENTIFIER_POINTER(DECL_NAME(F->decl))); + return false; + + } + + } + + } + + return return_default; + + } +}; + +} diff --git a/instrumentation/afl-gcc-pass.so.cc b/instrumentation/afl-gcc-pass.so.cc index bb5483fc..795bbd8a 100644 --- a/instrumentation/afl-gcc-pass.so.cc +++ b/instrumentation/afl-gcc-pass.so.cc @@ -124,50 +124,8 @@ entry edge for the entry block. */ -#include "../include/config.h" -#include "../include/debug.h" - -#include <stdio.h> -#include <stdlib.h> -#include <unistd.h> - -#ifdef likely - #undef likely -#endif -#ifdef unlikely - #undef unlikely -#endif - -#include <list> -#include <string> -#include <fstream> - -#include <algorithm> -#include <fnmatch.h> - -#include <gcc-plugin.h> -#include <plugin-version.h> -#include <toplev.h> -#include <tree-pass.h> -#include <context.h> -#include <tree.h> -#include <gimplify.h> -#include <basic-block.h> -#include <tree-ssa-alias.h> -#include <gimple-expr.h> -#include <gimple.h> -#include <gimple-iterator.h> -#include <stringpool.h> -#include <gimple-ssa.h> -#if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) >= \ - 60200 /* >= version 6.2.0 */ - #include <tree-vrp.h> -#endif -#include <tree-ssanames.h> -#include <tree-phinodes.h> -#include <ssa-iterators.h> - -#include <intl.h> +#include "afl-gcc-common.h" +#include "memmodel.h" /* This plugin, being under the same license as GCC, satisfies the "GPL-compatible Software" definition in the GCC RUNTIME LIBRARY @@ -191,12 +149,10 @@ static constexpr struct pass_data afl_pass_data = { }; -struct afl_pass : gimple_opt_pass { +struct afl_pass : afl_base_pass { afl_pass(bool quiet, unsigned int ratio) - : gimple_opt_pass(afl_pass_data, g), - be_quiet(quiet), - debug(!!getenv("AFL_DEBUG")), + : afl_base_pass(quiet, !!getenv("AFL_DEBUG"), afl_pass_data), inst_ratio(ratio), #ifdef AFL_GCC_OUT_OF_LINE out_of_line(!!(AFL_GCC_OUT_OF_LINE)), @@ -210,13 +166,6 @@ struct afl_pass : gimple_opt_pass { } - /* Are we outputting to a non-terminal, or running with AFL_QUIET - set? */ - const bool be_quiet; - - /* Are we running with AFL_DEBUG set? */ - const bool debug; - /* How likely (%) is a block to be instrumented? */ const unsigned int inst_ratio; @@ -297,21 +246,22 @@ struct afl_pass : gimple_opt_pass { gimple_build_assign(ntry, POINTER_PLUS_EXPR, map_ptr, indx); gimple_seq_add_stmt(&seq, idx_map); - /* Increment the counter in idx_map. */ - tree memref = build2(MEM_REF, TREE_TYPE(TREE_TYPE(ntry)), ntry, - build_zero_cst(TREE_TYPE(ntry))); - if (blocks == 0) - cntr = create_tmp_var(TREE_TYPE(memref), ".afl_edge_count"); - - /* Load the count from the entry. */ - auto load_cntr = gimple_build_assign(cntr, memref); - gimple_seq_add_stmt(&seq, load_cntr); - /* Prepare to add constant 1 to it. */ - tree incrv = build_one_cst(TREE_TYPE(cntr)); + tree incrv = build_one_cst(TREE_TYPE(TREE_TYPE(ntry))); if (neverZero) { + /* Increment the counter in idx_map. */ + tree memref = build2(MEM_REF, TREE_TYPE(TREE_TYPE(ntry)), ntry, + build_zero_cst(TREE_TYPE(ntry))); + + if (blocks == 0) + cntr = create_tmp_var(TREE_TYPE(memref), ".afl_edge_count"); + + /* Load the count from the entry. */ + auto load_cntr = gimple_build_assign(cntr, memref); + gimple_seq_add_stmt(&seq, load_cntr); + /* NeverZero: if count wrapped around to zero, advance to one. */ if (blocks == 0) { @@ -348,15 +298,24 @@ struct afl_pass : gimple_opt_pass { in xincr. */ incrv = xincr; - } + /* Add the increment (1 or the overflow bit) to count. */ + auto incr_cntr = gimple_build_assign(cntr, PLUS_EXPR, cntr, incrv); + gimple_seq_add_stmt(&seq, incr_cntr); + + /* Store count in the map entry. */ + auto store_cntr = gimple_build_assign(unshare_expr(memref), cntr); + gimple_seq_add_stmt(&seq, store_cntr); - /* Add the increment (1 or the overflow bit) to count. */ - auto incr_cntr = gimple_build_assign(cntr, PLUS_EXPR, cntr, incrv); - gimple_seq_add_stmt(&seq, incr_cntr); + } else { - /* Store count in the map entry. */ - auto store_cntr = gimple_build_assign(unshare_expr(memref), cntr); - gimple_seq_add_stmt(&seq, store_cntr); + /* Use a serialized memory model. */ + tree memmod = build_int_cst(integer_type_node, MEMMODEL_SEQ_CST); + + tree fadd = builtin_decl_explicit(BUILT_IN_ATOMIC_FETCH_ADD_1); + auto incr_cntr = gimple_build_call(fadd, 3, ntry, incrv, memmod); + gimple_seq_add_stmt(&seq, incr_cntr); + + } /* Store bid >> 1 in __afl_prev_loc. */ auto shift_loc = @@ -456,6 +415,8 @@ struct afl_pass : gimple_opt_pass { thread-local variable. */ static inline tree get_afl_area_ptr_decl() { + /* If type changes, the size N in FETCH_ADD_<N> must be adjusted + in builtin calls above. */ tree type = build_pointer_type(unsigned_char_type_node); tree decl = build_decl(BUILTINS_LOCATION, VAR_DECL, get_identifier("__afl_area_ptr"), type); @@ -490,420 +451,11 @@ struct afl_pass : gimple_opt_pass { } -#define report_fatal_error(msg) BADF(msg) - - std::list<std::string> allowListFiles; - std::list<std::string> allowListFunctions; - std::list<std::string> denyListFiles; - std::list<std::string> denyListFunctions; - - /* Note: this ignore check is also called in isInInstrumentList() */ - bool isIgnoreFunction(function *F) { - - // Starting from "LLVMFuzzer" these are functions used in libfuzzer based - // fuzzing campaign installations, e.g. oss-fuzz - - static constexpr const char *ignoreList[] = { - - "asan.", - "llvm.", - "sancov.", - "__ubsan_", - "ign.", - "__afl_", - "_fini", - "__libc_csu", - "__asan", - "__msan", - "__cmplog", - "__sancov", - "msan.", - "LLVMFuzzerM", - "LLVMFuzzerC", - "LLVMFuzzerI", - "__decide_deferred", - "maybe_duplicate_stderr", - "discard_output", - "close_stdout", - "dup_and_close_stderr", - "maybe_close_fd_mask", - "ExecuteFilesOnyByOne" - - }; - - const char *name = IDENTIFIER_POINTER(DECL_NAME(F->decl)); - int len = IDENTIFIER_LENGTH(DECL_NAME(F->decl)); - - for (auto const &ignoreListFunc : ignoreList) { - - if (strncmp(name, ignoreListFunc, len) == 0) { return true; } - - } - - return false; - - } - - void initInstrumentList() { - - char *allowlist = getenv("AFL_GCC_ALLOWLIST"); - if (!allowlist) allowlist = getenv("AFL_GCC_INSTRUMENT_FILE"); - if (!allowlist) allowlist = getenv("AFL_GCC_WHITELIST"); - if (!allowlist) allowlist = getenv("AFL_LLVM_ALLOWLIST"); - if (!allowlist) allowlist = getenv("AFL_LLVM_INSTRUMENT_FILE"); - if (!allowlist) allowlist = getenv("AFL_LLVM_WHITELIST"); - char *denylist = getenv("AFL_GCC_DENYLIST"); - if (!denylist) denylist = getenv("AFL_GCC_BLOCKLIST"); - if (!denylist) denylist = getenv("AFL_LLVM_DENYLIST"); - if (!denylist) denylist = getenv("AFL_LLVM_BLOCKLIST"); - - if (allowlist && denylist) - FATAL( - "You can only specify either AFL_GCC_ALLOWLIST or AFL_GCC_DENYLIST " - "but not both!"); - - if (allowlist) { - - std::string line; - std::ifstream fileStream; - fileStream.open(allowlist); - if (!fileStream) report_fatal_error("Unable to open AFL_GCC_ALLOWLIST"); - getline(fileStream, line); - - while (fileStream) { - - int is_file = -1; - std::size_t npos; - std::string original_line = line; - - line.erase(std::remove_if(line.begin(), line.end(), ::isspace), - line.end()); - - // remove # and following - if ((npos = line.find("#")) != std::string::npos) - line = line.substr(0, npos); - - if (line.compare(0, 4, "fun:") == 0) { - - is_file = 0; - line = line.substr(4); - - } else if (line.compare(0, 9, "function:") == 0) { - - is_file = 0; - line = line.substr(9); - - } else if (line.compare(0, 4, "src:") == 0) { - - is_file = 1; - line = line.substr(4); - - } else if (line.compare(0, 7, "source:") == 0) { - - is_file = 1; - line = line.substr(7); - - } - - if (line.find(":") != std::string::npos) { - - FATAL("invalid line in AFL_GCC_ALLOWLIST: %s", original_line.c_str()); - - } - - if (line.length() > 0) { - - // if the entry contains / or . it must be a file - if (is_file == -1) - if (line.find("/") != std::string::npos || - line.find(".") != std::string::npos) - is_file = 1; - // otherwise it is a function - - if (is_file == 1) - allowListFiles.push_back(line); - else - allowListFunctions.push_back(line); - - } - - getline(fileStream, line); - - } - - if (debug) - DEBUGF("loaded allowlist with %zu file and %zu function entries\n", - allowListFiles.size(), allowListFunctions.size()); - - } - - if (denylist) { - - std::string line; - std::ifstream fileStream; - fileStream.open(denylist); - if (!fileStream) report_fatal_error("Unable to open AFL_GCC_DENYLIST"); - getline(fileStream, line); - - while (fileStream) { - - int is_file = -1; - std::size_t npos; - std::string original_line = line; - - line.erase(std::remove_if(line.begin(), line.end(), ::isspace), - line.end()); - - // remove # and following - if ((npos = line.find("#")) != std::string::npos) - line = line.substr(0, npos); - - if (line.compare(0, 4, "fun:") == 0) { - - is_file = 0; - line = line.substr(4); - - } else if (line.compare(0, 9, "function:") == 0) { - - is_file = 0; - line = line.substr(9); - - } else if (line.compare(0, 4, "src:") == 0) { - - is_file = 1; - line = line.substr(4); - - } else if (line.compare(0, 7, "source:") == 0) { - - is_file = 1; - line = line.substr(7); - - } - - if (line.find(":") != std::string::npos) { - - FATAL("invalid line in AFL_GCC_DENYLIST: %s", original_line.c_str()); - - } - - if (line.length() > 0) { - - // if the entry contains / or . it must be a file - if (is_file == -1) - if (line.find("/") != std::string::npos || - line.find(".") != std::string::npos) - is_file = 1; - // otherwise it is a function - - if (is_file == 1) - denyListFiles.push_back(line); - else - denyListFunctions.push_back(line); - - } - - getline(fileStream, line); - - } - - if (debug) - DEBUGF("loaded denylist with %zu file and %zu function entries\n", - denyListFiles.size(), denyListFunctions.size()); - - } - - } - - /* Returns the source file name attached to the function declaration F. If - there is no source location information, returns an empty string. */ - std::string getSourceName(function *F) { - - return DECL_SOURCE_FILE(F->decl) ? DECL_SOURCE_FILE(F->decl) : ""; - - } - - bool isInInstrumentList(function *F) { - - bool return_default = true; - - // is this a function with code? If it is external we don't instrument it - // anyway and it can't be in the instrument file list. Or if it is it is - // ignored. - if (isIgnoreFunction(F)) return false; - - if (!denyListFiles.empty() || !denyListFunctions.empty()) { - - if (!denyListFunctions.empty()) { - - std::string instFunction = IDENTIFIER_POINTER(DECL_NAME(F->decl)); - - for (std::list<std::string>::iterator it = denyListFunctions.begin(); - it != denyListFunctions.end(); ++it) { - - /* We don't check for filename equality here because - * filenames might actually be full paths. Instead we - * check that the actual filename ends in the filename - * specified in the list. We also allow UNIX-style pattern - * matching */ - - if (instFunction.length() >= it->length()) { - - if (fnmatch(("*" + *it).c_str(), instFunction.c_str(), 0) == 0) { - - if (debug) - DEBUGF( - "Function %s is in the deny function list, not " - "instrumenting ... \n", - instFunction.c_str()); - return false; - - } - - } - - } - - } - - if (!denyListFiles.empty()) { - - std::string source_file = getSourceName(F); - - if (!source_file.empty()) { - - for (std::list<std::string>::iterator it = denyListFiles.begin(); - it != denyListFiles.end(); ++it) { - - /* We don't check for filename equality here because - * filenames might actually be full paths. Instead we - * check that the actual filename ends in the filename - * specified in the list. We also allow UNIX-style pattern - * matching */ - - if (source_file.length() >= it->length()) { - - if (fnmatch(("*" + *it).c_str(), source_file.c_str(), 0) == 0) { - - return false; - - } - - } - - } - - } else { - - // we could not find out the location. in this case we say it is not - // in the instrument file list - if (!be_quiet) - WARNF( - "No debug information found for function %s, will be " - "instrumented (recompile with -g -O[1-3]).", - IDENTIFIER_POINTER(DECL_NAME(F->decl))); - - } - - } - - } - - // if we do not have a instrument file list return true - if (!allowListFiles.empty() || !allowListFunctions.empty()) { - - return_default = false; - - if (!allowListFunctions.empty()) { - - std::string instFunction = IDENTIFIER_POINTER(DECL_NAME(F->decl)); - - for (std::list<std::string>::iterator it = allowListFunctions.begin(); - it != allowListFunctions.end(); ++it) { - - /* We don't check for filename equality here because - * filenames might actually be full paths. Instead we - * check that the actual filename ends in the filename - * specified in the list. We also allow UNIX-style pattern - * matching */ - - if (instFunction.length() >= it->length()) { - - if (fnmatch(("*" + *it).c_str(), instFunction.c_str(), 0) == 0) { - - if (debug) - DEBUGF( - "Function %s is in the allow function list, instrumenting " - "... \n", - instFunction.c_str()); - return true; - - } - - } - - } - - } - - if (!allowListFiles.empty()) { - - std::string source_file = getSourceName(F); - - if (!source_file.empty()) { - - for (std::list<std::string>::iterator it = allowListFiles.begin(); - it != allowListFiles.end(); ++it) { - - /* We don't check for filename equality here because - * filenames might actually be full paths. Instead we - * check that the actual filename ends in the filename - * specified in the list. We also allow UNIX-style pattern - * matching */ - - if (source_file.length() >= it->length()) { - - if (fnmatch(("*" + *it).c_str(), source_file.c_str(), 0) == 0) { - - if (debug) - DEBUGF( - "Function %s is in the allowlist (%s), instrumenting ... " - "\n", - IDENTIFIER_POINTER(DECL_NAME(F->decl)), - source_file.c_str()); - return true; - - } - - } - - } - - } else { - - // we could not find out the location. In this case we say it is not - // in the instrument file list - if (!be_quiet) - WARNF( - "No debug information found for function %s, will not be " - "instrumented (recompile with -g -O[1-3]).", - IDENTIFIER_POINTER(DECL_NAME(F->decl))); - return false; - - } - - } - - } - - return return_default; - - } - }; static struct plugin_info afl_plugin = { - .version = "20220907", + .version = "20220420", .help = G_("AFL gcc plugin\n\ \n\ Set AFL_QUIET in the environment to silence it.\n\ diff --git a/src/afl-cc.c b/src/afl-cc.c index 4a56169f..96342659 100644 --- a/src/afl-cc.c +++ b/src/afl-cc.c @@ -422,8 +422,24 @@ static void edit_params(u32 argc, char **argv, char **envp) { if (compiler_mode == GCC_PLUGIN) { - char *fplugin_arg = alloc_printf("-fplugin=%s/afl-gcc-pass.so", obj_path); - cc_params[cc_par_cnt++] = fplugin_arg; + char *fplugin_arg; + + if (cmplog_mode) + { + fplugin_arg = alloc_printf("-fplugin=%s/afl-gcc-cmplog-pass.so", + obj_path); + cc_params[cc_par_cnt++] = fplugin_arg; + fplugin_arg = alloc_printf("-fplugin=%s/afl-gcc-cmptrs-pass.so", + obj_path); + cc_params[cc_par_cnt++] = fplugin_arg; + } + else + { + fplugin_arg = alloc_printf("-fplugin=%s/afl-gcc-pass.so", + obj_path); + cc_params[cc_par_cnt++] = fplugin_arg; + } + cc_params[cc_par_cnt++] = "-fno-if-conversion"; cc_params[cc_par_cnt++] = "-fno-if-conversion2"; @@ -1879,6 +1895,7 @@ int main(int argc, char **argv, char **envp) { if (have_gcc_plugin) SAYF( "\nGCC Plugin-specific environment variables:\n" + " AFL_GCC_CMPLOG: log operands of comparisons (RedQueen mutator)\n" " AFL_GCC_OUT_OF_LINE: disable inlined instrumentation\n" " AFL_GCC_SKIP_NEVERZERO: do not skip zero on trace counters\n" " AFL_GCC_INSTRUMENT_FILE: enable selective instrumentation by " @@ -2149,9 +2166,7 @@ int main(int argc, char **argv, char **envp) { } - cmplog_mode = getenv("AFL_CMPLOG") || getenv("AFL_LLVM_CMPLOG"); - if (!be_quiet && cmplog_mode) - printf("CmpLog mode by <andreafioraldi@gmail.com>\n"); + cmplog_mode = getenv("AFL_CMPLOG") || getenv("AFL_LLVM_CMPLOG") || getenv("AFL_GCC_CMPLOG"); #if !defined(__ANDROID__) && !defined(ANDROID) ptr = find_object("afl-compiler-rt.o", argv[0]); diff --git a/test-instr.c b/test-instr.c index b2caa1fe..f304e208 100644 --- a/test-instr.c +++ b/test-instr.c @@ -58,12 +58,21 @@ int main(int argc, char **argv) { // we support three input cases (plus a 4th if stdin is used but there is no // input) - if (buf[0] == '0') - printf("Looks like a zero to me!\n"); - else if (buf[0] == '1') - printf("Pretty sure that is a one!\n"); - else - printf("Neither one or zero? How quaint!\n"); + switch (buf[0]) { + + case '0': + printf("Looks like a zero to me!\n"); + break; + + case '1': + printf("Pretty sure that is a one!\n"); + break; + + default: + printf("Neither one or zero? How quaint!\n"); + break; + + } return 0; |