#!/usr/bin/env python3# SPDX-License-Identifier: GPL-2.0## Program to allow users to fuzz test Hyper-V drivers# by interfacing with Hyper-V debugfs attributes.# Current test methods available:# 1. delay testing## Current file/directory structure of hyper-V debugfs:# /sys/kernel/debug/hyperv/UUID# /sys/kernel/debug/hyperv/UUID/<test-state filename># /sys/kernel/debug/hyperv/UUID/<test-method sub-directory>## author: Branden Bonaby <brandonbonaby94@gmail.com> import osimport cmdimport argparseimport globfrom argparse import RawDescriptionHelpFormatterfrom argparse import RawTextHelpFormatterfrom enum import Enum # Do not change unless, you change the debugfs attributes# in /drivers/hv/debugfs.c. All fuzz testing# attributes will start with "fuzz_test". # debugfs path for hyperv must exist before proceedingdebugfs_hyperv_path = "/sys/kernel/debug/hyperv"if not os.path.isdir(debugfs_hyperv_path): print("{} doesn't exist/check permissions".format(debugfs_hyperv_path)) exit(-1) class dev_state(Enum): off = 0 on = 1 # File names, that correspond to the files created in# /drivers/hv/debugfs.cclass f_names(Enum): state_f = "fuzz_test_state" buff_f = "fuzz_test_buffer_interrupt_delay" mess_f = "fuzz_test_message_delay" # Both single_actions and all_actions are used# for error checking and to allow for some subparser# names to be abbreviated. Do not abbreviate the# test method names, as it will become less intuitive# as to what the user can do. If you do decide to# abbreviate the test method name, make sure the main# function reflects this change. all_actions = [ "disable_all", "D", "enable_all", "view_all", "V"] single_actions = [ "disable_single", "d", "enable_single", "view_single", "v"] def main(): file_map = recursive_file_lookup(debugfs_hyperv_path, dict()) args = parse_args() if (not args.action): print ("Error, no options selected...exiting") exit(-1) arg_set = { k for (k,v) in vars(args).items() if v and k != "action" } arg_set.add(args.action) path = args.path if "path" in arg_set else None if (path and path[-1] == "/"): path = path[:-1] validate_args_path(path, arg_set, file_map) if (path and "enable_single" in arg_set): state_path = locate_state(path, file_map) set_test_state(state_path, dev_state.on.value, args.quiet) # Use subparsers as the key for different actions if ("delay" in arg_set): validate_delay_values(args.delay_time) if (args.enable_all): set_delay_all_devices(file_map, args.delay_time, args.quiet) else: set_delay_values(path, file_map, args.delay_time, args.quiet) elif ("disable_all" in arg_set or "D" in arg_set): disable_all_testing(file_map) elif ("disable_single" in arg_set or "d" in arg_set): disable_testing_single_device(path, file_map) elif ("view_all" in arg_set or "V" in arg_set): get_all_devices_test_status(file_map) elif ("view_single" in arg_set or "v" in arg_set): get_device_test_values(path, file_map) # Get the state locationdef locate_state(device, file_map): return file_map[device][f_names.state_f.value] # Validate delay values to make sure they are acceptable to# enable delays on a devicedef validate_delay_values(delay): if (delay[0] == -1 and delay[1] == -1): print("\nError, At least 1 value must be greater than 0") exit(-1) for i in delay: if (i < -1 or i == 0 or i > 1000): print("\nError, Values must be equal to -1 " "or be > 0 and <= 1000") exit(-1) # Validate argument pathdef validate_args_path(path, arg_set, file_map): if (not path and any(element in arg_set for element in single_actions)): print("Error, path (-p) REQUIRED for the specified option. " "Use (-h) to check usage.") exit(-1) elif (path and any(item in arg_set for item in all_actions)): print("Error, path (-p) NOT REQUIRED for the specified option. " "Use (-h) to check usage." ) exit(-1) elif (path not in file_map and any(item in arg_set for item in single_actions)): print("Error, path '{}' not a valid vmbus device".format(path)) exit(-1) # display Testing status of single devicedef get_device_test_values(path, file_map): for name in file_map[path]: file_location = file_map[path][name] print( name + " = " + str(read_test_files(file_location))) # Create a map of the vmbus devices and their associated files# [key=device, value = [key = filename, value = file path]]def recursive_file_lookup(path, file_map): for f_path in glob.iglob(path + '**/*'): if (os.path.isfile(f_path)): if (f_path.rsplit("/",2)[0] == debugfs_hyperv_path): directory = f_path.rsplit("/",1)[0] else: directory = f_path.rsplit("/",2)[0] f_name = f_path.split("/")[-1] if (file_map.get(directory)): file_map[directory].update({f_name:f_path}) else: file_map[directory] = {f_name:f_path} elif (os.path.isdir(f_path)): recursive_file_lookup(f_path,file_map) return file_map # display Testing state of devicesdef get_all_devices_test_status(file_map): for device in file_map: if (get_test_state(locate_state(device, file_map)) == 1): print("Testing = ON for: {}" .format(device.split("/")[5])) else: print("Testing = OFF for: {}" .format(device.split("/")[5])) # read the vmbus device files, path must be absolute path before callingdef read_test_files(path): try: with open(path,"r") as f: file_value = f.readline().strip() return int(file_value) except IOError as e: errno, strerror = e.args print("I/O error({0}): {1} on file {2}" .format(errno, strerror, path)) exit(-1) except ValueError: print ("Element to int conversion error in: \n{}".format(path)) exit(-1) # writing to vmbus device files, path must be absolute path before callingdef write_test_files(path, value): try: with open(path,"w") as f: f.write("{}".format(value)) except IOError as e: errno, strerror = e.args print("I/O error({0}): {1} on file {2}" .format(errno, strerror, path)) exit(-1) # set testing state of devicedef set_test_state(state_path, state_value, quiet): write_test_files(state_path, state_value) if (get_test_state(state_path) == 1): if (not quiet): print("Testing = ON for device: {}" .format(state_path.split("/")[5])) else: if (not quiet): print("Testing = OFF for device: {}" .format(state_path.split("/")[5])) # get testing state of devicedef get_test_state(state_path): #state == 1 - test = ON #state == 0 - test = OFF return read_test_files(state_path) # write 1 - 1000 microseconds, into a single device using the# fuzz_test_buffer_interrupt_delay and fuzz_test_message_delay# debugfs attributesdef set_delay_values(device, file_map, delay_length, quiet): try: interrupt = file_map[device][f_names.buff_f.value] message = file_map[device][f_names.mess_f.value] # delay[0]- buffer interrupt delay, delay[1]- message delay if (delay_length[0] >= 0 and delay_length[0] <= 1000): write_test_files(interrupt, delay_length[0]) if (delay_length[1] >= 0 and delay_length[1] <= 1000): write_test_files(message, delay_length[1]) if (not quiet): print("Buffer delay testing = {} for: {}" .format(read_test_files(interrupt), interrupt.split("/")[5])) print("Message delay testing = {} for: {}" .format(read_test_files(message), message.split("/")[5])) except IOError as e: errno, strerror = e.args print("I/O error({0}): {1} on files {2}{3}" .format(errno, strerror, interrupt, message)) exit(-1) # enabling delay testing on all devicesdef set_delay_all_devices(file_map, delay, quiet): for device in (file_map): set_test_state(locate_state(device, file_map), dev_state.on.value, quiet) set_delay_values(device, file_map, delay, quiet) # disable all testing on a SINGLE device.def disable_testing_single_device(device, file_map): for name in file_map[device]: file_location = file_map[device][name] write_test_files(file_location, dev_state.off.value) print("ALL testing now OFF for {}".format(device.split("/")[-1])) # disable all testing on ALL devicesdef disable_all_testing(file_map): for device in file_map: disable_testing_single_device(device, file_map) def parse_args(): parser = argparse.ArgumentParser(prog = "vmbus_testing",usage ="\n" "%(prog)s [delay] [-h] [-e|-E] -t [-p]\n" "%(prog)s [view_all | V] [-h]\n" "%(prog)s [disable_all | D] [-h]\n" "%(prog)s [disable_single | d] [-h|-p]\n" "%(prog)s [view_single | v] [-h|-p]\n" "%(prog)s --version\n", description = "\nUse lsvmbus to get vmbus device type " "information.\n" "\nThe debugfs root path is " "/sys/kernel/debug/hyperv", formatter_class = RawDescriptionHelpFormatter) subparsers = parser.add_subparsers(dest = "action") parser.add_argument("--version", action = "version", version = '%(prog)s 0.1.0') parser.add_argument("-q","--quiet", action = "store_true", help = "silence none important test messages." " This will only work when enabling testing" " on a device.") # Use the path parser to hold the --path attribute so it can # be shared between subparsers. Also do the same for the state # parser, as all testing methods will use --enable_all and # enable_single. path_parser = argparse.ArgumentParser(add_help=False) path_parser.add_argument("-p","--path", metavar = "", help = "Debugfs path to a vmbus device. The path " "must be the absolute path to the device.") state_parser = argparse.ArgumentParser(add_help=False) state_group = state_parser.add_mutually_exclusive_group(required = True) state_group.add_argument("-E", "--enable_all", action = "store_const", const = "enable_all", help = "Enable the specified test type " "on ALL vmbus devices.") state_group.add_argument("-e", "--enable_single", action = "store_const", const = "enable_single", help = "Enable the specified test type on a " "SINGLE vmbus device.") parser_delay = subparsers.add_parser("delay", parents = [state_parser, path_parser], help = "Delay the ring buffer interrupt or the " "ring buffer message reads in microseconds.", prog = "vmbus_testing", usage = "%(prog)s [-h]\n" "%(prog)s -E -t [value] [value]\n" "%(prog)s -e -t [value] [value] -p", description = "Delay the ring buffer interrupt for " "vmbus devices, or delay the ring buffer message " "reads for vmbus devices (both in microseconds). This " "is only on the host to guest channel.") parser_delay.add_argument("-t", "--delay_time", metavar = "", nargs = 2, type = check_range, default =[0,0], required = (True), help = "Set [buffer] & [message] delay time. " "Value constraints: -1 == value " "or 0 < value <= 1000.\n" "Use -1 to keep the previous value for that delay " "type, or a value > 0 <= 1000 to change the delay " "time.") parser_dis_all = subparsers.add_parser("disable_all", aliases = ['D'], prog = "vmbus_testing", usage = "%(prog)s [disable_all | D] -h\n" "%(prog)s [disable_all | D]\n", help = "Disable ALL testing on ALL vmbus devices.", description = "Disable ALL testing on ALL vmbus " "devices.") parser_dis_single = subparsers.add_parser("disable_single", aliases = ['d'], parents = [path_parser], prog = "vmbus_testing", usage = "%(prog)s [disable_single | d] -h\n" "%(prog)s [disable_single | d] -p\n", help = "Disable ALL testing on a SINGLE vmbus device.", description = "Disable ALL testing on a SINGLE vmbus " "device.") parser_view_all = subparsers.add_parser("view_all", aliases = ['V'], help = "View the test state for ALL vmbus devices.", prog = "vmbus_testing", usage = "%(prog)s [view_all | V] -h\n" "%(prog)s [view_all | V]\n", description = "This shows the test state for ALL the " "vmbus devices.") parser_view_single = subparsers.add_parser("view_single", aliases = ['v'],parents = [path_parser], help = "View the test values for a SINGLE vmbus " "device.", description = "This shows the test values for a SINGLE " "vmbus device.", prog = "vmbus_testing", usage = "%(prog)s [view_single | v] -h\n" "%(prog)s [view_single | v] -p") return parser.parse_args() # value checking for range checking input in parserdef check_range(arg1): try: val = int(arg1) except ValueError as err: raise argparse.ArgumentTypeError(str(err)) if val < -1 or val > 1000: message = ("\n\nvalue must be -1 or 0 < value <= 1000. " "Value program received: {}\n").format(val) raise argparse.ArgumentTypeError(message) return val if __name__ == "__main__": main()
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
#
# Program to allow users to fuzz test Hyper-V drivers
# by interfacing with Hyper-V debugfs attributes.
# Current test methods available:
# 1. delay testing
#
# Current file/directory structure of hyper-V debugfs:
# /sys/kernel/debug/hyperv/UUID
# /sys/kernel/debug/hyperv/UUID/<test-state filename>
# /sys/kernel/debug/hyperv/UUID/<test-method sub-directory>
#
# author: Branden Bonaby <brandonbonaby94@gmail.com>
import os
import cmd
import argparse
import glob
from argparse import RawDescriptionHelpFormatter
from argparse import RawTextHelpFormatter
from enum import Enum
# Do not change unless, you change the debugfs attributes
# in /drivers/hv/debugfs.c. All fuzz testing
# attributes will start with "fuzz_test".
# debugfs path for hyperv must exist before proceeding
debugfs_hyperv_path = "/sys/kernel/debug/hyperv"
if not os.path.isdir(debugfs_hyperv_path):
print("{} doesn't exist/check permissions".format(debugfs_hyperv_path))
exit(-1)
class dev_state(Enum):
off = 0
on = 1
# File names, that correspond to the files created in
# /drivers/hv/debugfs.c
class f_names(Enum):
state_f = "fuzz_test_state"
buff_f = "fuzz_test_buffer_interrupt_delay"
mess_f = "fuzz_test_message_delay"
# Both single_actions and all_actions are used
# for error checking and to allow for some subparser
# names to be abbreviated. Do not abbreviate the
# test method names, as it will become less intuitive
# as to what the user can do. If you do decide to
# abbreviate the test method name, make sure the main
# function reflects this change.
all_actions = [
"disable_all",
"D",
"enable_all",
"view_all",
"V"
]
single_actions = [
"disable_single",
"d",
"enable_single",
"view_single",
"v"
]
def main():
file_map = recursive_file_lookup(debugfs_hyperv_path, dict())
args = parse_args()
if (not args.action):
print ("Error, no options selected...exiting")
exit(-1)
arg_set = { k for (k,v) in vars(args).items() if v and k != "action" }
arg_set.add(args.action)
path = args.path if "path" in arg_set else None
if (path and path[-1] == "/"):
path = path[:-1]
validate_args_path(path, arg_set, file_map)
if (path and "enable_single" in arg_set):
state_path = locate_state(path, file_map)
set_test_state(state_path, dev_state.on.value, args.quiet)
# Use subparsers as the key for different actions
if ("delay" in arg_set):
validate_delay_values(args.delay_time)
if (args.enable_all):
set_delay_all_devices(file_map, args.delay_time,
args.quiet)
else:
set_delay_values(path, file_map, args.delay_time,
args.quiet)
elif ("disable_all" in arg_set or "D" in arg_set):
disable_all_testing(file_map)
elif ("disable_single" in arg_set or "d" in arg_set):
disable_testing_single_device(path, file_map)
elif ("view_all" in arg_set or "V" in arg_set):
get_all_devices_test_status(file_map)
elif ("view_single" in arg_set or "v" in arg_set):
get_device_test_values(path, file_map)
# Get the state location
def locate_state(device, file_map):
return file_map[device][f_names.state_f.value]
# Validate delay values to make sure they are acceptable to
# enable delays on a device
def validate_delay_values(delay):
if (delay[0] == -1 and delay[1] == -1):
print("\nError, At least 1 value must be greater than 0")
exit(-1)
for i in delay:
if (i < -1 or i == 0 or i > 1000):
print("\nError, Values must be equal to -1 "
"or be > 0 and <= 1000")
exit(-1)
# Validate argument path
def validate_args_path(path, arg_set, file_map):
if (not path and any(element in arg_set for element in single_actions)):
print("Error, path (-p) REQUIRED for the specified option. "
"Use (-h) to check usage.")
exit(-1)
elif (path and any(item in arg_set for item in all_actions)):
print("Error, path (-p) NOT REQUIRED for the specified option. "
"Use (-h) to check usage." )
exit(-1)
elif (path not in file_map and any(item in arg_set
for item in single_actions)):
print("Error, path '{}' not a valid vmbus device".format(path))
exit(-1)
# display Testing status of single device
def get_device_test_values(path, file_map):
for name in file_map[path]:
file_location = file_map[path][name]
print( name + " = " + str(read_test_files(file_location)))
# Create a map of the vmbus devices and their associated files
# [key=device, value = [key = filename, value = file path]]
def recursive_file_lookup(path, file_map):
for f_path in glob.iglob(path + '**/*'):
if (os.path.isfile(f_path)):
if (f_path.rsplit("/",2)[0] == debugfs_hyperv_path):
directory = f_path.rsplit("/",1)[0]
else:
directory = f_path.rsplit("/",2)[0]
f_name = f_path.split("/")[-1]
if (file_map.get(directory)):
file_map[directory].update({f_name:f_path})
else:
file_map[directory] = {f_name:f_path}
elif (os.path.isdir(f_path)):
recursive_file_lookup(f_path,file_map)
return file_map
# display Testing state of devices
def get_all_devices_test_status(file_map):
for device in file_map:
if (get_test_state(locate_state(device, file_map)) == 1):
print("Testing = ON for: {}"
.format(device.split("/")[5]))
else:
print("Testing = OFF for: {}"
.format(device.split("/")[5]))
# read the vmbus device files, path must be absolute path before calling
def read_test_files(path):
try:
with open(path,"r") as f:
file_value = f.readline().strip()
return int(file_value)
except IOError as e:
errno, strerror = e.args
print("I/O error({0}): {1} on file {2}"
.format(errno, strerror, path))
exit(-1)
except ValueError:
print ("Element to int conversion error in: \n{}".format(path))
exit(-1)
# writing to vmbus device files, path must be absolute path before calling
def write_test_files(path, value):
try:
with open(path,"w") as f:
f.write("{}".format(value))
except IOError as e:
errno, strerror = e.args
print("I/O error({0}): {1} on file {2}"
.format(errno, strerror, path))
exit(-1)
# set testing state of device
def set_test_state(state_path, state_value, quiet):
write_test_files(state_path, state_value)
if (get_test_state(state_path) == 1):
if (not quiet):
print("Testing = ON for device: {}"
.format(state_path.split("/")[5]))
else:
if (not quiet):
print("Testing = OFF for device: {}"
.format(state_path.split("/")[5]))
# get testing state of device
def get_test_state(state_path):
#state == 1 - test = ON
#state == 0 - test = OFF
return read_test_files(state_path)
# write 1 - 1000 microseconds, into a single device using the
# fuzz_test_buffer_interrupt_delay and fuzz_test_message_delay
# debugfs attributes
def set_delay_values(device, file_map, delay_length, quiet):
try:
interrupt = file_map[device][f_names.buff_f.value]
message = file_map[device][f_names.mess_f.value]
# delay[0]- buffer interrupt delay, delay[1]- message delay
if (delay_length[0] >= 0 and delay_length[0] <= 1000):
write_test_files(interrupt, delay_length[0])
if (delay_length[1] >= 0 and delay_length[1] <= 1000):
write_test_files(message, delay_length[1])
if (not quiet):
print("Buffer delay testing = {} for: {}"
.format(read_test_files(interrupt),
interrupt.split("/")[5]))
print("Message delay testing = {} for: {}"
.format(read_test_files(message),
message.split("/")[5]))
except IOError as e:
errno, strerror = e.args
print("I/O error({0}): {1} on files {2}{3}"
.format(errno, strerror, interrupt, message))
exit(-1)
# enabling delay testing on all devices
def set_delay_all_devices(file_map, delay, quiet):
for device in (file_map):
set_test_state(locate_state(device, file_map),
dev_state.on.value,
quiet)
set_delay_values(device, file_map, delay, quiet)
# disable all testing on a SINGLE device.
def disable_testing_single_device(device, file_map):
for name in file_map[device]:
file_location = file_map[device][name]
write_test_files(file_location, dev_state.off.value)
print("ALL testing now OFF for {}".format(device.split("/")[-1]))
# disable all testing on ALL devices
def disable_all_testing(file_map):
for device in file_map:
disable_testing_single_device(device, file_map)
def parse_args():
parser = argparse.ArgumentParser(prog = "vmbus_testing",usage ="\n"
"%(prog)s [delay] [-h] [-e|-E] -t [-p]\n"
"%(prog)s [view_all | V] [-h]\n"
"%(prog)s [disable_all | D] [-h]\n"
"%(prog)s [disable_single | d] [-h|-p]\n"
"%(prog)s [view_single | v] [-h|-p]\n"
"%(prog)s --version\n",
description = "\nUse lsvmbus to get vmbus device type "
"information.\n" "\nThe debugfs root path is "
"/sys/kernel/debug/hyperv",
formatter_class = RawDescriptionHelpFormatter)
subparsers = parser.add_subparsers(dest = "action")
parser.add_argument("--version", action = "version",
version = '%(prog)s 0.1.0')
parser.add_argument("-q","--quiet", action = "store_true",
help = "silence none important test messages."
" This will only work when enabling testing"
" on a device.")
# Use the path parser to hold the --path attribute so it can
# be shared between subparsers. Also do the same for the state
# parser, as all testing methods will use --enable_all and
# enable_single.
path_parser = argparse.ArgumentParser(add_help=False)
path_parser.add_argument("-p","--path", metavar = "",
help = "Debugfs path to a vmbus device. The path "
"must be the absolute path to the device.")
state_parser = argparse.ArgumentParser(add_help=False)
state_group = state_parser.add_mutually_exclusive_group(required = True)
state_group.add_argument("-E", "--enable_all", action = "store_const",
const = "enable_all",
help = "Enable the specified test type "
"on ALL vmbus devices.")
state_group.add_argument("-e", "--enable_single",
action = "store_const",
const = "enable_single",
help = "Enable the specified test type on a "
"SINGLE vmbus device.")
parser_delay = subparsers.add_parser("delay",
parents = [state_parser, path_parser],
help = "Delay the ring buffer interrupt or the "
"ring buffer message reads in microseconds.",
prog = "vmbus_testing",
usage = "%(prog)s [-h]\n"
"%(prog)s -E -t [value] [value]\n"
"%(prog)s -e -t [value] [value] -p",
description = "Delay the ring buffer interrupt for "
"vmbus devices, or delay the ring buffer message "
"reads for vmbus devices (both in microseconds). This "
"is only on the host to guest channel.")
parser_delay.add_argument("-t", "--delay_time", metavar = "", nargs = 2,
type = check_range, default =[0,0], required = (True),
help = "Set [buffer] & [message] delay time. "
"Value constraints: -1 == value "
"or 0 < value <= 1000.\n"
"Use -1 to keep the previous value for that delay "
"type, or a value > 0 <= 1000 to change the delay "
"time.")
parser_dis_all = subparsers.add_parser("disable_all",
aliases = ['D'], prog = "vmbus_testing",
usage = "%(prog)s [disable_all | D] -h\n"
"%(prog)s [disable_all | D]\n",
help = "Disable ALL testing on ALL vmbus devices.",
description = "Disable ALL testing on ALL vmbus "
"devices.")
parser_dis_single = subparsers.add_parser("disable_single",
aliases = ['d'],
parents = [path_parser], prog = "vmbus_testing",
usage = "%(prog)s [disable_single | d] -h\n"
"%(prog)s [disable_single | d] -p\n",
help = "Disable ALL testing on a SINGLE vmbus device.",
description = "Disable ALL testing on a SINGLE vmbus "
"device.")
parser_view_all = subparsers.add_parser("view_all", aliases = ['V'],
help = "View the test state for ALL vmbus devices.",
prog = "vmbus_testing",
usage = "%(prog)s [view_all | V] -h\n"
"%(prog)s [view_all | V]\n",
description = "This shows the test state for ALL the "
"vmbus devices.")
parser_view_single = subparsers.add_parser("view_single",
aliases = ['v'],parents = [path_parser],
help = "View the test values for a SINGLE vmbus "
"device.",
description = "This shows the test values for a SINGLE "
"vmbus device.", prog = "vmbus_testing",
usage = "%(prog)s [view_single | v] -h\n"
"%(prog)s [view_single | v] -p")
return parser.parse_args()
# value checking for range checking input in parser
def check_range(arg1):
try:
val = int(arg1)
except ValueError as err:
raise argparse.ArgumentTypeError(str(err))
if val < -1 or val > 1000:
message = ("\n\nvalue must be -1 or 0 < value <= 1000. "
"Value program received: {}\n").format(val)
raise argparse.ArgumentTypeError(message)
return val
if __name__ == "__main__":
main()