summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicolas Boichat <drinkcat@chromium.org>2018-06-20 14:21:43 +0800
committerchrome-bot <chrome-bot@chromium.org>2018-08-16 00:30:08 -0700
commit4a4e2c71a0f6aaa50e0728922f84a7d54c14380a (patch)
tree4bbc05387f7b479bc4a3dcb925174676f03d9356
parent165ee29673b058ba5f4550d5b6e1dfecb179bb22 (diff)
downloadchrome-ec-4a4e2c71a0f6aaa50e0728922f84a7d54c14380a.tar.gz
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 <drinkcat@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/1107523 Reviewed-by: Manoj Gupta <manojgupta@chromium.org> Reviewed-by: Randall Spangler <rspangler@chromium.org> Reviewed-by: Jonathan Metzman <metzman@chromium.org>
-rw-r--r--Makefile.rules26
-rw-r--r--Makefile.toolchain6
-rw-r--r--chip/host/reboot.c9
-rw-r--r--chip/host/reboot.h2
-rw-r--r--chip/host/system.c9
-rw-r--r--chip/host/uart.c11
-rw-r--r--common/system.c2
-rw-r--r--core/host/main.c43
-rw-r--r--core/host/task.c15
-rw-r--r--include/system.h5
-rw-r--r--include/test_util.h5
-rw-r--r--test/build.mk4
-rw-r--r--test/host_command_fuzz.c169
-rw-r--r--test/host_command_fuzz.tasklist17
-rw-r--r--test/test_config.h18
15 files changed, 325 insertions, 16 deletions
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 <string.h>
#include <unistd.h>
+#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 <pthread.h>
+#include <signal.h>
#include <stdio.h>
#include <termio.h>
#include <unistd.h>
@@ -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 <pthread.h>
+#include <sys/time.h>
+
+#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 */