Spdk/test/app/fuzz/vhost_fuzz/vhost_fuzz.c
paul luse a6dbe3721e update Intel copyright notices
per Intel policy to include file commit date using git cmd
below.  The policy does not apply to non-Intel (C) notices.

git log --follow -C90% --format=%ad --date default <file> | tail -1

and then pull just the 4 digit year from the result.

Intel copyrights were not added to files where Intel either had
no contribution ot the contribution lacked substance (ie license
header updates, formatting changes, etc).  Contribution date used
"--follow -C95%" to get the most accurate date.

Note that several files in this patch didn't end the license/(c)
block with a blank comment line so these were added as the vast
majority of files do have this last blank line.  Simply there for
consistency.

Signed-off-by: paul luse <paul.e.luse@intel.com>
Change-Id: Id5b7ce4f658fe87132f14139ead58d6e285c04d4
Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/15192
Tested-by: SPDK CI Jenkins <sys_sgci@intel.com>
Reviewed-by: Jim Harris <james.r.harris@intel.com>
Reviewed-by: Ben Walker <benjamin.walker@intel.com>
Community-CI: Mellanox Build Bot
2022-11-10 08:28:53 +00:00

1094 lines
31 KiB
C

/* SPDX-License-Identifier: BSD-3-Clause
* Copyright (C) 2019 Intel Corporation. All rights reserved.
* Copyright (c) 2019 Mellanox Technologies LTD. All rights reserved.
*/
#include "spdk/stdinc.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)
{
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 controlq 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, sizeof(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;
}
}
rc = spdk_app_start(&opts, begin_fuzz, NULL);
spdk_app_fini();
return rc;
}