test: add a fuzzer for the vhost API

Using the vhost_user API on the initiator side, we can craft arbitrary
requests to fuzz the vhost target APIs. This script currently supports
vhost_blk, but will support both vhost_blk and vhost_scsi.

Change-Id: I7f0af6ca2adabbc18b7029ea77b33f47fce9c16b
Signed-off-by: Seth Howell <seth.howell@intel.com>
Reviewed-on: https://review.gerrithub.io/c/spdk/spdk/+/454682
Reviewed-by: Darek Stojaczyk <dariusz.stojaczyk@intel.com>
Reviewed-by: Shuhei Matsumoto <shuhei.matsumoto.xt@hitachi.com>
Tested-by: SPDK CI Jenkins <sys_sgci@intel.com>
This commit is contained in:
Seth Howell 2019-05-15 15:49:30 -07:00 committed by Darek Stojaczyk
parent 733262359a
commit 66203a88d6
11 changed files with 1664 additions and 0 deletions

View File

@ -231,6 +231,7 @@ if [ $SPDK_RUN_FUNCTIONAL_TEST -eq 1 ]; then
run_test suite ./test/vhost/initiator/blockdev.sh
run_test suite ./test/spdkcli/virtio.sh
run_test suite ./test/vhost/shared/shared.sh
run_test suite ./test/vhost/fuzz/fuzz.sh
report_test_completion "vhost_initiator"
timing_exit vhost_initiator
fi

View File

@ -36,6 +36,9 @@ include $(SPDK_ROOT_DIR)/mk/spdk.common.mk
DIRS-y += nvme_fuzz
ifeq ($(OS),Linux)
DIRS-$(CONFIG_VIRTIO) += vhost_fuzz
endif
.PHONY: all clean $(DIRS-y)
all: $(DIRS-y)

106
test/app/fuzz/common/fuzz_rpc.py Executable file
View File

@ -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)

1
test/app/fuzz/vhost_fuzz/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
vhost_fuzz

View File

@ -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

View File

@ -0,0 +1,45 @@
# 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 start_subsystem_init
~~~

View File

@ -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
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -0,0 +1,114 @@
/*-
* 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);
if (w == NULL) {
fprintf(stderr, "Unable to allocate an rpc response.\n");
free(req.socket);
return;
}
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);

70
test/vhost/fuzz/fuzz.sh Executable file
View File

@ -0,0 +1,70 @@
#!/usr/bin/env bash
set -e
rootdir=$(readlink -f $(dirname $0))/../../..
source $rootdir/test/common/autotest_common.sh
source "$rootdir/scripts/common.sh"
VHOST_APP="$rootdir/app/vhost/vhost -p 0"
FUZZ_RPC_SOCK="/var/tmp/spdk_fuzz.sock"
FUZZ_APP="$rootdir/test/app/fuzz/vhost_fuzz/vhost_fuzz -r $FUZZ_RPC_SOCK --wait-for-rpc"
vhost_rpc_py="$rootdir/scripts/rpc.py"
fuzz_generic_rpc_py="$rootdir/scripts/rpc.py -s $FUZZ_RPC_SOCK"
fuzz_specific_rpc_py="$rootdir/test/app/fuzz/common/fuzz_rpc.py -s $FUZZ_RPC_SOCK"
timing_enter fuzz_test
$VHOST_APP >$output_dir/vhost_fuzz_tgt_output.txt 2>&1 &
vhostpid=$!
waitforlisten $vhostpid
trap "killprocess $vhostpid; exit 1" SIGINT SIGTERM exit
$FUZZ_APP -t 10 2>$output_dir/vhost_fuzz_output1.txt &
fuzzpid=$!
waitforlisten $fuzzpid $FUZZ_RPC_SOCK
trap "killprocess $vhostpid; killprocess $fuzzpid; exit 1" SIGINT SIGTERM exit
$vhost_rpc_py construct_malloc_bdev -b Malloc0 64 512
$vhost_rpc_py construct_vhost_blk_controller Vhost.1 Malloc0
$vhost_rpc_py construct_malloc_bdev -b Malloc1 64 512
$vhost_rpc_py construct_vhost_scsi_controller naa.VhostScsi0.1
$vhost_rpc_py add_vhost_scsi_lun naa.VhostScsi0.1 0 Malloc1
$vhost_rpc_py construct_malloc_bdev -b Malloc2 64 512
$vhost_rpc_py construct_vhost_scsi_controller naa.VhostScsi0.2
$vhost_rpc_py add_vhost_scsi_lun naa.VhostScsi0.2 0 Malloc2
# test the vhost blk controller with valid data buffers.
$fuzz_specific_rpc_py fuzz_vhost_create_dev -s `pwd`/Vhost.1 -b -v
# test the vhost scsi I/O queue with valid data buffers on a valid lun.
$fuzz_specific_rpc_py fuzz_vhost_create_dev -s `pwd`/naa.VhostScsi0.1 -l -v
# test the vhost scsi management queue with valid data buffers.
$fuzz_specific_rpc_py fuzz_vhost_create_dev -s `pwd`/naa.VhostScsi0.2 -v -m
# The test won't actually begin until this option is passed in.
$fuzz_generic_rpc_py start_subsystem_init
wait $fuzzpid
$FUZZ_APP -j $rootdir/test/app/fuzz/vhost_fuzz/example.json 2>$output_dir/vhost_fuzz_output2.txt &
fuzzpid=$!
waitforlisten $fuzzpid $FUZZ_RPC_SOCK
# re-evaluate fuzzpid
trap "killprocess $vhostpid; killprocess $fuzzpid; exit 1" SIGINT SIGTERM exit
$fuzz_specific_rpc_py fuzz_vhost_create_dev -s `pwd`/Vhost.1 -b -v
$fuzz_specific_rpc_py fuzz_vhost_create_dev -s `pwd`/naa.VhostScsi0.1 -l -v
$fuzz_specific_rpc_py fuzz_vhost_create_dev -s `pwd`/naa.VhostScsi0.2 -v -m
$fuzz_generic_rpc_py start_subsystem_init
wait $fuzzpid
trap - SIGINT SIGTERM exit
killprocess $vhostpid
killprocess $fuzzpid
timing_exit fuzz_test