Spdk/test/app/fuzz/llvm_vfio_fuzz/llvm_vfio_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

505 lines
13 KiB
C

/* SPDX-License-Identifier: BSD-3-Clause
* Copyright (C) 2022 Intel Corporation. All rights reserved.
*/
#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/file.h"
#include "spdk/util.h"
#include "spdk/vfio_user_pci.h"
#include <linux/vfio.h>
#include "spdk/vfio_user_spec.h"
#define VFIO_MAXIMUM_SPARSE_MMAP_REGIONS 8
typedef int (*fuzzer_fn)(const uint8_t *data, size_t size, struct vfio_device *dev);
struct fuzz_type {
fuzzer_fn fn;
uint32_t bytes_per_cmd;
};
#define VFIO_USER_MAX_PAYLOAD_SIZE (4096)
static uint8_t payload[VFIO_USER_MAX_PAYLOAD_SIZE];
static char *g_ctrlr_path;
static int32_t g_time_in_sec = 10;
static char *g_corpus_dir;
static uint8_t *g_repro_data;
static size_t g_repro_size;
static pthread_t g_fuzz_td;
static pthread_t g_reactor_td;
static struct fuzz_type *g_fuzzer;
struct io_thread {
int lba_num;
char *write_buf;
char *read_buf;
size_t buf_size;
struct spdk_poller *run_poller;
struct spdk_thread *thread;
struct spdk_nvme_ctrlr *io_ctrlr;
pthread_t io_td;
struct spdk_nvme_ns *io_ns;
struct spdk_nvme_qpair *io_qpair;
char *io_ctrlr_path;
bool io_processing;
bool terminate;
} g_io_thread;
static int
fuzz_vfio_user_version(const uint8_t *data, size_t size, struct vfio_device *dev)
{
struct vfio_user_version *version = (struct vfio_user_version *)payload;
version->major = ((uint16_t)data[0] << 8) + (uint16_t)data[1];
version->minor = ((uint16_t)data[2] << 8) + (uint16_t)data[3];
return spdk_vfio_user_dev_send_request(dev, VFIO_USER_VERSION, payload,
sizeof(struct vfio_user_version),
sizeof(payload), NULL, 0);
}
static int
fuzz_vfio_user_region_rw(const uint8_t *data, size_t size, struct vfio_device *dev)
{
uint8_t buf[4];
uint64_t offset = 0;
offset = ((uint64_t)data[0] << 8) + (uint64_t)data[1];
offset = (SPDK_ALIGN_FLOOR(offset, 4)) % 4096;
memcpy(buf, &data[2], sizeof(buf));
/* writes to BAR0 depending on the register, therefore the return value is never checked */
spdk_vfio_user_pci_bar_access(dev, VFIO_PCI_BAR0_REGION_INDEX, offset, sizeof(buf),
&buf, true);
return spdk_vfio_user_pci_bar_access(dev, VFIO_PCI_BAR0_REGION_INDEX, offset, sizeof(buf),
&buf, false);
}
static struct fuzz_type g_fuzzers[] = {
{ .fn = fuzz_vfio_user_region_rw, .bytes_per_cmd = 6},
{ .fn = fuzz_vfio_user_version, .bytes_per_cmd = 4},
{ .fn = NULL, .bytes_per_cmd = 0}
};
#define NUM_FUZZERS (SPDK_COUNTOF(g_fuzzers) - 1)
static int
TestOneInput(const uint8_t *data, size_t size)
{
struct vfio_device *dev = NULL;
char ctrlr_path[PATH_MAX];
int ret = 0;
snprintf(ctrlr_path, sizeof(ctrlr_path), "%s/cntrl", g_ctrlr_path);
ret = access(ctrlr_path, F_OK);
if (ret != 0) {
fprintf(stderr, "Access path %s failed\n", ctrlr_path);
spdk_app_stop(-1);
return -1;
}
dev = spdk_vfio_user_setup(ctrlr_path);
if (dev == NULL) {
fprintf(stderr, "spdk_vfio_user_setup() failed for controller path '%s'\n",
ctrlr_path);
spdk_app_stop(-1);
return -1;
}
/* run cmds here */
if (g_fuzzer->fn != NULL) {
g_fuzzer->fn(data, size, dev);
}
spdk_vfio_user_release(dev);
return 0;
}
int LLVMFuzzerRunDriver(int *argc, char ***argv, int (*UserCb)(const uint8_t *Data, size_t Size));
static void
io_terminate(void *ctx)
{
((struct io_thread *)ctx)->terminate = true;
}
static void
exit_handler(void)
{
if (g_io_thread.io_ctrlr_path) {
spdk_thread_send_msg(g_io_thread.thread, io_terminate, &g_io_thread);
} else {
spdk_app_stop(0);
}
pthread_join(g_reactor_td, NULL);
}
static void *
start_fuzzer(void *ctx)
{
char *_argv[] = {
"spdk",
"-len_control=0",
"-detect_leaks=1",
NULL,
NULL,
NULL
};
char time_str[128];
char len_str[128];
char **argv = _argv;
int argc = SPDK_COUNTOF(_argv);
uint32_t len = 0;
spdk_unaffinitize_thread();
len = 10 * g_fuzzer->bytes_per_cmd;
snprintf(len_str, sizeof(len_str), "-max_len=%d", len);
argv[argc - 3] = len_str;
snprintf(time_str, sizeof(time_str), "-max_total_time=%d", g_time_in_sec);
argv[argc - 2] = time_str;
argv[argc - 1] = g_corpus_dir;
atexit(exit_handler);
if (g_repro_data) {
printf("Running single test based on reproduction data file.\n");
TestOneInput(g_repro_data, g_repro_size);
printf("Done.\n");
} else {
LLVMFuzzerRunDriver(&argc, &argv, TestOneInput);
/* TODO: in the normal case, LLVMFuzzerRunDriver never returns - it calls exit()
* directly and we never get here. But this behavior isn't really documented
* anywhere by LLVM.
*/
}
return NULL;
}
static void
read_complete(void *arg, const struct spdk_nvme_cpl *completion)
{
int sectors_num = 0;
struct io_thread *io = (struct io_thread *)arg;
if (spdk_nvme_cpl_is_error(completion)) {
spdk_nvme_qpair_print_completion(io->io_qpair, (struct spdk_nvme_cpl *)completion);
fprintf(stderr, "I/O read error status: %s\n",
spdk_nvme_cpl_get_status_string(&completion->status));
io->io_processing = false;
pthread_kill(g_fuzz_td, SIGSEGV);
return;
}
if (memcmp(io->read_buf, io->write_buf, io->buf_size)) {
fprintf(stderr, "I/O corrupt, value not the same\n");
pthread_kill(g_fuzz_td, SIGSEGV);
}
sectors_num = spdk_nvme_ns_get_num_sectors(io->io_ns);
io->lba_num = (io->lba_num + 1) % sectors_num;
io->io_processing = false;
}
static void
write_complete(void *arg, const struct spdk_nvme_cpl *completion)
{
int rc = 0;
struct io_thread *io = (struct io_thread *)arg;
if (spdk_nvme_cpl_is_error(completion)) {
spdk_nvme_qpair_print_completion(io->io_qpair,
(struct spdk_nvme_cpl *)completion);
fprintf(stderr, "I/O write error status: %s\n",
spdk_nvme_cpl_get_status_string(&completion->status));
io->io_processing = false;
pthread_kill(g_fuzz_td, SIGSEGV);
return;
}
rc = spdk_nvme_ns_cmd_read(io->io_ns, io->io_qpair,
io->read_buf, io->lba_num, 1,
read_complete, io, 0);
if (rc != 0) {
fprintf(stderr, "starting read I/O failed\n");
io->io_processing = false;
pthread_kill(g_fuzz_td, SIGSEGV);
}
}
static int
io_poller(void *ctx)
{
int ret = 0;
struct io_thread *io = (struct io_thread *)ctx;
size_t i;
unsigned int seed = 0;
int *write_buf = (int *)io->write_buf;
if (io->io_processing) {
spdk_nvme_qpair_process_completions(io->io_qpair, 0);
return SPDK_POLLER_BUSY;
}
if (io->terminate) {
/* detaching controller here cause deadlock */
spdk_poller_unregister(&(io->run_poller));
spdk_free(io->write_buf);
spdk_free(io->read_buf);
spdk_app_stop(0);
return SPDK_POLLER_IDLE;
}
/* Compiler should optimize the "/ sizeof(int)" into a right shift. */
for (i = 0; i < io->buf_size / sizeof(int); i++) {
write_buf[i] = rand_r(&seed);
}
io->io_processing = true;
ret = spdk_nvme_ns_cmd_write(io->io_ns, io->io_qpair,
io->write_buf, io->lba_num, 1,
write_complete, io, 0);
if (ret < 0) {
fprintf(stderr, "starting write I/O failed\n");
pthread_kill(g_fuzz_td, SIGSEGV);
return SPDK_POLLER_IDLE;
}
return SPDK_POLLER_IDLE;
}
static void
start_io_poller(void *ctx)
{
struct io_thread *io = (struct io_thread *)ctx;
io->run_poller = SPDK_POLLER_REGISTER(io_poller, ctx, 0);
if (io->run_poller == NULL) {
fprintf(stderr, "Failed to register a poller for IO.\n");
spdk_app_stop(-1);
pthread_kill(g_fuzz_td, SIGSEGV);
}
}
static void *
init_io(void *ctx)
{
struct spdk_nvme_transport_id trid = {};
int nsid = 0;
snprintf(trid.traddr, sizeof(trid.traddr), "%s", g_io_thread.io_ctrlr_path);
trid.trtype = SPDK_NVME_TRANSPORT_VFIOUSER;
g_io_thread.io_ctrlr = spdk_nvme_connect(&trid, NULL, 0);
if (g_io_thread.io_ctrlr == NULL) {
fprintf(stderr, "spdk_nvme_connect() failed for transport address '%s'\n",
trid.traddr);
spdk_app_stop(-1);
pthread_kill(g_fuzz_td, SIGSEGV);
return NULL;
}
g_io_thread.io_qpair = spdk_nvme_ctrlr_alloc_io_qpair(g_io_thread.io_ctrlr, NULL, 0);
if (g_io_thread.io_qpair == NULL) {
spdk_nvme_detach(g_io_thread.io_ctrlr);
fprintf(stderr, "spdk_nvme_ctrlr_alloc_io_qpair failed\n");
spdk_app_stop(-1);
pthread_kill(g_fuzz_td, SIGSEGV);
return NULL;
}
if (spdk_nvme_ctrlr_get_num_ns(g_io_thread.io_ctrlr) == 0) {
fprintf(stderr, "no namespaces for IO\n");
spdk_app_stop(-1);
pthread_kill(g_fuzz_td, SIGSEGV);
return NULL;
}
nsid = spdk_nvme_ctrlr_get_first_active_ns(g_io_thread.io_ctrlr);
g_io_thread.io_ns = spdk_nvme_ctrlr_get_ns(g_io_thread.io_ctrlr, nsid);
if (!g_io_thread.io_ns) {
fprintf(stderr, "no io_ns for IO\n");
spdk_app_stop(-1);
pthread_kill(g_fuzz_td, SIGSEGV);
return NULL;
}
g_io_thread.buf_size = spdk_nvme_ns_get_sector_size(g_io_thread.io_ns);
g_io_thread.read_buf = spdk_zmalloc(g_io_thread.buf_size, 0x1000, NULL,
SPDK_ENV_SOCKET_ID_ANY, SPDK_MALLOC_DMA);
g_io_thread.write_buf = spdk_zmalloc(g_io_thread.buf_size, 0x1000, NULL,
SPDK_ENV_SOCKET_ID_ANY, SPDK_MALLOC_DMA);
if (!g_io_thread.write_buf || !g_io_thread.read_buf) {
fprintf(stderr, "cannot allocated memory for io buffers\n");
spdk_app_stop(-1);
pthread_kill(g_fuzz_td, SIGSEGV);
return NULL;
}
g_io_thread.thread = spdk_thread_create("io_thread", NULL);
spdk_thread_send_msg(g_io_thread.thread, start_io_poller, &g_io_thread);
return NULL;
}
static void
begin_fuzz(void *ctx)
{
g_reactor_td = pthread_self();
pthread_create(&g_fuzz_td, NULL, start_fuzzer, NULL);
/* posix thread is use to avoid deadlock during spdk_nvme_connect
* vfio-user version negotiation may block when waiting for response
*/
if (g_io_thread.io_ctrlr_path) {
pthread_create(&g_io_thread.io_td, NULL, init_io, NULL);
}
}
static void
vfio_fuzz_usage(void)
{
fprintf(stderr, " -D Path of corpus directory.\n");
fprintf(stderr, " -F Path for ctrlr that should be fuzzed.\n");
fprintf(stderr, " -N Name of reproduction data file.\n");
fprintf(stderr, " -t Time to run fuzz tests (in seconds). Default: 10\n");
fprintf(stderr, " -Y Path of addition controller to perform io.\n");
fprintf(stderr, " -Z Fuzzer to run (0 to %lu)\n", NUM_FUZZERS - 1);
}
static int
vfio_fuzz_parse(int ch, char *arg)
{
long long tmp = 0;
FILE *repro_file = NULL;
switch (ch) {
case 'D':
g_corpus_dir = strdup(optarg);
if (!g_corpus_dir) {
fprintf(stderr, "cannot strdup: %s\n", optarg);
return -ENOMEM;
}
break;
case 'F':
g_ctrlr_path = strdup(optarg);
if (!g_ctrlr_path) {
fprintf(stderr, "cannot strdup: %s\n", optarg);
return -ENOMEM;
}
break;
case 'N':
repro_file = fopen(optarg, "r");
if (repro_file == NULL) {
fprintf(stderr, "could not open %s: %s\n", optarg, spdk_strerror(errno));
return -1;
}
g_repro_data = spdk_posix_file_load(repro_file, &g_repro_size);
if (g_repro_data == NULL) {
fprintf(stderr, "could not load data for file %s\n", optarg);
return -1;
}
break;
case 'Y':
g_io_thread.io_ctrlr_path = strdup(optarg);
if (!g_io_thread.io_ctrlr_path) {
fprintf(stderr, "cannot strdup: %s\n", optarg);
return -ENOMEM;
}
break;
case 't':
case 'Z':
tmp = spdk_strtoll(optarg, 10);
if (tmp < 0 || tmp >= INT_MAX) {
fprintf(stderr, "Invalid value '%s' for option -%c.\n", optarg, ch);
return -EINVAL;
}
switch (ch) {
case 't':
g_time_in_sec = tmp;
break;
case 'Z':
if ((unsigned long)tmp >= NUM_FUZZERS) {
fprintf(stderr, "Invalid fuzz type %lld (max %lu)\n", tmp, NUM_FUZZERS - 1);
return -EINVAL;
}
g_fuzzer = &g_fuzzers[tmp];
break;
}
break;
case '?':
default:
return -EINVAL;
}
return 0;
}
static void
fuzz_shutdown(void)
{
/* If the user terminates the fuzzer prematurely, it is likely due
* to an input hang. So raise a SIGSEGV signal which will cause the
* fuzzer to generate a crash file for the last input.
*
* Note that the fuzzer will always generate a crash file, even if
* we get our TestOneInput() function (which is called by the fuzzer)
* to pthread_exit(). So just doing the SIGSEGV here in all cases is
* simpler than trying to differentiate between hung inputs and
* an impatient user.
*/
pthread_kill(g_fuzz_td, SIGSEGV);
}
int
main(int argc, char **argv)
{
struct spdk_app_opts opts = {};
int rc = 0;
spdk_app_opts_init(&opts, sizeof(opts));
opts.name = "vfio_fuzz";
opts.shutdown_cb = fuzz_shutdown;
if ((rc = spdk_app_parse_args(argc, argv, &opts, "D:F:N:t:Y:Z:", NULL, vfio_fuzz_parse,
vfio_fuzz_usage) != SPDK_APP_PARSE_ARGS_SUCCESS)) {
return rc;
}
if (!g_corpus_dir) {
fprintf(stderr, "Must specify corpus dir with -D option\n");
return -1;
}
if (!g_ctrlr_path) {
fprintf(stderr, "Must specify ctrlr path with -F option\n");
return -1;
}
if (!g_fuzzer) {
fprintf(stderr, "Must specify fuzzer with -Z option\n");
return -1;
}
rc = spdk_app_start(&opts, begin_fuzz, NULL);
spdk_app_fini();
return rc;
}