From patchwork Wed Oct 11 17:52:41 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: George Dunlap X-Patchwork-Id: 10000261 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id EF4C260244 for ; Wed, 11 Oct 2017 17:55:35 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id E450228ADE for ; Wed, 11 Oct 2017 17:55:35 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id D915E28AFD; Wed, 11 Oct 2017 17:55:35 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-4.2 required=2.0 tests=BAYES_00, RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from lists.xenproject.org (lists.xenproject.org [192.237.175.120]) (using TLSv1.2 with cipher AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id E9D5F28ADE for ; Wed, 11 Oct 2017 17:55:34 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=lists.xenproject.org) by lists.xenproject.org with esmtp (Exim 4.84_2) (envelope-from ) id 1e2LBh-0000bG-A8; Wed, 11 Oct 2017 17:52:57 +0000 Received: from mail6.bemta5.messagelabs.com ([195.245.231.135]) by lists.xenproject.org with esmtp (Exim 4.84_2) (envelope-from ) id 1e2LBg-0000Zv-4F for xen-devel@lists.xenproject.org; Wed, 11 Oct 2017 17:52:56 +0000 Received: from [85.158.139.211] by server-1.bemta-5.messagelabs.com id 99/5A-15990-77A5ED95; Wed, 11 Oct 2017 17:52:55 +0000 X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFprKIsWRWlGSWpSXmKPExsXitHRDpG5Z1L1 Ig6kP9C2+b5nM5MDocfjDFZYAxijWzLyk/IoE1oy1564xFvRGV8zdu5OtgfGRUxcjJ4eEgL/E hm9rWEBsNgE9iXnHvwLZHBwiAioSt/cadDFycTAL7GeU6P36gw2kRlggXmLZxNVgNSwCqhK/J vCCmLwCdhLLNjNDTJSXeL/gPiOIzQkUfvP4PiuILSRgK7Fi6V5GCFtVYvGDo+wgNq+AoMTJmU /ALmAWkJA4+OIF8wRG3llIUrOQpBYwMq1i1ChOLSpLLdI1MtJLKspMzyjJTczM0TU0MNXLTS0 uTkxPzUlMKtZLzs/dxAgMnHoGBsYdjHva/Q4xSnIwKYnyBuvdixTiS8pPqcxILM6ILyrNSS0+ xCjDwaEkwasYCZQTLEpNT61Iy8wBhjBMWoKDR0mEtx8kzVtckJhbnJkOkTrFaMxx6MXtP0wcH Tfv/mESYsnLz0uVEudtAykVACnNKM2DGwSLrUuMslLCvIwMDAxCPAWpRbmZJajyrxjFORiVhH n/RwBN4cnMK4Hb9wroFCagU0TT7oCcUpKIkJJqYPSQ053idDpsAv+5o58DNJ999pgv8OtYUKP MQRvfj7PmOScflv/FxLn5eopaq53qmssPDbw3JTbt3VQYt14k78Hu6d0JPdZdgk/rok5s3fP9 kQuH4oTvJ27IHN12f10Kw5b6/r6TZedffIoS+zfxw3Yxp1iTPEF/g0Orkx47T78+n8/07gzHi flKLMUZiYZazEXFiQDJ5EOFqAIAAA== X-Env-Sender: prvs=450528267=George.Dunlap@citrix.com X-Msg-Ref: server-8.tower-206.messagelabs.com!1507744369!106410454!4 X-Originating-IP: [66.165.176.89] X-SpamReason: No, hits=0.0 required=7.0 tests=sa_preprocessor: VHJ1c3RlZCBJUDogNjYuMTY1LjE3Ni44OSA9PiAyMDMwMDc=\n, received_headers: No Received headers X-StarScan-Received: X-StarScan-Version: 9.4.45; banners=-,-,- X-VirusChecked: Checked Received: (qmail 27655 invoked from network); 11 Oct 2017 17:52:54 -0000 Received: from smtp.citrix.com (HELO SMTP.CITRIX.COM) (66.165.176.89) by server-8.tower-206.messagelabs.com with RC4-SHA encrypted SMTP; 11 Oct 2017 17:52:54 -0000 X-IronPort-AV: E=Sophos;i="5.43,362,1503360000"; d="scan'208";a="445492270" From: George Dunlap To: Date: Wed, 11 Oct 2017 18:52:41 +0100 Message-ID: <20171011175243.19871-10-george.dunlap@citrix.com> X-Mailer: git-send-email 2.14.2 In-Reply-To: <20171011175243.19871-1-george.dunlap@citrix.com> References: <20171011175243.19871-1-george.dunlap@citrix.com> MIME-Version: 1.0 Cc: Ian Jackson , Wei Liu , George Dunlap , Jan Beulich , Andrew Cooper Subject: [Xen-devel] [PATCH v4 10/12] fuzz/x86_emulate: Add --rerun option to try to track down instability X-BeenThere: xen-devel@lists.xen.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: Xen developer discussion List-Unsubscribe: , List-Post: List-Help: List-Subscribe: , Errors-To: xen-devel-bounces@lists.xen.org Sender: "Xen-devel" X-Virus-Scanned: ClamAV using ClamSMTP Current stability numbers are not 100%. In order to help track this down, add a --rerun option which will run the same input twice, resetting the state between each run, and comparing the state afterwards. If the state differs, call abort(). This allows AFL to help the process of tracking down what state is not being reset properly between runs by proving testcases that demonstrate the behavior. To do this: - Move ctxt into struct fuzz-state to simplify handling - Rather than copying the data into input, treat the data handed as immutable and point each "copy" to it - Factor out various steps (setting up fuzz state, running an individual test) so that they can be efficiently run either once or twice (as necessary) - Compare the states afterwards, printing what's different and calling abort() if anything is found. Signed-off-by: George Dunlap --- v4: - Fix some stylistic issues - Rename PINE macro to PRINT_NE - Remove unnecessary printf - Fix size_t format string v3: - Make new local functions static - Avoid losing const-ness of pointer in setup_fuzz_state() - Remove useless *_size initialization - Remove extra blank line - Use ARRAY_SIZE() when appropriate - Move opt_rerun declaration into fuzz-emul.h - Change loop variable to unsigned int - Print segment contents when segments differ v2: - Fix some coding style issues - Port over previous changes CC: Ian Jackson CC: Wei Liu CC: Andrew Cooper CC: Jan Beulich --- tools/fuzz/x86_instruction_emulator/afl-harness.c | 8 +- tools/fuzz/x86_instruction_emulator/fuzz-emul.c | 188 ++++++++++++++++++---- tools/fuzz/x86_instruction_emulator/fuzz-emul.h | 1 + 3 files changed, 168 insertions(+), 29 deletions(-) diff --git a/tools/fuzz/x86_instruction_emulator/afl-harness.c b/tools/fuzz/x86_instruction_emulator/afl-harness.c index 23a77a73c0..0489034642 100644 --- a/tools/fuzz/x86_instruction_emulator/afl-harness.c +++ b/tools/fuzz/x86_instruction_emulator/afl-harness.c @@ -23,10 +23,12 @@ int main(int argc, char **argv) enum { OPT_MIN_SIZE, OPT_COMPACT, + OPT_RERUN, }; static const struct option lopts[] = { { "min-input-size", no_argument, NULL, OPT_MIN_SIZE }, { "compact", required_argument, NULL, OPT_COMPACT }, + { "rerun", no_argument, NULL, OPT_RERUN }, { 0, 0, 0, 0 } }; int c = getopt_long_only(argc, argv, "", lopts, NULL); @@ -45,8 +47,12 @@ int main(int argc, char **argv) opt_compact = atoi(optarg); break; + case OPT_RERUN: + opt_rerun = true; + break; + case '?': - printf("Usage: %s [--compact=0|1] $FILE [$FILE...] | [--min-input-size]\n", argv[0]); + printf("Usage: %s [--compact=0|1] [--rerun] $FILE [$FILE...] | [--min-input-size]\n", argv[0]); exit(-1); break; diff --git a/tools/fuzz/x86_instruction_emulator/fuzz-emul.c b/tools/fuzz/x86_instruction_emulator/fuzz-emul.c index 3ea8a8b726..f1621f98da 100644 --- a/tools/fuzz/x86_instruction_emulator/fuzz-emul.c +++ b/tools/fuzz/x86_instruction_emulator/fuzz-emul.c @@ -49,6 +49,7 @@ struct fuzz_state /* Emulation ops, some of which are disabled based on options. */ struct x86_emulate_ops ops; + struct x86_emulate_ctxt ctxt; }; bool opt_compact = true; @@ -493,6 +494,12 @@ static int fuzz_read_msr( const struct fuzz_state *s = ctxt->data; unsigned int idx; + /* + * NB at the moment dump_state() relies on the fact that this + * cannot fail. If we add in fuzzed failures we'll have to handle + * that differently. + */ + switch ( reg ) { case MSR_TSC_AUX: @@ -613,6 +620,7 @@ static void dump_state(struct x86_emulate_ctxt *ctxt) printf(" rip: %"PRIx64"\n", regs->rip); + /* read_msr() never fails at the moment */ fuzz_read_msr(MSR_EFER, &val, ctxt); printf("EFER: %"PRIx64"\n", val); } @@ -787,9 +795,8 @@ enum { printf("Disabling hook "#h"\n"); \ } -static void disable_hooks(struct x86_emulate_ctxt *ctxt) +static void disable_hooks(struct fuzz_state *s) { - struct fuzz_state *s = ctxt->data; unsigned long bitmap = s->options; /* See also sanitize_input, some hooks can't be disabled. */ @@ -837,7 +844,7 @@ static void disable_hooks(struct x86_emulate_ctxt *ctxt) * - ...bases to below 1Mb, 16-byte aligned * - ...selectors to (base >> 4) */ -static void sanitize_input(struct x86_emulate_ctxt *ctxt) +static void sanitize_state(struct x86_emulate_ctxt *ctxt) { struct fuzz_state *s = ctxt->data; struct cpu_user_regs *regs = ctxt->regs; @@ -884,20 +891,146 @@ int LLVMFuzzerInitialize(int *argc, char ***argv) return 0; } -int LLVMFuzzerTestOneInput(const uint8_t *data_p, size_t size) +static void setup_fuzz_state(struct fuzz_state *state, const void *data_p, size_t size) { - struct fuzz_state state = { - .ops = all_fuzzer_ops, - }; - struct x86_emulate_ctxt ctxt = { - .data = &state, - .regs = &state.regs, - .addr_size = 8 * sizeof(void *), - .sp_size = 8 * sizeof(void *), - }; + memset(state, 0, sizeof(*state)); + state->corpus = data_p; + state->data_num = size; +} + +static int runtest(struct fuzz_state *state) { int rc; - if ( size <= fuzz_minimal_input_size() ) + struct x86_emulate_ctxt *ctxt = &state->ctxt; + + state->ops = all_fuzzer_ops; + + ctxt->data = state; + ctxt->regs = &state->regs; + + setup_state(ctxt); + + sanitize_state(ctxt); + + disable_hooks(state); + + do { + /* FIXME: Until we actually implement SIGFPE handling properly */ + setup_fpu_exception_handler(); + + set_sizes(ctxt); + dump_state(ctxt); + + rc = x86_emulate(ctxt, &state->ops); + printf("Emulation result: %d\n", rc); + } while ( rc == X86EMUL_OKAY ); + + return 0; +} + +static void compare_states(struct fuzz_state state[2]) +{ + /* First zero any "internal" pointers */ + state[0].corpus = state[1].corpus = NULL; + state[0].ctxt.data = state[1].ctxt.data = NULL; + state[0].ctxt.regs = state[1].ctxt.regs = NULL; + + if ( memcmp(&state[0], &state[1], sizeof(struct fuzz_state)) ) + { + unsigned int i; + + printf("State mismatch\n"); + + for ( i = 0; i < ARRAY_SIZE(state[0].cr); i++ ) + if ( state[0].cr[i] != state[1].cr[i] ) + printf("cr[%u]: %lx != %lx\n", + i, state[0].cr[i], state[1].cr[i]); + + for ( i = 0; i < ARRAY_SIZE(state[0].msr); i++ ) + if ( state[0].msr[i] != state[1].msr[i] ) + printf("msr[%u]: %lx != %lx\n", + i, state[0].msr[i], state[1].msr[i]); + + for ( i = 0; i < ARRAY_SIZE(state[0].segments); i++ ) + if ( memcmp(&state[0].segments[i], &state[1].segments[i], + sizeof(state[0].segments[0])) ) + printf("segments[%u]: [%x:%x:%x:%lx] != [%x:%x:%x:%lx]!\n", i, + (unsigned)state[0].segments[i].sel, + (unsigned)state[0].segments[i].attr, + state[0].segments[i].limit, + state[0].segments[i].base, + (unsigned)state[1].segments[i].sel, + (unsigned)state[1].segments[i].attr, + state[1].segments[i].limit, + state[1].segments[i].base); + + if ( state[0].data_num != state[1].data_num ) + printf("data_num: %lx != %lx\n", state[0].data_num, + state[1].data_num); + if ( state[0].data_index != state[1].data_index ) + printf("data_index: %lx != %lx\n", state[0].data_index, + state[1].data_index); + + if ( memcmp(&state[0].regs, &state[1].regs, sizeof(state[0].regs)) ) + { + printf("registers differ!\n"); + /* Print If Not Equal */ +#define PRINT_NE(elem)\ + if ( state[0].elem != state[1].elem ) \ + printf(#elem " differ: %lx != %lx\n", \ + (unsigned long)state[0].elem, \ + (unsigned long)state[1].elem) + PRINT_NE(regs.r15); + PRINT_NE(regs.r14); + PRINT_NE(regs.r13); + PRINT_NE(regs.r12); + PRINT_NE(regs.rbp); + PRINT_NE(regs.rbx); + PRINT_NE(regs.r10); + PRINT_NE(regs.r11); + PRINT_NE(regs.r9); + PRINT_NE(regs.r8); + PRINT_NE(regs.rax); + PRINT_NE(regs.rcx); + PRINT_NE(regs.rdx); + PRINT_NE(regs.rsi); + PRINT_NE(regs.rdi); + + for ( i = offsetof(struct cpu_user_regs, error_code) / sizeof(unsigned); + i < sizeof(state[1].regs)/sizeof(unsigned); i++ ) + { + printf("[%04zu] %08x %08x\n", + i * sizeof(unsigned), ((unsigned *)&state[0].regs)[i], + ((unsigned *)&state[1].regs)[i]); + } + } + + if ( memcmp(&state[0].ops, &state[1].ops, sizeof(state[0].ops)) ) + printf("ops differ!\n"); + + if ( memcmp(&state[0].ctxt, &state[1].ctxt, sizeof(state[0].ctxt)) ) + { + printf("ctxt differs!\n"); + for ( i = 0; i < sizeof(state[0].ctxt)/sizeof(unsigned); i++ ) + { + printf("[%04lu] %08x %08x\n", + i * sizeof(unsigned), ((unsigned *)&state[0].ctxt)[i], + ((unsigned *)&state[1].ctxt)[i]); + } + + } + + abort(); + } +} + +bool opt_rerun = false; + +int LLVMFuzzerTestOneInput(const uint8_t *data_p, size_t size) +{ + struct fuzz_state state[2]; + + if ( size < fuzz_minimal_input_size() ) { printf("Input too small\n"); return 1; @@ -909,25 +1042,24 @@ int LLVMFuzzerTestOneInput(const uint8_t *data_p, size_t size) return 1; } - state.corpus = (void*)data_p; - state.data_num = size; - - setup_state(&ctxt); + setup_fuzz_state(&state[0], data_p, size); + + if ( opt_rerun ) + printf("||| INITIAL RUN |||\n"); + + runtest(&state[0]); - sanitize_input(&ctxt); + if ( !opt_rerun ) + return 0; - disable_hooks(&ctxt); + /* Reset all global state variables again */ + setup_fuzz_state(&state[1], data_p, size); - do { - /* FIXME: Until we actually implement SIGFPE handling properly */ - setup_fpu_exception_handler(); + printf("||| SECOND RUN |||\n"); - set_sizes(&ctxt); - dump_state(&ctxt); + runtest(&state[1]); - rc = x86_emulate(&ctxt, &state.ops); - printf("Emulation result: %d\n", rc); - } while ( rc == X86EMUL_OKAY ); + compare_states(state); return 0; } diff --git a/tools/fuzz/x86_instruction_emulator/fuzz-emul.h b/tools/fuzz/x86_instruction_emulator/fuzz-emul.h index 85d21cbf0f..4863bf2166 100644 --- a/tools/fuzz/x86_instruction_emulator/fuzz-emul.h +++ b/tools/fuzz/x86_instruction_emulator/fuzz-emul.h @@ -6,6 +6,7 @@ extern int LLVMFuzzerTestOneInput(const uint8_t *data_p, size_t size); extern unsigned int fuzz_minimal_input_size(void); extern bool opt_compact; +extern bool opt_rerun; #define INPUT_SIZE 4096