diff --git a/CONFIG b/CONFIG index 6aa76e7e3..5f552fe81 100644 --- a/CONFIG +++ b/CONFIG @@ -65,6 +65,10 @@ CONFIG_ASAN=n # Build with Undefined Behavior Sanitizer enabled CONFIG_UBSAN=n +# Build with LLVM fuzzing enabled +CONFIG_FUZZER=n +CONFIG_FUZZER_LIB= + # Build with Thread Sanitizer enabled CONFIG_TSAN=n diff --git a/configure b/configure index 31ed9fc34..e2a42a7a0 100755 --- a/configure +++ b/configure @@ -101,6 +101,10 @@ function usage() { echo " --without-wpdk The argument must be a directory containing lib and include." echo " --with-usdt Build with userspace DTrace probes enabled." echo " --without-usdt No path required." + echo " --with-fuzzer Build with LLVM fuzzing enabled." + echo " Path to clang_rt.fuzzer_no_main library required." + echo " Requires setting CC and CXX to clang." + echo " (Typically /usr/lib/llvm-VER/lib/clang/VER/lib/linux/libclang_rt.fuzzer_no_main-ARCH.a)" echo "" echo "Environment variables:" echo "" @@ -482,6 +486,19 @@ for i in "$@"; do --without-usdt) CONFIG[USDT]=n ;; + --with-fuzzer) + echo "Must specify fuzzer library path with --with-fuzzer" + usage + exit 1 + ;; + --with-fuzzer=*) + CONFIG[FUZZER]=y + CONFIG[FUZZER_LIB]=$(readlink -f ${i#*=}) + ;; + --without-fuzzer) + CONFIG[FUZZER]=n + CONFIG[FUZZER_LIB]= + ;; --) break ;; @@ -888,6 +905,11 @@ if [ "${CONFIG[CET]}" = "y" ]; then fi fi +if [[ "${CONFIG[FUZZER]}" = "y" && "$CC_TYPE" != "clang" ]]; then + echo "--with-fuzzer requires setting CC and CXX to clang." + exit 1 +fi + if [[ "${CONFIG[ISAL]}" = "y" ]]; then if [ ! -f "$rootdir"/isa-l/autogen.sh ]; then echo "ISA-L was not found; To install ISA-L run:" diff --git a/mk/spdk.common.mk b/mk/spdk.common.mk index 355cf3987..a4fd0cdd8 100644 --- a/mk/spdk.common.mk +++ b/mk/spdk.common.mk @@ -279,6 +279,12 @@ COMMON_CFLAGS += -fsanitize=thread LDFLAGS += -fsanitize=thread endif +ifeq ($(CONFIG_FUZZER),y) +COMMON_CFLAGS += -fsanitize=fuzzer-no-link +LDFLAGS += -fsanitize=fuzzer-no-link +SYS_LIBS += $(CONFIG_FUZZER_LIB) +endif + SPDK_GIT_COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null) ifneq (, $(SPDK_GIT_COMMIT)) COMMON_CFLAGS += -DSPDK_GIT_COMMIT=$(SPDK_GIT_COMMIT) diff --git a/test/app/fuzz/Makefile b/test/app/fuzz/Makefile index 1f0a81b92..285cd116e 100644 --- a/test/app/fuzz/Makefile +++ b/test/app/fuzz/Makefile @@ -37,6 +37,10 @@ include $(SPDK_ROOT_DIR)/mk/spdk.common.mk DIRS-y += nvme_fuzz DIRS-y += iscsi_fuzz +ifeq ($(CONFIG_FUZZER),y) +DIRS-y += llvm_nvme_fuzz +endif + ifeq ($(OS),Linux) DIRS-$(CONFIG_VIRTIO) += vhost_fuzz endif diff --git a/test/app/fuzz/llvm_nvme_fuzz/.gitignore b/test/app/fuzz/llvm_nvme_fuzz/.gitignore new file mode 100644 index 000000000..df61ec494 --- /dev/null +++ b/test/app/fuzz/llvm_nvme_fuzz/.gitignore @@ -0,0 +1 @@ +llvm_nvme_fuzz diff --git a/test/app/fuzz/llvm_nvme_fuzz/Makefile b/test/app/fuzz/llvm_nvme_fuzz/Makefile new file mode 100644 index 000000000..d9e05143a --- /dev/null +++ b/test/app/fuzz/llvm_nvme_fuzz/Makefile @@ -0,0 +1,44 @@ +# +# Copyright (c) Intel Corporation. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk + +APP = llvm_nvme_fuzz + +C_SRCS := llvm_nvme_fuzz.c + +LDFLAGS+= -lstdc++ + +SPDK_LIB_LIST = $(ALL_MODULES_LIST) event event_nvmf + +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk diff --git a/test/app/fuzz/llvm_nvme_fuzz/llvm_nvme_fuzz.c b/test/app/fuzz/llvm_nvme_fuzz/llvm_nvme_fuzz.c new file mode 100644 index 000000000..7e1f7f4d8 --- /dev/null +++ b/test/app/fuzz/llvm_nvme_fuzz/llvm_nvme_fuzz.c @@ -0,0 +1,323 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spdk/stdinc.h" +#include "spdk/conf.h" +#include "spdk/env.h" +#include "spdk/event.h" +#include "spdk/util.h" +#include "spdk/string.h" +#include "spdk/nvme_spec.h" +#include "spdk/nvme.h" +#include "spdk/likely.h" + +static const uint8_t *g_data; +static bool g_trid_specified = false; +static int32_t g_time_in_sec = 10; +static char *g_corpus_dir; +static pthread_t g_fuzz_td; +static bool g_shutdown; + +#define MAX_COMMANDS 5 + +struct fuzz_command { + struct spdk_nvme_cmd cmd; + void *buf; + uint32_t len; +}; + +static struct fuzz_command g_cmds[MAX_COMMANDS]; + +typedef void (*fuzz_build_cmd_fn)(struct fuzz_command *cmd); + +struct fuzz_type { + fuzz_build_cmd_fn fn; + uint32_t bytes_per_cmd; +}; + +static void +fuzz_admin_command(struct fuzz_command *cmd) +{ + memcpy(&cmd->cmd, g_data, sizeof(cmd->cmd)); + g_data += sizeof(cmd->cmd); + + /* ASYNC_EVENT_REQUEST won't complete, so pick a different opcode. */ + if (cmd->cmd.opc == SPDK_NVME_OPC_ASYNC_EVENT_REQUEST) { + cmd->cmd.opc = SPDK_NVME_OPC_SET_FEATURES; + } +} + +static void +fuzz_log_page_command(struct fuzz_command *cmd) +{ + memset(&cmd->cmd, 0, sizeof(cmd->cmd)); + + cmd->cmd.opc = SPDK_NVME_OPC_GET_LOG_PAGE; + + /* Only fuzz some of the more interesting parts of the GET_LOG_PAGE command. */ + + cmd->cmd.cdw10_bits.raw = 0; + cmd->cmd.cdw10_bits.get_log_page.numdl = (g_data[0] << 8) + g_data[1]; + cmd->cmd.cdw10_bits.get_log_page.lid = g_data[2]; + cmd->cmd.cdw10_bits.get_log_page.lsp = g_data[3] & (0x60 >> 5); + cmd->cmd.cdw10_bits.get_log_page.rae = g_data[3] & (0x80 >> 7); + + cmd->cmd.cdw11_bits.raw = 0; + cmd->cmd.cdw11_bits.get_log_page.numdu = g_data[3] & (0x18 >> 3); + + /* Log Page Offset Lower */ + cmd->cmd.cdw12 = (g_data[4] << 8) + g_data[5]; + + /* Offset Type */ + cmd->cmd.cdw14 = g_data[3] & (0x01 >> 0); + + /* Log Page Offset Upper */ + cmd->cmd.cdw13 = g_data[3] & (0x06 >> 1); + + g_data += 6; +} + +static struct fuzz_type g_fuzzers[] = { + { .fn = fuzz_admin_command, .bytes_per_cmd = sizeof(struct spdk_nvme_cmd) }, + { .fn = fuzz_log_page_command, .bytes_per_cmd = 6 }, + { .fn = NULL, .bytes_per_cmd = 0 } +}; + +#define NUM_FUZZERS (SPDK_COUNTOF(g_fuzzers) - 1) + +static struct fuzz_type *g_fuzzer; + +struct spdk_nvme_transport_id g_trid; +static struct spdk_nvme_ctrlr *g_ctrlr; +static void +nvme_fuzz_cpl_cb(void *cb_arg, const struct spdk_nvme_cpl *cpl) +{ + int *outstanding = cb_arg; + + assert(*outstanding > 0); + (*outstanding)--; +} + +static int +run_cmds(uint32_t queue_depth) +{ + int rc, outstanding = 0; + uint32_t i; + + for (i = 0; i < queue_depth; i++) { + struct fuzz_command *cmd = &g_cmds[i]; + + g_fuzzer->fn(cmd); + outstanding++; + rc = spdk_nvme_ctrlr_cmd_admin_raw(g_ctrlr, &cmd->cmd, cmd->buf, cmd->len, nvme_fuzz_cpl_cb, + &outstanding); + if (rc) { + return rc; + } + } + + while (outstanding > 0 && !g_shutdown) { + spdk_nvme_ctrlr_process_admin_completions(g_ctrlr); + } + return 0; +} + +static int TestOneInput(const uint8_t *data, size_t size) +{ + struct spdk_nvme_detach_ctx *detach_ctx = NULL; + + g_ctrlr = spdk_nvme_connect(&g_trid, NULL, 0); + if (g_ctrlr == NULL) { + fprintf(stderr, "spdk_nvme_connect() failed for transport address '%s'\n", + g_trid.traddr); + spdk_app_stop(-1); + } + + g_data = data; + + run_cmds(size / g_fuzzer->bytes_per_cmd); + spdk_nvme_detach_async(g_ctrlr, &detach_ctx); + + if (detach_ctx) { + spdk_nvme_detach_poll(detach_ctx); + } + + if (g_shutdown) { + pthread_exit(NULL); + } + + return 0; +} + +int LLVMFuzzerRunDriver(int *argc, char ***argv, int (*UserCb)(const uint8_t *Data, size_t Size)); + +static void * +start_fuzzer(void *ctx) +{ + char *_argv[] = { + "spdk", + "-len_control=0", + "-detect_leaks=1", + NULL, + NULL, + NULL + }; + char time_str[128]; + char len_str[128]; + char **argv = _argv; + int argc = SPDK_COUNTOF(_argv); + uint32_t len; + + len = MAX_COMMANDS * g_fuzzer->bytes_per_cmd; + snprintf(len_str, sizeof(len_str), "-max_len=%d", len); + argv[argc - 3] = len_str; + snprintf(time_str, sizeof(time_str), "-max_total_time=%d", g_time_in_sec); + argv[argc - 2] = time_str; + argv[argc - 1] = g_corpus_dir; + + LLVMFuzzerRunDriver(&argc, &argv, TestOneInput); + spdk_app_stop(0); + return NULL; +} + +static void +begin_fuzz(void *ctx) +{ + int i; + + for (i = 0; i < MAX_COMMANDS; i++) { + g_cmds[i].buf = spdk_malloc(4096, 0, NULL, SPDK_ENV_LCORE_ID_ANY, SPDK_MALLOC_DMA); + } + + pthread_create(&g_fuzz_td, NULL, start_fuzzer, NULL); +} + +static void +nvme_fuzz_usage(void) +{ + fprintf(stderr, " -F Transport ID for subsystem that should be fuzzed.\n"); + fprintf(stderr, " -t Time to run fuzz tests (in seconds). Default: 10\n"); + fprintf(stderr, " -Z Fuzzer to run (0 to %lu)\n", NUM_FUZZERS - 1); +} + +static int +nvme_fuzz_parse(int ch, char *arg) +{ + long long tmp; + int rc; + + switch (ch) { + case 'D': + g_corpus_dir = strdup(optarg); + break; + case 'F': + if (g_trid_specified) { + fprintf(stderr, "Can only specify one trid\n"); + return -1; + } + g_trid_specified = true; + rc = spdk_nvme_transport_id_parse(&g_trid, optarg); + if (rc < 0) { + fprintf(stderr, "failed to parse transport ID: %s\n", optarg); + return -1; + } + break; + case 't': + case 'Z': + tmp = spdk_strtoll(optarg, 10); + if (tmp < 0 || tmp >= INT_MAX) { + fprintf(stderr, "Invalid value '%s' for option -%c.\n", optarg, ch); + return -EINVAL; + } + switch (ch) { + case 't': + g_time_in_sec = tmp; + break; + case 'Z': + if ((unsigned long)tmp >= NUM_FUZZERS) { + fprintf(stderr, "Invalid fuzz type %lld (max %lu)\n", tmp, NUM_FUZZERS - 1); + return -EINVAL; + } + g_fuzzer = &g_fuzzers[tmp]; + break; + } + break; + case '?': + default: + return -EINVAL; + } + return 0; +} + +static void +fuzz_shutdown(void) +{ + g_shutdown = true; + /* Wait for the fuzz thread to exit before calling spdk_app_stop(). */ + pthread_join(g_fuzz_td, NULL); + spdk_app_stop(-1); +} + +int +main(int argc, char **argv) +{ + struct spdk_app_opts opts = {}; + int rc; + + spdk_app_opts_init(&opts, sizeof(opts)); + opts.name = "nvme_fuzz"; + opts.shutdown_cb = fuzz_shutdown; + + if ((rc = spdk_app_parse_args(argc, argv, &opts, "D:F:t:Z:", NULL, nvme_fuzz_parse, + nvme_fuzz_usage) != SPDK_APP_PARSE_ARGS_SUCCESS)) { + return rc; + } + + if (!g_corpus_dir) { + fprintf(stderr, "Must specify corpus dir with -D option\n"); + return -1; + } + + if (!g_trid_specified) { + fprintf(stderr, "Must specify trid with -F option\n"); + return -1; + } + + if (!g_fuzzer) { + fprintf(stderr, "Must specify fuzzer with -Z option\n"); + return -1; + } + + rc = spdk_app_start(&opts, begin_fuzz, NULL); + + return rc; +} diff --git a/test/common/skipped_build_files.txt b/test/common/skipped_build_files.txt index 2b3a9c1d9..c3b1287d4 100644 --- a/test/common/skipped_build_files.txt +++ b/test/common/skipped_build_files.txt @@ -41,3 +41,7 @@ test/external_code/passthru/vbdev_passthru test/external_code/passthru/vbdev_passthru_rpc test/external_code/nvme/nvme test/external_code/nvme/identify + +# This file requires some additional work in CI and test scripts before it can +# be part of autotest. +test/app/fuzz/llvm_nvme_fuzz/llvm_nvme_fuzz diff --git a/test/nvmf/target/fuzz_json.conf b/test/nvmf/target/fuzz_json.conf new file mode 100644 index 000000000..cd524cca7 --- /dev/null +++ b/test/nvmf/target/fuzz_json.conf @@ -0,0 +1,123 @@ +{ + "subsystems": [ + { + "subsystem": "bdev", + "config": [ + { + "method": "bdev_set_options", + "params": { + "bdev_io_pool_size": 65535, + "bdev_io_cache_size": 256, + "bdev_auto_examine": true + } + }, + { + "method": "bdev_nvme_set_options", + "params": { + "action_on_timeout": "none", + "timeout_us": 0, + "timeout_admin_us": 0, + "keep_alive_timeout_ms": 10000, + "retry_count": 4, + "arbitration_burst": 0, + "low_priority_weight": 0, + "medium_priority_weight": 0, + "high_priority_weight": 0, + "nvme_adminq_poll_period_us": 10000, + "nvme_ioq_poll_period_us": 0, + "io_queue_requests": 0, + "delay_cmd_submit": true + } + }, + { + "method": "bdev_nvme_set_hotplug", + "params": { + "period_us": 100000, + "enable": false + } + }, + { + "method": "bdev_malloc_create", + "params": { + "name": "Malloc0", + "num_blocks": 131072, + "block_size": 512, + "uuid": "6d6a0bf0-b712-40a7-8730-8f45797cc355" + } + }, + { + "method": "bdev_wait_for_examine" + } + ] + }, + { + "subsystem": "nvmf", + "config": [ + { + "method": "nvmf_set_config", + "params": { + "poll_groups_mask": "0x1", + "admin_cmd_passthru": { + "identify_ctrlr": false + } + } + }, + { + "method": "nvmf_create_transport", + "params": { + "trtype": "TCP", + "max_queue_depth": 128, + "max_io_qpairs_per_ctrlr": 127, + "in_capsule_data_size": 4096, + "max_io_size": 131072, + "io_unit_size": 8192, + "max_aq_depth": 128, + "num_shared_buffers": 511, + "buf_cache_size": 32, + "dif_insert_or_strip": false, + "c2h_success": false, + "sock_priority": 0, + "abort_timeout_sec": 1 + } + }, + { + "method": "nvmf_create_subsystem", + "params": { + "nqn": "nqn.2016-06.io.spdk:cnode1", + "allow_any_host": true, + "serial_number": "SPDK00000000000001", + "model_number": "SPDK bdev Controller", + "max_namespaces": 32, + "min_cntlid": 1, + "max_cntlid": 65519, + "ana_reporting": false + } + }, + { + "method": "nvmf_subsystem_add_listener", + "params": { + "nqn": "nqn.2016-06.io.spdk:cnode1", + "listen_address": { + "trtype": "TCP", + "adrfam": "IPv4", + "traddr": "127.0.0.1", + "trsvcid": "4420" + } + } + }, + { + "method": "nvmf_subsystem_add_ns", + "params": { + "nqn": "nqn.2016-06.io.spdk:cnode1", + "namespace": { + "nsid": 1, + "bdev_name": "Malloc0", + "nguid": "6D6A0BF0B71240A787308F45797CC355", + "uuid": "6d6a0bf0-b712-40a7-8730-8f45797cc355" + } + } + } + ] + } + ] +} diff --git a/test/nvmf/target/llvm_nvme_fuzz.sh b/test/nvmf/target/llvm_nvme_fuzz.sh new file mode 100755 index 000000000..b1f3fe615 --- /dev/null +++ b/test/nvmf/target/llvm_nvme_fuzz.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +TIME=10 +FUZZER=0 +for i in "$@"; do + case "$i" in + --time=*) + TIME="${i#*=}" + ;; + --fuzzer=*) + FUZZER="${i#*=}" + ;; + esac +done + +testdir=$(readlink -f $(dirname $0)) +rootdir=$(readlink -f $testdir/../../..) +source $rootdir/test/common/autotest_common.sh + +trap 'process_shm --id 0; exit 1' SIGINT SIGTERM EXIT + +mkdir -p /tmp/corpus$FUZZER +trid="trtype:tcp adrfam:IPv4 subnqn:nqn.2016-06.io.spdk:cnode1 traddr:127.0.0.1 trsvcid:4420" + +$rootdir/test/app/fuzz/llvm_nvme_fuzz/llvm_nvme_fuzz -m 0x1 -i 0 -F "$trid" -c $testdir/fuzz_json.conf -t $TIME -D /tmp/corpus$FUZZER -Z $FUZZER + +trap - SIGINT SIGTERM EXIT