diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:54:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:54:28 +0000 |
commit | e6918187568dbd01842d8d1d2c808ce16a894239 (patch) | |
tree | 64f88b554b444a49f656b6c656111a145cbbaa28 /src/spdk/test/app/fuzz | |
parent | Initial commit. (diff) | |
download | ceph-e6918187568dbd01842d8d1d2c808ce16a894239.tar.xz ceph-e6918187568dbd01842d8d1d2c808ce16a894239.zip |
Adding upstream version 18.2.2.upstream/18.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/spdk/test/app/fuzz')
19 files changed, 4430 insertions, 0 deletions
diff --git a/src/spdk/test/app/fuzz/Makefile b/src/spdk/test/app/fuzz/Makefile new file mode 100644 index 000000000..1f0a81b92 --- /dev/null +++ b/src/spdk/test/app/fuzz/Makefile @@ -0,0 +1,48 @@ +# +# 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. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y += nvme_fuzz +DIRS-y += iscsi_fuzz + +ifeq ($(OS),Linux) +DIRS-$(CONFIG_VIRTIO) += vhost_fuzz +endif +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/test/app/fuzz/common/fuzz_common.h b/src/spdk/test/app/fuzz/common/fuzz_common.h new file mode 100644 index 000000000..7619f4fb1 --- /dev/null +++ b/src/spdk/test/app/fuzz/common/fuzz_common.h @@ -0,0 +1,303 @@ +/*- + * 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/env.h" +#include "spdk/file.h" +#include "spdk/base64.h" +#include "spdk/json.h" + +#define DEFAULT_RUNTIME 30 /* seconds */ +#define MAX_RUNTIME_S 86400 /* 24 hours */ +#define IO_TIMEOUT_S 5 + +#define UNSIGNED_2BIT_MAX ((1 << 2) - 1) +#define UNSIGNED_4BIT_MAX ((1 << 4) - 1) +#define UNSIGNED_8BIT_MAX ((1 << 8) - 1) + +typedef bool (*json_parse_fn)(void *ele, struct spdk_json_val *val, size_t num_vals); + +static void +fuzz_fill_random_bytes(char *character_repr, size_t len, unsigned int *rand_seed) +{ + size_t i; + + for (i = 0; i < len; i++) { + character_repr[i] = rand_r(rand_seed) % UINT8_MAX; + } +} + +static uint64_t +fuzz_refresh_timeout(void) +{ + uint64_t current_ticks; + uint64_t new_timeout_ticks; + + current_ticks = spdk_get_ticks(); + + new_timeout_ticks = current_ticks + IO_TIMEOUT_S * spdk_get_ticks_hz(); + assert(new_timeout_ticks > current_ticks); + + return new_timeout_ticks; +} + +static char * +fuzz_get_value_base_64_buffer(void *item, size_t len) +{ + char *value_string; + size_t total_size; + int rc; + + /* Null pointer */ + total_size = spdk_base64_get_encoded_strlen(len) + 1; + + value_string = calloc(1, total_size); + if (value_string == NULL) { + return NULL; + } + + rc = spdk_base64_encode(value_string, item, len); + if (rc < 0) { + free(value_string); + return NULL; + } + + return value_string; +} + +static int +fuzz_get_base_64_buffer_value(void *item, size_t len, char *buf, size_t buf_len) +{ + size_t size_of_data; + char *new_buf; + int rc; + + new_buf = malloc(buf_len + 1); + if (new_buf == NULL) { + return -ENOMEM; + } + + snprintf(new_buf, buf_len + 1, "%s", buf); + + size_of_data = spdk_base64_get_decoded_len(buf_len); + + if (size_of_data < len) { + free(new_buf); + return -EINVAL; + } + + rc = spdk_base64_decode(item, &size_of_data, new_buf); + + if (rc || size_of_data != len) { + free(new_buf); + return -EINVAL; + } + + free(new_buf); + return 0; +} + +static ssize_t +read_json_into_buffer(const char *filename, struct spdk_json_val **values, void **file_data) +{ + FILE *file = fopen(filename, "r"); + size_t file_data_size; + ssize_t num_json_values = 0, rc; + + if (file == NULL) { + /* errno is set by fopen */ + return 0; + } + + *file_data = spdk_posix_file_load(file, &file_data_size); + if (*file_data == NULL) { + fclose(file); + return 0; + } + + fclose(file); + + num_json_values = spdk_json_parse(*file_data, file_data_size, NULL, 0, NULL, + SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS); + + *values = calloc(num_json_values, sizeof(**values)); + if (values == NULL) { + free(*file_data); + *file_data = NULL; + return 0; + } + + rc = spdk_json_parse(*file_data, file_data_size, *values, num_json_values, NULL, + SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS); + if (num_json_values != rc) { + free(*values); + *values = NULL; + free(*file_data); + *file_data = NULL; + return 0; + } + + return num_json_values; +} + +static size_t +double_arr_size(void **buffer, size_t num_ele, size_t ele_size) +{ + void *tmp; + size_t new_num_ele, allocation_size; + + if (num_ele > SIZE_MAX / 2) { + return 0; + } + + new_num_ele = num_ele * 2; + + if (new_num_ele > SIZE_MAX / ele_size) { + return 0; + } + + allocation_size = new_num_ele * ele_size; + + tmp = realloc(*buffer, allocation_size); + if (tmp != NULL) { + *buffer = tmp; + return new_num_ele; + } + + return 0; +} + +static uint64_t +fuzz_parse_args_into_array(const char *file, void **arr, size_t ele_size, const char *obj_name, + json_parse_fn cb_fn) +{ + ssize_t i, num_json_values; + struct spdk_json_val *values = NULL, *values_head = NULL, *obj_start; + void *file_data = NULL;; + char *arr_idx_pointer; + size_t num_arr_elements, arr_elements_used, values_in_obj; + bool rc; + + num_json_values = read_json_into_buffer(file, &values_head, &file_data); + values = values_head; + if (num_json_values == 0 || values == NULL) { + if (file_data != NULL) { + free(file_data); + } + fprintf(stderr, "The file provided does not exist or we were unable to parse it.\n"); + return 0; + } + + num_arr_elements = 10; + arr_elements_used = 0; + *arr = calloc(num_arr_elements, ele_size); + arr_idx_pointer = (char *)*arr; + if (arr_idx_pointer == NULL) { + free(values); + free(file_data); + return 0; + } + + i = 0; + while (i < num_json_values) { + if (values->type != SPDK_JSON_VAL_NAME) { + i++; + values++; + continue; + } + + if (!strncmp(values->start, obj_name, values->len)) { + i++; + values++; + assert(values->type == SPDK_JSON_VAL_OBJECT_BEGIN); + obj_start = values; + values_in_obj = spdk_json_val_len(obj_start); + values += values_in_obj; + i += values_in_obj; + + rc = cb_fn((void *)arr_idx_pointer, obj_start, values_in_obj); + if (rc == false) { + fprintf(stderr, "failed to parse file after %lu elements.\n", arr_elements_used); + goto fail; + } + + arr_idx_pointer += ele_size; + arr_elements_used++; + if (arr_elements_used == num_arr_elements) { + num_arr_elements = double_arr_size(arr, num_arr_elements, ele_size); + if (num_arr_elements == 0) { + fprintf(stderr, "failed to allocate enough space for all json elements in your file.\n"); + goto fail; + } else { + /* reset the array element position in case the pointer changed. */ + arr_idx_pointer = ((char *)*arr) + arr_elements_used * ele_size; + } + } + + continue; + } else { + i++; + values++; + continue; + } + } + + if (arr_elements_used == 0) { + goto fail; + } + + free(values_head); + free(file_data); + return arr_elements_used; +fail: + free(values_head); + free(file_data); + free(*arr); + *arr = NULL; + return 0; +} + +static int +fuzz_parse_json_num(struct spdk_json_val *val, uint64_t max_val, uint64_t *val_ptr) +{ + uint64_t tmp_val; + int rc; + + rc = spdk_json_number_to_uint64(val, &tmp_val); + if (rc || tmp_val > max_val) { + return -EINVAL; + } else { + *val_ptr = tmp_val; + return 0; + } +} diff --git a/src/spdk/test/app/fuzz/common/fuzz_rpc.py b/src/spdk/test/app/fuzz/common/fuzz_rpc.py new file mode 100755 index 000000000..05cb67ed8 --- /dev/null +++ b/src/spdk/test/app/fuzz/common/fuzz_rpc.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 + +from rpc.client import print_dict, JSONRPCException + +import logging +import argparse +import rpc +import sys +import shlex + +try: + from shlex import quote +except ImportError: + from pipes import quote + + +def print_array(a): + print(" ".join((quote(v) for v in a))) + + +def _fuzz_vhost_create_dev(client, socket, is_blk, use_bogus_buffer, use_valid_buffer, test_scsi_tmf, valid_lun): + """Create a new device in the vhost fuzzer. + + Args: + socket: A valid unix domain socket for the dev to bind to. + is_blk: if set, create a virtio_blk device, otherwise use scsi. + use_bogus_buffer: if set, pass an invalid memory address as a buffer accompanying requests. + use_valid_buffer: if set, pass in a valid memory buffer with requests. Overrides use_bogus_buffer. + test_scsi_tmf: Test scsi management commands on the given device. Valid if and only if is_blk is false. + valid_lun: Supply only a valid lun number when submitting commands to the given device. Valid if and only if is_blk is false. + + Returns: + True or False + """ + + params = {"socket": socket, + "is_blk": is_blk, + "use_bogus_buffer": use_bogus_buffer, + "use_valid_buffer": use_valid_buffer, + "test_scsi_tmf": test_scsi_tmf, + "valid_lun": valid_lun} + + return client.call("fuzz_vhost_create_dev", params) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description='SPDK RPC command line interface. NOTE: spdk/scripts/ is expected in PYTHONPATH') + parser.add_argument('-s', dest='server_addr', + help='RPC domain socket path or IP address', default='/var/tmp/spdk.sock') + parser.add_argument('-p', dest='port', + help='RPC port number (if server_addr is IP address)', + default=5260, type=int) + parser.add_argument('-t', dest='timeout', + help='Timeout as a floating point number expressed in seconds waiting for response. Default: 60.0', + default=60.0, type=float) + parser.add_argument('-v', dest='verbose', action='store_const', const="INFO", + help='Set verbose mode to INFO', default="ERROR") + parser.add_argument('--verbose', dest='verbose', choices=['DEBUG', 'INFO', 'ERROR'], + help="""Set verbose level. """) + subparsers = parser.add_subparsers(help='RPC methods') + + def fuzz_vhost_create_dev(args): + _fuzz_vhost_create_dev( + args.client, + args.socket, + args.is_blk, + args.use_bogus_buffer, + args.use_valid_buffer, + args.test_scsi_tmf, + args.valid_lun) + + p = subparsers.add_parser('fuzz_vhost_create_dev', help="Add a new device to the vhost fuzzer.") + p.add_argument('-s', '--socket', help="Path to a valid unix domain socket for dev binding.") + p.add_argument('-b', '--is-blk', help='The specified socket corresponds to a vhost-blk dev.', action='store_true') + p.add_argument('-u', '--use-bogus-buffer', help='Pass bogus buffer addresses with requests when fuzzing.', action='store_true') + p.add_argument('-v', '--use-valid-buffer', help='Pass valid buffers when fuzzing. overrides use-bogus-buffer.', action='store_true') + p.add_argument('-m', '--test-scsi-tmf', help='for a scsi device, test scsi management commands.', action='store_true') + p.add_argument('-l', '--valid-lun', help='for a scsi device, test only using valid lun IDs.', action='store_true') + p.set_defaults(func=fuzz_vhost_create_dev) + + def call_rpc_func(args): + try: + args.func(args) + except JSONRPCException as ex: + print(ex.message) + exit(1) + + def execute_script(parser, client, fd): + for rpc_call in map(str.rstrip, fd): + if not rpc_call.strip(): + continue + args = parser.parse_args(shlex.split(rpc_call)) + args.client = client + call_rpc_func(args) + + args = parser.parse_args() + args.client = rpc.client.JSONRPCClient(args.server_addr, args.port, args.timeout, log_level=getattr(logging, args.verbose.upper())) + if hasattr(args, 'func'): + call_rpc_func(args) + elif sys.stdin.isatty(): + # No arguments and no data piped through stdin + parser.print_help() + exit(1) + else: + execute_script(parser, args.client, sys.stdin) diff --git a/src/spdk/test/app/fuzz/iscsi_fuzz/.gitignore b/src/spdk/test/app/fuzz/iscsi_fuzz/.gitignore new file mode 100644 index 000000000..3982bd220 --- /dev/null +++ b/src/spdk/test/app/fuzz/iscsi_fuzz/.gitignore @@ -0,0 +1 @@ +iscsi_fuzz diff --git a/src/spdk/test/app/fuzz/iscsi_fuzz/Makefile b/src/spdk/test/app/fuzz/iscsi_fuzz/Makefile new file mode 100644 index 000000000..0131e089a --- /dev/null +++ b/src/spdk/test/app/fuzz/iscsi_fuzz/Makefile @@ -0,0 +1,51 @@ +# +# 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 = iscsi_fuzz + +CFLAGS += -I$(SPDK_ROOT_DIR)/lib +CFLAGS += -I$(SPDK_ROOT_DIR)/include + +C_SRCS := iscsi_fuzz.c + +ISCSI_OBJS = md5 param conn tgt_node init_grp portal_grp + +LIBS += $(SCSI_OBJS:%=$(SPDK_ROOT_DIR)/lib/scsi/%.o) +LIBS += $(ISCSI_OBJS:%=$(SPDK_ROOT_DIR)/lib/iscsi/%.o) + +SPDK_LIB_LIST += $(SOCK_MODULES_LIST) +SPDK_LIB_LIST += conf event json jsonrpc log scsi bdev notify rpc sock thread trace util + +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk diff --git a/src/spdk/test/app/fuzz/iscsi_fuzz/README.md b/src/spdk/test/app/fuzz/iscsi_fuzz/README.md new file mode 100644 index 000000000..c9ef02e6a --- /dev/null +++ b/src/spdk/test/app/fuzz/iscsi_fuzz/README.md @@ -0,0 +1,27 @@ +# Overview + +This application is intended to fuzz test the iSCSI target by submitting +randomized PDU commands through a simulated iSCSI initiator. + +# Input + +1. iSCSI initiator send a login request PDU to iSCSI Target. Once a session is connected, +2. iSCSI initiator send huge amount and random PDUs continuously to iSCSI Target. +3. iSCSI initiator send a logout request PDU to iSCSI Target in the end. +Especially, iSCSI initiator need to build different bhs according to different bhs opcode. +Then iSCSI initiator will receive all kinds of responsed opcodes from iSCSI Target. +The application will terminate when run time expires (see the -t flag). + +# Output + +By default, the fuzzer will print commands that: +1. Complete successfully back from the target, or +2. Are outstanding at the time of a connection error occurs. +Commands are dumped as named objects in json format which can then be supplied back to the +script for targeted debugging on a subsequent run. + +At the end of each test run, a summary is printed in the following format: + +~~~ +device 0x11c3b90 stats: Sent 1543 valid opcode PDUs, 16215 invalid opcode PDUs. +~~~ diff --git a/src/spdk/test/app/fuzz/iscsi_fuzz/iscsi_fuzz.c b/src/spdk/test/app/fuzz/iscsi_fuzz/iscsi_fuzz.c new file mode 100644 index 000000000..359b95981 --- /dev/null +++ b/src/spdk/test/app/fuzz/iscsi_fuzz/iscsi_fuzz.c @@ -0,0 +1,1092 @@ +/*- + * 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/event.h" +#include "spdk/util.h" +#include "spdk/string.h" +#include "spdk/likely.h" +#include "spdk/json.h" +#include "spdk/endian.h" +#include "spdk/bdev.h" +#include "spdk/notify.h" +#include "spdk/scsi.h" +#include "spdk_internal/mock.h" +#include "spdk/scsi_spec.h" +#include "iscsi/conn.h" +#include "iscsi/iscsi.c" +#include "scsi/scsi_internal.h" +#include "spdk/sock.h" + +#define GET_PDU_LOOP_COUNT 16 +#define DEFAULT_RUNTIME 30 /* seconds */ +#define MAX_RUNTIME_S 86400 /* 24 hours */ + +/* Global run state */ +uint64_t g_runtime_ticks; +int g_runtime; +int g_num_active_threads; +bool g_run = true; +bool g_is_valid_opcode = true; + +struct spdk_log_flag SPDK_LOG_ISCSI = { + .name = "iscsi", + .enabled = false, +}; + +/* Global resources */ +TAILQ_HEAD(, spdk_iscsi_pdu) g_get_pdu_list; +TAILQ_HEAD(, fuzz_iscsi_dev_ctx) g_dev_list = TAILQ_HEAD_INITIALIZER(g_dev_list); +struct spdk_poller *g_app_completion_poller; +void *g_valid_buffer; +unsigned int g_random_seed; +char *g_tgt_ip = "127.0.0.1"; +char *g_tgt_port = "3260"; +/* TBD: Discovery login to get target information. We use fixed IQN for target for now. */ +char *g_tgt_name = "iqn.2016-06.io.spdk:disk1"; +char *g_init_name = "iqn.2016-06.io.spdk:fuzzinit"; + +struct fuzz_iscsi_iov_ctx { + struct iovec iov_req; + struct iovec iov_data; + struct iovec iov_resp; +}; + +struct fuzz_iscsi_io_ctx { + struct fuzz_iscsi_iov_ctx iov_ctx; + union { + struct iscsi_bhs *bhs; + struct iscsi_bhs_nop_out *nop_out_req; + struct iscsi_bhs_scsi_req *scsi_req; + struct iscsi_bhs_task_req *task_req; + struct iscsi_bhs_login_req *login_req; + struct iscsi_bhs_text_req *text_req; + struct iscsi_bhs_data_out *data_out_req; + struct iscsi_bhs_logout_req *logout_req; + struct iscsi_bhs_snack_req *snack_req; + } req; +}; + +struct fuzz_iscsi_dev_ctx { + struct spdk_iscsi_sess sess; + struct spdk_iscsi_conn *conn; + struct fuzz_iscsi_io_ctx io_ctx; + + struct spdk_thread *thread; + struct spdk_poller *poller; + unsigned int random_seed, current_cmd_sn; + uint64_t num_sent_pdus; + uint64_t num_valid_pdus; + + TAILQ_ENTRY(fuzz_iscsi_dev_ctx) link; +}; + +static void +fuzz_fill_random_bytes(char *character_repr, size_t len, unsigned int *rand_seed) +{ + size_t i; + + for (i = 0; i < len; i++) { + character_repr[i] = rand_r(rand_seed) % UINT8_MAX; + } +} + +static char * +fuzz_get_value_base_64_buffer(void *item, size_t len) +{ + char *value_string; + size_t total_size; + int rc; + + /* Null pointer */ + total_size = spdk_base64_get_encoded_strlen(len) + 1; + + value_string = calloc(1, total_size); + if (value_string == NULL) { + return NULL; + } + + rc = spdk_base64_encode(value_string, item, len); + if (rc < 0) { + free(value_string); + return NULL; + } + + return value_string; +} + +int +iscsi_chap_get_authinfo(struct iscsi_chap_auth *auth, const char *authuser, + int ag_tag) +{ + return 0; +} + +void +shutdown_iscsi_conns_done(void) +{ + return; +} + +void +iscsi_put_pdu(struct spdk_iscsi_pdu *pdu) +{ + if (!pdu) { + return; + } + + pdu->ref--; + if (pdu->ref < 0) { + pdu->ref = 0; + } + + if (pdu->ref == 0) { + if (pdu->data) { + free(pdu->data); + } + free(pdu); + } +} + +struct spdk_iscsi_pdu * +iscsi_get_pdu(struct spdk_iscsi_conn *conn) +{ + struct spdk_iscsi_pdu *pdu; + + pdu = calloc(1, sizeof(*pdu)); + if (!pdu) { + return NULL; + } + + pdu->ref = 1; + pdu->conn = conn; + + return pdu; +} + +static void +iscsi_task_free(struct spdk_scsi_task *scsi_task) +{ + struct spdk_iscsi_task *task = iscsi_task_from_scsi_task(scsi_task); + + assert(task->parent == NULL); + + iscsi_task_disassociate_pdu(task); + assert(task->conn->pending_task_cnt > 0); + task->conn->pending_task_cnt--; + free(task); +} + +struct spdk_iscsi_task * +iscsi_task_get(struct spdk_iscsi_conn *conn, struct spdk_iscsi_task *parent, + spdk_scsi_task_cpl cpl_fn) +{ + struct spdk_iscsi_task *task; + + /* iSCSI subtask is not necessary for now. */ + assert(parent == NULL); + + task = calloc(1, sizeof(*task)); + if (!task) { + printf("Unable to get task\n"); + abort(); + } + + task->conn = conn; + assert(conn->pending_task_cnt < UINT32_MAX); + conn->pending_task_cnt++; + spdk_scsi_task_construct(&task->scsi, cpl_fn, iscsi_task_free); + + return task; +} + +static void +cleanup(void) +{ + struct fuzz_iscsi_dev_ctx *dev_ctx, *tmp; + + TAILQ_FOREACH_SAFE(dev_ctx, &g_dev_list, link, tmp) { + printf("device %p stats: Sent %lu valid opcode PDUs, %lu invalid opcode PDUs.\n", + dev_ctx, dev_ctx->num_valid_pdus, + dev_ctx->num_sent_pdus - dev_ctx->num_valid_pdus); + free(dev_ctx); + } + + spdk_free(g_valid_buffer); +} + +/* data dumping functions begin */ +static int +dump_iscsi_cmd(void *ctx, const void *data, size_t size) +{ + fprintf(stderr, "%s\n", (const char *)data); + return 0; +} + +static void +print_scsi_io_data(struct spdk_json_write_ctx *w, struct fuzz_iscsi_io_ctx *io_ctx) +{ + char *data_segment_len; + + data_segment_len = fuzz_get_value_base_64_buffer((void *)io_ctx->req.bhs->data_segment_len, + sizeof(io_ctx->req.bhs->data_segment_len)); + + spdk_json_write_named_uint32(w, "opcode", io_ctx->req.bhs->opcode); + spdk_json_write_named_uint32(w, "immediate", io_ctx->req.bhs->immediate); + spdk_json_write_named_uint32(w, "reserved", io_ctx->req.bhs->reserved); + spdk_json_write_named_uint32(w, "total_ahs_len", io_ctx->req.bhs->total_ahs_len); + spdk_json_write_named_string(w, "data_segment_len", data_segment_len); + spdk_json_write_named_uint32(w, "itt", io_ctx->req.bhs->itt); + spdk_json_write_named_uint32(w, "exp_stat_sn", io_ctx->req.bhs->exp_stat_sn); + + free(data_segment_len); +} + +static void +print_req_obj(struct fuzz_iscsi_dev_ctx *dev_ctx, struct fuzz_iscsi_io_ctx *io_ctx) +{ + struct spdk_json_write_ctx *w; + + w = spdk_json_write_begin(dump_iscsi_cmd, NULL, SPDK_JSON_WRITE_FLAG_FORMATTED); + spdk_json_write_named_object_begin(w, "bhs"); + print_scsi_io_data(w, io_ctx); + spdk_json_write_object_end(w); + spdk_json_write_end(w); +} + +/* data dumping functions end */ + +/* dev initialization begin */ +static int +fuzz_iscsi_dev_init(void) +{ + struct fuzz_iscsi_dev_ctx *dev_ctx; + int rc = 0; + + dev_ctx = calloc(1, sizeof(*dev_ctx)); + if (dev_ctx == NULL) { + return -ENOMEM; + } + + dev_ctx->thread = spdk_get_thread(); + if (dev_ctx->thread == NULL) { + fprintf(stderr, "Unable to get a thread for a fuzz device.\n"); + rc = -EINVAL; + goto error_out; + } + + dev_ctx->current_cmd_sn = 0; + + TAILQ_INSERT_TAIL(&g_dev_list, dev_ctx, link); + return 0; + +error_out: + free(dev_ctx); + return rc; +} +/* dev initialization end */ + +/* build requests begin */ +static void +prep_iscsi_pdu_bhs_opcode_cmd(struct fuzz_iscsi_dev_ctx *dev_ctx, struct fuzz_iscsi_io_ctx *io_ctx) +{ + io_ctx->iov_ctx.iov_req.iov_len = sizeof(struct iscsi_bhs); + fuzz_fill_random_bytes((char *)io_ctx->req.bhs, sizeof(struct iscsi_bhs), + &dev_ctx->random_seed); +} +/* build requests end */ + +static int +iscsi_pdu_hdr_op_login_rsp(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + return 0; +} + +static int +iscsi_fuzz_pdu_hdr_handle(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + int opcode; + int rc = 0; + + opcode = pdu->bhs.opcode; + if (opcode == ISCSI_OP_LOGIN_RSP) { + return iscsi_pdu_hdr_op_login_rsp(conn, pdu); + } + + switch (opcode) { + case ISCSI_OP_LOGOUT_RSP: + fprintf(stderr, "Received logout hdr_handle response opcode(0x26) from Target.\n"); + conn->is_logged_out = true; + break; + case ISCSI_OP_NOPIN: + case ISCSI_OP_SCSI_RSP: + case ISCSI_OP_TASK_RSP: + case ISCSI_OP_TEXT_RSP: + case ISCSI_OP_SCSI_DATAIN: + case ISCSI_OP_R2T: + case ISCSI_OP_ASYNC: + case ISCSI_OP_VENDOR_3C: + case ISCSI_OP_VENDOR_3D: + case ISCSI_OP_VENDOR_3E: + fprintf(stderr, "Received hdr_handle response opcode from Target is 0x%x.\n", pdu->bhs.opcode); + break; + case ISCSI_OP_REJECT: + fprintf(stderr, "Received rejected hdr_handle response opcode(0x3f) from Target.\n"); + break; + default: + rc = -1; + break; + } + + return rc; +} + +static int +iscsi_pdu_payload_op_login_rsp(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + struct iscsi_bhs_login_rsp *rsph; + + rsph = (struct iscsi_bhs_login_rsp *)&pdu->bhs; + if (rsph == NULL) { + return -1; + } + + assert(rsph->tsih != 0); + assert(rsph->status_class == 0); + assert(ISCSI_BHS_LOGIN_GET_TBIT(rsph->flags)); + assert(!(rsph->flags & ISCSI_LOGIN_CONTINUE)); + assert((rsph->flags & ISCSI_LOGIN_NEXT_STAGE_MASK) == ISCSI_LOGIN_NEXT_STAGE_3); + + /* We got the Login Final Response and move to Full-Feature Phase. */ + conn->full_feature = 1; + return 0; +} + +static int +iscsi_fuzz_pdu_payload_handle(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + int opcode; + int rc = 0; + + opcode = pdu->bhs.opcode; + fprintf(stderr, "Received payload_handle response opcode from Target is 0x%x.\n", opcode); + + switch (opcode) { + case ISCSI_OP_LOGIN_RSP: + rc = iscsi_pdu_payload_op_login_rsp(conn, pdu); + break; + case ISCSI_OP_NOPIN: + case ISCSI_OP_SCSI_RSP: + case ISCSI_OP_TASK_RSP: + case ISCSI_OP_TEXT_RSP: + case ISCSI_OP_SCSI_DATAIN: + case ISCSI_OP_R2T: + case ISCSI_OP_ASYNC: + case ISCSI_OP_VENDOR_3C: + case ISCSI_OP_VENDOR_3D: + case ISCSI_OP_VENDOR_3E: + case ISCSI_OP_REJECT: + break; + default: + rc = -1; + break; + } + + return rc; +} + +static int +iscsi_fuzz_read_pdu(struct spdk_iscsi_conn *conn) +{ + enum iscsi_pdu_recv_state prev_state; + struct spdk_iscsi_pdu *pdu; + uint32_t data_len; + int rc; + + do { + prev_state = conn->pdu_recv_state; + pdu = conn->pdu_in_progress; + + switch (conn->pdu_recv_state) { + case ISCSI_PDU_RECV_STATE_AWAIT_PDU_READY: + assert(conn->pdu_in_progress == NULL); + + conn->pdu_in_progress = iscsi_get_pdu(conn); + if (conn->pdu_in_progress == NULL) { + return SPDK_ISCSI_CONNECTION_FATAL; + } + TAILQ_INSERT_TAIL(&g_get_pdu_list, conn->pdu_in_progress, tailq); + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_AWAIT_PDU_HDR; + break; + case ISCSI_PDU_RECV_STATE_AWAIT_PDU_HDR: + if (pdu->bhs_valid_bytes < ISCSI_BHS_LEN) { + rc = iscsi_conn_read_data(conn, + ISCSI_BHS_LEN - pdu->bhs_valid_bytes, + (uint8_t *)&pdu->bhs + pdu->bhs_valid_bytes); + if (rc < 0) { + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_ERROR; + break; + } + pdu->bhs_valid_bytes += rc; + if (pdu->bhs_valid_bytes < ISCSI_BHS_LEN) { + return 0; + } + } + + pdu->data_segment_len = ISCSI_ALIGN(DGET24(pdu->bhs.data_segment_len)); + + rc = iscsi_fuzz_pdu_hdr_handle(conn, pdu); + if (rc < 0) { + printf("Critical error is detected. Close the connection\n"); + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_ERROR; + break; + } + + if (conn->is_logged_out) { + printf("pdu received after logout\n"); + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_ERROR; + break; + } + + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_AWAIT_PDU_PAYLOAD; + break; + case ISCSI_PDU_RECV_STATE_AWAIT_PDU_PAYLOAD: + data_len = pdu->data_segment_len; + if (data_len != 0 && pdu->data_buf == NULL) { + pdu->data_buf = calloc(1, data_len); + if (pdu->data_buf == NULL) { + return 0; + } + pdu->data = pdu->data_buf; + } + + /* copy the actual data into local buffer */ + if (pdu->data_valid_bytes < data_len) { + rc = iscsi_conn_read_data_segment(conn, pdu, data_len); + if (rc < 0) { + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_ERROR; + break; + } + pdu->data_valid_bytes += rc; + if (pdu->data_valid_bytes < data_len) { + return 0; + } + } + + /* All data for this PDU has now been read from the socket. */ + spdk_trace_record(TRACE_ISCSI_READ_PDU, conn->id, pdu->data_valid_bytes, + (uintptr_t)pdu, pdu->bhs.opcode); + + if (!pdu->is_rejected) { + rc = iscsi_fuzz_pdu_payload_handle(conn, pdu); + } else { + rc = 0; + } + if (rc == 0) { + spdk_trace_record(TRACE_ISCSI_TASK_EXECUTED, 0, 0, (uintptr_t)pdu, 0); + conn->pdu_in_progress = NULL; + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_AWAIT_PDU_READY; + return 1; + } else { + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_ERROR; + } + break; + case ISCSI_PDU_RECV_STATE_ERROR: + return SPDK_ISCSI_CONNECTION_FATAL; + default: + assert(false); + printf("code should not come here\n"); + break; + } + } while (prev_state != conn->pdu_recv_state); + + return 0; +} + +#define GET_PDU_LOOP_COUNT 16 + +static int +fuzz_iscsi_handle_incoming_pdus(struct spdk_iscsi_conn *conn) +{ + int i, rc; + + /* Read new PDUs from network */ + for (i = 0; i < GET_PDU_LOOP_COUNT; i++) { + rc = iscsi_fuzz_read_pdu(conn); + if (rc == 0) { + break; + } else if (rc < 0) { + return rc; + } + } + + return i; +} + +static void +fuzz_iscsi_send_login_request(struct fuzz_iscsi_dev_ctx *dev_ctx, uint8_t session_type) +{ + struct fuzz_iscsi_io_ctx *io_ctx = NULL; + struct spdk_iscsi_pdu *req_pdu; + struct iscsi_bhs_login_req *login_req; + struct spdk_iscsi_conn *conn = dev_ctx->conn; + + req_pdu = iscsi_get_pdu(conn); + req_pdu->writev_offset = 0; + req_pdu->hdigest_valid_bytes = 0; + req_pdu->ahs_valid_bytes = 0; + req_pdu->data_buf_len = 8192; + req_pdu->data = calloc(1, 8192); + assert(req_pdu->data != NULL); + req_pdu->data_segment_len = 0; + + login_req = (struct iscsi_bhs_login_req *)&req_pdu->bhs; + io_ctx = &dev_ctx->io_ctx; + io_ctx->req.login_req = login_req; + io_ctx->req.login_req->version_min = 0; + /* a new session */ + io_ctx->req.login_req->tsih = 0; + + req_pdu->bhs.opcode = ISCSI_OP_LOGIN; + req_pdu->bhs.immediate = 1; + req_pdu->bhs.reserved = 0; + req_pdu->bhs_valid_bytes = ISCSI_BHS_LEN; + req_pdu->bhs.total_ahs_len = 0; + + /* An initiator that chooses to operate without iSCSI security and with + * all the operational parameters taking the default values issues the + * Login with the T bit set to 1, the CSG set to + * LoginOperationalNegotiation, and the NSG set to FullFeaturePhase. + * + * Byte / 0 | 1 | 2 | 3 | + * |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| + * +---------------+---------------+---------------+---------------+ + * 0|.|1| 0x03 |T|C|.|.|CSG|NSG| Version-max | Version-min | + */ + req_pdu->bhs.flags = ISCSI_LOGIN_TRANSIT | (ISCSI_OPERATIONAL_NEGOTIATION_PHASE << 2) | + ISCSI_FULL_FEATURE_PHASE; + + req_pdu->data_segment_len = iscsi_append_text(conn, "InitiatorName", g_init_name, + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "HeaderDigest", "None", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "DataDigest", "None", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "DefaultTime2Wait", "2", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "DefaultTime2Retain", "0", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "IFMarker", "No", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "OFMarker", "No", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "ErrorRecoveryLevel", "0", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + + if (session_type == SESSION_TYPE_DISCOVERY) { + /* Discovery PDU */ + conn->sess->session_type = SESSION_TYPE_DISCOVERY; + req_pdu->data_segment_len = iscsi_append_text(conn, "SessionType", "Discovery", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "MaxRecvDataSegmentLength", "32768", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + } else { + /* Login PDU */ + conn->sess->session_type = SESSION_TYPE_NORMAL; + req_pdu->data_segment_len = iscsi_append_text(conn, "SessionType", "Normal", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "TargetName", g_tgt_name, + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "InitialR2T", "No", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "ImmediateData", "Yes", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "MaxBurstLength", "16776192", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "FirstBurstLength", "262144", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "MaxOutstandingR2T", "1", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "MaxConnections", "1", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "DataPDUInOrder", "Yes", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "DataSequenceInOrder", "Yes", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + req_pdu->data_segment_len = iscsi_append_text(conn, "MaxRecvDataSegmentLength", "262144", + req_pdu->data, req_pdu->data_buf_len, req_pdu->data_segment_len); + } + + DSET24(req_pdu->bhs.data_segment_len, req_pdu->data_segment_len); + iscsi_conn_write_pdu(conn, req_pdu, iscsi_conn_pdu_generic_complete, NULL); +} + +static void +fuzz_iscsi_send_logout_request(struct fuzz_iscsi_dev_ctx *dev_ctx) +{ + struct fuzz_iscsi_io_ctx *io_ctx = NULL; + struct spdk_iscsi_pdu *req_pdu; + struct iscsi_bhs_logout_req *logout_req; + struct spdk_iscsi_conn *conn = dev_ctx->conn; + + conn->is_logged_out = true; + + req_pdu = iscsi_get_pdu(conn); + req_pdu->writev_offset = 0; + req_pdu->hdigest_valid_bytes = 0; + req_pdu->ahs_valid_bytes = 0; + req_pdu->data_buf_len = 0; + + logout_req = (struct iscsi_bhs_logout_req *)&req_pdu->bhs; + io_ctx = &dev_ctx->io_ctx; + io_ctx->req.logout_req = logout_req; + + req_pdu->bhs.opcode = ISCSI_OP_LOGOUT; + req_pdu->bhs.immediate = 1; + req_pdu->bhs.reserved = 0; + req_pdu->bhs_valid_bytes = ISCSI_BHS_LEN; + req_pdu->bhs.total_ahs_len = 0; + req_pdu->bhs.flags = 0; + + DSET24(req_pdu->bhs.data_segment_len, 0); + iscsi_conn_write_pdu(conn, req_pdu, iscsi_conn_pdu_generic_complete, conn); +} + +static void +iscsi_fuzz_conn_reset(struct spdk_iscsi_conn *conn, struct spdk_iscsi_sess *sess) +{ + conn->sess = sess; + conn->data_in_cnt = 0; + conn->params = NULL; + conn->header_digest = true; + conn->data_digest = false; + conn->header_digest = 0; + conn->MaxRecvDataSegmentLength = 8192; + conn->full_feature = 0; + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_AWAIT_PDU_READY; + conn->pdu_in_progress = NULL; + conn->is_logged_out = 0; +} + +static void +iscsi_fuzz_sock_connect(struct spdk_iscsi_conn *conn) +{ + const char *host = g_tgt_ip; + const char *port = g_tgt_port; + char saddr[INET6_ADDRSTRLEN], caddr[INET6_ADDRSTRLEN]; + uint16_t cport, sport; + int rc = 0; + + conn->sock = spdk_sock_connect(host, spdk_strtol(port, 10), NULL); + if (conn->sock == NULL) { + fprintf(stderr, "connect error(%d): %s\n", errno, spdk_strerror(errno)); + spdk_sock_close(&conn->sock); + return; + } + fprintf(stderr, "\nConnecting to the server on %s:%s\n", host, port); + + rc = spdk_sock_getaddr(conn->sock, saddr, sizeof(saddr), &sport, caddr, sizeof(caddr), &cport); + if (rc < 0) { + fprintf(stderr, "Cannot get connection addresses\n"); + spdk_sock_close(&conn->sock); + return; + } + + fprintf(stderr, "Connection accepted from (%s, %hu) to (%s, %hu)\n", caddr, cport, saddr, sport); + +} + +static void +check_successful_op(struct fuzz_iscsi_dev_ctx *dev_ctx, struct fuzz_iscsi_io_ctx *io_ctx) +{ + if (g_is_valid_opcode) { + fprintf(stderr, "Sent a valid opcode PDU.\n"); + dev_ctx->num_valid_pdus++; + } else { + fprintf(stderr, "Sent an invalid opcode PDU.\n"); + } +} + +/* submit requests begin */ +static void +dev_submit_requests(struct fuzz_iscsi_dev_ctx *dev_ctx) +{ + struct fuzz_iscsi_io_ctx *io_ctx = NULL; + uint8_t opcode; + struct spdk_iscsi_pdu *req_pdu; + struct iscsi_bhs *bhs; + struct iscsi_bhs_nop_out *nop_out_req; + struct iscsi_bhs_scsi_req *scsi_req; + struct iscsi_bhs_task_req *task_req; + struct iscsi_bhs_text_req *text_req; + struct iscsi_bhs_data_out *data_out_req; + struct iscsi_bhs_snack_req *snack_req; + unsigned int rand_seed; + bool is_p99; + + g_is_valid_opcode = true; + + /* Random PDU */ + opcode = rand() % 0x3f; + fprintf(stderr, "Random request bhs.opcode of Initiator is 0x%x.\n", opcode); + + if ((opcode == ISCSI_OP_LOGIN) || (opcode == ISCSI_OP_LOGOUT)) { + /* only need send next */ + fprintf(stderr, "LOGIN and LOGOUT opcodes are ignored here.\n"); + return; + } + + req_pdu = iscsi_get_pdu(dev_ctx->conn); + req_pdu->writev_offset = 0; + req_pdu->hdigest_valid_bytes = 0; + req_pdu->ahs_valid_bytes = 0; + req_pdu->data_buf_len = 0; + + dev_ctx->conn->sess->session_type = SESSION_TYPE_NORMAL; + + io_ctx = &dev_ctx->io_ctx; + + switch (opcode) { + case ISCSI_OP_NOPOUT: + nop_out_req = (struct iscsi_bhs_nop_out *)&req_pdu->bhs; + io_ctx->req.nop_out_req = nop_out_req; + break; + case ISCSI_OP_SCSI: + scsi_req = (struct iscsi_bhs_scsi_req *)&req_pdu->bhs; + io_ctx->req.scsi_req = scsi_req; + break; + case ISCSI_OP_TASK: + task_req = (struct iscsi_bhs_task_req *)&req_pdu->bhs; + io_ctx->req.task_req = task_req; + break; + case ISCSI_OP_TEXT: + text_req = (struct iscsi_bhs_text_req *)&req_pdu->bhs; + io_ctx->req.text_req = text_req; + break; + case ISCSI_OP_SCSI_DATAOUT: + data_out_req = (struct iscsi_bhs_data_out *)&req_pdu->bhs; + io_ctx->req.data_out_req = data_out_req; + break; + case ISCSI_OP_SNACK: + snack_req = (struct iscsi_bhs_snack_req *)&req_pdu->bhs; + io_ctx->req.snack_req = snack_req; + break; + default: + bhs = (struct iscsi_bhs *)&req_pdu->bhs; + io_ctx->req.bhs = bhs; + g_is_valid_opcode = false; + break; + } + + prep_iscsi_pdu_bhs_opcode_cmd(dev_ctx, io_ctx); + io_ctx->req.bhs->opcode = opcode; + req_pdu->bhs.opcode = opcode; + req_pdu->bhs.immediate = 1; + req_pdu->bhs.reserved = 0; + req_pdu->bhs_valid_bytes = ISCSI_BHS_LEN; + req_pdu->bhs.total_ahs_len = 0; + req_pdu->bhs.stat_sn = 0; + DSET24(req_pdu->bhs.data_segment_len, 0); + + if (opcode <= ISCSI_OP_TEXT) { + rand_seed = time(NULL); + is_p99 = rand_r(&rand_seed) % 100 == 0 ? true : false; + if (!is_p99) { /* Remaining 1% */ + switch (opcode) { + case ISCSI_OP_NOPOUT: + if (req_pdu->bhs.immediate) { + io_ctx->req.nop_out_req->cmd_sn = dev_ctx->current_cmd_sn; + } else { + io_ctx->req.nop_out_req->cmd_sn = dev_ctx->current_cmd_sn++; + } + break; + case ISCSI_OP_SCSI: + if (req_pdu->bhs.immediate) { + io_ctx->req.scsi_req->cmd_sn = dev_ctx->current_cmd_sn; + } else { + io_ctx->req.scsi_req->cmd_sn = dev_ctx->current_cmd_sn++; + } + break; + case ISCSI_OP_TASK: + if (req_pdu->bhs.immediate) { + io_ctx->req.task_req->cmd_sn = dev_ctx->current_cmd_sn; + } else { + io_ctx->req.task_req->cmd_sn = dev_ctx->current_cmd_sn++; + } + break; + case ISCSI_OP_TEXT: + if (req_pdu->bhs.immediate) { + io_ctx->req.text_req->cmd_sn = dev_ctx->current_cmd_sn; + } else { + io_ctx->req.text_req->cmd_sn = dev_ctx->current_cmd_sn++; + } + break; + default: + break; + } + } + } + + if (opcode == ISCSI_OP_SCSI) { + /* avoid ((R_bit != 0) && (W_bit != 0)) is true */ + io_ctx->req.scsi_req->read_bit = 0; + io_ctx->req.scsi_req->write_bit = 0; + } + + if (opcode == ISCSI_OP_TEXT) { + /* avoid: (F_bit && C_bit) is true */ + io_ctx->req.text_req->flags = 0; + /* avoid: correct itt is not equal to the current itt */ + io_ctx->req.text_req->itt = 0; + } + + fprintf(stderr, "Dumping this request bhs contents now.\n"); + print_req_obj(dev_ctx, io_ctx); + + check_successful_op(dev_ctx, io_ctx); + dev_ctx->num_sent_pdus++; + + iscsi_conn_write_pdu(dev_ctx->conn, req_pdu, + iscsi_conn_pdu_generic_complete, NULL); +} +/* submit requests end */ + +static int +poll_dev(void *ctx) +{ + struct fuzz_iscsi_dev_ctx *dev_ctx = ctx; + struct spdk_iscsi_conn *conn = dev_ctx->conn; + uint64_t current_ticks; + struct spdk_iscsi_pdu *pdu, *tmp; + + current_ticks = spdk_get_ticks(); + if (current_ticks > g_runtime_ticks) { + g_run = false; + } + + if (!g_run) { + /* Logout PDU */ + fuzz_iscsi_send_logout_request(dev_ctx); + fuzz_iscsi_handle_incoming_pdus(conn); + + TAILQ_FOREACH_SAFE(pdu, &g_get_pdu_list, tailq, tmp) { + TAILQ_REMOVE(&g_get_pdu_list, pdu, tailq); + iscsi_put_pdu(pdu); + } + + spdk_sock_close(&conn->sock); + + TAILQ_FOREACH_SAFE(pdu, &conn->write_pdu_list, tailq, tmp) { + TAILQ_REMOVE(&conn->write_pdu_list, pdu, tailq); + iscsi_put_pdu(pdu); + } + + free(conn); + + spdk_poller_unregister(&dev_ctx->poller); + __sync_sub_and_fetch(&g_num_active_threads, 1); + + return -1; + } + + if (conn->is_logged_out) { + spdk_sock_close(&conn->sock); + iscsi_fuzz_conn_reset(conn, &dev_ctx->sess); + iscsi_fuzz_sock_connect(conn); + usleep(1000); + + /* Login PDU */ + fuzz_iscsi_send_login_request(dev_ctx, SESSION_TYPE_NORMAL); + } else if (conn->full_feature == 1) { + dev_submit_requests(dev_ctx); + } + + spdk_sock_flush(conn->sock); + + fuzz_iscsi_handle_incoming_pdus(conn); + + return 0; +} + +static void +start_io(void *ctx) +{ + struct fuzz_iscsi_dev_ctx *dev_ctx = ctx; + + dev_ctx->sess.ExpCmdSN = 0; + dev_ctx->sess.MaxCmdSN = 64; + dev_ctx->sess.MaxBurstLength = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + dev_ctx->sess.MaxOutstandingR2T = 1; + dev_ctx->sess.tag = 1; + dev_ctx->sess.tsih = 256; + + dev_ctx->conn = calloc(1, sizeof(*dev_ctx->conn)); + assert(dev_ctx->conn != NULL); + TAILQ_INIT(&dev_ctx->conn->write_pdu_list); + + iscsi_fuzz_conn_reset(dev_ctx->conn, &dev_ctx->sess); + iscsi_fuzz_sock_connect(dev_ctx->conn); + usleep(1000); + + /* Login PDU */ + fuzz_iscsi_send_login_request(dev_ctx, SESSION_TYPE_NORMAL); + + if (g_random_seed) { + dev_ctx->random_seed = g_random_seed; + } else { + dev_ctx->random_seed = spdk_get_ticks(); + } + + dev_ctx->poller = SPDK_POLLER_REGISTER(poll_dev, dev_ctx, 0); + if (dev_ctx->poller == NULL) { + return; + } +} + +static int +check_app_completion(void *ctx) +{ + if (g_num_active_threads == 0) { + spdk_poller_unregister(&g_app_completion_poller); + printf("Fuzzing completed. Shutting down the fuzz application.\n\n"); + cleanup(); + spdk_app_stop(0); + } + return 0; +} + +static void +begin_iscsi_fuzz(void *ctx) +{ + struct fuzz_iscsi_dev_ctx *dev_ctx; + int rc; + + g_runtime_ticks = spdk_get_ticks() + g_runtime * spdk_get_ticks_hz(); + + g_valid_buffer = spdk_malloc(0x1000, 0x200, NULL, SPDK_ENV_LCORE_ID_ANY, SPDK_MALLOC_SHARE); + if (g_valid_buffer == NULL) { + fprintf(stderr, "Failed to allocate a valid buffer for random PDUs\n"); + goto out; + } + + rc = fuzz_iscsi_dev_init(); + if (rc) { + fprintf(stderr, "fuzz_iscsi_dev_init() failed.\n"); + goto out; + } + + TAILQ_FOREACH(dev_ctx, &g_dev_list, link) { + assert(dev_ctx->thread != NULL); + spdk_thread_send_msg(dev_ctx->thread, start_io, dev_ctx); + __sync_add_and_fetch(&g_num_active_threads, 1); + } + + g_app_completion_poller = SPDK_POLLER_REGISTER(check_app_completion, NULL, 1000000); + if (g_app_completion_poller == NULL) { + fprintf(stderr, "Failed to register a poller for test completion checking.\n"); + goto out; + } + + return; +out: + cleanup(); + spdk_app_stop(0); +} + +static void +iscsi_fuzz_usage(void) +{ + fprintf(stderr, " -T <path> iSCSI Target IP address.\n"); + fprintf(stderr, " -S <integer> Seed value for test.\n"); + fprintf(stderr, + " -t <integer> Time in seconds to run the fuzz test. Only valid if -j is not specified.\n"); +} + +static int +iscsi_fuzz_parse(int ch, char *arg) +{ + int64_t error_test; + + switch (ch) { + case 'T': + g_tgt_ip = optarg; + break; + case 'S': + error_test = spdk_strtol(arg, 10); + if (error_test < 0) { + fprintf(stderr, "Invalid value supplied for the random seed.\n"); + return -1; + } else { + g_random_seed = error_test; + } + break; + case 't': + g_runtime = spdk_strtol(optarg, 10); + if (g_runtime <= 0 || g_runtime > MAX_RUNTIME_S) { + fprintf(stderr, "You must supply a positive runtime value less than %d.\n", MAX_RUNTIME_S); + return -1; + } + break; + case '?': + default: + iscsi_fuzz_usage(); + return -EINVAL; + } + + return 0; +} + +int +main(int argc, char **argv) +{ + struct spdk_app_opts opts = {}; + int rc; + + g_runtime = DEFAULT_RUNTIME; + srand((unsigned)time(0)); + + TAILQ_INIT(&g_get_pdu_list); + + spdk_app_opts_init(&opts); + opts.name = "iscsi_fuzz"; + + if ((rc = spdk_app_parse_args(argc, argv, &opts, "T:S:t:", NULL, iscsi_fuzz_parse, + iscsi_fuzz_usage) != SPDK_APP_PARSE_ARGS_SUCCESS)) { + return rc; + } + + rc = spdk_app_start(&opts, begin_iscsi_fuzz, NULL); + + return rc; +} diff --git a/src/spdk/test/app/fuzz/nvme_fuzz/.gitignore b/src/spdk/test/app/fuzz/nvme_fuzz/.gitignore new file mode 100644 index 000000000..801146458 --- /dev/null +++ b/src/spdk/test/app/fuzz/nvme_fuzz/.gitignore @@ -0,0 +1 @@ +nvme_fuzz diff --git a/src/spdk/test/app/fuzz/nvme_fuzz/Makefile b/src/spdk/test/app/fuzz/nvme_fuzz/Makefile new file mode 100644 index 000000000..b7ad5e172 --- /dev/null +++ b/src/spdk/test/app/fuzz/nvme_fuzz/Makefile @@ -0,0 +1,49 @@ +# +# 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 = nvme_fuzz + +CFLAGS += -I$(SPDK_ROOT_DIR)/test/app/fuzz/common + +C_SRCS := nvme_fuzz.c + +SPDK_LIB_LIST += $(SOCK_MODULES_LIST) +SPDK_LIB_LIST += conf event json jsonrpc log nvme rpc sock thread trace util + +ifeq ($(CONFIG_RDMA),y) +SPDK_LIB_LIST += rdma +endif + +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk diff --git a/src/spdk/test/app/fuzz/nvme_fuzz/README.md b/src/spdk/test/app/fuzz/nvme_fuzz/README.md new file mode 100644 index 000000000..2f188b5b8 --- /dev/null +++ b/src/spdk/test/app/fuzz/nvme_fuzz/README.md @@ -0,0 +1,52 @@ +# Overview + +This application is intended to fuzz test the NVMe-oF target or a physical NVMe drive by +submitting randomized NVMe commands through the SPDK NVMe initiator. Both local and remote +drives are configured through a .ini style config file (See the -C option on the application). +Multiple controllers and namespaces can be exposed to the fuzzer at a time. In order to +handle multiple namespaces, the fuzzer will round robin assign a thread to each namespace and +submit commands to that thread at a set queue depth. (currently 128 for I/O, 16 for Admin). The +application will terminate under three conditions: + +1. The user specified run time expires (see the -t flag). +2. One of the target controllers stops completing I/O operations back to the fuzzer i.e. controller timeout. +3. The user specified a json file containing operations to run and the fuzzer has received valid completions for all of them. + +# Output + +By default, the fuzzer will print commands that: + +1. Complete successfully back from the target, or +2. Are outstanding at the time of a controller timeout. + +Commands are dumped as named objects in json format which can then be supplied back to the +script for targeted debugging on a subsequent run. See `Debugging` below. +By default no output is generated when a specific command is returned with a failed status. +This can be overridden with the -V flag. if -V is specified, each command will be dumped as +it is completed in the JSON format specified above. +At the end of each test run, a summary is printed for each namespace in the following format: + +~~~ +NS: 0x200079262300 admin qp, Total commands completed: 462459, total successful commands: 1960, random_seed: 4276918833 +~~~ + +# Debugging + +If a controller hangs when processing I/O generated by the fuzzer, the fuzzer will stop +submitting I/O and dump out all outstanding I/O on the qpair that timed out. The I/O are +dumped as valid json. You can combine the dumped commands from the fuzzer into a json +array in a file and then pass them to the fuzzer using the -j option. Please see the +example.json file in this directory for an example of a properly formed array of command +structures. + +Please note that you can also craft your own custom command values by using the output +from the fuzzer as a template. + +# JSON Format + +Most of the variables in the spdk_nvme_cmd structure are represented as numbers in JSON. +The only exception to this rule is the dptr union. This is a 16 byte union structure that +is represented as a base64 string. If writing custom commands for input, please note this +distinction or the application will be unable to load your custom input. + +Happy Fuzzing! diff --git a/src/spdk/test/app/fuzz/nvme_fuzz/example.json b/src/spdk/test/app/fuzz/nvme_fuzz/example.json new file mode 100644 index 000000000..95540746e --- /dev/null +++ b/src/spdk/test/app/fuzz/nvme_fuzz/example.json @@ -0,0 +1,290 @@ +{ +"struct spdk_nvme_cmd": { + "opc": 7, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 24, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 43, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 12, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 7, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 24, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 43, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 12, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 7, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 24, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 43, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 12, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 7, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 24, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 43, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + }, + "struct spdk_nvme_cmd": { + "opc": 12, + "fuse": 1, + "rsvd1": 13, + "psdt": 1, + "cid": 56732, + "nsid": 1, + "rsvd2": 1516848792, + "rsvd3": 1233945838, + "mptr": 3452736735, + "dptr": "FHENPcH+xM0tioD+0SrNrQ==", + "cdw10": 3190735246, + "cdw11": 2629178873, + "cdw12": 138580308, + "cdw13": 1603605200, + "cdw14": 3031880384, + "cdw15": 644909208 + } +} diff --git a/src/spdk/test/app/fuzz/nvme_fuzz/nvme_fuzz.c b/src/spdk/test/app/fuzz/nvme_fuzz/nvme_fuzz.c new file mode 100644 index 000000000..127bc1bff --- /dev/null +++ b/src/spdk/test/app/fuzz/nvme_fuzz/nvme_fuzz.c @@ -0,0 +1,931 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. All rights reserved. + * Copyright (c) 2019 Mellanox Technologies LTD. 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" +#include "spdk/json.h" +#include "fuzz_common.h" + +#define UNIQUE_OPCODES 256 + +const char g_nvme_cmd_json_name[] = "struct spdk_nvme_cmd"; +char *g_conf_file; +char *g_json_file = NULL; +uint64_t g_runtime_ticks; +unsigned int g_seed_value = 0; +int g_runtime; + +int g_num_active_threads = 0; +uint32_t g_admin_depth = 16; +uint32_t g_io_depth = 128; + +bool g_valid_ns_only = false; +bool g_verbose_mode = false; +bool g_run_admin_commands = false; +bool g_run; + +struct spdk_poller *g_app_completion_poller; +bool g_successful_io_opcodes[UNIQUE_OPCODES] = {0}; +bool g_successful_admin_opcodes[UNIQUE_OPCODES] = {0}; + +struct spdk_nvme_cmd *g_cmd_array; +size_t g_cmd_array_size; + +/* I need context objects here because I need to keep track of all I/O that are in flight. */ +struct nvme_fuzz_request { + struct spdk_nvme_cmd cmd; + struct nvme_fuzz_qp *qp; + TAILQ_ENTRY(nvme_fuzz_request) link; +}; + +struct nvme_fuzz_trid { + struct spdk_nvme_transport_id trid; + TAILQ_ENTRY(nvme_fuzz_trid) tailq; +}; + +struct nvme_fuzz_ctrlr { + struct spdk_nvme_ctrlr *ctrlr; + TAILQ_ENTRY(nvme_fuzz_ctrlr) tailq; +}; + +struct nvme_fuzz_qp { + struct spdk_nvme_qpair *qpair; + /* array of context objects equal in length to the queue depth */ + struct nvme_fuzz_request *req_ctx; + TAILQ_HEAD(, nvme_fuzz_request) free_ctx_objs; + TAILQ_HEAD(, nvme_fuzz_request) outstanding_ctx_objs; + unsigned int random_seed; + uint64_t completed_cmd_counter; + uint64_t submitted_cmd_counter; + uint64_t successful_completed_cmd_counter; + uint64_t timeout_tsc; + uint32_t num_cmds_outstanding; + bool timed_out; + bool is_admin; +}; + +struct nvme_fuzz_ns { + struct spdk_nvme_ns *ns; + struct spdk_nvme_ctrlr *ctrlr; + struct spdk_thread *thread; + struct spdk_poller *req_poller; + struct nvme_fuzz_qp io_qp; + struct nvme_fuzz_qp a_qp; + uint32_t nsid; + TAILQ_ENTRY(nvme_fuzz_ns) tailq; +}; + +static TAILQ_HEAD(, nvme_fuzz_ns) g_ns_list = TAILQ_HEAD_INITIALIZER(g_ns_list); +static TAILQ_HEAD(, nvme_fuzz_ctrlr) g_ctrlr_list = TAILQ_HEAD_INITIALIZER(g_ctrlr_list); +static TAILQ_HEAD(, nvme_fuzz_trid) g_trid_list = TAILQ_HEAD_INITIALIZER(g_trid_list); + +static bool +parse_nvme_cmd_obj(void *item, struct spdk_json_val *value, size_t num_values) +{ + struct spdk_nvme_cmd *cmd = item; + struct spdk_json_val *next_val; + uint64_t tmp_val; + size_t i = 0; + + while (i < num_values) { + if (value->type == SPDK_JSON_VAL_NAME) { + next_val = value + 1; + if (!strncmp(value->start, "opc", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UNSIGNED_8BIT_MAX, &tmp_val)) { + goto invalid; + } + cmd->opc = tmp_val; + } + } else if (!strncmp(value->start, "fuse", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UNSIGNED_2BIT_MAX, &tmp_val)) { + goto invalid; + } + cmd->fuse = tmp_val; + } + } else if (!strncmp(value->start, "rsvd1", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UNSIGNED_4BIT_MAX, &tmp_val)) { + goto invalid; + } + cmd->rsvd1 = tmp_val; + } + } else if (!strncmp(value->start, "psdt", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UNSIGNED_2BIT_MAX, &tmp_val)) { + goto invalid; + } + cmd->psdt = tmp_val; + } + } else if (!strncmp(value->start, "cid", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UINT16_MAX, &tmp_val)) { + goto invalid; + } + cmd->cid = tmp_val; + } + } else if (!strncmp(value->start, "nsid", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UINT32_MAX, &tmp_val)) { + goto invalid; + } + cmd->nsid = tmp_val; + } + } else if (!strncmp(value->start, "rsvd2", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UINT32_MAX, &tmp_val)) { + goto invalid; + } + cmd->rsvd2 = tmp_val; + } + } else if (!strncmp(value->start, "rsvd3", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UINT32_MAX, &tmp_val)) { + goto invalid; + } + cmd->rsvd3 = tmp_val; + } + } else if (!strncmp(value->start, "mptr", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UINT64_MAX, &tmp_val)) { + goto invalid; + } + cmd->mptr = tmp_val; + } + } else if (!strncmp(value->start, "dptr", value->len)) { + if (next_val->type == SPDK_JSON_VAL_STRING) { + if (fuzz_get_base_64_buffer_value(&cmd->dptr, sizeof(cmd->dptr), (char *)next_val->start, + next_val->len)) { + goto invalid; + } + } + } else if (!strncmp(value->start, "cdw10", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UINT32_MAX, &tmp_val)) { + goto invalid; + } + cmd->cdw10 = tmp_val; + } + } else if (!strncmp(value->start, "cdw11", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UINT32_MAX, &tmp_val)) { + goto invalid; + } + cmd->cdw11 = tmp_val; + } + } else if (!strncmp(value->start, "cdw12", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UINT32_MAX, &tmp_val)) { + goto invalid; + } + cmd->cdw12 = tmp_val; + } + } else if (!strncmp(value->start, "cdw13", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UINT32_MAX, &tmp_val)) { + goto invalid; + } + cmd->cdw13 = tmp_val; + } + } else if (!strncmp(value->start, "cdw14", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UINT32_MAX, &tmp_val)) { + goto invalid; + } + cmd->cdw14 = tmp_val; + } + } else if (!strncmp(value->start, "cdw15", value->len)) { + if (next_val->type == SPDK_JSON_VAL_NUMBER) { + if (fuzz_parse_json_num(next_val, UINT32_MAX, &tmp_val)) { + goto invalid; + } + cmd->cdw15 = tmp_val; + } + } + } + i++; + value++; + } + return true; + +invalid: + fprintf(stderr, "Invalid value supplied for cmd->%.*s: %.*s\n", value->len, (char *)value->start, + next_val->len, (char *)next_val->start); + return false; +} + +static void +report_successful_opcodes(bool *array, int length) +{ + int i; + + for (i = 0; i < length; i++) { + if (array[i] == true) { + printf("%d, ", i); + } + } + printf("\n"); +} + +static int +print_nvme_cmd(void *cb_ctx, const void *data, size_t size) +{ + fprintf(stderr, "%s\n", (const char *)data); + return 0; +} + +static void +json_dump_nvme_cmd(struct spdk_nvme_cmd *cmd) +{ + struct spdk_json_write_ctx *w; + char *dptr_value; + + dptr_value = fuzz_get_value_base_64_buffer(&cmd->dptr, sizeof(cmd->dptr)); + if (dptr_value == NULL) { + fprintf(stderr, "Unable to allocate buffer context for printing command.\n"); + return; + } + + w = spdk_json_write_begin(print_nvme_cmd, cmd, SPDK_JSON_WRITE_FLAG_FORMATTED); + if (w == NULL) { + fprintf(stderr, "Unable to allocate json context for printing command.\n"); + free(dptr_value); + return; + } + + spdk_json_write_named_object_begin(w, g_nvme_cmd_json_name); + spdk_json_write_named_uint32(w, "opc", cmd->opc); + spdk_json_write_named_uint32(w, "fuse", cmd->fuse); + spdk_json_write_named_uint32(w, "rsvd1", cmd->rsvd1); + spdk_json_write_named_uint32(w, "psdt", cmd->psdt); + spdk_json_write_named_uint32(w, "cid", cmd->cid); + spdk_json_write_named_uint32(w, "nsid", cmd->nsid); + spdk_json_write_named_uint32(w, "rsvd2", cmd->rsvd2); + spdk_json_write_named_uint32(w, "rsvd3", cmd->rsvd3); + spdk_json_write_named_uint32(w, "mptr", cmd->mptr); + spdk_json_write_named_string(w, "dptr", dptr_value); + spdk_json_write_named_uint32(w, "cdw10", cmd->cdw10); + spdk_json_write_named_uint32(w, "cdw11", cmd->cdw11); + spdk_json_write_named_uint32(w, "cdw12", cmd->cdw12); + spdk_json_write_named_uint32(w, "cdw13", cmd->cdw13); + spdk_json_write_named_uint32(w, "cdw14", cmd->cdw14); + spdk_json_write_named_uint32(w, "cdw15", cmd->cdw15); + spdk_json_write_object_end(w); + + free(dptr_value); + spdk_json_write_end(w); +} + +static void +json_dump_nvme_cmd_list(struct nvme_fuzz_qp *qp) +{ + struct nvme_fuzz_request *ctx; + + TAILQ_FOREACH(ctx, &qp->outstanding_ctx_objs, link) { + json_dump_nvme_cmd(&ctx->cmd); + } +} + +static void +handle_timeout(struct nvme_fuzz_qp *qp, bool is_admin) +{ + fprintf(stderr, "An %s queue has timed out. Dumping all outstanding commands from that queue\n", + is_admin ? "Admin" : "I/O"); + json_dump_nvme_cmd_list(qp); + qp->timed_out = true; +} + +static void submit_ns_cmds(struct nvme_fuzz_ns *ns_entry); + +static void +nvme_fuzz_cpl_cb(void *cb_arg, const struct spdk_nvme_cpl *cpl) +{ + struct nvme_fuzz_request *ctx = cb_arg; + struct nvme_fuzz_qp *qp = ctx->qp; + + qp->completed_cmd_counter++; + if (spdk_unlikely(cpl->status.sc == SPDK_NVME_SC_SUCCESS)) { + fprintf(stderr, "The following %s command (command num %lu) completed successfully\n", + qp->is_admin ? "Admin" : "I/O", qp->completed_cmd_counter); + qp->successful_completed_cmd_counter++; + json_dump_nvme_cmd(&ctx->cmd); + + if (qp->is_admin) { + __sync_bool_compare_and_swap(&g_successful_admin_opcodes[ctx->cmd.opc], false, true); + } else { + __sync_bool_compare_and_swap(&g_successful_io_opcodes[ctx->cmd.opc], false, true); + } + } else if (g_verbose_mode == true) { + fprintf(stderr, "The following %s command (command num %lu) failed as expected.\n", + qp->is_admin ? "Admin" : "I/O", qp->completed_cmd_counter); + json_dump_nvme_cmd(&ctx->cmd); + } + + qp->timeout_tsc = fuzz_refresh_timeout(); + TAILQ_REMOVE(&qp->outstanding_ctx_objs, ctx, link); + TAILQ_INSERT_HEAD(&qp->free_ctx_objs, ctx, link); + assert(qp->num_cmds_outstanding > 0); + qp->num_cmds_outstanding--; +} + +static int +poll_for_completions(void *arg) +{ + struct nvme_fuzz_ns *ns_entry = arg; + uint64_t current_ticks = spdk_get_ticks(); + uint64_t *counter; + if (!ns_entry->io_qp.timed_out) { + spdk_nvme_qpair_process_completions(ns_entry->io_qp.qpair, 0); + /* SAlways have to process admin completions for the purposes of keep alive. */ + spdk_nvme_ctrlr_process_admin_completions(ns_entry->ctrlr); + } + + if (g_cmd_array) { + if (g_run_admin_commands) { + counter = &ns_entry->a_qp.submitted_cmd_counter; + } else { + counter = &ns_entry->io_qp.submitted_cmd_counter; + } + + if (*counter >= g_cmd_array_size) { + g_run = false; + } + } else { + if (current_ticks > g_runtime_ticks) { + g_run = false; + } + } + + if (ns_entry->a_qp.timeout_tsc < current_ticks && !ns_entry->a_qp.timed_out && + ns_entry->a_qp.num_cmds_outstanding > 0) { + handle_timeout(&ns_entry->a_qp, true); + } + + if (ns_entry->io_qp.timeout_tsc < current_ticks && !ns_entry->io_qp.timed_out && + ns_entry->io_qp.num_cmds_outstanding > 0) { + handle_timeout(&ns_entry->io_qp, false); + } + + submit_ns_cmds(ns_entry); + + if (g_run) { + return 0; + } + /* + * We either processed all I/O properly and can shut down normally, or we + * had a qp time out and we need to exit without reducing the values to 0. + */ + if (ns_entry->io_qp.num_cmds_outstanding == 0 && + ns_entry->a_qp.num_cmds_outstanding == 0) { + goto exit_handler; + } else if (ns_entry->io_qp.timed_out && (!g_run_admin_commands || ns_entry->a_qp.timed_out)) { + goto exit_handler; + } else { + return 0; + } + +exit_handler: + spdk_poller_unregister(&ns_entry->req_poller); + __sync_sub_and_fetch(&g_num_active_threads, 1); + spdk_thread_exit(ns_entry->thread); + return 0; +} + +static void +prep_nvme_cmd(struct nvme_fuzz_ns *ns_entry, struct nvme_fuzz_qp *qp, struct nvme_fuzz_request *ctx) +{ + if (g_cmd_array) { + memcpy(&ctx->cmd, &g_cmd_array[qp->submitted_cmd_counter], sizeof(ctx->cmd)); + } else { + fuzz_fill_random_bytes((char *)&ctx->cmd, sizeof(ctx->cmd), &qp->random_seed); + + if (g_valid_ns_only) { + ctx->cmd.nsid = ns_entry->nsid; + } + } +} + +static int +submit_qp_cmds(struct nvme_fuzz_ns *ns, struct nvme_fuzz_qp *qp) +{ + struct nvme_fuzz_request *ctx; + int rc; + + if (qp->timed_out) { + return 0; + } + /* If we are reading from an array, we need to stop after the last one. */ + while ((qp->submitted_cmd_counter < g_cmd_array_size || g_cmd_array_size == 0) && + !TAILQ_EMPTY(&qp->free_ctx_objs)) { + ctx = TAILQ_FIRST(&qp->free_ctx_objs); + do { + prep_nvme_cmd(ns, qp, ctx); + } while (qp->is_admin && ctx->cmd.opc == SPDK_NVME_OPC_ASYNC_EVENT_REQUEST); + + TAILQ_REMOVE(&qp->free_ctx_objs, ctx, link); + TAILQ_INSERT_HEAD(&qp->outstanding_ctx_objs, ctx, link); + qp->num_cmds_outstanding++; + qp->submitted_cmd_counter++; + if (qp->is_admin) { + rc = spdk_nvme_ctrlr_cmd_admin_raw(ns->ctrlr, &ctx->cmd, NULL, 0, nvme_fuzz_cpl_cb, ctx); + } else { + rc = spdk_nvme_ctrlr_cmd_io_raw(ns->ctrlr, qp->qpair, &ctx->cmd, NULL, 0, nvme_fuzz_cpl_cb, ctx); + } + if (rc) { + return rc; + } + } + return 0; +} + +static void +submit_ns_cmds(struct nvme_fuzz_ns *ns_entry) +{ + int rc; + + if (!g_run) { + return; + } + + if (g_run_admin_commands) { + rc = submit_qp_cmds(ns_entry, &ns_entry->a_qp); + if (rc) { + goto err_exit; + } + } + + if (g_cmd_array == NULL || !g_run_admin_commands) { + rc = submit_qp_cmds(ns_entry, &ns_entry->io_qp); + } +err_exit: + if (rc) { + /* + * I see the prospect of having a broken qpair on one ns as interesting + * enough to recommend stopping the application. + */ + fprintf(stderr, "Unable to submit command with rc %d\n", rc); + g_run = false; + } +} + +static void +free_namespaces(void) +{ + struct nvme_fuzz_ns *ns, *tmp; + + TAILQ_FOREACH_SAFE(ns, &g_ns_list, tailq, tmp) { + printf("NS: %p I/O qp, Total commands completed: %lu, total successful commands: %lu, random_seed: %u\n", + ns->ns, + ns->io_qp.completed_cmd_counter, ns->io_qp.successful_completed_cmd_counter, ns->io_qp.random_seed); + printf("NS: %p admin qp, Total commands completed: %lu, total successful commands: %lu, random_seed: %u\n", + ns->ns, + ns->a_qp.completed_cmd_counter, ns->a_qp.successful_completed_cmd_counter, ns->a_qp.random_seed); + + TAILQ_REMOVE(&g_ns_list, ns, tailq); + if (ns->io_qp.qpair) { + spdk_nvme_ctrlr_free_io_qpair(ns->io_qp.qpair); + } + if (ns->io_qp.req_ctx) { + free(ns->io_qp.req_ctx); + } + if (ns->a_qp.req_ctx) { + free(ns->a_qp.req_ctx); + } + free(ns); + } +} + +static void +free_controllers(void) +{ + struct nvme_fuzz_ctrlr *ctrlr, *tmp; + + TAILQ_FOREACH_SAFE(ctrlr, &g_ctrlr_list, tailq, tmp) { + TAILQ_REMOVE(&g_ctrlr_list, ctrlr, tailq); + spdk_nvme_detach(ctrlr->ctrlr); + free(ctrlr); + } +} + +static void +free_trids(void) +{ + struct nvme_fuzz_trid *trid, *tmp; + + TAILQ_FOREACH_SAFE(trid, &g_trid_list, tailq, tmp) { + TAILQ_REMOVE(&g_trid_list, trid, tailq); + free(trid); + } +} + +static void +register_ns(struct spdk_nvme_ctrlr *ctrlr, struct spdk_nvme_ns *ns, uint32_t nsid) +{ + struct nvme_fuzz_ns *ns_entry; + + ns_entry = calloc(1, sizeof(struct nvme_fuzz_ns)); + if (ns_entry == NULL) { + fprintf(stderr, "Unable to allocate an entry for a namespace\n"); + return; + } + + ns_entry->ns = ns; + ns_entry->ctrlr = ctrlr; + ns_entry->nsid = nsid; + + TAILQ_INIT(&ns_entry->io_qp.free_ctx_objs); + TAILQ_INIT(&ns_entry->io_qp.outstanding_ctx_objs); + if (g_run_admin_commands) { + ns_entry->a_qp.qpair = NULL; + TAILQ_INIT(&ns_entry->a_qp.free_ctx_objs); + TAILQ_INIT(&ns_entry->a_qp.outstanding_ctx_objs); + } + TAILQ_INSERT_TAIL(&g_ns_list, ns_entry, tailq); +} + +static void +register_ctrlr(struct spdk_nvme_ctrlr *ctrlr) +{ + struct nvme_fuzz_ctrlr *ctrlr_entry; + uint32_t nsid; + struct spdk_nvme_ns *ns; + + ctrlr_entry = calloc(1, sizeof(struct nvme_fuzz_ctrlr)); + if (ctrlr_entry == NULL) { + fprintf(stderr, "Unable to allocate an entry for a controller\n"); + return; + } + + ctrlr_entry->ctrlr = ctrlr; + TAILQ_INSERT_TAIL(&g_ctrlr_list, ctrlr_entry, tailq); + + for (nsid = spdk_nvme_ctrlr_get_first_active_ns(ctrlr); nsid != 0; + nsid = spdk_nvme_ctrlr_get_next_active_ns(ctrlr, nsid)) { + ns = spdk_nvme_ctrlr_get_ns(ctrlr, nsid); + if (ns == NULL) { + continue; + } + register_ns(ctrlr, ns, nsid); + } +} + +static void +attach_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid, + struct spdk_nvme_ctrlr *ctrlr, const struct spdk_nvme_ctrlr_opts *opts) +{ + register_ctrlr(ctrlr); +} + +static bool +probe_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid, struct spdk_nvme_ctrlr_opts *opts) +{ + printf("Controller trtype %s\ttraddr %s\n", spdk_nvme_transport_id_trtype_str(trid->trtype), + trid->traddr); + + return true; +} + +static int +prep_qpair(struct nvme_fuzz_ns *ns, struct nvme_fuzz_qp *qp, uint32_t max_qdepth) +{ + uint32_t i; + + /* ensure that each qpair gets a unique random seed for maximum command dispersion. */ + + if (g_seed_value != 0) { + qp->random_seed = g_seed_value; + } else { + /* Take the low 32 bits of spdk_get_ticks. This should be more granular than time(). */ + qp->random_seed = spdk_get_ticks(); + } + + qp->timeout_tsc = fuzz_refresh_timeout(); + + qp->req_ctx = calloc(max_qdepth, sizeof(struct nvme_fuzz_request)); + if (qp->req_ctx == NULL) { + fprintf(stderr, "Unable to allocate I/O contexts for I/O qpair.\n"); + return -1; + } + + for (i = 0; i < max_qdepth; i++) { + qp->req_ctx[i].qp = qp; + TAILQ_INSERT_HEAD(&qp->free_ctx_objs, &qp->req_ctx[i], link); + } + + return 0; +} + +static int +prepare_qpairs(void) +{ + struct spdk_nvme_io_qpair_opts opts; + struct nvme_fuzz_ns *ns_entry; + + TAILQ_FOREACH(ns_entry, &g_ns_list, tailq) { + spdk_nvme_ctrlr_get_default_io_qpair_opts(ns_entry->ctrlr, &opts, sizeof(opts)); + ns_entry->io_qp.qpair = spdk_nvme_ctrlr_alloc_io_qpair(ns_entry->ctrlr, &opts, sizeof(opts)); + if (ns_entry->io_qp.qpair == NULL) { + fprintf(stderr, "Unable to create a qpair for a namespace\n"); + return -1; + } + + ns_entry->io_qp.is_admin = false; + if (prep_qpair(ns_entry, &ns_entry->io_qp, g_io_depth) != 0) { + fprintf(stderr, "Unable to allocate request contexts for I/O qpair.\n"); + return -1; + } + + if (g_run_admin_commands) { + ns_entry->a_qp.is_admin = true; + if (prep_qpair(ns_entry, &ns_entry->a_qp, g_admin_depth) != 0) { + fprintf(stderr, "Unable to allocate request contexts for admin qpair.\n"); + return -1; + } + } + } + return 0; +} + +static void +start_ns_poller(void *ctx) +{ + struct nvme_fuzz_ns *ns_entry = ctx; + + ns_entry->req_poller = SPDK_POLLER_REGISTER(poll_for_completions, ns_entry, 0); + submit_ns_cmds(ns_entry); +} + +static int +check_app_completion(void *ctx) +{ + + if (g_num_active_threads <= 0) { + spdk_poller_unregister(&g_app_completion_poller); + if (g_cmd_array) { + free(g_cmd_array); + } + printf("Fuzzing completed. Shutting down the fuzz application\n\n"); + printf("Dumping successful admin opcodes:\n"); + report_successful_opcodes(g_successful_admin_opcodes, UNIQUE_OPCODES); + printf("Dumping successful io opcodes:\n"); + report_successful_opcodes(g_successful_io_opcodes, UNIQUE_OPCODES); + free_namespaces(); + free_controllers(); + free_trids(); + spdk_app_stop(0); + } + return 0; +} + +static void +begin_fuzz(void *ctx) +{ + struct nvme_fuzz_ns *ns_entry; + struct nvme_fuzz_trid *trid; + int rc; + + if (!spdk_iommu_is_enabled()) { + /* Don't set rc to an error code here. We don't want to fail an automated test based on this. */ + fprintf(stderr, "The IOMMU must be enabled to run this program to avoid unsafe memory accesses.\n"); + rc = 0; + goto out; + } + + TAILQ_FOREACH(trid, &g_trid_list, tailq) { + if (spdk_nvme_probe(&trid->trid, trid, probe_cb, attach_cb, NULL) != 0) { + fprintf(stderr, "spdk_nvme_probe() failed for transport address '%s'\n", + trid->trid.traddr); + rc = -1; + goto out; + } + } + + if (TAILQ_EMPTY(&g_ns_list)) { + fprintf(stderr, "No valid NVMe Namespaces to fuzz\n"); + rc = -EINVAL; + goto out; + } + + rc = prepare_qpairs(); + + if (rc < 0) { + fprintf(stderr, "Unable to prepare the qpairs\n"); + goto out; + } + + g_runtime_ticks = spdk_get_ticks() + g_runtime * spdk_get_ticks_hz(); + + /* Assigning all of the threads and then starting them makes cleanup easier. */ + TAILQ_FOREACH(ns_entry, &g_ns_list, tailq) { + ns_entry->thread = spdk_thread_create(NULL, NULL); + if (ns_entry->thread == NULL) { + fprintf(stderr, "Failed to allocate thread for namespace.\n"); + goto out; + } + } + + TAILQ_FOREACH(ns_entry, &g_ns_list, tailq) { + spdk_thread_send_msg(ns_entry->thread, start_ns_poller, ns_entry); + __sync_add_and_fetch(&g_num_active_threads, 1); + } + + g_app_completion_poller = SPDK_POLLER_REGISTER(check_app_completion, NULL, 1000000); + return; +out: + printf("Shutting down the fuzz application\n"); + free_namespaces(); + free_controllers(); + free_trids(); + spdk_app_stop(rc); +} + +static int +parse_trids(void) +{ + struct spdk_conf *config = NULL; + struct spdk_conf_section *sp; + const char *trid_char; + struct nvme_fuzz_trid *current_trid; + int num_subsystems = 0; + int rc = 0; + + if (g_conf_file) { + config = spdk_conf_allocate(); + if (!config) { + fprintf(stderr, "Unable to allocate an spdk_conf object\n"); + return -1; + } + + rc = spdk_conf_read(config, g_conf_file); + if (rc) { + fprintf(stderr, "Unable to convert the conf file into a readable system\n"); + rc = -1; + goto exit; + } + + sp = spdk_conf_find_section(config, "Nvme"); + + if (sp == NULL) { + fprintf(stderr, "No Nvme configuration in conf file\n"); + goto exit; + } + + while ((trid_char = spdk_conf_section_get_nmval(sp, "TransportID", num_subsystems, 0)) != NULL) { + current_trid = malloc(sizeof(struct nvme_fuzz_trid)); + if (!current_trid) { + fprintf(stderr, "Unable to allocate memory for transport ID\n"); + rc = -1; + goto exit; + } + rc = spdk_nvme_transport_id_parse(¤t_trid->trid, trid_char); + + if (rc < 0) { + fprintf(stderr, "failed to parse transport ID: %s\n", trid_char); + free(current_trid); + rc = -1; + goto exit; + } + TAILQ_INSERT_TAIL(&g_trid_list, current_trid, tailq); + num_subsystems++; + } + } + +exit: + if (config != NULL) { + spdk_conf_free(config); + } + return rc; +} + +static void +nvme_fuzz_usage(void) +{ + fprintf(stderr, " -a Perform admin commands. if -j is specified, \ +only admin commands will run. Otherwise they will be run in tandem with I/O commands.\n"); + fprintf(stderr, " -C <path> Path to a configuration file.\n"); + fprintf(stderr, + " -j <path> Path to a json file containing named objects of type spdk_nvme_cmd. If this option is specified, -t will be ignored.\n"); + fprintf(stderr, " -N Target only valid namespace with commands. \ +This helps dig deeper into other errors besides invalid namespace.\n"); + fprintf(stderr, " -S <integer> Seed value for test.\n"); + fprintf(stderr, + " -t <integer> Time in seconds to run the fuzz test. Only valid if -j is not specified.\n"); + fprintf(stderr, " -V Enable logging of each submitted command.\n"); +} + +static int +nvme_fuzz_parse(int ch, char *arg) +{ + int64_t error_test; + + switch (ch) { + case 'a': + g_run_admin_commands = true; + break; + case 'C': + g_conf_file = optarg; + break; + case 'j': + g_json_file = optarg; + break; + case 'N': + g_valid_ns_only = true; + break; + case 'S': + error_test = spdk_strtol(arg, 10); + if (error_test < 0) { + fprintf(stderr, "Invalid value supplied for the random seed.\n"); + return -1; + } else { + g_seed_value = error_test; + } + break; + case 't': + g_runtime = spdk_strtol(optarg, 10); + if (g_runtime < 0 || g_runtime > MAX_RUNTIME_S) { + fprintf(stderr, "You must supply a positive runtime value less than 86401.\n"); + return -1; + } + break; + case 'V': + g_verbose_mode = true; + break; + case '?': + default: + return -EINVAL; + } + return 0; +} + +int +main(int argc, char **argv) +{ + struct spdk_app_opts opts = {}; + int rc; + + spdk_app_opts_init(&opts); + opts.name = "nvme_fuzz"; + + g_runtime = DEFAULT_RUNTIME; + g_run = true; + + if ((rc = spdk_app_parse_args(argc, argv, &opts, "aC:j:NS:t:V", NULL, nvme_fuzz_parse, + nvme_fuzz_usage) != SPDK_APP_PARSE_ARGS_SUCCESS)) { + return rc; + } + + if (g_conf_file) { + parse_trids(); + } + + if (g_json_file != NULL) { + g_cmd_array_size = fuzz_parse_args_into_array(g_json_file, (void **)&g_cmd_array, + sizeof(struct spdk_nvme_cmd), g_nvme_cmd_json_name, parse_nvme_cmd_obj); + if (g_cmd_array_size == 0) { + fprintf(stderr, "The provided json file did not contain any valid commands. Exiting."); + return -EINVAL; + } + } + + rc = spdk_app_start(&opts, begin_fuzz, NULL); + + return rc; +} diff --git a/src/spdk/test/app/fuzz/vhost_fuzz/.gitignore b/src/spdk/test/app/fuzz/vhost_fuzz/.gitignore new file mode 100644 index 000000000..2df201f3f --- /dev/null +++ b/src/spdk/test/app/fuzz/vhost_fuzz/.gitignore @@ -0,0 +1 @@ +vhost_fuzz diff --git a/src/spdk/test/app/fuzz/vhost_fuzz/Makefile b/src/spdk/test/app/fuzz/vhost_fuzz/Makefile new file mode 100644 index 000000000..69b8d1866 --- /dev/null +++ b/src/spdk/test/app/fuzz/vhost_fuzz/Makefile @@ -0,0 +1,42 @@ +# +# 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 + +APP = vhost_fuzz + +CFLAGS += -I$(SPDK_ROOT_DIR)/test/app/fuzz/common + +C_SRCS := vhost_fuzz_rpc.c vhost_fuzz.c + +SPDK_LIB_LIST += event conf json jsonrpc rpc util log sock trace thread virtio +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk diff --git a/src/spdk/test/app/fuzz/vhost_fuzz/README.md b/src/spdk/test/app/fuzz/vhost_fuzz/README.md new file mode 100644 index 000000000..ab9656c5b --- /dev/null +++ b/src/spdk/test/app/fuzz/vhost_fuzz/README.md @@ -0,0 +1,46 @@ +# Overview + +This application is intended to fuzz test the SPDK vhost target by supplying +malformed or invalid requests across a unix domain socket. This fuzzer +currently supports fuzzing both vhost block and vhost scsi devices. When +fuzzing a vhost scsi device, users can select whether to fuzz the scsi I/O +queue or the scsi admin queue. Please see the NVMe fuzzer readme for information +on how output is generated, debugging procedures, and the JSON format expected +when supplying preconstructed values to the fuzzer. + +# Request Types + +Like the NVMe fuzzer, there is an example json file showing the types of requests +that the application accepts. Since the vhost application accepts both vhost block +and vhost scsi commands, there are three distinct object types that can be passed in +to the application. + +1. vhost_blk_cmd +2. vhost_scsi_cmd +3. vhost_scsi_mgmt_cmd + +Each one of these objects contains distinct data types and they should not be used interchangeably. + +All three of the data types begin with three iovec structures describing the request, data, and response +memory locations. By default, these values are overwritten by the application even when supplied as part +of a json file. This is because the request and resp data pointers are intended to point to portions of +the data structure. + +If you want to override these iovec values using a json file, you can specify the -k option. +In most cases, this will just result in the application failing all I/O immediately since +the request will no longer point to a valid memory location. + +It is possible to supply all three types of requests in a single array to the application. They will be parsed and +submitted to the proper block devices. + +# RPC + +The vhost fuzzer differs from the NVMe fuzzer in that it expects devices to be configured via rpc. The fuzzer should +always be started with the --wait-for-rpc argument. Please see below for an example of starting the fuzzer. + +~~~ +./test/app/fuzz/vhost_fuzz/vhost_fuzz -t 30 --wait-for-rpc & +./scripts/rpc.py fuzz_vhost_create_dev -s ./Vhost.1 -b -V +./scripts/rpc.py fuzz_vhost_create_dev -s ./naa.VhostScsi0.1 -l -V +./scripts/rpc.py framework_start_init +~~~ diff --git a/src/spdk/test/app/fuzz/vhost_fuzz/example.json b/src/spdk/test/app/fuzz/vhost_fuzz/example.json new file mode 100644 index 000000000..9157350f8 --- /dev/null +++ b/src/spdk/test/app/fuzz/vhost_fuzz/example.json @@ -0,0 +1,95 @@ +{ + "vhost_scsi_mgmt_cmd": { + "req_iov": { + "iov_base": "20007960ff60", + "iov_len": 51 + }, + "data_iov": { + "iov_base": "2000794dbe00", + "iov_len": 1024 + }, + "resp_iov": { + "iov_base": "20007960ff98", + "iov_len": 108 + }, + "lun": "AQA5vBf3KyE=", + "tag": 6163879237324549222, + "task_attr": 247, + "prio": 242, + "crn": 169, + "cdb": "ErxZ/qpHBau8gPzjbpotpbTnOW/2g0ns2yRh4jhe5kc=" + }, + "vhost_scsi_mgmt_cmd": { + "req_iov": { + "iov_base": "20007960fe78", + "iov_len": 51 + }, + "data_iov": { + "iov_base": "2000794dbe00", + "iov_len": 1024 + }, + "resp_iov": { + "iov_base": "20007960feb0", + "iov_len": 108 + }, + "lun": "AQAwWRrhAoo=", + "tag": 10457151189012466200, + "task_attr": 97, + "prio": 158, + "crn": 41, + "cdb": "Ejjxdzl8KwRDhq+MPfY3J3niYfAHj+2irE8Q2vIfQIk=" + }, + "vhost_scsi_cmd": { + "req_iov": { + "iov_base": "20007960fe78", + "iov_len": 24 + }, + "data_iov": { + "iov_base": "20007960fe78", + "iov_len": 1024 + }, + "resp_iov": { + "iov_base": "20007960fe78", + "iov_len": 5 + }, + "type": 3, + "subtype": 872683406, + "lun": "LdaLkHOIQxI=", + "tag": 8452696012704506104 + }, + "vhost_scsi_cmd": { + "req_iov": { + "iov_base": "20007960fe78", + "iov_len": 24 + }, + "data_iov": { + "iov_base": "20007960fe78", + "iov_len": 1024 + }, + "resp_iov": { + "iov_base": "20007960fe78", + "iov_len": 5 + }, + "type": 3, + "subtype": 872683406, + "lun": "LdaLkHOIQxI=", + "tag": 8452696012704506104 + }, + "vhost_blk_cmd": { + "req_iov": { + "iov_base": "20007960fe78", + "iov_len": 24 + }, + "data_iov": { + "iov_base": "20007960fe78", + "iov_len": 1024 + }, + "resp_iov": { + "iov_base": "20007960fe78", + "iov_len": 5 + }, + "type": 2, + "ioprio": 4343, + "sector": 24323523 + } +} diff --git a/src/spdk/test/app/fuzz/vhost_fuzz/vhost_fuzz.c b/src/spdk/test/app/fuzz/vhost_fuzz/vhost_fuzz.c new file mode 100644 index 000000000..47dbfbc65 --- /dev/null +++ b/src/spdk/test/app/fuzz/vhost_fuzz/vhost_fuzz.c @@ -0,0 +1,1146 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. All rights reserved. + * Copyright (c) 2019 Mellanox Technologies LTD. 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/json.h" +#include "spdk/event.h" +#include "spdk/likely.h" +#include "spdk/util.h" +#include "spdk/string.h" +#include "spdk_internal/virtio.h" +#include "spdk_internal/vhost_user.h" + +#include "fuzz_common.h" +#include "vhost_fuzz.h" + +#include <linux/virtio_blk.h> +#include <linux/virtio_scsi.h> + +/* Features desired/implemented by virtio blk. */ +#define VIRTIO_BLK_DEV_SUPPORTED_FEATURES \ + (1ULL << VIRTIO_BLK_F_BLK_SIZE | \ + 1ULL << VIRTIO_BLK_F_TOPOLOGY | \ + 1ULL << VIRTIO_BLK_F_MQ | \ + 1ULL << VIRTIO_BLK_F_RO | \ + 1ULL << VIRTIO_BLK_F_DISCARD | \ + 1ULL << VIRTIO_RING_F_EVENT_IDX | \ + 1ULL << VHOST_USER_F_PROTOCOL_FEATURES) + +/* Features desired/implemented by virtio scsi. */ +#define VIRTIO_SCSI_DEV_SUPPORTED_FEATURES \ + (1ULL << VIRTIO_SCSI_F_INOUT | \ + 1ULL << VIRTIO_SCSI_F_HOTPLUG | \ + 1ULL << VIRTIO_RING_F_EVENT_IDX | \ + 1ULL << VHOST_USER_F_PROTOCOL_FEATURES) + +#define VIRTIO_DEV_FIXED_QUEUES 2 +#define VIRTIO_SCSI_CONTROLQ 0 +#define VIRTIO_SCSI_EVENTQ 1 +#define VIRTIO_REQUESTQ 2 +#define FUZZ_MAX_QUEUES 3 + +#define FUZZ_QUEUE_DEPTH 128 + +#define BLK_IO_NAME "vhost_blk_cmd" +#define SCSI_IO_NAME "vhost_scsi_cmd" +#define SCSI_MGMT_NAME "vhost_scsi_mgmt_cmd" + +struct fuzz_vhost_iov_ctx { + struct iovec iov_req; + struct iovec iov_data; + struct iovec iov_resp; +}; + +struct fuzz_vhost_io_ctx { + struct fuzz_vhost_iov_ctx iovs; + union { + struct virtio_blk_outhdr blk_req; + struct virtio_scsi_cmd_req scsi_req; + struct virtio_scsi_ctrl_tmf_req scsi_tmf_req; + } req; + union { + uint8_t blk_resp; + struct virtio_scsi_cmd_resp scsi_resp; + union { + struct virtio_scsi_ctrl_tmf_resp scsi_tmf_resp; + struct virtio_scsi_ctrl_an_resp an_resp; + } scsi_tmf_resp; + } resp; + + TAILQ_ENTRY(fuzz_vhost_io_ctx) link; +}; + +struct fuzz_vhost_dev_ctx { + struct virtio_dev virtio_dev; + struct spdk_thread *thread; + struct spdk_poller *poller; + + struct fuzz_vhost_io_ctx *io_ctx_array; + TAILQ_HEAD(, fuzz_vhost_io_ctx) free_io_ctx; + TAILQ_HEAD(, fuzz_vhost_io_ctx) outstanding_io_ctx; + + unsigned int random_seed; + + uint64_t submitted_io; + uint64_t completed_io; + uint64_t successful_io; + uint64_t timeout_tsc; + + bool socket_is_blk; + bool test_scsi_tmf; + bool valid_lun; + bool use_bogus_buffer; + bool use_valid_buffer; + bool timed_out; + + TAILQ_ENTRY(fuzz_vhost_dev_ctx) link; +}; + +/* Global run state */ +uint64_t g_runtime_ticks; +int g_runtime; +int g_num_active_threads; +bool g_run = true; +bool g_verbose_mode = false; + +/* Global resources */ +TAILQ_HEAD(, fuzz_vhost_dev_ctx) g_dev_list = TAILQ_HEAD_INITIALIZER(g_dev_list); +struct spdk_poller *g_run_poller; +void *g_valid_buffer; +unsigned int g_random_seed; + + +/* Global parameters and resources for parsed commands */ +bool g_keep_iov_pointers = false; +char *g_json_file = NULL; +struct fuzz_vhost_io_ctx *g_blk_cmd_array = NULL; +struct fuzz_vhost_io_ctx *g_scsi_cmd_array = NULL; +struct fuzz_vhost_io_ctx *g_scsi_mgmt_cmd_array = NULL; + +size_t g_blk_cmd_array_size; +size_t g_scsi_cmd_array_size; +size_t g_scsi_mgmt_cmd_array_size; + +static void +cleanup(void) +{ + struct fuzz_vhost_dev_ctx *dev_ctx, *tmp; + printf("Fuzzing completed.\n"); + TAILQ_FOREACH_SAFE(dev_ctx, &g_dev_list, link, tmp) { + printf("device %p stats: Completed I/O: %lu, Successful I/O: %lu\n", dev_ctx, + dev_ctx->completed_io, dev_ctx->successful_io); + virtio_dev_release_queue(&dev_ctx->virtio_dev, VIRTIO_REQUESTQ); + if (!dev_ctx->socket_is_blk) { + virtio_dev_release_queue(&dev_ctx->virtio_dev, VIRTIO_SCSI_EVENTQ); + virtio_dev_release_queue(&dev_ctx->virtio_dev, VIRTIO_SCSI_CONTROLQ); + } + virtio_dev_stop(&dev_ctx->virtio_dev); + virtio_dev_destruct(&dev_ctx->virtio_dev); + if (dev_ctx->io_ctx_array) { + spdk_free(dev_ctx->io_ctx_array); + } + free(dev_ctx); + } + + spdk_free(g_valid_buffer); + + if (g_blk_cmd_array) { + free(g_blk_cmd_array); + } + if (g_scsi_cmd_array) { + free(g_scsi_cmd_array); + } + if (g_scsi_mgmt_cmd_array) { + free(g_scsi_mgmt_cmd_array); + } +} + +/* Get a memory address that is random and not located in our hugepage memory. */ +static void * +get_invalid_mem_address(uint64_t length) +{ + uint64_t chosen_address = 0x0; + + while (true) { + chosen_address = rand(); + chosen_address = (chosen_address << 32) | rand(); + if (spdk_vtophys((void *)chosen_address, &length) == SPDK_VTOPHYS_ERROR) { + return (void *)chosen_address; + } + } + return NULL; +} + +/* dev initialization code begin. */ +static int +virtio_dev_init(struct virtio_dev *vdev, const char *socket_path, uint64_t flags, + uint16_t max_queues) +{ + int rc; + + rc = virtio_user_dev_init(vdev, "dev_ctx", socket_path, 1024); + if (rc != 0) { + fprintf(stderr, "Failed to initialize virtual bdev\n"); + return rc; + } + + rc = virtio_dev_reset(vdev, flags); + if (rc != 0) { + return rc; + } + + rc = virtio_dev_start(vdev, max_queues, VIRTIO_DEV_FIXED_QUEUES); + if (rc != 0) { + return rc; + } + + rc = virtio_dev_acquire_queue(vdev, VIRTIO_REQUESTQ); + if (rc < 0) { + fprintf(stderr, "Couldn't get an unused queue for the io_channel.\n"); + virtio_dev_stop(vdev); + return rc; + } + return 0; +} + +static int +blk_dev_init(struct virtio_dev *vdev, const char *socket_path, uint16_t max_queues) +{ + uint16_t host_max_queues; + int rc; + + if (virtio_dev_has_feature(vdev, VIRTIO_BLK_F_MQ)) { + rc = virtio_dev_read_dev_config(vdev, offsetof(struct virtio_blk_config, num_queues), + &host_max_queues, sizeof(host_max_queues)); + if (rc) { + fprintf(stderr, "%s: config read failed: %s\n", vdev->name, spdk_strerror(-rc)); + return rc; + } + } else { + host_max_queues = 1; + } + + if (max_queues == 0) { + fprintf(stderr, "%s: requested 0 request queues (%"PRIu16" available).\n", + vdev->name, host_max_queues); + return -EINVAL; + } + + if (max_queues > host_max_queues) { + fprintf(stderr, "%s: requested %"PRIu16" request queues " + "but only %"PRIu16" available.\n", + vdev->name, max_queues, host_max_queues); + max_queues = host_max_queues; + } + + return virtio_dev_init(vdev, socket_path, VIRTIO_BLK_DEV_SUPPORTED_FEATURES, max_queues); +} + +static int +scsi_dev_init(struct virtio_dev *vdev, const char *socket_path, uint16_t max_queues) +{ + int rc; + + rc = virtio_dev_init(vdev, socket_path, VIRTIO_SCSI_DEV_SUPPORTED_FEATURES, max_queues); + if (rc != 0) { + return rc; + } + + rc = virtio_dev_acquire_queue(vdev, VIRTIO_SCSI_CONTROLQ); + if (rc != 0) { + SPDK_ERRLOG("Failed to acquire the controlq.\n"); + return rc; + } + + rc = virtio_dev_acquire_queue(vdev, VIRTIO_SCSI_EVENTQ); + if (rc != 0) { + SPDK_ERRLOG("Failed to acquire the eventq.\n"); + virtio_dev_release_queue(vdev, VIRTIO_SCSI_CONTROLQ); + return rc; + } + + return 0; +} + +int +fuzz_vhost_dev_init(const char *socket_path, bool is_blk_dev, bool use_bogus_buffer, + bool use_valid_buffer, bool valid_lun, bool test_scsi_tmf) +{ + struct fuzz_vhost_dev_ctx *dev_ctx; + int rc = 0, i; + + dev_ctx = calloc(1, sizeof(*dev_ctx)); + if (dev_ctx == NULL) { + return -ENOMEM; + } + + dev_ctx->socket_is_blk = is_blk_dev; + dev_ctx->use_bogus_buffer = use_bogus_buffer; + dev_ctx->use_valid_buffer = use_valid_buffer; + dev_ctx->valid_lun = valid_lun; + dev_ctx->test_scsi_tmf = test_scsi_tmf; + + TAILQ_INIT(&dev_ctx->free_io_ctx); + TAILQ_INIT(&dev_ctx->outstanding_io_ctx); + + assert(sizeof(*dev_ctx->io_ctx_array) <= UINT64_MAX / FUZZ_QUEUE_DEPTH); + dev_ctx->io_ctx_array = spdk_malloc(sizeof(*dev_ctx->io_ctx_array) * FUZZ_QUEUE_DEPTH, 0x0, NULL, + SPDK_ENV_LCORE_ID_ANY, SPDK_MALLOC_SHARE); + if (dev_ctx->io_ctx_array == NULL) { + free(dev_ctx); + return -ENOMEM; + } + + for (i = 0; i < FUZZ_QUEUE_DEPTH; i++) { + TAILQ_INSERT_HEAD(&dev_ctx->free_io_ctx, &dev_ctx->io_ctx_array[i], link); + } + + dev_ctx->thread = spdk_thread_create(NULL, NULL); + if (dev_ctx->thread == NULL) { + fprintf(stderr, "Unable to allocate a thread for a fuzz device.\n"); + rc = -ENOMEM; + goto error_out; + } + + if (is_blk_dev) { + rc = blk_dev_init(&dev_ctx->virtio_dev, socket_path, FUZZ_MAX_QUEUES); + } else { + rc = scsi_dev_init(&dev_ctx->virtio_dev, socket_path, FUZZ_MAX_QUEUES); + } + + if (rc) { + fprintf(stderr, "Unable to prepare the device to perform I/O.\n"); + goto error_out; + } + + TAILQ_INSERT_TAIL(&g_dev_list, dev_ctx, link); + return 0; + +error_out: + spdk_free(dev_ctx->io_ctx_array); + free(dev_ctx); + return rc; +} +/* dev initialization code end */ + +/* data dumping functions begin */ +static int +dump_virtio_cmd(void *ctx, const void *data, size_t size) +{ + fprintf(stderr, "%s\n", (const char *)data); + return 0; +} + +static void +print_blk_io_data(struct spdk_json_write_ctx *w, struct fuzz_vhost_io_ctx *io_ctx) +{ + spdk_json_write_named_uint32(w, "type", io_ctx->req.blk_req.type); + spdk_json_write_named_uint32(w, "ioprio", io_ctx->req.blk_req.ioprio); + spdk_json_write_named_uint64(w, "sector", io_ctx->req.blk_req.sector); +} + +static void +print_scsi_tmf_io_data(struct spdk_json_write_ctx *w, struct fuzz_vhost_io_ctx *io_ctx) +{ + char *lun_data; + + lun_data = fuzz_get_value_base_64_buffer(io_ctx->req.scsi_tmf_req.lun, + sizeof(io_ctx->req.scsi_tmf_req.lun)); + + spdk_json_write_named_uint32(w, "type", io_ctx->req.scsi_tmf_req.type); + spdk_json_write_named_uint32(w, "subtype", io_ctx->req.scsi_tmf_req.subtype); + spdk_json_write_named_string(w, "lun", lun_data); + spdk_json_write_named_uint64(w, "tag", io_ctx->req.scsi_tmf_req.tag); + + free(lun_data); +} + +static void +print_scsi_io_data(struct spdk_json_write_ctx *w, struct fuzz_vhost_io_ctx *io_ctx) +{ + char *lun_data; + char *cdb_data; + + lun_data = fuzz_get_value_base_64_buffer(io_ctx->req.scsi_req.lun, + sizeof(io_ctx->req.scsi_req.lun)); + cdb_data = fuzz_get_value_base_64_buffer(io_ctx->req.scsi_req.cdb, + sizeof(io_ctx->req.scsi_req.cdb)); + + spdk_json_write_named_string(w, "lun", lun_data); + spdk_json_write_named_uint64(w, "tag", io_ctx->req.scsi_req.tag); + spdk_json_write_named_uint32(w, "task_attr", io_ctx->req.scsi_req.task_attr); + spdk_json_write_named_uint32(w, "prio", io_ctx->req.scsi_req.prio); + spdk_json_write_named_uint32(w, "crn", io_ctx->req.scsi_req.crn); + spdk_json_write_named_string(w, "cdb", cdb_data); + + free(lun_data); + free(cdb_data); +} + +static void +print_iov_obj(struct spdk_json_write_ctx *w, const char *iov_name, struct iovec *iov) +{ + /* "0x" + up to 16 digits + null terminator */ + char hex_addr[19]; + int rc; + + rc = snprintf(hex_addr, 19, "%lx", (uintptr_t)iov->iov_base); + + /* default to 0. */ + if (rc < 0 || rc >= 19) { + hex_addr[0] = '0'; + hex_addr[1] = '\0'; + } + + spdk_json_write_named_object_begin(w, iov_name); + spdk_json_write_named_string(w, "iov_base", hex_addr); + spdk_json_write_named_uint64(w, "iov_len", iov->iov_len); + spdk_json_write_object_end(w); +} + +static void +print_iovs(struct spdk_json_write_ctx *w, struct fuzz_vhost_io_ctx *io_ctx) +{ + print_iov_obj(w, "req_iov", &io_ctx->iovs.iov_req); + print_iov_obj(w, "data_iov", &io_ctx->iovs.iov_data); + print_iov_obj(w, "resp_iov", &io_ctx->iovs.iov_resp); +} + +static void +print_req_obj(struct fuzz_vhost_dev_ctx *dev_ctx, struct fuzz_vhost_io_ctx *io_ctx) +{ + + struct spdk_json_write_ctx *w; + + w = spdk_json_write_begin(dump_virtio_cmd, NULL, SPDK_JSON_WRITE_FLAG_FORMATTED); + + if (dev_ctx->socket_is_blk) { + spdk_json_write_named_object_begin(w, BLK_IO_NAME); + print_iovs(w, io_ctx); + print_blk_io_data(w, io_ctx); + } else if (dev_ctx->test_scsi_tmf) { + spdk_json_write_named_object_begin(w, SCSI_MGMT_NAME); + print_iovs(w, io_ctx); + print_scsi_tmf_io_data(w, io_ctx); + } else { + spdk_json_write_named_object_begin(w, SCSI_IO_NAME); + print_iovs(w, io_ctx); + print_scsi_io_data(w, io_ctx); + } + spdk_json_write_object_end(w); + spdk_json_write_end(w); +} + +static void +dump_outstanding_io(struct fuzz_vhost_dev_ctx *dev_ctx) +{ + struct fuzz_vhost_io_ctx *io_ctx, *tmp; + + TAILQ_FOREACH_SAFE(io_ctx, &dev_ctx->outstanding_io_ctx, link, tmp) { + print_req_obj(dev_ctx, io_ctx); + TAILQ_REMOVE(&dev_ctx->outstanding_io_ctx, io_ctx, link); + TAILQ_INSERT_TAIL(&dev_ctx->free_io_ctx, io_ctx, link); + } +} +/* data dumping functions end */ + +/* data parsing functions begin */ +static int +hex_value(uint8_t c) +{ +#define V(x, y) [x] = y + 1 + static const int8_t val[256] = { + V('0', 0), V('1', 1), V('2', 2), V('3', 3), V('4', 4), + V('5', 5), V('6', 6), V('7', 7), V('8', 8), V('9', 9), + V('A', 0xA), V('B', 0xB), V('C', 0xC), V('D', 0xD), V('E', 0xE), V('F', 0xF), + V('a', 0xA), V('b', 0xB), V('c', 0xC), V('d', 0xD), V('e', 0xE), V('f', 0xF), + }; +#undef V + + return val[c] - 1; +} + +static int +fuzz_json_decode_hex_uint64(const struct spdk_json_val *val, void *out) +{ + uint64_t *out_val = out; + size_t i; + char *val_pointer = val->start; + int current_val; + + if (val->len > 16) { + return -EINVAL; + } + + *out_val = 0; + for (i = 0; i < val->len; i++) { + *out_val = *out_val << 4; + current_val = hex_value(*val_pointer); + if (current_val < 0) { + return -EINVAL; + } + *out_val += current_val; + val_pointer++; + } + + return 0; +} + +static const struct spdk_json_object_decoder fuzz_vhost_iov_decoders[] = { + {"iov_base", offsetof(struct iovec, iov_base), fuzz_json_decode_hex_uint64}, + {"iov_len", offsetof(struct iovec, iov_len), spdk_json_decode_uint64}, +}; + +static size_t +parse_iov_struct(struct iovec *iovec, struct spdk_json_val *value) +{ + int rc; + + if (value->type != SPDK_JSON_VAL_OBJECT_BEGIN) { + return -1; + } + + rc = spdk_json_decode_object(value, + fuzz_vhost_iov_decoders, + SPDK_COUNTOF(fuzz_vhost_iov_decoders), + iovec); + if (rc) { + return -1; + } + + while (value->type != SPDK_JSON_VAL_OBJECT_END) { + value++; + rc++; + } + + /* The +1 instructs the calling function to skip over the OBJECT_END function. */ + rc += 1; + return rc; +} + +static bool +parse_vhost_blk_cmds(void *item, struct spdk_json_val *value, size_t num_values) +{ + struct fuzz_vhost_io_ctx *io_ctx = item; + struct spdk_json_val *prev_value; + int nested_object_size; + uint64_t tmp_val; + size_t i = 0; + + while (i < num_values) { + nested_object_size = 1; + if (value->type == SPDK_JSON_VAL_NAME) { + prev_value = value; + value++; + i++; + if (!strncmp(prev_value->start, "req_iov", prev_value->len)) { + nested_object_size = parse_iov_struct(&io_ctx->iovs.iov_req, value); + } else if (!strncmp(prev_value->start, "data_iov", prev_value->len)) { + nested_object_size = parse_iov_struct(&io_ctx->iovs.iov_data, value); + } else if (!strncmp(prev_value->start, "resp_iov", prev_value->len)) { + nested_object_size = parse_iov_struct(&io_ctx->iovs.iov_data, value); + } else if (!strncmp(prev_value->start, "type", prev_value->len)) { + if (fuzz_parse_json_num(value, UINT32_MAX, &tmp_val)) { + nested_object_size = -1; + } else { + io_ctx->req.blk_req.type = tmp_val; + } + } else if (!strncmp(prev_value->start, "ioprio", prev_value->len)) { + if (fuzz_parse_json_num(value, UINT32_MAX, &tmp_val)) { + nested_object_size = -1; + } else { + io_ctx->req.blk_req.ioprio = tmp_val; + } + } else if (!strncmp(prev_value->start, "sector", prev_value->len)) { + if (fuzz_parse_json_num(value, UINT64_MAX, &tmp_val)) { + nested_object_size = -1; + } else { + io_ctx->req.blk_req.sector = tmp_val; + } + } + } + if (nested_object_size < 0) { + fprintf(stderr, "Invalid value supplied for io_ctx->%.*s: %.*s\n", prev_value->len, + (char *)prev_value->start, value->len, (char *)value->start); + return false; + } + value += nested_object_size; + i += nested_object_size; + } + return true; +} + +static bool +parse_vhost_scsi_cmds(void *item, struct spdk_json_val *value, size_t num_values) +{ + struct fuzz_vhost_io_ctx *io_ctx = item; + struct spdk_json_val *prev_value; + int nested_object_size; + uint64_t tmp_val; + size_t i = 0; + + while (i < num_values) { + nested_object_size = 1; + if (value->type == SPDK_JSON_VAL_NAME) { + prev_value = value; + value++; + i++; + if (!strncmp(prev_value->start, "req_iov", prev_value->len)) { + nested_object_size = parse_iov_struct(&io_ctx->iovs.iov_req, value); + } else if (!strncmp(prev_value->start, "data_iov", prev_value->len)) { + nested_object_size = parse_iov_struct(&io_ctx->iovs.iov_data, value); + } else if (!strncmp(prev_value->start, "resp_iov", prev_value->len)) { + nested_object_size = parse_iov_struct(&io_ctx->iovs.iov_data, value); + } else if (!strncmp(prev_value->start, "lun", prev_value->len)) { + if (fuzz_get_base_64_buffer_value(&io_ctx->req.scsi_req.lun, + sizeof(io_ctx->req.scsi_req.lun), + (char *)value->start, + value->len)) { + nested_object_size = -1; + } + } else if (!strncmp(prev_value->start, "tag", prev_value->len)) { + if (fuzz_parse_json_num(value, UINT64_MAX, &tmp_val)) { + nested_object_size = -1; + } else { + io_ctx->req.scsi_req.tag = tmp_val; + } + } else if (!strncmp(prev_value->start, "task_attr", prev_value->len)) { + if (fuzz_parse_json_num(value, UINT8_MAX, &tmp_val)) { + nested_object_size = -1; + } else { + io_ctx->req.scsi_req.task_attr = tmp_val; + } + } else if (!strncmp(prev_value->start, "prio", prev_value->len)) { + if (fuzz_parse_json_num(value, UINT8_MAX, &tmp_val)) { + nested_object_size = -1; + } else { + io_ctx->req.scsi_req.prio = tmp_val; + } + } else if (!strncmp(prev_value->start, "crn", prev_value->len)) { + if (fuzz_parse_json_num(value, UINT8_MAX, &tmp_val)) { + nested_object_size = -1; + } else { + io_ctx->req.scsi_req.crn = tmp_val; + } + } else if (!strncmp(prev_value->start, "cdb", prev_value->len)) { + if (fuzz_get_base_64_buffer_value(&io_ctx->req.scsi_req.cdb, + sizeof(io_ctx->req.scsi_req.cdb), + (char *)value->start, + value->len)) { + nested_object_size = -1; + } + } + } + if (nested_object_size < 0) { + fprintf(stderr, "Invalid value supplied for io_ctx->%.*s: %.*s\n", prev_value->len, + (char *)prev_value->start, value->len, (char *)value->start); + return false; + } + value += nested_object_size; + i += nested_object_size; + } + return true; + +} + +static bool +parse_vhost_scsi_mgmt_cmds(void *item, struct spdk_json_val *value, size_t num_values) +{ + struct fuzz_vhost_io_ctx *io_ctx = item; + struct spdk_json_val *prev_value; + int nested_object_size; + uint64_t tmp_val; + size_t i = 0; + + while (i < num_values) { + nested_object_size = 1; + if (value->type == SPDK_JSON_VAL_NAME) { + prev_value = value; + value++; + i++; + if (!strncmp(prev_value->start, "req_iov", prev_value->len)) { + nested_object_size = parse_iov_struct(&io_ctx->iovs.iov_req, value); + } else if (!strncmp(prev_value->start, "data_iov", prev_value->len)) { + nested_object_size = parse_iov_struct(&io_ctx->iovs.iov_data, value); + } else if (!strncmp(prev_value->start, "resp_iov", prev_value->len)) { + nested_object_size = parse_iov_struct(&io_ctx->iovs.iov_data, value); + } else if (!strncmp(prev_value->start, "type", prev_value->len)) { + if (fuzz_parse_json_num(value, UINT32_MAX, &tmp_val)) { + nested_object_size = -1; + } else { + io_ctx->req.scsi_tmf_req.type = tmp_val; + } + } else if (!strncmp(prev_value->start, "subtype", prev_value->len)) { + if (fuzz_parse_json_num(value, UINT32_MAX, &tmp_val)) { + nested_object_size = -1; + } else { + io_ctx->req.scsi_tmf_req.subtype = tmp_val; + } + } else if (!strncmp(prev_value->start, "lun", prev_value->len)) { + if (fuzz_get_base_64_buffer_value(&io_ctx->req.scsi_tmf_req.lun, + sizeof(io_ctx->req.scsi_tmf_req.lun), + (char *)value->start, + value->len)) { + nested_object_size = -1; + } + } else if (!strncmp(prev_value->start, "tag", prev_value->len)) { + if (fuzz_parse_json_num(value, UINT64_MAX, &tmp_val)) { + nested_object_size = -1; + } else { + io_ctx->req.scsi_tmf_req.tag = tmp_val; + } + } + } + if (nested_object_size < 0) { + fprintf(stderr, "Invalid value supplied for io_ctx->%.*s: %.*s\n", prev_value->len, + (char *)prev_value->start, value->len, (char *)value->start); + return false; + } + value += nested_object_size; + i += nested_object_size; + } + return true; +} +/* data parsing functions end */ + +/* build requests begin */ +static void +craft_io_from_array(struct fuzz_vhost_io_ctx *src_ctx, struct fuzz_vhost_io_ctx *dest_ctx) +{ + if (g_keep_iov_pointers) { + dest_ctx->iovs = src_ctx->iovs; + } + dest_ctx->req = src_ctx->req; +} + +static void +craft_virtio_scsi_req(struct fuzz_vhost_dev_ctx *dev_ctx, struct fuzz_vhost_io_ctx *io_ctx) +{ + io_ctx->iovs.iov_req.iov_len = sizeof(io_ctx->req.scsi_req); + io_ctx->iovs.iov_resp.iov_len = sizeof(io_ctx->resp.scsi_resp); + fuzz_fill_random_bytes((char *)&io_ctx->req.scsi_req, sizeof(io_ctx->req.scsi_req), + &dev_ctx->random_seed); + /* TODO: set up the logic to find all luns on the target. Right now we are just assuming the first is OK. */ + if (dev_ctx->valid_lun) { + io_ctx->req.scsi_req.lun[0] = 1; + io_ctx->req.scsi_req.lun[1] = 0; + } +} + +static void +craft_virtio_scsi_tmf_req(struct fuzz_vhost_dev_ctx *dev_ctx, struct fuzz_vhost_io_ctx *io_ctx) +{ + io_ctx->iovs.iov_req.iov_len = sizeof(io_ctx->req.scsi_tmf_req); + io_ctx->iovs.iov_resp.iov_len = sizeof(io_ctx->resp.scsi_tmf_resp); + fuzz_fill_random_bytes((char *)&io_ctx->req.scsi_tmf_req, sizeof(io_ctx->req.scsi_tmf_req), + &dev_ctx->random_seed); + /* TODO: set up the logic to find all luns on the target. Right now we are just assuming the first is OK. */ + if (dev_ctx->valid_lun) { + io_ctx->req.scsi_tmf_req.lun[0] = 1; + io_ctx->req.scsi_tmf_req.lun[1] = 0; + } + + /* Valid controlqueue commands have to be of type 0, 1, or 2. Any others just return immediately from the target. */ + /* Try to only test the opcodes that will exercise extra paths in the target side. But allow for at least one invalid value. */ + io_ctx->req.scsi_tmf_req.type = rand() % 4; +} + +static void +craft_virtio_blk_req(struct fuzz_vhost_io_ctx *io_ctx) +{ + io_ctx->iovs.iov_req.iov_len = sizeof(io_ctx->req.blk_req); + io_ctx->iovs.iov_resp.iov_len = sizeof(io_ctx->resp.blk_resp); + io_ctx->req.blk_req.type = rand(); + io_ctx->req.blk_req.sector = rand(); +} + +static void +craft_virtio_req_rsp_pair(struct fuzz_vhost_dev_ctx *dev_ctx, struct fuzz_vhost_io_ctx *io_ctx) +{ + struct fuzz_vhost_iov_ctx *iovs = &io_ctx->iovs; + + /* + * Always set these buffer values up front. + * If the user wants to override this with the json values, + * they can specify -k when starting the app. */ + iovs->iov_req.iov_base = &io_ctx->req; + if (dev_ctx->use_bogus_buffer) { + iovs->iov_data.iov_len = rand(); + iovs->iov_data.iov_base = get_invalid_mem_address(iovs->iov_data.iov_len); + } else if (dev_ctx->use_valid_buffer) { + iovs->iov_data.iov_len = 1024; + iovs->iov_data.iov_base = g_valid_buffer; + } + iovs->iov_resp.iov_base = &io_ctx->resp; + + if (dev_ctx->socket_is_blk && g_blk_cmd_array) { + craft_io_from_array(&g_blk_cmd_array[dev_ctx->submitted_io], io_ctx); + return; + } else if (dev_ctx->test_scsi_tmf && g_scsi_mgmt_cmd_array) { + craft_io_from_array(&g_scsi_mgmt_cmd_array[dev_ctx->submitted_io], io_ctx); + return; + } else if (g_scsi_cmd_array) { + craft_io_from_array(&g_scsi_cmd_array[dev_ctx->submitted_io], io_ctx); + return; + } + + if (dev_ctx->socket_is_blk) { + craft_virtio_blk_req(io_ctx); + } else if (dev_ctx->test_scsi_tmf) { + craft_virtio_scsi_tmf_req(dev_ctx, io_ctx); + } else { + craft_virtio_scsi_req(dev_ctx, io_ctx); + } +} +/* build requests end */ + +/* submit requests begin */ +static uint64_t +get_max_num_io(struct fuzz_vhost_dev_ctx *dev_ctx) +{ + if (dev_ctx->socket_is_blk) { + return g_blk_cmd_array_size; + } else if (dev_ctx->test_scsi_tmf) { + return g_scsi_mgmt_cmd_array_size; + } else { + return g_scsi_cmd_array_size; + } +} + +static int +submit_virtio_req_rsp_pair(struct fuzz_vhost_dev_ctx *dev_ctx, struct virtqueue *vq, + struct fuzz_vhost_io_ctx *io_ctx) +{ + struct fuzz_vhost_iov_ctx *iovs = &io_ctx->iovs; + int num_iovs = 2, rc; + + num_iovs += dev_ctx->use_bogus_buffer || dev_ctx->use_valid_buffer ? 1 : 0; + + rc = virtqueue_req_start(vq, io_ctx, num_iovs); + if (rc) { + return rc; + } + virtqueue_req_add_iovs(vq, &iovs->iov_req, 1, SPDK_VIRTIO_DESC_RO); + /* blk and scsi requests favor different orders for the iov objects. */ + if (dev_ctx->socket_is_blk) { + if (dev_ctx->use_bogus_buffer || dev_ctx->use_valid_buffer) { + virtqueue_req_add_iovs(vq, &iovs->iov_data, 1, SPDK_VIRTIO_DESC_WR); + } + virtqueue_req_add_iovs(vq, &iovs->iov_resp, 1, SPDK_VIRTIO_DESC_WR); + } else { + virtqueue_req_add_iovs(vq, &iovs->iov_resp, 1, SPDK_VIRTIO_DESC_WR); + if (dev_ctx->use_bogus_buffer || dev_ctx->use_valid_buffer) { + virtqueue_req_add_iovs(vq, &iovs->iov_data, 1, SPDK_VIRTIO_DESC_WR); + } + } + virtqueue_req_flush(vq); + return 0; +} + +static void +dev_submit_requests(struct fuzz_vhost_dev_ctx *dev_ctx, struct virtqueue *vq, + uint64_t max_io_to_submit) +{ + struct fuzz_vhost_io_ctx *io_ctx; + int rc; + + while (!TAILQ_EMPTY(&dev_ctx->free_io_ctx) && dev_ctx->submitted_io < max_io_to_submit) { + io_ctx = TAILQ_FIRST(&dev_ctx->free_io_ctx); + craft_virtio_req_rsp_pair(dev_ctx, io_ctx); + rc = submit_virtio_req_rsp_pair(dev_ctx, vq, io_ctx); + if (rc == 0) { + TAILQ_REMOVE(&dev_ctx->free_io_ctx, io_ctx, link); + TAILQ_INSERT_TAIL(&dev_ctx->outstanding_io_ctx, io_ctx, link); + dev_ctx->submitted_io++; + } else if (rc == -ENOMEM) { + /* There are just not enough available buffers right now. try later. */ + return; + } else if (rc == -EINVAL) { + /* The virtqueue must be broken. We know we can fit at least three descriptors */ + fprintf(stderr, "One of the virtqueues for dev %p is broken. stopping all devices.\n", dev_ctx); + g_run = 0; + } + } +} +/* submit requests end */ + +/* complete requests begin */ +static void +check_successful_op(struct fuzz_vhost_dev_ctx *dev_ctx, struct fuzz_vhost_io_ctx *io_ctx) +{ + bool is_successful = false; + + if (dev_ctx->socket_is_blk) { + if (io_ctx->resp.blk_resp == 0) { + is_successful = true; + } + } else if (dev_ctx->test_scsi_tmf) { + if (io_ctx->resp.scsi_tmf_resp.scsi_tmf_resp.response == 0 && + io_ctx->resp.scsi_tmf_resp.an_resp.response == 0) { + is_successful = true; + } + } else { + if (io_ctx->resp.scsi_resp.status == 0) { + is_successful = true; + } + } + + if (is_successful) { + fprintf(stderr, "An I/O completed without an error status. This could be worth looking into.\n"); + fprintf(stderr, + "There is also a good chance that the target just failed before setting a status.\n"); + dev_ctx->successful_io++; + print_req_obj(dev_ctx, io_ctx); + } else if (g_verbose_mode) { + fprintf(stderr, "The following I/O failed as expected.\n"); + print_req_obj(dev_ctx, io_ctx); + } +} + +static void +complete_io(struct fuzz_vhost_dev_ctx *dev_ctx, struct fuzz_vhost_io_ctx *io_ctx) +{ + TAILQ_REMOVE(&dev_ctx->outstanding_io_ctx, io_ctx, link); + TAILQ_INSERT_HEAD(&dev_ctx->free_io_ctx, io_ctx, link); + check_successful_op(dev_ctx, io_ctx); + dev_ctx->completed_io++; + dev_ctx->timeout_tsc = fuzz_refresh_timeout(); +} + +static int +poll_dev(void *ctx) +{ + struct fuzz_vhost_dev_ctx *dev_ctx = ctx; + struct virtqueue *vq; + struct fuzz_vhost_io_ctx *io_ctx[FUZZ_QUEUE_DEPTH]; + int num_active_threads; + uint64_t max_io_to_complete = UINT64_MAX; + uint64_t current_ticks; + uint32_t len[FUZZ_QUEUE_DEPTH]; + uint16_t num_cpl, i; + + if (g_json_file) { + max_io_to_complete = get_max_num_io(dev_ctx); + } + + if (!dev_ctx->socket_is_blk && dev_ctx->test_scsi_tmf) { + vq = dev_ctx->virtio_dev.vqs[VIRTIO_SCSI_CONTROLQ]; + } else { + vq = dev_ctx->virtio_dev.vqs[VIRTIO_REQUESTQ]; + } + + num_cpl = virtio_recv_pkts(vq, (void **)io_ctx, len, FUZZ_QUEUE_DEPTH); + + for (i = 0; i < num_cpl; i++) { + complete_io(dev_ctx, io_ctx[i]); + } + + current_ticks = spdk_get_ticks(); + + if (current_ticks > dev_ctx->timeout_tsc) { + dev_ctx->timed_out = true; + g_run = false; + fprintf(stderr, "The VQ on device %p timed out. Dumping contents now.\n", dev_ctx); + dump_outstanding_io(dev_ctx); + } + + if (current_ticks > g_runtime_ticks) { + g_run = 0; + } + + if (!g_run || dev_ctx->completed_io >= max_io_to_complete) { + if (TAILQ_EMPTY(&dev_ctx->outstanding_io_ctx)) { + spdk_poller_unregister(&dev_ctx->poller); + num_active_threads = __sync_sub_and_fetch(&g_num_active_threads, 1); + if (num_active_threads == 0) { + g_run = 0; + } + spdk_thread_exit(dev_ctx->thread); + } + return 0; + } + + dev_submit_requests(dev_ctx, vq, max_io_to_complete); + return 0; +} +/* complete requests end */ + +static void +start_io(void *ctx) +{ + struct fuzz_vhost_dev_ctx *dev_ctx = ctx; + + if (g_random_seed) { + dev_ctx->random_seed = g_random_seed; + } else { + dev_ctx->random_seed = spdk_get_ticks(); + } + + dev_ctx->timeout_tsc = fuzz_refresh_timeout(); + + dev_ctx->poller = SPDK_POLLER_REGISTER(poll_dev, dev_ctx, 0); + if (dev_ctx->poller == NULL) { + return; + } + +} + +static int +end_fuzz(void *ctx) +{ + if (!g_run && !g_num_active_threads) { + spdk_poller_unregister(&g_run_poller); + cleanup(); + spdk_app_stop(0); + } + return 0; +} + +static void +begin_fuzz(void *ctx) +{ + struct fuzz_vhost_dev_ctx *dev_ctx; + + g_runtime_ticks = spdk_get_ticks() + spdk_get_ticks_hz() * g_runtime; + + g_valid_buffer = spdk_malloc(0x1000, 0x200, NULL, SPDK_ENV_LCORE_ID_ANY, SPDK_MALLOC_SHARE); + if (g_valid_buffer == NULL) { + fprintf(stderr, "Failed to allocate a valid buffer for I/O\n"); + goto out; + } + + g_run_poller = SPDK_POLLER_REGISTER(end_fuzz, NULL, 0); + if (g_run_poller == NULL) { + fprintf(stderr, "Failed to register a poller for test completion checking.\n"); + } + + TAILQ_FOREACH(dev_ctx, &g_dev_list, link) { + assert(dev_ctx->thread != NULL); + spdk_thread_send_msg(dev_ctx->thread, start_io, dev_ctx); + __sync_add_and_fetch(&g_num_active_threads, 1); + } + + return; +out: + cleanup(); + spdk_app_stop(0); +} + +static void +fuzz_vhost_usage(void) +{ + fprintf(stderr, " -j <path> Path to a json file containing named objects.\n"); + fprintf(stderr, + " -k Keep the iov pointer addresses from the json file. only valid with -j.\n"); + fprintf(stderr, " -S <integer> Seed value for test.\n"); + fprintf(stderr, " -t <integer> Time in seconds to run the fuzz test.\n"); + fprintf(stderr, " -V Enable logging of each submitted command.\n"); +} + +static int +fuzz_vhost_parse(int ch, char *arg) +{ + int64_t error_test; + + switch (ch) { + case 'j': + g_json_file = optarg; + break; + case 'k': + g_keep_iov_pointers = true; + break; + case 'S': + error_test = spdk_strtol(arg, 10); + if (error_test < 0) { + fprintf(stderr, "Invalid value supplied for the random seed.\n"); + return -1; + } else { + g_random_seed = spdk_strtol(arg, 10); + } + break; + case 't': + g_runtime = spdk_strtol(arg, 10); + if (g_runtime < 0 || g_runtime > MAX_RUNTIME_S) { + fprintf(stderr, "You must supply a positive runtime value less than 86401.\n"); + return -1; + } + break; + case 'V': + g_verbose_mode = true; + break; + case '?': + default: + return -EINVAL; + } + return 0; +} + +int +main(int argc, char **argv) +{ + struct spdk_app_opts opts = {}; + int rc; + + spdk_app_opts_init(&opts); + opts.name = "vhost_fuzz"; + g_runtime = DEFAULT_RUNTIME; + + rc = spdk_app_parse_args(argc, argv, &opts, "j:kS:t:V", NULL, fuzz_vhost_parse, fuzz_vhost_usage); + if (rc != SPDK_APP_PARSE_ARGS_SUCCESS) { + fprintf(stderr, "Unable to parse the application arguments.\n"); + return -1; + } + + if (g_json_file != NULL) { + g_blk_cmd_array_size = fuzz_parse_args_into_array(g_json_file, + (void **)&g_blk_cmd_array, + sizeof(struct fuzz_vhost_io_ctx), + BLK_IO_NAME, parse_vhost_blk_cmds); + g_scsi_cmd_array_size = fuzz_parse_args_into_array(g_json_file, + (void **)&g_scsi_cmd_array, + sizeof(struct fuzz_vhost_io_ctx), + SCSI_IO_NAME, parse_vhost_scsi_cmds); + g_scsi_mgmt_cmd_array_size = fuzz_parse_args_into_array(g_json_file, + (void **)&g_scsi_mgmt_cmd_array, + sizeof(struct fuzz_vhost_io_ctx), + SCSI_IO_NAME, parse_vhost_scsi_mgmt_cmds); + if (g_blk_cmd_array_size == 0 && g_scsi_cmd_array_size == 0 && g_scsi_mgmt_cmd_array_size == 0) { + fprintf(stderr, "The provided json file did not contain any valid commands. Exiting.\n"); + return -EINVAL; + } + } + + spdk_app_start(&opts, begin_fuzz, NULL); +} diff --git a/src/spdk/test/app/fuzz/vhost_fuzz/vhost_fuzz.h b/src/spdk/test/app/fuzz/vhost_fuzz/vhost_fuzz.h new file mode 100644 index 000000000..df71a846d --- /dev/null +++ b/src/spdk/test/app/fuzz/vhost_fuzz/vhost_fuzz.h @@ -0,0 +1,41 @@ +/*- + * 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. + */ + + +#ifndef VHOST_FUZZ_H +#define VHOST_FUZZ_H + +int fuzz_vhost_dev_init(const char *socket_path, bool is_blk_dev, bool use_bogus_buffer, + bool use_valid_buffer, bool valid_lun, bool test_scsi_tmf); + +#endif diff --git a/src/spdk/test/app/fuzz/vhost_fuzz/vhost_fuzz_rpc.c b/src/spdk/test/app/fuzz/vhost_fuzz/vhost_fuzz_rpc.c new file mode 100644 index 000000000..b60e3f097 --- /dev/null +++ b/src/spdk/test/app/fuzz/vhost_fuzz/vhost_fuzz_rpc.c @@ -0,0 +1,108 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. All rights reserved. + * Copyright (c) 2018 Mellanox Technologies LTD. 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/rpc.h" +#include "spdk/util.h" + +#include "vhost_fuzz.h" + +struct rpc_fuzz_vhost_dev_create { + char *socket; + bool is_blk; + bool use_bogus_buffer; + bool use_valid_buffer; + bool valid_lun; + bool test_scsi_tmf; +}; + +static const struct spdk_json_object_decoder rpc_fuzz_vhost_dev_create_decoders[] = { + {"socket", offsetof(struct rpc_fuzz_vhost_dev_create, socket), spdk_json_decode_string}, + {"is_blk", offsetof(struct rpc_fuzz_vhost_dev_create, is_blk), spdk_json_decode_bool, true}, + {"use_bogus_buffer", offsetof(struct rpc_fuzz_vhost_dev_create, use_bogus_buffer), spdk_json_decode_bool, true}, + {"use_valid_buffer", offsetof(struct rpc_fuzz_vhost_dev_create, use_valid_buffer), spdk_json_decode_bool, true}, + {"valid_lun", offsetof(struct rpc_fuzz_vhost_dev_create, valid_lun), spdk_json_decode_bool, true}, + {"test_scsi_tmf", offsetof(struct rpc_fuzz_vhost_dev_create, test_scsi_tmf), spdk_json_decode_bool, true}, +}; + +static void +spdk_rpc_fuzz_vhost_create_dev(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct spdk_json_write_ctx *w; + struct rpc_fuzz_vhost_dev_create req = {0}; + int rc; + + if (spdk_json_decode_object(params, rpc_fuzz_vhost_dev_create_decoders, + SPDK_COUNTOF(rpc_fuzz_vhost_dev_create_decoders), &req)) { + fprintf(stderr, "Unable to parse the request.\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Unable to parse the object parameters.\n"); + return; + } + + if (strlen(req.socket) > PATH_MAX) { + fprintf(stderr, "Socket address is too long.\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Unable to parse the object parameters.\n"); + free(req.socket); + return; + } + + rc = fuzz_vhost_dev_init(req.socket, req.is_blk, req.use_bogus_buffer, req.use_valid_buffer, + req.valid_lun, req.test_scsi_tmf); + + if (rc != 0) { + if (rc == -ENOMEM) { + fprintf(stderr, "No valid memory for device initialization.\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR, + "No memory returned from host.\n"); + } else if (rc == -EINVAL) { + fprintf(stderr, "Invalid device parameters provided.\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Parameters provided were invalid.\n"); + } else { + fprintf(stderr, "unknown error from the guest.\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR, + "Unexpected error code.\n"); + } + } else { + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(request, w); + } + + free(req.socket); + return; +} +SPDK_RPC_REGISTER("fuzz_vhost_create_dev", spdk_rpc_fuzz_vhost_create_dev, SPDK_RPC_STARTUP); |