Spdk/test/app/fuzz/vhost_fuzz/vhost_fuzz.c
Changpeng Liu a02483e67c module/bdev_virtio_scsi: use the correct num_queues value
Parameter `num_queues` for virtio_scsi PCI device means
maximum number of queues, it SHOULD include the `eventq`
and `controlq`, while for `vhost_user` RPC call, it means
the number of IO queues, so here we use it as `max_queues`
in lib/virtio and add the fixed number queues for `vhost_user`
SCSI device.

Also fix `vhost_fuzz` to get `num_queues` earlier than
negotiate the feature bits.

Change-Id: I41b3da5e4b4dc37127befd414226ea6eafcd9ad0
Signed-off-by: Changpeng Liu <changpeng.liu@intel.com>
Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/13791
Tested-by: SPDK CI Jenkins <sys_sgci@intel.com>
Reviewed-by: Jim Harris <james.r.harris@intel.com>
Reviewed-by: Tomasz Zawadzki <tomasz.zawadzki@intel.com>
2022-08-04 11:24:40 +00:00

1094 lines
31 KiB
C

/* SPDX-License-Identifier: BSD-3-Clause
* Copyright (c) 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;
}