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>
This commit is contained in:
Nicolas Boichat 2018-06-20 14:21:43 +08:00 committed by chrome-bot
parent 165ee29673
commit 4a4e2c71a0
15 changed files with 325 additions and 16 deletions

View File

@ -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

View File

@ -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

View File

@ -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 */

View File

@ -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

View File

@ -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";

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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.

View File

@ -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);

View File

@ -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

169
test/host_command_fuzz.c Normal file
View File

@ -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;
}

View File

@ -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

View File

@ -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 */