From 4a4e2c71a0f6aaa50e0728922f84a7d54c14380a Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Wed, 20 Jun 2018 14:21:43 +0800 Subject: test: host_command_fuzz: fuzzing test Writing fuzzing tests is a little tricky, as clang takes over the main function. Instead, we start the test main function in a thread, and have LLVMFuzzerTestOneInput prepare the host command buffer, and wake the TEST_RUNNER task. To make fuzzing faster, we only send somehow correctly formed requests, with a valid checksum and length (this can be disabled with an option). We also make sure that the emulator does not hibernate, reboot or jump to a different image when fuzzing is enabled. BRANCH=none BUG=chromium:854975 TEST=make buildfuzztests -j ASAN_OPTIONS="log_path=stderr" \ build/host/host_command_fuzz/host_command_fuzz.exe -timeout=5 Change-Id: I27b25e44c405f118dfc1296247479245e15e54b4 Signed-off-by: Nicolas Boichat Reviewed-on: https://chromium-review.googlesource.com/1107523 Reviewed-by: Manoj Gupta Reviewed-by: Randall Spangler Reviewed-by: Jonathan Metzman --- Makefile.rules | 26 +++++-- Makefile.toolchain | 6 +- chip/host/reboot.c | 9 +++ chip/host/reboot.h | 2 + chip/host/system.c | 9 +++ chip/host/uart.c | 11 +++ common/system.c | 2 +- core/host/main.c | 43 +++++++++- core/host/task.c | 15 +++- include/system.h | 5 +- include/test_util.h | 5 ++ test/build.mk | 4 + test/host_command_fuzz.c | 169 ++++++++++++++++++++++++++++++++++++++++ test/host_command_fuzz.tasklist | 17 ++++ test/test_config.h | 18 +++++ 15 files changed, 325 insertions(+), 16 deletions(-) create mode 100644 test/host_command_fuzz.c create mode 100644 test/host_command_fuzz.tasklist diff --git a/Makefile.rules b/Makefile.rules index 8b9c998338..a11635b6b3 100644 --- a/Makefile.rules +++ b/Makefile.rules @@ -83,7 +83,10 @@ cmd_c_to_host = $(HOSTCC) $(HOST_CFLAGS) $(HOST_LDFLAGS) -MMD -MF $@.d -o $@ \ $(sort $(foreach c,$($(*F)-objs),util/$(c:%.o=%.c)) $(wildcard $*.c)) cmd_cxx_to_host = $(HOSTCXX) -std=c++0x $(COMMON_WARN) $(HOST_CXXFLAGS)\ -I ./$($(notdir $@)_ROOT) -o $@ $(filter %.cc,$^) $($(notdir $@)_LIBS) -cmd_host_test = ./util/run_host_test $* $(silent) +cmd_host_test = $(MAKE) --no-print-directory BOARD=host PROJECT=$* \ + V=$(V) out=build/host/$* TEST_BUILD=y EMU_BUILD=y $(TEST_FLAG) \ + CROSS_COMPILE= build/host/$*/$*.exe +cmd_run_host_test = ./util/run_host_test $* $(silent) # generate new version.h, compare if it changed and replace if so cmd_version = ./util/getversion.sh > $@.tmp && cmp -s $@.tmp $@ || mv $@.tmp $@; rm -f $@.tmp cmd_vif = $(out)/util/genvif -b $(BOARD) -o $(out) @@ -245,14 +248,10 @@ run-test-targets=$(foreach t,$(test-list-host),run-$(t)) .PHONY: $(host-test-targets) $(run-test-targets) $(host-test-targets): host-%: - @set -e ; \ - $(call echo," BUILD host - build/host/$*") \ - $(MAKE) --no-print-directory BOARD=host PROJECT=$* \ - V=$(V) out=build/host/$* TEST_BUILD=y EMU_BUILD=y $(TEST_FLAG) \ - CROSS_COMPILE= build/host/$*/$*.exe + $(call quiet,host_test,BUILD ) $(run-test-targets): run-%: host-% - $(call quiet,host_test,TEST ) + $(call quiet,run_host_test,TEST ) .PHONY: hosttests runtests hosttests: $(host-test-targets) @@ -321,6 +320,19 @@ coverage: TEST_FLAG=TEST_COVERAGE=y coverage: $(cov-test-targets) $(call quiet,report_cov,REPORT ) +# Fuzzing tests + +fuzz-test-targets=$(foreach t,$(fuzz-test-list-host),host-$(t)) + +.PHONY: $(fuzz-test-targets) + +$(fuzz-test-targets): host-%: + $(call quiet,host_test,BUILD ) + +.PHONY: buildfuzztests +buildfuzztests: TEST_FLAG=TEST_FUZZ=y TEST_ASAN=y +buildfuzztests: $(fuzz-test-targets) + $(out)/firmware_image.lds: common/firmware_image.lds.S $(call quiet,lds,LDS ) $(out)/%.lds: core/$(CORE)/ec.lds.S diff --git a/Makefile.toolchain b/Makefile.toolchain index c0ecdc91ca..698106889a 100644 --- a/Makefile.toolchain +++ b/Makefile.toolchain @@ -60,7 +60,8 @@ CFLAGS_TEST=$(if $(TEST_BUILD),-DTEST_BUILD \ $(if $(EMU_BUILD),-DEMU_BUILD) \ $(if $($(PROJECT)-scale),-DTEST_TIME_SCALE=$($(PROJECT)-scale)) \ -DTEST_$(PROJECT) -DTEST_$(UC_PROJECT) \ - $(if $(TEST_ASAN),-fsanitize=address) + $(if $(TEST_ASAN),-fsanitize=address) \ + $(if $(TEST_FUZZ),-fsanitize=fuzzer-no-link -DTEST_FUZZ) CFLAGS_COVERAGE=$(if $(TEST_COVERAGE),-fprofile-arcs -ftest-coverage \ -DTEST_COVERAGE,) CFLAGS_DEFINE=-DOUTDIR=$(out)/$(BLD) -DCHIP=$(CHIP) -DBOARD_TASKFILE=$(_tsk_lst_file) \ @@ -108,7 +109,8 @@ LDFLAGS=-nostdlib -g -Wl,-X -Wl,--gc-sections -Wl,--build-id=none \ BUILD_LDFLAGS=$(LIBFTDI_LDLIBS) HOST_TEST_LDFLAGS=-Wl,-T core/host/host_exe.lds -lrt -pthread -rdynamic -lm\ $(if $(TEST_COVERAGE),-fprofile-arcs,) \ - $(if $(TEST_ASAN), -fsanitize=address) + $(if $(TEST_ASAN), -fsanitize=address) \ + $(if $(TEST_FUZZ), -fsanitize=fuzzer) # utility function to provide overridable defaults # $1: name of variable to set diff --git a/chip/host/reboot.c b/chip/host/reboot.c index 7fdb6f7f8e..c6eb83ce30 100644 --- a/chip/host/reboot.c +++ b/chip/host/reboot.c @@ -8,10 +8,18 @@ #include #include +#include "console.h" #include "host_test.h" #include "reboot.h" #include "test_util.h" +#ifdef TEST_FUZZ +/* reboot breaks fuzzing, let's just not do it. */ +void emulator_reboot(void) +{ + ccprints("Emulator would reboot here. Fuzzing: doing nothing."); +} +#else /* !TEST_FUZZ */ __attribute__((noreturn)) void emulator_reboot(void) { @@ -21,3 +29,4 @@ void emulator_reboot(void) while (1) ; } +#endif /* !TEST_FUZZ */ diff --git a/chip/host/reboot.h b/chip/host/reboot.h index 113569bb9b..524cd564a1 100644 --- a/chip/host/reboot.h +++ b/chip/host/reboot.h @@ -8,7 +8,9 @@ #ifndef __CROS_EC_REBOOT_H #define __CROS_EC_REBOOT_H +#ifndef TEST_FUZZ __attribute__((noreturn)) +#endif void emulator_reboot(void); #endif diff --git a/chip/host/system.c b/chip/host/system.c index 69ed1c83a1..cc0c307d95 100644 --- a/chip/host/system.c +++ b/chip/host/system.c @@ -173,6 +173,15 @@ test_mockable int system_is_locked(void) return 0; } +#ifdef TEST_FUZZ +/* When fuzzing, do not allow sysjumps. */ +int system_run_image_copy(enum system_image_copy_t copy) +{ + ccprints("Emulator would sysjump here. Fuzzing: doing nothing."); + return EC_ERROR_UNKNOWN; +} +#endif + const char *system_get_chip_vendor(void) { return "chromeos"; diff --git a/chip/host/uart.c b/chip/host/uart.c index cebd529d43..cc0ca04d09 100644 --- a/chip/host/uart.c +++ b/chip/host/uart.c @@ -6,6 +6,7 @@ /* UART driver for emulator */ #include +#include #include #include #include @@ -20,7 +21,9 @@ static int stopped = 1; static int init_done; +#ifndef TEST_FUZZ static pthread_t input_thread; +#endif #define INPUT_BUFFER_SIZE 16 static int char_available; @@ -132,6 +135,11 @@ void uart_inject_char(char *s, int sz) } } +/* + * We do not really need console input when fuzzing, and having it enabled + * breaks terminal when an error is detected. + */ +#ifndef TEST_FUZZ static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t uart_monitor_initialized = PTHREAD_COND_INITIALIZER; @@ -170,14 +178,17 @@ void *uart_monitor_stdin(void *d) return 0; } +#endif /* !TEST_FUZZ */ void uart_init(void) { +#ifndef TEST_FUZZ /* Create UART monitor thread and wait for it to initialize. */ pthread_mutex_lock(&mutex); pthread_create(&input_thread, NULL, uart_monitor_stdin, NULL); pthread_cond_wait(&uart_monitor_initialized, &mutex); pthread_mutex_unlock(&mutex); +#endif stopped = 1; /* Not transmitting yet */ init_done = 1; diff --git a/common/system.c b/common/system.c index 926d9aa11d..ca3ec6f06d 100644 --- a/common/system.c +++ b/common/system.c @@ -585,7 +585,7 @@ int system_is_in_rw(void) return is_rw_image(system_get_image_copy()); } -int system_run_image_copy(enum system_image_copy_t copy) +test_mockable int system_run_image_copy(enum system_image_copy_t copy) { uintptr_t base; uintptr_t init_addr; diff --git a/core/host/main.c b/core/host/main.c index 64976c0c89..fa92b996c5 100644 --- a/core/host/main.c +++ b/core/host/main.c @@ -28,10 +28,8 @@ const char *__get_prog_name(void) return __prog_name; } -int main(int argc, char **argv) +static int test_main(void) { - __prog_name = argv[0]; - /* * In order to properly service IRQs before task switching is enabled, * we must set up our signal handler for the main thread. @@ -67,3 +65,42 @@ int main(int argc, char **argv) return 0; } + +#ifdef TEST_FUZZ +/* + * Fuzzing tests need to start the main function in a thread, so that + * LLVMFuzzerTestOneInput can run freely. + */ +void *_main_thread(void *a) +{ + test_main(); + return NULL; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + static int initialized; + static pthread_t main_t; + /* + * We lose the program name as LLVM fuzzer takes over main function: + * make up one. + */ + static const char *name = "ec-fuzz"; + + if (!initialized) { + __prog_name = name; + pthread_create(&main_t, NULL, _main_thread, NULL); + initialized = 1; + /* We can't sleep yet, busy loop waiting for tasks to start. */ + wait_for_task_started_nosleep(); + } + + return test_fuzz_one_input(data, size); +} +#else +int main(int argc, char **argv) +{ + __prog_name = argv[0]; + return test_main(); +} +#endif diff --git a/core/host/task.c b/core/host/task.c index 870f8c3dc3..f75456cbb0 100644 --- a/core/host/task.c +++ b/core/host/task.c @@ -279,7 +279,7 @@ task_id_t task_get_running(void) return running_task_id; } -void wait_for_task_started(void) +static void _wait_for_task_started(int can_sleep) { int i, ok; @@ -287,7 +287,8 @@ void wait_for_task_started(void) ok = 1; for (i = 0; i < TASK_ID_COUNT - 1; ++i) if (!tasks[i].started) { - msleep(10); + if (can_sleep) + msleep(10); ok = 0; break; } @@ -296,6 +297,16 @@ void wait_for_task_started(void) } } +void wait_for_task_started(void) +{ + _wait_for_task_started(1); +} + +void wait_for_task_started_nosleep(void) +{ + _wait_for_task_started(0); +} + static task_id_t task_get_next_wake(void) { int i; diff --git a/include/system.h b/include/system.h index ef7c9f5f9b..bce8689503 100644 --- a/include/system.h +++ b/include/system.h @@ -280,7 +280,10 @@ const char *system_get_build_info(void); * * @param flags Reset flags; see SYSTEM_RESET_* above. */ -void system_reset(int flags) __attribute__((noreturn)); +#ifndef TEST_FUZZ +__attribute__((noreturn)) +#endif +void system_reset(int flags); /** * Set a scratchpad register to the specified value. diff --git a/include/test_util.h b/include/test_util.h index a1ab956359..c85986b229 100644 --- a/include/test_util.h +++ b/include/test_util.h @@ -107,6 +107,9 @@ void test_init(void); /* Test entry point */ void run_test(void); +/* Test entry point for fuzzing tests. */ +int test_fuzz_one_input(const uint8_t *data, unsigned int size); + /* Resets test error count */ void test_reset(void); @@ -144,8 +147,10 @@ void interrupt_generator_udelay(unsigned us); #ifdef EMU_BUILD void wait_for_task_started(void); +void wait_for_task_started_nosleep(void); #else static inline void wait_for_task_started(void) { } +static inline void wait_for_task_started_nosleep(void) { } #endif uint32_t prng(uint32_t seed); diff --git a/test/build.mk b/test/build.mk index d1ca94b7dc..967231684e 100644 --- a/test/build.mk +++ b/test/build.mk @@ -65,6 +65,9 @@ test-list-host += vboot test-list-host += x25519 endif +# Fuzzing tests +fuzz-test-list-host = host_command_fuzz + base32-y=base32.o battery_get_params_smart-y=battery_get_params_smart.o bklight_lid-y=bklight_lid.o @@ -81,6 +84,7 @@ fan-y=fan.o flash-y=flash.o hooks-y=hooks.o host_command-y=host_command.o +host_command_fuzz-y=host_command_fuzz.o inductive_charging-y=inductive_charging.o interrupt-scale=10 interrupt-y=interrupt.o diff --git a/test/host_command_fuzz.c b/test/host_command_fuzz.c new file mode 100644 index 0000000000..7f0bfad7a5 --- /dev/null +++ b/test/host_command_fuzz.c @@ -0,0 +1,169 @@ +/* Copyright 2018 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Fuzz host command. + */ + +#include +#include + +#include "common.h" +#include "console.h" +#include "host_command.h" +#include "host_test.h" +#include "task.h" +#include "test_util.h" +#include "timer.h" +#include "util.h" + +/* Only test requests with valid size and checksum (makes fuzzing faster) */ +#define VALID_REQUEST_ONLY + +#define TASK_EVENT_FUZZ TASK_EVENT_CUSTOM(1) +#define TASK_EVENT_HOSTCMD_DONE TASK_EVENT_CUSTOM(2) + +/* Request/response buffer size (and maximum command length) */ +#define BUFFER_SIZE 128 + +struct host_packet pkt; +static uint8_t resp_buf[BUFFER_SIZE]; +struct ec_host_response *resp = (struct ec_host_response *)resp_buf; +static uint8_t req_buf[BUFFER_SIZE]; +static struct ec_host_request *req = (struct ec_host_request *)req_buf; + +static void hostcmd_respond(struct host_packet *pkt) +{ + task_set_event(TASK_ID_TEST_RUNNER, TASK_EVENT_HOSTCMD_DONE, 0); +} + +static char calculate_checksum(const char *buf, int size) +{ + int c = 0; + int i; + + for (i = 0; i < size; ++i) + c += buf[i]; + + return -c; +} + +struct chunk { + int start; + int size; +}; + +static int hostcmd_fill(const uint8_t *data, size_t size) +{ + static int first = 1; + +#ifdef VALID_REQUEST_ONLY + const int checksum_offset = offsetof(struct ec_host_request, checksum); + const int checksum_size = sizeof(req->checksum); + const int data_len_offset = offsetof(struct ec_host_request, data_len); + const int data_len_size = sizeof(req->data_len); + + struct chunk chunks[3]; + + chunks[0].start = 0; + chunks[0].size = checksum_offset; + chunks[1].start = chunks[0].start + chunks[0].size + checksum_size; + chunks[1].size = data_len_offset - chunks[1].start; + chunks[2].start = chunks[1].start + chunks[1].size + data_len_size; + chunks[2].size = sizeof(req_buf) - chunks[2].start; +#else + struct chunk chunks[1] = { {0, sizeof(req_buf)} }; +#endif + + int ipos = 0; + int i; + int req_size = 0; + + /* + * TODO(chromium:854975): We should probably malloc req_buf with the + * correct size, to make we do not read uninitialized req_buf data. + */ + memset(req_buf, 0, sizeof(req_buf)); + + /* + * Fill in req_buf, according to chunks defined above (i.e. skipping + * over checksum and data_len. + */ + for (i = 0; i < ARRAY_SIZE(chunks) && ipos < size; i++) { + int cp_size = MIN(chunks[i].size, size-ipos); + + memcpy(req_buf + chunks[i].start, data + ipos, cp_size); + + ipos += cp_size; + + req_size = chunks[i].start + cp_size; + } + + /* Not enough space in req_buf. */ + if (ipos != size) + return -1; + + pkt.request_size = req_size; + req->data_len = req_size - sizeof(*req); + req->checksum = calculate_checksum(req_buf, req_size); + + /* + * Print the full request on the first fuzzing attempt: useful to + * report bugs, and write up commit messages when reproducing + * issues. + */ + if (first) { + ccprintf("Request: cmd=%04x data=%.*h\n", + req->command, req_size, req_buf); + first = 0; + } + + pkt.send_response = hostcmd_respond; + pkt.request = (const void *)req_buf; + pkt.request_max = BUFFER_SIZE; + pkt.response = (void *)resp_buf; + pkt.response_max = BUFFER_SIZE; + pkt.driver_result = 0; + + return 0; +} + +static pthread_cond_t done_cond; +static pthread_mutex_t lock; + +void run_test(void) +{ + ccprints("Fuzzing task started"); + wait_for_task_started(); + + while (1) { + task_wait_event_mask(TASK_EVENT_FUZZ, -1); + /* Send the host command (pkt prepared by main thread). */ + host_packet_receive(&pkt); + task_wait_event_mask(TASK_EVENT_HOSTCMD_DONE, -1); + pthread_cond_signal(&done_cond); + } +} + +int test_fuzz_one_input(const uint8_t *data, unsigned int size) +{ + /* Fill in req_buf. */ + if (hostcmd_fill(data, size) < 0) + return 0; + + task_set_event(TASK_ID_TEST_RUNNER, TASK_EVENT_FUZZ, 0); + pthread_cond_wait(&done_cond, &lock); + +#ifdef VALID_REQUEST_ONLY + /* + * We carefully crafted all our requests to have a valid checksum, so + * we should never receive an invalid checksum error. (but ignore + * EC_CMD_TEST_PROTOCOL, as it can lead to arbitrary result values). + */ + ASSERT(req->command == EC_CMD_TEST_PROTOCOL || + resp->result != EC_RES_INVALID_CHECKSUM); +#endif + + return 0; +} + diff --git a/test/host_command_fuzz.tasklist b/test/host_command_fuzz.tasklist new file mode 100644 index 0000000000..de4df33e13 --- /dev/null +++ b/test/host_command_fuzz.tasklist @@ -0,0 +1,17 @@ +/* Copyright 2018 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/** + * List of enabled tasks in the priority order + * + * The first one has the lowest priority. + * + * For each task, use the macro TASK_TEST(n, r, d, s) where : + * 'n' in the name of the task + * 'r' in the main routine of the task + * 'd' in an opaque parameter passed to the routine at startup + * 's' is the stack size in bytes; must be a multiple of 8 + */ +#define CONFIG_TEST_TASK_LIST diff --git a/test/test_config.h b/test/test_config.h index 76e071b556..8efe426e38 100644 --- a/test/test_config.h +++ b/test/test_config.h @@ -294,5 +294,23 @@ enum nvmem_vars { #define CONFIG_CURVE25519 #endif /* TEST_X25519 */ +#ifdef TEST_FUZZ +/* Disable hibernate: We never want to exit while fuzzing. */ +#undef CONFIG_HIBERNATE +#endif + +#ifdef TEST_HOST_COMMAND_FUZZ +#undef CONFIG_HOSTCMD_DEBUG_MODE + +/* Defining this make fuzzing slower, but exercises additional code paths. */ +#define FUZZ_HOSTCMD_VERBOSE + +#ifdef FUZZ_HOSTCMD_VERBOSE +#define CONFIG_HOSTCMD_DEBUG_MODE HCDEBUG_PARAMS +#else +#define CONFIG_HOSTCMD_DEBUG_MODE HCDEBUG_OFF +#endif /* ! FUZZ_HOSTCMD_VERBOSE */ +#endif /* TEST_HOST_COMMAND_FUZZ */ + #endif /* TEST_BUILD */ #endif /* __TEST_TEST_CONFIG_H */ -- cgit v1.2.1