From b12fdcf838bb2c8b6726900ab62ef706d686a3c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6gel?= <hoegma01@thu.de> Date: Sun, 16 Jun 2024 00:49:08 +0200 Subject: [PATCH 01/19] Add skeletton code of GreyBoxFuzzer --- .../fuzzing/fuzzer_utils/GreyBoxFuzzer.py | 29 +++ .../fuzzer_utils/fuzzer_tools/Mutator.py | 204 ++++++++++++++++++ .../fuzzer_tools/PowerSchedule.py | 42 ++++ .../fuzzing/fuzzer_utils/fuzzer_tools/Seed.py | 36 ++++ 4 files changed, 311 insertions(+) create mode 100644 custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py create mode 100644 custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py create mode 100644 custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/PowerSchedule.py create mode 100644 custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py diff --git a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py new file mode 100644 index 00000000..1838b26e --- /dev/null +++ b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py @@ -0,0 +1,29 @@ +from typing import Callable + +class GreyBoxFuzzer: + """GreyBox fuzzer class, inherits from the abstract fuzzer class.""" + + def __init__(self): + """initialize GreyBoxFuzzer""" + print("Initialize GreyBoxFuzzer") + + def fuzz(self, seed_template: list, function: Callable, rounds: int = 1): + """The function returns a list of the number of passed and failed tests. + The seed is changed randomly in any number of rounds (defined by rounds). + + :param seed_template: The seed_template is a list of input types for the function. + The entries must correspond to the valid function parameters of the function to be tested. + e.g.: ["INT", "FLOAT", "STRING", "BOOLEAN"] + :type seed_template: list + :param function: The function to test + :type function: Callable + :param rounds: SSpecifies how often the function should be tested with different inputs. The default is 1. + :type rounds: int + + :return: Returns a list indicating how many tests were successful and how many failed. + :rtype: list + """ + print("Fuzzing...") + + + diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py new file mode 100644 index 00000000..e679485c --- /dev/null +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py @@ -0,0 +1,204 @@ +from random import random +import math + +class Mutator: + def __init__(self): + """initialize Mutator""" + print("Initialize Mutator") + + def delete_random_char(self, string: str) -> str: + """Returns string with a random character deleted. + + This function takes a string `string` as input and returns a new string + with one random character removed from it. + + :param string: Any string from which a character is to be removed. + :type string: str + + :return: Returns the input string `string` with one randomly chosen character deleted. + :rtype: str + """ + if string == "": + # If the string is empty, there's no character to delete, so return the empty string. + return string + + # Generate a random integer position within the range of the string's indices. + pos = random.randint(0, len(string) - 1) + + # Create a new string by excluding the character at the random position. + # This is done by concatenating the substring before the random position and + # the substring after the random position. + return string[:pos] + string[pos + 1 :] + + def insert_random_char(self, string: str) -> str: + """Returns string with a random character inserted. + + This function takes a string `string` as input and returns a new string + with a random character inserted at a random position within the string. + + :param string: Any string where a character is to be inserted. + :type string: str + + :return: Returns the input string `string` with one randomly chosen character inserted at a random position. + :rtype: str + """ + # Generate a random position within the range of the string's length (including the end of the string). + pos = random.randint(0, len(string)) + + # Generate a random character from the ASCII range 32 to 126 (printable characters). + random_character = chr(random.randrange(32, 127)) + + # Create a new string by inserting the random character at the random position. + # This is done by concatenating the substring before the random position, the random character, + # and the substring after the random position. + return string[:pos] + random_character + string[pos:] + + def flip_random_char(self, string: str) -> str: + """Returns string with a random bit flipped in a random position. + + This function takes a string `string` as input and returns a new string + where one randomly chosen character has one of its bits flipped + at a random bit position. + + :param string: Any string where a character's bit is to be flipped. + :type string: str + + :return: Returns the input string `string` with one character's bit flipped at a random position. + :rtype: str + """ + if string == "": + # If the string is empty, there's no character to flip, so return the empty string. + return string + + # Generate a random integer position within the range of the string's indices. + pos = random.randint(0, len(string) - 1) + + # Get the character at the randomly chosen position. + c = string[pos] + + # Generate a random bit position between 0 and 6 (since we are assuming 7-bit ASCII characters). + bit = 1 << random.randint(0, 6) + + # Flip the bit at the generated bit position using XOR. + new_c = chr(ord(c) ^ bit) + + # Create a new string by replacing the character at the random position with the new character. + # This is done by concatenating the substring before the random position, the new character, + # and the substring after the random position. + return string[:pos] + new_c + string[pos + 1 :] + + def get_random_float(self) -> float: + """Returns a random float value modified by a randomly chosen multiplier. + + This function generates a random float value between 0.0 and 1.0, and then + multiplies it by a randomly selected value from the list `self.__multiplier`. + + :return: A random positiv float value. + :rtype: float + """ + # Generate a random float between 0.0 and 1.0. + random_float = random.random() + + # Multiply the random float by a randomly chosen multiplier from the list `self.__multiplier`. + random_float *= random.choice(self.__multiplier) + + # Return the modified random float. + return random_float + + def check_inf(self, number: float) -> float: + """Checks if the number is infinite and replaces it with a random value if true. + + This function takes a floating-point number `number` as input. If the number is + positive or negative infinity, it replaces the number with a random value between + 0.0 and 1.0. It also logs this replacement. + + :param number: The number to check for infinity. + :type number: float + + :return: Returns the original number if it is not finite; otherwise, returns a random value between 0.0 and 1.0. + :rtype: float + """ + if math.isinf(number): + # If the number is infinite, replace it with a random value between 0.0 and 1.0. + number = random.random() + self.__logger.debug( + "The return value would be - or + INF, set it to a random value between 0.0 and 1.0" + ) + + # Return the potentially modified number. + return number + + def add_random_number(self, number: float) -> float: + """Returns the input number with a random float added. + + This function takes a floating-point number `number` as input and adds + a random float to it. The random float is obtained from the private method + `__get_random_float`. + + :param number: The number to which a random float will be added. + :type number: float + + :return: Returns the input number `number` with an added random float, + after ensuring the result is not infinite using the `__check_inf` method. + :rtype: float + """ + number += self.__get_random_float() + + # Check if the resulting number is infinite. + return self.__check_inf(number) + + def sub_random_number(self, number: float) -> float: + """Subtracts a random float from the given number. + + This function takes a float `number` as input and subtracts a randomly + generated float from it. The resulting number is then checked for + infinity values using the `__check_inf` method. + + :param number: The input number from which a random float will be subtracted. + :type number: float + + :return: Returns the resulting number after subtracting a random float and checking for infinity. + :rtype: float + """ + number -= self.__get_random_float() + + # Check if the resulting number is infinite. + return self.__check_inf(number) + + def mult_random_number(self, number: float) -> float: + """Returns the result of multiplying the input number by a random float. + + This function takes a floating-point number `number` as input and returns + a new floating-point number which is the result of multiplying the input + number by a randomly generated float. It also checks if the result is + infinite. + + :param number: A floating-point number to be multiplied by a random float. + :type number: float + + :return: Returns the input number multiplied by a random float, + after checking if the result is infinite. + :rtype: float + """ + number *= self.__get_random_float() + + # Check if the resulting number is infinite. + return self.__check_inf(number) + + def div_random_number(self, number: float) -> float: + """Divides the input number by a randomly generated float. + + This function takes a float `number` as input and divides it by + a random float generated by the `__get_random_float` method. It then + returns the result of this division after checking for infinity. + + :param number: The float number to be divided. + :type number: float + + :return: Returns the input number divided by a random float. + :rtype: float + """ + number /= self.__get_random_float() + + # Check if the resulting number is infinite. + return self.__check_inf(number) \ No newline at end of file diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/PowerSchedule.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/PowerSchedule.py new file mode 100644 index 00000000..ff85b12d --- /dev/null +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/PowerSchedule.py @@ -0,0 +1,42 @@ +from typing import List +from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed + +class PowerSchedule: + + def __init__(self): + """initialize PowerSchedule""" + print("Initialize PowerSchedule") + + def assignEnergy(self, population: List[Seed]) -> None: + """Assigns energy to seeds. + + This function takes a population of Seeds and assigns to every seed an energy of 1. + + :param population: List of type Seed. + :type population: list + """ + for seed in population: + seed.energy = 1 + + def normalizedEnergy(self, population: List[Seed]) -> None: + """Normalize energy of all seeds. + + This function takes a population of Seeds and normalizes the energy by dividing the + energy of every seed through sum of all energy values. + + :param population: List of type Seed. + :type population: list + """ + + def choose(self, population: List[Seed]) -> Seed: + """Returns a seed that was selected based on the energy. + + This function takes a population of Seeds and returns and selects a Seed by the energy of the seed. + The higher the energy, the more likely it is that the seed will be selected. + + :param population: List of type Seed. + :type population: list + + :return: Returns a seed. + :rtype: Seed + """ \ No newline at end of file diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py new file mode 100644 index 00000000..cfd65e05 --- /dev/null +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py @@ -0,0 +1,36 @@ +from typing import List + +class Seed: + + energy = 0 + seed_values = [] + + def __init__(self, energy: int = 0, seed_values: list = []): + """initialize PowerSchedule""" + self.energy = energy + self.seed_values = seed_values + + +class SeedManager: + + def __init__(self): + """initialize PowerSchedule""" + + def create_random_seed_population(self, seed_template: list, amount_seeds: int) -> List[Seed]: + """Returns a population of seeds with random values specified by the seed_template. + + This function takes a list 'seed_template' an creates random seeds based on the seed template. + The number of random seeds is specified by 'amount_seeds'. A list of the random seeds is returned. + + :param seed_template: A list of the data types of the seeds. + :type seed_template: list + + :param amount_seeds: Amount of random seeds which will be created. + :type amount_seeds: int + + :return: Returns a list of random seed objects. + :rtype: list + """ + + + From edfe5dddcc76988e0544f9210aa8265c2a467ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6gel?= <hoegma01@thu.de> Date: Sun, 16 Jun 2024 13:39:45 +0200 Subject: [PATCH 02/19] Extended GreyBoxFuzzers + GreyBoxFuzzer tools - Added DataTypeCreator: Necessary for creating certain data types - Extended SeedManager: Population can be created --- .../fuzzing/fuzzer_utils/GreyBoxFuzzer.py | 10 +- .../test/fuzzing/fuzzer_utils/ParamRunner.py | 4 +- .../fuzzer_tools/DataTypeCreator.py | 149 ++++++++++++++++++ .../fuzzing/fuzzer_utils/fuzzer_tools/Seed.py | 81 ++++++++++ .../grey_box/test_grey_box_on_helpers.py | 54 +++++++ 5 files changed, 293 insertions(+), 5 deletions(-) create mode 100644 custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py create mode 100644 custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py diff --git a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py index 1838b26e..42918382 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py +++ b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py @@ -1,13 +1,15 @@ -from typing import Callable +from custom_components.test.fuzzing.fuzzer_utils.Fuzzer import Fuzzer +from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed +from typing import Callable, List -class GreyBoxFuzzer: +class GreyBoxFuzzer(Fuzzer): """GreyBox fuzzer class, inherits from the abstract fuzzer class.""" def __init__(self): """initialize GreyBoxFuzzer""" print("Initialize GreyBoxFuzzer") - def fuzz(self, seed_template: list, function: Callable, rounds: int = 1): + def fuzz(self, seed_population: List[Seed], seed_template: list, function: Callable, rounds: int = 1): """The function returns a list of the number of passed and failed tests. The seed is changed randomly in any number of rounds (defined by rounds). @@ -23,6 +25,8 @@ def fuzz(self, seed_template: list, function: Callable, rounds: int = 1): :return: Returns a list indicating how many tests were successful and how many failed. :rtype: list """ + for seed in seed_population: + print(seed.seed_values) print("Fuzzing...") diff --git a/custom_components/test/fuzzing/fuzzer_utils/ParamRunner.py b/custom_components/test/fuzzing/fuzzer_utils/ParamRunner.py index 2d1a0674..d24016eb 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/ParamRunner.py +++ b/custom_components/test/fuzzing/fuzzer_utils/ParamRunner.py @@ -61,9 +61,9 @@ def run(self, function, param_set: list) -> dict: self.__logger.error(f"Exception: {e}") if test_results["failed_tests"] > 0: - self.__logger.error(f"Summary: {test_results["failed_tests"]} param_sets failed for the function {function.__name__}") + self.__logger.error(f"Summary: {test_results['failed_tests']} param_sets failed for the function {function.__name__}") else: - self.__logger.info(f"Summary: {test_results["passed_tests"]} param_sets passed for the function {function.__name__}") + self.__logger.info(f"Summary: {test_results['passed_tests']} param_sets passed for the function {function.__name__}") return test_results diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py new file mode 100644 index 00000000..83daa818 --- /dev/null +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py @@ -0,0 +1,149 @@ +import random +import string + +class DataTypeCreator: + + def __init__(self): + """initialize DataTypeCreator""" + + def create_int(self, amount_digits: int) -> int: + """Returns an int value with a certain number of digits. + + This function takes a value 'amount_digits' and returns an integer with this amount of digits. + + :param amount_digits: Amount of digits the integer should have + :type amount_digits: int + + :return: Returns an integer with a certain amount of digits. + :rtype: int + """ + seed_value = '' + for digit in range(amount_digits): + if digit == 0: + # Decide if negative of positive int + rand_val = random.randint(0,1) + if rand_val == 0: + seed_value += '-' + # First digit should not be a 0 + rand_val = str(random.randint(1,9)) + seed_value += rand_val + else: + rand_val = str(random.randint(0,9)) + seed_value += rand_val + + # cast to int type and append to seed + if digit == amount_digits-1: + return int(seed_value) + + def create_uint(self, amount_digits: int) -> int: + """Returns an uint value with a certain number of digits. + + This function takes a value 'amount_digits' and returns an unsigned integer with this amount of digits. + + :param amount_digits: Amount of digits the unsigned integer should have + :type amount_digits: uint + + :return: Returns an unsigned integer with a certain amount of digits. + :rtype: int + """ + seed_value = '' + for digit in range(amount_digits): + if digit == 0: + # First digit should not be a 0 + rand_val = str(random.randint(1,9)) + seed_value += rand_val + else: + rand_val = str(random.randint(0,9)) + seed_value += rand_val + + # cast to int type and append to seed + if digit == amount_digits-1: + return int(seed_value) + + def create_float(self, amount_digits: int) -> int: + """Returns an int value with a certain number of digits. + + This function takes a value 'amount_digits' and returns an integer with this amount of digits. + + :param amount_digits: Amount of digits the integer should have + :type amount_digits: int + + :return: Returns an integer with a certain amount of digits. + :rtype: int + """ + print("create float") + + def create_string_only_letters(self, amount_chars: int) -> int: + """Returns an string with a certain number of chars. + + This function takes a value 'amount_chars' and returns an string with this amount of chars. + + :param amount_chars: Amount of chars the string should have + :type amount_chars: int + + :return: Returns an string with a certain amount of chars. + :rtype: string + """ + seed_value = '' + for character in range(amount_chars): + random_letter = random.choice(string.ascii_letters) + + seed_value += random_letter + + # cast to int type and append to seed + if character == amount_chars-1: + return seed_value + + def create_string_special_characters(self, amount_chars: int) -> int: + """Returns an string with a certain number of chars. + + This function takes a value 'amount_chars' and returns an string with this amount of chars. + The string includes uppercase and lowercase letters and special charakters. + Special charakters = "!@#$%^&*()_+-=[]{}|;:',.<>?/`~". + + :param amount_chars: Amount of chars the string should have + :type amount_chars: int + + :return: Returns an string with a certain amount of chars. + :rtype: string + """ + special_characters = "!@#$%^&*()_+-=[]{}|;:',.<>?/`~" + seed_value = '' + for character in range(amount_chars): + rand_value = random.randint(0,4) + if rand_value == 0: + random_letter = random.choice(special_characters) + else: + random_letter = random.choice(string.ascii_letters) + + seed_value += random_letter + + # cast to int type and append to seed + if character == amount_chars-1: + return seed_value + + def create_random_string(self, amount_chars: int) -> int: + """Returns an string with a certain number of chars. + + This function takes a value 'amount_chars' and returns an string with this amount of chars. + + :param amount_chars: Amount of chars the string should have + :type amount_chars: int + + :return: Returns an string with a certain amount of chars. + :rtype: string + """ + + def create_byte(self, amount_digits: int) -> int: + """Returns an int value with a certain number of digits. + + This function takes a value 'amount_digits' and returns an integer with this amount of digits. + + :param amount_digits: Amount of digits the integer should have + :type amount_digits: int + + :return: Returns an integer with a certain amount of digits. + :rtype: int + """ + print("create byte") + diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py index cfd65e05..0585920c 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py @@ -1,4 +1,8 @@ from typing import List +from custom_components.test.fuzzing.fuzzer_utils.ValuePoolFuzzer import ValuePoolFuzzer +from custom_components.test.fuzzing.fuzzer_utils.ParamRunner import ParamRunner +from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.DataTypeCreator import DataTypeCreator +import random class Seed: @@ -13,6 +17,10 @@ def __init__(self, energy: int = 0, seed_values: list = []): class SeedManager: + __value_pool_fuzzer = ValuePoolFuzzer() + __param_runner = ParamRunner() + __data_type_creator = DataTypeCreator() + def __init__(self): """initialize PowerSchedule""" @@ -31,6 +39,79 @@ def create_random_seed_population(self, seed_template: list, amount_seeds: int) :return: Returns a list of random seed objects. :rtype: list """ + param_combi = random.randint(1, len(seed_template)) + param_set = self.__value_pool_fuzzer.fuzz(len(seed_template),seed_template, param_combi) + param_set = self.__param_runner.limit_param_set(param_set, amount_seeds) + + seed_population = [] + for param in param_set: + seed = Seed(energy=1, seed_values=param) + seed_population.append(seed) + + return seed_population + + def create_specific_seed_population(self, seed_template: list, seed_specification: list, amount_seeds: int) -> List[Seed]: + """Returns a population of seeds with specific values based on the seed template and seed specifiction. + + This function takes two list 'seed_template' and 'seed_specification' and creates seeds. + The number of specific seeds is specified by 'amount_seeds'. A list of the random seeds is returned. + + :param seed_template: A list of the data types of the seeds. + Supported data types: "INT", "UINT", "FLOAT", "STRING", "BOOL", "BYTE", "LIST", "DICT", "DATE", + E.g.: ["STRING", "INT", "INT"] + :type seed_template: list + + :param seed_specification: A list that provides the number of digits for each data type in seed_template. + If a random data type is to be initialised anyway, this must be marked with an 'r'. + E.g.: [5, 2, 'r'] + :type seed_specification: list + + :param amount_seeds: Amount of specific seeds which will be created. + :type amount_seeds: int + + :return: Returns a list of specific seed objects. + :rtype: list + """ + param_combi = random.randint(1, len(seed_template)) + + # Throw exception if seed_specification and seed_template aren't the same length + if len(seed_template) != len(seed_specification): + raise ValueError("Length of seed_template and seed_specification must be the same length.") + + seed_population = [] + + for _ in range(amount_seeds): + param_set = [] + for seed_spec, data_type in zip(seed_specification, seed_template): + if data_type == "INT": + param_set.append(self.__data_type_creator.create_int(seed_spec)) + elif data_type == "UINT": + param_set.append(self.__data_type_creator.create_uint(seed_spec)) + elif data_type == "FLOAT": + print("create_float") + elif data_type == "STRING": + rand_val = random.randint(0,1) + if rand_val == 0: + param_set.append(self.__data_type_creator.create_string_only_letters(seed_spec)) + elif rand_val == 1: + param_set.append(self.__data_type_creator.create_string_special_characters(seed_spec)) + elif data_type == "BOOL": + param_set.append(random.choice([True, False])) + elif data_type == "BYTE": + print("create_byte") + elif data_type == "LIST": + print("create_list") + elif data_type == "DICT": + print("create_dict") + elif data_type == "DATE": + print("create_date") + + seed = Seed(1, param_set) + seed_population.append(seed) + + return seed_population + + diff --git a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py new file mode 100644 index 00000000..81de8a4c --- /dev/null +++ b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py @@ -0,0 +1,54 @@ +from custom_components.test.fuzzing.fuzzer_utils.GreyBoxFuzzer import GreyBoxFuzzer +from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import SeedManager, Seed +from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.DataTypeCreator import DataTypeCreator + +from custom_components.loxone.helpers import ( + map_range, + hass_to_lox, + lox_to_hass, + lox2lox_mapped, + lox2hass_mapped, + to_hass_color_temp, + to_loxone_color_temp, + get_room_name_from_room_uuid, + get_cat_name_from_cat_uuid, + add_room_and_cat_to_value_values, + get_miniserver_type, + get_all, +) + +# Function to test the grey box fuzzer +def crashme(s: str) -> None: + cnt = 0 + if len(s) > 0 and s[0] == 'b': + cnt += 1 + if len(s) > 1 and s[1] == 'a': + cnt += 1 + if len(s) > 2 and s[2] == 'd': + cnt += 1 + if len(s) > 3 and s[3] == '!': + cnt += 1 + if cnt >= 3: + raise Exception() + + +seed_manager = SeedManager() +grey_box_fuzzer = GreyBoxFuzzer() + +# seed specification + +seed_template = ["STRING"] +seed_specification = [4] +amount_seeds = 10 + +# create a population + +#seed_population = seed_manager.create_random_seed_population(seed_template, 3) +seed_population = seed_manager.create_specific_seed_population(seed_template,seed_specification,amount_seeds) + +# Print seeds in population +for i in seed_population: + print(i.seed_values) + + +grey_box_fuzzer.fuzz(seed_population, seed_template, crashme,10) From 6492c1a8167cf1cb9f2a92ad0bcf72249a949660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6gel?= <hoegma01@thu.de> Date: Sun, 16 Jun 2024 14:28:29 +0200 Subject: [PATCH 03/19] Extend DataTypeCreator - Extended methods for creating random int, uint, string so that random values can be created --- .../fuzzing/fuzzer_utils/GreyBoxFuzzer.py | 3 +- .../fuzzer_tools/DataTypeCreator.py | 78 +++++++++++-------- .../fuzzing/fuzzer_utils/fuzzer_tools/Seed.py | 12 ++- .../grey_box/test_grey_box_on_helpers.py | 5 +- 4 files changed, 60 insertions(+), 38 deletions(-) diff --git a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py index 42918382..3d8ac6a4 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py +++ b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py @@ -25,8 +25,7 @@ def fuzz(self, seed_population: List[Seed], seed_template: list, function: Calla :return: Returns a list indicating how many tests were successful and how many failed. :rtype: list """ - for seed in seed_population: - print(seed.seed_values) + print("Fuzzing...") diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py index 83daa818..43b5764f 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py @@ -3,10 +3,13 @@ class DataTypeCreator: + MAX_INT = (1 << 31) - 1 + MAX_UINT = (1 << 32) - 1 + def __init__(self): """initialize DataTypeCreator""" - def create_int(self, amount_digits: int) -> int: + def create_int(self, amount_digits: int = 10, random_creation: bool = True) -> int: """Returns an int value with a certain number of digits. This function takes a value 'amount_digits' and returns an integer with this amount of digits. @@ -14,28 +17,35 @@ def create_int(self, amount_digits: int) -> int: :param amount_digits: Amount of digits the integer should have :type amount_digits: int + :param random_creation: + :type random_creation: boolean + :return: Returns an integer with a certain amount of digits. :rtype: int """ - seed_value = '' - for digit in range(amount_digits): - if digit == 0: - # Decide if negative of positive int - rand_val = random.randint(0,1) - if rand_val == 0: - seed_value += '-' - # First digit should not be a 0 - rand_val = str(random.randint(1,9)) - seed_value += rand_val - else: - rand_val = str(random.randint(0,9)) - seed_value += rand_val - - # cast to int type and append to seed - if digit == amount_digits-1: - return int(seed_value) + if random_creation == True: + random_seed_value = random.randint(-self.MAX_INT, self.MAX_INT) + return random_seed_value + else: + seed_value = '' + for digit in range(amount_digits): + if digit == 0: + # Decide if negative of positive int + rand_val = random.randint(0,1) + if rand_val == 0: + seed_value += '-' + # First digit should not be a 0 + rand_val = str(random.randint(1,9)) + seed_value += rand_val + else: + rand_val = str(random.randint(0,9)) + seed_value += rand_val + + # cast to int type and append to seed + if digit == amount_digits-1: + return int(seed_value) - def create_uint(self, amount_digits: int) -> int: + def create_uint(self, amount_digits: int = 10, random_creation: bool = True) -> int: """Returns an uint value with a certain number of digits. This function takes a value 'amount_digits' and returns an unsigned integer with this amount of digits. @@ -46,19 +56,23 @@ def create_uint(self, amount_digits: int) -> int: :return: Returns an unsigned integer with a certain amount of digits. :rtype: int """ - seed_value = '' - for digit in range(amount_digits): - if digit == 0: - # First digit should not be a 0 - rand_val = str(random.randint(1,9)) - seed_value += rand_val - else: - rand_val = str(random.randint(0,9)) - seed_value += rand_val - - # cast to int type and append to seed - if digit == amount_digits-1: - return int(seed_value) + if random_creation == True: + random_seed_value = random.randint(0, self.MAX_UINT) + return random_seed_value + else: + seed_value = '' + for digit in range(amount_digits): + if digit == 0: + # First digit should not be a 0 + rand_val = str(random.randint(1,9)) + seed_value += rand_val + else: + rand_val = str(random.randint(0,9)) + seed_value += rand_val + + # cast to int type and append to seed + if digit == amount_digits-1: + return int(seed_value) def create_float(self, amount_digits: int) -> int: """Returns an int value with a certain number of digits. diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py index 0585920c..64b4e6f6 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py @@ -16,7 +16,8 @@ def __init__(self, energy: int = 0, seed_values: list = []): class SeedManager: - + RANGE_RANDOM_INT = 9 + RANGE_RANDOM_STRING = 100 __value_pool_fuzzer = ValuePoolFuzzer() __param_runner = ParamRunner() __data_type_creator = DataTypeCreator() @@ -84,12 +85,19 @@ def create_specific_seed_population(self, seed_template: list, seed_specificatio param_set = [] for seed_spec, data_type in zip(seed_specification, seed_template): if data_type == "INT": - param_set.append(self.__data_type_creator.create_int(seed_spec)) + if seed_spec == 'r': + param_set.append(self.__data_type_creator.create_int(seed_spec,True)) + else: + param_set.append(self.__data_type_creator.create_int(seed_spec,False)) elif data_type == "UINT": + if seed_spec == 'r': + seed_spec = random.randint(1, self.RANGE_RANDOM_INT) param_set.append(self.__data_type_creator.create_uint(seed_spec)) elif data_type == "FLOAT": print("create_float") elif data_type == "STRING": + if seed_spec == 'r': + seed_spec = random.randint(1, self.RANGE_RANDOM_STRING) rand_val = random.randint(0,1) if rand_val == 0: param_set.append(self.__data_type_creator.create_string_only_letters(seed_spec)) diff --git a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py index 81de8a4c..33370867 100644 --- a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py +++ b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py @@ -34,11 +34,12 @@ def crashme(s: str) -> None: seed_manager = SeedManager() grey_box_fuzzer = GreyBoxFuzzer() +data_type = DataTypeCreator() # seed specification -seed_template = ["STRING"] -seed_specification = [4] +seed_template = ["STRING", "INT"] +seed_specification = ['r', 'r'] amount_seeds = 10 # create a population From 261f4ba30d861c79450c53abd44849607a9c265a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6gel?= <hoegma01@thu.de> Date: Mon, 24 Jun 2024 23:27:29 +0200 Subject: [PATCH 04/19] Add branch coverage to GreyBoxFuzzer It is now possible to track the branch coverage of a test function. --- .coverage | Bin 0 -> 53248 bytes .../fuzzing/fuzzer_utils/GreyBoxFuzzer.py | 71 ++++++++++-- .../fuzzing/fuzzer_utils/GreyBoxRunner.py | 78 +++++++++++++ .../fuzzing/fuzzer_utils/fuzzer_tools/Seed.py | 104 ++---------------- .../grey_box/test_grey_box_on_helpers.py | 34 ++++-- 5 files changed, 177 insertions(+), 110 deletions(-) create mode 100644 .coverage create mode 100644 custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py diff --git a/.coverage b/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..e31f6ae8427feeee31e12da6bf66f97b970931d4 GIT binary patch literal 53248 zcmeI)L2TPp7zc1WY2zkM_F7Gwmu6Ys1-fc&rwR~a9AFz2nwYe~I(DGKbCQ?VEU`P= zS=&iyYKJrd;si(F2uH*Xi4#IxxFEy{i3>tVlPchZIIsiXdwy=3t(_)q(mM9Hn)I{% z-t&7uzxNz3ozJ~`+KUBm1!2dH`BTP-VVcH^oEwI*hh8OmrHe(uK)OP|%`Ndx;T~i9 z<hLdEx>3x1V6Zn!XV_Hn<I<DEKNdUr_l7^U+<b#}V1WPxAOL~KTVU<gqGgxM=K9;Q zTW^avbQ>Z}-b*K6n>{`^%jb@tI6cdgZTxV7(=s*1kMl5C=1U^vEw3%O=Qq8E8+-l& zj~7Lnx*Lh6ywK4$y41;p(<;A=9Ir{aVzEF)EQMaj4OjUku{x;|=%dAAB~CU_A;Mem zWeR>+XP@AqXo*nx4G|@^IP5hi3KQ3_6)b!2Uh{@fZF0i~{S5cDhBow?a;ilIHG;4y zYhMptzp*HyN$zg8un|x_SK_3#VbD1m2fXe1s+oS|#a`fZu_7AXSTwh7;G{%L7sA`x z8Y=6eY|zCdr#{veonGc`a_ZzdbBikH2PtbWqdYWaR(1BfojQ@f)$J_FlS4U@1INKm zId{q%2fDHMd~UWmm%aA(oz9e8p3x*%*Y_@W#pcPM<h`7esu_MT$z>y`2Rt#Hv!B~* z8q_PrN_4qRpSK*h8wYCLp_A{-&|C4H(ILyO)yx~0RY#FLPTh;6%^=^`X*M_S&~@bg zo%@X(CH<X5j-qy(2Q4*ZWNx0E9O}(m$IQ_b!wuy?<AF{{HGQbPO;(ob!0YXx-rv_$ zkBI~g5Sk0pWUa2>kV7SsZD<5nMCdMvX}vd@79Ms-qOLQO&)KuJox09u7jR}a!@^oQ zXW5mCxt>?uNS`hJ%j)Yw>6zSS8cla~gd26kbRxOiB$?*u&64RD(R-*lQ&!GCUfHQR zI-N74gP}ERz_R!4GuM(4A?GFe8tiL?+|nBYw_OPCz4}XTyvXmYyWC6_A0N!w&+pr* z;<5_PjC{{se<P!+e50)NJ2dIbSGKRpa!aquuZY#DD{h-+lO@*+X~@zXoLm98OxScO zv0D$ivFg8aRdr8!?djDwDWJEW+Ec0tm7Iq6*&vEMx~$5}R&*@g%EuSoNX|mhIIjmm zTeyC5ZJ->dB08oQaaD&quSzfU%7|8F84Fj&Rh`m{i=5`?XwaqOi-Jza^_%*7B`?EC zsWkV?!6}cEOd^z#kJF%xXo%icW?HD;D`%{<{OKh!k)?LrW-{F6N!e23@}jt-xa4Z| zk{gTI>j*BBsLH60wd_W`5enK#*V2jc?A^M7JX_9;e3y60L-M2CTFtcVBS*}&t{#lZ zJmW-kQSZ2Ewe@PIuan%iv)5nF(1c5$cDD8tX}5(#t|mv2lUs5I>xn{|N`KZVOvoF2 zll^PZ4;BbO00Izz00bZa0SG_<0uX=z1pa>lSu<l=^8P==el^%%^o9ij5P$##AOHaf zKmY;|fB*y_0D(tSpqR;4S^6g)GtA6ZZ2d<7&rUxx{d9p^m0>pxc9Z@6Xf_bFh5!U0 z009U<00Izz00bZa0SG`K6)0w_X8NxHnL@Ub*Z&BR{QZAuurN)D-w=QR1Rwwb2tWV= z5P$##AOHaf+`quuusL9?S7wjRpZC4<?autn^f}QHQ7ppwb3rR!rU$Dy=ji!8p%<?@ zbF_n=1T_D0NRKpdP6U28nm@aGI#{9SuFN;OQ5<v}daA+_t@?2^A5-%AR`=>vnP(vs zt4=*wQCppK%?W&GQMBn{NfA9SL4N;lvdEwxED(SI1Rwwb2tWV=5P$##AOHafJUD^1 z;equ^ewW<;<N1FN?!=-b2tWV=5P$##AOHafKmY;|fWR&k$eLNBlzjhx%V4*5VGU6w z2tWV=5P$##AOHafKmY;|fB*#UTOgY)<>mMP#>jmag#sV|0SG_<0uX=z1Rwwb2tWV= z5ZJ{6^80_>|L@`xgsMRR0uX=z1Rwwb2tWV=5P$##9)N)S{(qD$80;VRC;OfK!hT}k zvv1f}><ji8`-FYO-e>D<l`T;cED(SI1Rwwb2tWV=5P$##AOHaf++DyL9n9xVv&O9v z74Qv<so+q}+M|N%A*-Z<gH@}jg33W_SOsGhtDu7Nn3Y$7UABf)P}{I96;x|hP6d^! zHK>Ae#Trn-fwGlVfqlTrs9?WsnJU=WFTejEWf$cA{~zo(_Bs2R?)raVAF_AZckCML zvai`C_9eT(+`Bgrdm#V;2tWV=5P$##AOHafKmY=dp+G(-Z};hbe^f_&V?;-XYD`Df zLwj^|uv*el<zP`qW0heYmB$J?vdeiL)i#E7M0fr=s#J42Dpv+|bf7$-Bl|#BNBix} mkg8vPV?X}?|6|xY(PRif00Izz00bZa0SG_<0uXrY1pWiq<i0-u literal 0 HcmV?d00001 diff --git a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py index 3d8ac6a4..8d6b8d5c 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py +++ b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py @@ -1,26 +1,42 @@ from custom_components.test.fuzzing.fuzzer_utils.Fuzzer import Fuzzer from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed +from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.DataTypeCreator import DataTypeCreator from typing import Callable, List +import random class GreyBoxFuzzer(Fuzzer): """GreyBox fuzzer class, inherits from the abstract fuzzer class.""" + RANGE_RANDOM_INT = 9 + RANGE_RANDOM_STRING = 100 + __data_type_creator = DataTypeCreator() + def __init__(self): """initialize GreyBoxFuzzer""" print("Initialize GreyBoxFuzzer") - def fuzz(self, seed_population: List[Seed], seed_template: list, function: Callable, rounds: int = 1): - """The function returns a list of the number of passed and failed tests. - The seed is changed randomly in any number of rounds (defined by rounds). + def fuzz(self, + seed_template: list, + seed_specification: list = None, + amount_seeds: int = 20) -> List[Seed]: + """Returns a population of seeds with specific values based on the seed template and seed specifiction. + + This function takes two lists 'seed_template' and 'seed_specification' and creates seeds. + The number of seeds is specified by 'amount_seeds'. A list of the random seeds is returned. :param seed_template: The seed_template is a list of input types for the function. The entries must correspond to the valid function parameters of the function to be tested. e.g.: ["INT", "FLOAT", "STRING", "BOOLEAN"] :type seed_template: list - :param function: The function to test - :type function: Callable - :param rounds: SSpecifies how often the function should be tested with different inputs. The default is 1. - :type rounds: int + + :param seed_specification: A list that provides the number of digits for each data type in seed_template. + If a random data type is to be initialised anyway, this must be marked with an 'r'. + Defalault value ist random for every data type. + E.g.: [5, 2, 'r'] + :type seed_specification: list + + :param amount_seeds: Amount of seeds which will be created. + :type amount_seeds: int :return: Returns a list indicating how many tests were successful and how many failed. :rtype: list @@ -28,5 +44,46 @@ def fuzz(self, seed_population: List[Seed], seed_template: list, function: Calla print("Fuzzing...") + # Throw exception if seed_specification and seed_template aren't the same length + if len(seed_template) != len(seed_specification): + raise ValueError("Length of seed_template and seed_specification must be the same length.") + + seed_population = [] + for _ in range(amount_seeds): + param_set = [] + for seed_spec, data_type in zip(seed_specification, seed_template): + if data_type == "INT": + if seed_spec == 'r': + param_set.append(self.__data_type_creator.create_int(seed_spec,True)) + else: + param_set.append(self.__data_type_creator.create_int(seed_spec,False)) + elif data_type == "UINT": + if seed_spec == 'r': + seed_spec = random.randint(1, self.RANGE_RANDOM_INT) + param_set.append(self.__data_type_creator.create_uint(seed_spec)) + elif data_type == "FLOAT": + print("create_float") + elif data_type == "STRING": + if seed_spec == 'r': + seed_spec = random.randint(1, self.RANGE_RANDOM_STRING) + rand_val = random.randint(0,1) + if rand_val == 0: + param_set.append(self.__data_type_creator.create_string_only_letters(seed_spec)) + elif rand_val == 1: + param_set.append(self.__data_type_creator.create_string_special_characters(seed_spec)) + elif data_type == "BOOL": + param_set.append(random.choice([True, False])) + elif data_type == "BYTE": + print("create_byte") + elif data_type == "LIST": + print("create_list") + elif data_type == "DICT": + print("create_dict") + elif data_type == "DATE": + print("create_date") + + seed = Seed(1, param_set) + seed_population.append(seed) + return seed_population \ No newline at end of file diff --git a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py new file mode 100644 index 00000000..0641d3df --- /dev/null +++ b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py @@ -0,0 +1,78 @@ +import logging +import inspect +import coverage +from typing import Callable, List + +from custom_components.test.fuzzing.fuzzer_utils.Runner import Runner +from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed, SeedManager + + +class GreyBoxRunner(Runner): + """Greybox runner class, inherits from the abstract runner class.""" + + __logger = None + __seed_manager = None + + def __init__(self): + """constructor""" + self.__logger = logging.getLogger(__name__) + self.__seed_manager = SeedManager() + + def run(self, function: Callable, seed_population: List[Seed], amount_runs: int = 10000) -> list: + """Executes all transferred parameter sets for the transferred function. + + :param function: The passed function which is to be fuzzed. + :type function: Callable + + :param seed_population: A list with seeds. A seed is a set of parameters. + :type seed_population: list + + :param amount_runs: The number of times the function is to be tested. + :param amount_runs: int + + :return: Returns a dict with 2 keys, + the key 'passed_tests' contains the number of passed tests, + the key 'failed_tests' contains the number of failed tests. + :rtype: dict + """ + coverages_seen = set() + cov = coverage.Coverage(branch=True) + + sig = inspect.signature(function) + num_params = len(sig.parameters) + self.__logger.debug(f"The given functions needs {str(num_params)} parameters") + + test_results = { + "passed_tests": 0, + "failed_tests": 0, + } + + for generation in range(0, amount_runs): + seed = self.__seed_manager.select_seed(seed_population) + cov.start() + try: + function(*seed.seed_values) + cov.stop() + cov.save() + test_results["passed_tests"] += 1 + self.__logger.debug(f"Test {generation} passed with parameters: {seed.seed_values}") + except Exception as e: + cov.stop() + cov.save() + test_results["failed_tests"] += 1 + self.__logger.error(f"Test {generation} failed with parameters: {seed.seed_values}.") + self.__logger.error(f"Exception: {e}") + + data = cov.get_data() + filename = next(iter(data.measured_files())) + branches_covered = data.arcs(filename) + + new_branches = set(branches_covered) - coverages_seen + coverages_seen.update(branches_covered) + + if new_branches: + self.__logger.debug(f"Newly Covered Branches: {new_branches}") + print(f"Test {generation}: Newly Covered Branches: {new_branches}") + + + return test_results \ No newline at end of file diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py index 64b4e6f6..73006d5a 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py @@ -16,108 +16,26 @@ def __init__(self, energy: int = 0, seed_values: list = []): class SeedManager: - RANGE_RANDOM_INT = 9 - RANGE_RANDOM_STRING = 100 - __value_pool_fuzzer = ValuePoolFuzzer() - __param_runner = ParamRunner() - __data_type_creator = DataTypeCreator() + counter = -1 def __init__(self): """initialize PowerSchedule""" - def create_random_seed_population(self, seed_template: list, amount_seeds: int) -> List[Seed]: - """Returns a population of seeds with random values specified by the seed_template. + def select_seed(self, seed_population: List[Seed]) -> Seed: + """Selects a seed based on their energy. - This function takes a list 'seed_template' an creates random seeds based on the seed template. - The number of random seeds is specified by 'amount_seeds'. A list of the random seeds is returned. + This function selects a seed. + The higher the energy of a seed, the more likely it is that a seed will be selected. - :param seed_template: A list of the data types of the seeds. - :type seed_template: list + :param seed_population: A list with seeds. A seed is a set of parameters. + :type seed_population: list - :param amount_seeds: Amount of random seeds which will be created. - :type amount_seeds: int - - :return: Returns a list of random seed objects. - :rtype: list - """ - param_combi = random.randint(1, len(seed_template)) - param_set = self.__value_pool_fuzzer.fuzz(len(seed_template),seed_template, param_combi) - param_set = self.__param_runner.limit_param_set(param_set, amount_seeds) - - seed_population = [] - for param in param_set: - seed = Seed(energy=1, seed_values=param) - seed_population.append(seed) - - return seed_population - - def create_specific_seed_population(self, seed_template: list, seed_specification: list, amount_seeds: int) -> List[Seed]: - """Returns a population of seeds with specific values based on the seed template and seed specifiction. - - This function takes two list 'seed_template' and 'seed_specification' and creates seeds. - The number of specific seeds is specified by 'amount_seeds'. A list of the random seeds is returned. - - :param seed_template: A list of the data types of the seeds. - Supported data types: "INT", "UINT", "FLOAT", "STRING", "BOOL", "BYTE", "LIST", "DICT", "DATE", - E.g.: ["STRING", "INT", "INT"] - :type seed_template: list - - :param seed_specification: A list that provides the number of digits for each data type in seed_template. - If a random data type is to be initialised anyway, this must be marked with an 'r'. - E.g.: [5, 2, 'r'] - :type seed_specification: list - - :param amount_seeds: Amount of specific seeds which will be created. - :type amount_seeds: int - - :return: Returns a list of specific seed objects. - :rtype: list + :return: Returns a single seed. + :rtype: Seed """ - param_combi = random.randint(1, len(seed_template)) - - # Throw exception if seed_specification and seed_template aren't the same length - if len(seed_template) != len(seed_specification): - raise ValueError("Length of seed_template and seed_specification must be the same length.") - - seed_population = [] - - for _ in range(amount_seeds): - param_set = [] - for seed_spec, data_type in zip(seed_specification, seed_template): - if data_type == "INT": - if seed_spec == 'r': - param_set.append(self.__data_type_creator.create_int(seed_spec,True)) - else: - param_set.append(self.__data_type_creator.create_int(seed_spec,False)) - elif data_type == "UINT": - if seed_spec == 'r': - seed_spec = random.randint(1, self.RANGE_RANDOM_INT) - param_set.append(self.__data_type_creator.create_uint(seed_spec)) - elif data_type == "FLOAT": - print("create_float") - elif data_type == "STRING": - if seed_spec == 'r': - seed_spec = random.randint(1, self.RANGE_RANDOM_STRING) - rand_val = random.randint(0,1) - if rand_val == 0: - param_set.append(self.__data_type_creator.create_string_only_letters(seed_spec)) - elif rand_val == 1: - param_set.append(self.__data_type_creator.create_string_special_characters(seed_spec)) - elif data_type == "BOOL": - param_set.append(random.choice([True, False])) - elif data_type == "BYTE": - print("create_byte") - elif data_type == "LIST": - print("create_list") - elif data_type == "DICT": - print("create_dict") - elif data_type == "DATE": - print("create_date") - - seed = Seed(1, param_set) - seed_population.append(seed) + self.counter += 1 + return seed_population[self.counter] - return seed_population diff --git a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py index 33370867..ed7e7a17 100644 --- a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py +++ b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py @@ -1,6 +1,6 @@ from custom_components.test.fuzzing.fuzzer_utils.GreyBoxFuzzer import GreyBoxFuzzer -from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import SeedManager, Seed -from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.DataTypeCreator import DataTypeCreator +from custom_components.test.fuzzing.fuzzer_utils.GreyBoxRunner import GreyBoxRunner +from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed from custom_components.loxone.helpers import ( map_range, @@ -32,24 +32,38 @@ def crashme(s: str) -> None: raise Exception() -seed_manager = SeedManager() grey_box_fuzzer = GreyBoxFuzzer() -data_type = DataTypeCreator() +grey_box_runner = GreyBoxRunner() # seed specification -seed_template = ["STRING", "INT"] -seed_specification = ['r', 'r'] amount_seeds = 10 +seed_template = ["STRING"] +seed_specification = ['r'] -# create a population -#seed_population = seed_manager.create_random_seed_population(seed_template, 3) -seed_population = seed_manager.create_specific_seed_population(seed_template,seed_specification,amount_seeds) +# create a population with fuzzer +#seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 20) + +seed_1 = Seed(1, ["bear"]) +seed_2 = Seed(1, ["rats"]) +seed_3 = Seed(1, ["rods"]) +seed_4 = Seed(1, ["hii!"]) +seed_5 = Seed(1, ["deer"]) +seed_6 = Seed(1, ["lol!"]) +seed_7 = Seed(1, ["bad!"]) + +seed_population = [seed_1, seed_2, seed_3, seed_4, seed_5, seed_6, seed_7] # Print seeds in population +print("##### Population #####") for i in seed_population: print(i.seed_values) +print("\n##### Execute Tests #####\n") + +test_results = grey_box_runner.run(crashme, seed_population, 7) -grey_box_fuzzer.fuzz(seed_population, seed_template, crashme,10) +print("\n##### Test restults #####\n") +print(f"Tests passed: {test_results['passed_tests']}") +print(f"Tests failed: {test_results['failed_tests']}") From 95a902033e41d83942ea6ceb3ffcad9a7fd163c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6gel?= <hoegma01@thu.de> Date: Tue, 25 Jun 2024 20:21:56 +0200 Subject: [PATCH 05/19] Adjusted selection of seeds Seeds will now be selected by the energy of the seed. --- .coverage | Bin 53248 -> 53248 bytes .../fuzzing/fuzzer_utils/GreyBoxRunner.py | 44 +++++++++++--- .../fuzzing/fuzzer_utils/fuzzer_tools/Seed.py | 55 +++++++++++++++++- .../test/fuzzing/grey_box/test.py | 11 ++++ .../grey_box/test_grey_box_on_helpers.py | 4 +- 5 files changed, 102 insertions(+), 12 deletions(-) create mode 100644 custom_components/test/fuzzing/grey_box/test.py diff --git a/.coverage b/.coverage index e31f6ae8427feeee31e12da6bf66f97b970931d4..88c58686d2bd01ad0e1666e005cff02f3cfbc2c3 100644 GIT binary patch delta 195 zcmZozz}&Ead4e<}*F+g-My`zsOXiDk@b6&Y|IYu8|0(|+{>%KQ_z&{$*eocpfZvRr zgO!t$kx~9NJ3E*mC(q6XX2{C1vw|7Yvg|BihNLt*GngSRIr-gsF`%IvL59BNf6RZA z|2+Ru{@wf=frifHx8`I6nF%zM1IhxL3T4U4u|ZkVvaC>+q%;eZB`&%7&3+L90FyW* Aq5uE@ delta 404 zcmXZXEl<Nx7{zg#t$o(L^t1_CdwY9(t8jb-6dz(1)C2~DKy|?p3}jfs1Od4$Gb9Lt zn86@WBshX9%woBF^0=Qk;(sQy)@0VY)LwK|?0eDT;O=%O!FUUQSmPU?c*iSV@Q69? zaf54I;0&iY#(0^;d#EY#!_f0=MKPml2#nHF#4sfd7=@ueLl@Lz<eIt+l~c$_6?GW4 zAz~Oy0Ye+|8A?-|k-2e;m1fiReCWbQoA6(J;Q=2m_YG5=V}%+gc)}4D7~#-`@1hQa zO}7hAL{$@uN(+%GBT-=nB3<-Fx$cQn-W8=P6xpUDau-C}1frqRzA#g5QJO7(p10$F DKRP!< diff --git a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py index 0641d3df..22ff3acb 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py +++ b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py @@ -1,6 +1,7 @@ import logging import inspect import coverage +import hashlib from typing import Callable, List from custom_components.test.fuzzing.fuzzer_utils.Runner import Runner @@ -12,6 +13,7 @@ class GreyBoxRunner(Runner): __logger = None __seed_manager = None + __branch_dict = {} def __init__(self): """constructor""" @@ -36,7 +38,7 @@ def run(self, function: Callable, seed_population: List[Seed], amount_runs: int :rtype: dict """ coverages_seen = set() - cov = coverage.Coverage(branch=True) + sig = inspect.signature(function) num_params = len(sig.parameters) @@ -49,6 +51,7 @@ def run(self, function: Callable, seed_population: List[Seed], amount_runs: int for generation in range(0, amount_runs): seed = self.__seed_manager.select_seed(seed_population) + cov = coverage.Coverage(branch=True) cov.start() try: function(*seed.seed_values) @@ -63,16 +66,41 @@ def run(self, function: Callable, seed_population: List[Seed], amount_runs: int self.__logger.error(f"Test {generation} failed with parameters: {seed.seed_values}.") self.__logger.error(f"Exception: {e}") + # check branch coverage data = cov.get_data() filename = next(iter(data.measured_files())) - branches_covered = data.arcs(filename) - - new_branches = set(branches_covered) - coverages_seen - coverages_seen.update(branches_covered) + branch_covered = data.arcs(filename) + new_branches = set(branch_covered) - coverages_seen + coverages_seen.update(branch_covered) if new_branches: - self.__logger.debug(f"Newly Covered Branches: {new_branches}") - print(f"Test {generation}: Newly Covered Branches: {new_branches}") + self.__logger.debug(f"Newly covered branches: {new_branches}") + print(f"Test {generation}, seed_value: {seed.seed_values}, Newly covered branches: {new_branches}") + else: + print(f"Test {generation}, seed_value: {seed.seed_values}, No newly covered branches") + + # Create hashes + hashed_branch = self.__hash_md5(str(branch_covered)) + print(f"Hashed branch: {hashed_branch}") + self.__store_hashed_branch(hashed_branch) + + # Adjust energy of seed + print(f"Energy before: {seed.energy}") + self.__seed_manager.adjust_energy(seed, self.__branch_dict, hashed_branch) + print(f"Energy after: {seed.energy}\n") + print("\n##### Hashed branches #####\n") + print(f"Branch_dict: {self.__branch_dict}") - return test_results \ No newline at end of file + return test_results + + def __hash_md5(self, branch_covered: str) -> str: + md5_hash = hashlib.md5() + md5_hash.update(branch_covered.encode('utf-8')) + return md5_hash.hexdigest() + + def __store_hashed_branch(self, hashed_branch: str): + if hashed_branch in self.__branch_dict: + self.__branch_dict[hashed_branch] += 1 + else: + self.__branch_dict[hashed_branch] = 1 diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py index 73006d5a..affee53b 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py @@ -17,6 +17,7 @@ def __init__(self, energy: int = 0, seed_values: list = []): class SeedManager: counter = -1 + __a = 2 def __init__(self): """initialize PowerSchedule""" @@ -33,8 +34,58 @@ def select_seed(self, seed_population: List[Seed]) -> Seed: :return: Returns a single seed. :rtype: Seed """ - self.counter += 1 - return seed_population[self.counter] + #print(f"seed population: {seed_population}") + normalized_energy = self.get_normalized_energy(seed_population) + #print(f"normalized energy: {normalized_energy}") + random_value = random.uniform(0,1) + #print(f"random_value: {random_value}") + for index, normalized_energy_val in enumerate(normalized_energy): + if random_value <= normalized_energy_val: + seed = seed_population[index] + break + + return seed + #self.counter += 1 + #return seed_population[self.counter] + + def adjust_energy(self, seed: Seed, branch_dict: dict, hashed_branch: str): + """Adjusts the energy of a given seed. + + This function changes the energy of a seed based on how many times the branch was executed. + The formula for the adustment is: e = 1 / number_path_exercised + The number_path_exercised is the number of the how many times the path was seen in total. + + :param seed: A seed with a value and energy attribute. + :type seed: Seed + + :param branch_dict: A dictionary with hashes of the paths and a value of how many times the path was exercised. + :type branch_dict: dict + + :param hashed_branch: A hash of a path. + :type hashed_branch: str + + :return: Returns a single seed. + :rtype: Seed + """ + number_path_exercised = branch_dict[hashed_branch] + seed.energy = 1 / number_path_exercised + + def get_normalized_energy(self, seed_population: List[Seed]) -> list: + total_energy = 0 + for seed in seed_population: + total_energy += seed.energy + + normalized_energy = [] + + for index, seed in enumerate(seed_population): + if index == 0: + normalized_energy.append(seed.energy / total_energy) + else: + normalized_energy.append(normalized_energy[index-1] + (seed.energy / total_energy)) + + return normalized_energy + + diff --git a/custom_components/test/fuzzing/grey_box/test.py b/custom_components/test/fuzzing/grey_box/test.py new file mode 100644 index 00000000..aa02ac3a --- /dev/null +++ b/custom_components/test/fuzzing/grey_box/test.py @@ -0,0 +1,11 @@ +import hashlib + +def hash_string_md5(input_string: str) -> str: + md5_hash = hashlib.md5() + md5_hash.update(input_string.encode('utf-8')) + return md5_hash.hexdigest() + +# Beispiel: +input_string = "Hello, world!" +hashed_string = hash_string_md5(input_string) +print(f"Der MD5-Hash von '{input_string}' ist: {hashed_string}") diff --git a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py index ed7e7a17..f727ac1b 100644 --- a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py +++ b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py @@ -47,9 +47,9 @@ def crashme(s: str) -> None: seed_1 = Seed(1, ["bear"]) seed_2 = Seed(1, ["rats"]) -seed_3 = Seed(1, ["rods"]) +seed_3 = Seed(1, ["code"]) seed_4 = Seed(1, ["hii!"]) -seed_5 = Seed(1, ["deer"]) +seed_5 = Seed(1, ["beer"]) seed_6 = Seed(1, ["lol!"]) seed_7 = Seed(1, ["bad!"]) From 47a90271d4ca79954de15856990539cd4b315bcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6gel?= <hoegma01@thu.de> Date: Wed, 26 Jun 2024 00:09:54 +0200 Subject: [PATCH 06/19] Add mutation to GreyBoxFuzzing It is now possible to mutate the seed values. UML is updated. --- .coverage | Bin 53248 -> 53248 bytes .../test/fuzzing/fuzzer_overview.puml | 94 ++++++++++++++ .../fuzzing/fuzzer_utils/GreyBoxFuzzer.py | 12 +- .../fuzzing/fuzzer_utils/GreyBoxRunner.py | 13 +- .../fuzzer_tools/DataTypeCreator.py | 10 +- .../fuzzer_utils/fuzzer_tools/Mutator.py | 60 +++++++-- .../fuzzer_tools/PowerSchedule.py | 42 ------ .../fuzzing/fuzzer_utils/fuzzer_tools/Seed.py | 7 +- .../test/fuzzing/grey_box/test.py | 11 -- .../grey_box/test_grey_box_on_helpers.py | 2 +- .../test_grey_box_on_helpers_complex.py | 120 ++++++++++++++++++ 11 files changed, 288 insertions(+), 83 deletions(-) delete mode 100644 custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/PowerSchedule.py delete mode 100644 custom_components/test/fuzzing/grey_box/test.py create mode 100644 custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers_complex.py diff --git a/.coverage b/.coverage index 88c58686d2bd01ad0e1666e005cff02f3cfbc2c3..0a027e526b0e2b154f46ef762231d7ac618edce7 100644 GIT binary patch delta 63 zcmZozz}&EadBchIBC_)AY^<D|jEpj}?5tphw9Mu^>!0z90cAMZKr%onHYf`ycVoYX F006UG3`GC{ delta 63 zcmZozz}&EadBchIB69NVY^<D|jEu5!?5tphwCv_P>!0z90cAMZKr%onHYf`ycVoYX F006X13{3z4 diff --git a/custom_components/test/fuzzing/fuzzer_overview.puml b/custom_components/test/fuzzing/fuzzer_overview.puml index 7a1da437..c0a1ad99 100644 --- a/custom_components/test/fuzzing/fuzzer_overview.puml +++ b/custom_components/test/fuzzing/fuzzer_overview.puml @@ -93,6 +93,69 @@ class "MutationalFuzzer" as MFuzzer << class >> { ''''''''''''''''''''''''''''''''''''''' 'Runner'''''''''''''''''''''''''''''''' ''''''''''''''''''''''''''''''''''''''' +''''''''''''''''''''''''''''''''''''''' +class "GreyBoxFuzzer" as GBFuzzer << class >> { + - __RANGE_RANDOM_INT: int = 9 + - __RANGE_RANDOM_STRING: int = 100 + - __data_type_creator: DataTypeCreator + -- + - __init__(self) : void + + fuzz(self, seed_template: list, seed_specification,: list = None, amount_seeds: int = 100) : list[Seed] +} +''''''''''''''''''''''''''''''''''''''' +class "GreyBoxRunner" as GBRunner << class >> { + - __seed_manager = SeedManager() + - __mutator = Mutator() + - __branch_dict = {} + -- + - __init__(self) : void + + run(self, function: Callable, seed_population: List[Seed], amount_runs: int = 10000) : list + - __hash_md5(self, branch_covered: str) : str + - __store_hashed_branch(self, hashed_branch: str) : void +} +''''''''''''''''''''''''''''''''''''''' +class "SeedManager" as SeedManager << class >> { + - __power_energy: int = 2 + -- + - __init__(self) : void + + select_seed(self, seed_population: List[Seed]) : Seed + + adjust_energy(self, seed: Seed, branch_dict: dict, hashed_branch: str) : void + + get_normalized_energy(self, seed_population: List[Seed]) : list +} +''''''''''''''''''''''''''''''''''''''' +class "Mutator" as Mutator << class >> { + -- + - __init__(self) : void + + mutate_grey_box_fuzzer(self, seed: Seed) : void + - __delete_random_char(self, s: str) : str + - __insert_random_char(self, s: str) : str + - __flip_random_char(self, s: str) : str + - __get_random_float(self) : float + - __check_inf(self, number: float) : float + - __add_random_number(self, number: float) : float + - __sub_random_number(self, number: float) : float + - __mult_random_number(self, number: float) : float + - __div_random_number(self, number: float) : float + - __grey_mutate_string(self, seed_value: str) : str +} +''''''''''''''''''''''''''''''''''''''' +class "Seed" as Seed << class >> { + - energy: int = 0 + - seed_values: list = [] + -- + - __init__(self, energy: int = 0, seed_values: list = []) : void +} +''''''''''''''''''''''''''''''''''''''' +class "DataTypeCreator" as DataTypeCreator << class >> { + - __MAX_INT: int = (1 << 31) - 1 + - __MAX_UINT = (1 << 32) - 1 + -- + - __init__(self) : void + - create_int(self, amount_digits: int = 10, random_creation: bool = True) : int + - create_uint(self, amount_digits: int = 10, random_creation: bool = True) : int + - create_string_special_characters(self, amount_chars: int) : str +} +''''''''''''''''''''''''''''''''''''''' abstract class "Runner" as runner << abstract class >> { -- - __init__(self) : void @@ -146,6 +209,21 @@ entity "test_mut_on_helpers.py" as test_MUT << test case >>{ test_map_range() : None } ''''''''''''''''''''''''''''''''''''''' +entity "test_grey_box_on_helpers.py" as test_GBox << test case >>{ + logger + grey_box_fuzzer = GreyBoxFuzzer + grey_box_runner = GreyBoxRunner + -- + +} +''''''''''''''''''''''''''''''''''''''' +entity "test_grey_box_on_helpers_complex.py" as test_GBox_complex << test case >>{ + logger + grey_box_fuzzer = GreyBoxFuzzer + grey_box_runner = GreyBoxRunner + -- +} +''''''''''''''''''''''''''''''''''''''' 'Fuzzer'''''''''''''''''''''''''''''''' ''''''''''''''''''''''''''''''''''''''' fuzzer <|-- VPFuzzer : inherits from < @@ -153,10 +231,21 @@ VPool <--* VPFuzzer: has access to < 'fuzzer <|-- GrFuzzer : inherits from < fuzzer <|-- GFuzzer : inherits from < fuzzer <|-- MFuzzer : inherits from < +GBFuzzer o-- DataTypeCreator : aggregates > +GBFuzzer --> Seed: uses > ''''''''''''''''''''''''''''''''''''''' 'Runner'''''''''''''''''''''''''''''''' ''''''''''''''''''''''''''''''''''''''' runner <|-- PRunner : inherits from < +GBRunner o-- Mutator : aggregates > +GBRunner o-- SeedManager : aggregates > +GBRunner --> Seed: uses > +''''''''''''''''''''''''''''''''''''''' +'Other classes''''''''''''''''''''''''' +''''''''''''''''''''''''''''''''''''''' +Mutator --> Seed: uses > +SeedManager --> Seed: uses > +DataTypeCreator --> Seed: uses > ''''''''''''''''''''''''''''''''''''''' 'Testcases''''''''''''''''''''''''''''' ''''''''''''''''''''''''''''''''''''''' @@ -174,4 +263,9 @@ MFuzzer "1"<--* test_MUT : needs a < PRunner "1"<--* test_MUT : needs a < GrFuzzer "1"<--* test_MUT : needs a < ipv4 "1"<--* test_MUT : needs a < +''''''''''''''''''''''''''''''''''''''' +GBFuzzer "1"<--* test_GBox : needs a < +GBFuzzer "1"<--* test_GBox_complex : needs a < +GBRunner "1"<--* test_GBox : needs a < +GBRunner "1"<--* test_GBox_complex : needs a < @enduml \ No newline at end of file diff --git a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py index 8d6b8d5c..5f2377c0 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py +++ b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py @@ -1,14 +1,14 @@ from custom_components.test.fuzzing.fuzzer_utils.Fuzzer import Fuzzer from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.DataTypeCreator import DataTypeCreator -from typing import Callable, List +from typing import List import random class GreyBoxFuzzer(Fuzzer): """GreyBox fuzzer class, inherits from the abstract fuzzer class.""" - RANGE_RANDOM_INT = 9 - RANGE_RANDOM_STRING = 100 + __RANGE_RANDOM_INT = 9 + __RANGE_RANDOM_STRING = 100 __data_type_creator = DataTypeCreator() def __init__(self): @@ -18,7 +18,7 @@ def __init__(self): def fuzz(self, seed_template: list, seed_specification: list = None, - amount_seeds: int = 20) -> List[Seed]: + amount_seeds: int = 100) -> List[Seed]: """Returns a population of seeds with specific values based on the seed template and seed specifiction. This function takes two lists 'seed_template' and 'seed_specification' and creates seeds. @@ -60,13 +60,13 @@ def fuzz(self, param_set.append(self.__data_type_creator.create_int(seed_spec,False)) elif data_type == "UINT": if seed_spec == 'r': - seed_spec = random.randint(1, self.RANGE_RANDOM_INT) + seed_spec = random.randint(1, self.__RANGE_RANDOM_INT) param_set.append(self.__data_type_creator.create_uint(seed_spec)) elif data_type == "FLOAT": print("create_float") elif data_type == "STRING": if seed_spec == 'r': - seed_spec = random.randint(1, self.RANGE_RANDOM_STRING) + seed_spec = random.randint(1, self.__RANGE_RANDOM_STRING) rand_val = random.randint(0,1) if rand_val == 0: param_set.append(self.__data_type_creator.create_string_only_letters(seed_spec)) diff --git a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py index 22ff3acb..a9c434cd 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py +++ b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py @@ -6,6 +6,7 @@ from custom_components.test.fuzzing.fuzzer_utils.Runner import Runner from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed, SeedManager +from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Mutator import Mutator class GreyBoxRunner(Runner): @@ -13,12 +14,14 @@ class GreyBoxRunner(Runner): __logger = None __seed_manager = None + __mutator = None __branch_dict = {} def __init__(self): """constructor""" self.__logger = logging.getLogger(__name__) self.__seed_manager = SeedManager() + self.__mutator = Mutator() def run(self, function: Callable, seed_population: List[Seed], amount_runs: int = 10000) -> list: """Executes all transferred parameter sets for the transferred function. @@ -38,7 +41,7 @@ def run(self, function: Callable, seed_population: List[Seed], amount_runs: int :rtype: dict """ coverages_seen = set() - + branch_counter = 0 sig = inspect.signature(function) num_params = len(sig.parameters) @@ -76,10 +79,11 @@ def run(self, function: Callable, seed_population: List[Seed], amount_runs: int if new_branches: self.__logger.debug(f"Newly covered branches: {new_branches}") print(f"Test {generation}, seed_value: {seed.seed_values}, Newly covered branches: {new_branches}") + branch_counter += 1 else: print(f"Test {generation}, seed_value: {seed.seed_values}, No newly covered branches") - # Create hashes + # Create hash and store it in dict hashed_branch = self.__hash_md5(str(branch_covered)) print(f"Hashed branch: {hashed_branch}") self.__store_hashed_branch(hashed_branch) @@ -89,8 +93,13 @@ def run(self, function: Callable, seed_population: List[Seed], amount_runs: int self.__seed_manager.adjust_energy(seed, self.__branch_dict, hashed_branch) print(f"Energy after: {seed.energy}\n") + # Mutate seed values + self.__mutator.mutate_grey_box_fuzzer(seed) + print("\n##### Hashed branches #####\n") print(f"Branch_dict: {self.__branch_dict}") + print("\n##### Covert branches #####\n") + print(f"In total there were {branch_counter} branches discovered ") return test_results diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py index 43b5764f..edee5551 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py @@ -3,8 +3,8 @@ class DataTypeCreator: - MAX_INT = (1 << 31) - 1 - MAX_UINT = (1 << 32) - 1 + __MAX_INT = (1 << 31) - 1 + __MAX_UINT = (1 << 32) - 1 def __init__(self): """initialize DataTypeCreator""" @@ -24,7 +24,7 @@ def create_int(self, amount_digits: int = 10, random_creation: bool = True) -> i :rtype: int """ if random_creation == True: - random_seed_value = random.randint(-self.MAX_INT, self.MAX_INT) + random_seed_value = random.randint(-self.__MAX_INT, self.__MAX_INT) return random_seed_value else: seed_value = '' @@ -57,7 +57,7 @@ def create_uint(self, amount_digits: int = 10, random_creation: bool = True) -> :rtype: int """ if random_creation == True: - random_seed_value = random.randint(0, self.MAX_UINT) + random_seed_value = random.randint(0, self.__MAX_UINT) return random_seed_value else: seed_value = '' @@ -108,7 +108,7 @@ def create_string_only_letters(self, amount_chars: int) -> int: if character == amount_chars-1: return seed_value - def create_string_special_characters(self, amount_chars: int) -> int: + def create_string_special_characters(self, amount_chars: int) -> str: """Returns an string with a certain number of chars. This function takes a value 'amount_chars' and returns an string with this amount of chars. diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py index e679485c..bc94bf0a 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py @@ -1,12 +1,26 @@ +from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed from random import random +import random import math class Mutator: def __init__(self): """initialize Mutator""" - print("Initialize Mutator") - def delete_random_char(self, string: str) -> str: + def mutate_grey_box_fuzzer(self, seed: Seed): + """Mutates all seed values. + + This function takes a seed and mutates all seed values of it. + + :param seed: A seed consists of a list of seed_values. + :type seed: Seed + """ + for index, seed_value in enumerate(seed.seed_values): + if isinstance(seed_value, str): + seed.seed_values[index] = self.__grey_mutate_string(seed_value) + + + def __delete_random_char(self, string: str) -> str: """Returns string with a random character deleted. This function takes a string `string` as input and returns a new string @@ -30,7 +44,7 @@ def delete_random_char(self, string: str) -> str: # the substring after the random position. return string[:pos] + string[pos + 1 :] - def insert_random_char(self, string: str) -> str: + def __insert_random_char(self, string: str) -> str: """Returns string with a random character inserted. This function takes a string `string` as input and returns a new string @@ -53,7 +67,7 @@ def insert_random_char(self, string: str) -> str: # and the substring after the random position. return string[:pos] + random_character + string[pos:] - def flip_random_char(self, string: str) -> str: + def __flip_random_char(self, string: str) -> str: """Returns string with a random bit flipped in a random position. This function takes a string `string` as input and returns a new string @@ -87,7 +101,7 @@ def flip_random_char(self, string: str) -> str: # and the substring after the random position. return string[:pos] + new_c + string[pos + 1 :] - def get_random_float(self) -> float: + def __get_random_float(self) -> float: """Returns a random float value modified by a randomly chosen multiplier. This function generates a random float value between 0.0 and 1.0, and then @@ -105,7 +119,7 @@ def get_random_float(self) -> float: # Return the modified random float. return random_float - def check_inf(self, number: float) -> float: + def __check_inf(self, number: float) -> float: """Checks if the number is infinite and replaces it with a random value if true. This function takes a floating-point number `number` as input. If the number is @@ -128,7 +142,7 @@ def check_inf(self, number: float) -> float: # Return the potentially modified number. return number - def add_random_number(self, number: float) -> float: + def __add_random_number(self, number: float) -> float: """Returns the input number with a random float added. This function takes a floating-point number `number` as input and adds @@ -147,7 +161,7 @@ def add_random_number(self, number: float) -> float: # Check if the resulting number is infinite. return self.__check_inf(number) - def sub_random_number(self, number: float) -> float: + def __sub_random_number(self, number: float) -> float: """Subtracts a random float from the given number. This function takes a float `number` as input and subtracts a randomly @@ -165,7 +179,7 @@ def sub_random_number(self, number: float) -> float: # Check if the resulting number is infinite. return self.__check_inf(number) - def mult_random_number(self, number: float) -> float: + def __mult_random_number(self, number: float) -> float: """Returns the result of multiplying the input number by a random float. This function takes a floating-point number `number` as input and returns @@ -185,7 +199,7 @@ def mult_random_number(self, number: float) -> float: # Check if the resulting number is infinite. return self.__check_inf(number) - def div_random_number(self, number: float) -> float: + def __div_random_number(self, number: float) -> float: """Divides the input number by a randomly generated float. This function takes a float `number` as input and divides it by @@ -201,4 +215,28 @@ def div_random_number(self, number: float) -> float: number /= self.__get_random_float() # Check if the resulting number is infinite. - return self.__check_inf(number) \ No newline at end of file + return self.__check_inf(number) + + def __grey_mutate_string(self, seed_value: str) -> str: + """Mutates a string random. + + This function takes a string and applies different mutations on it. + 1. delete random char + 2. insert random char + 3. flip random char + + :param seed_value: A string which should be mutated. + :type seed_value: str + + :return: Returns the mutated seed value. + :rtype: str + """ + random_val = random.choice([1, 2, 3]) + if random_val == 1: + seed_value = self.__delete_random_char(seed_value) + elif random_val == 2: + seed_value = self.__insert_random_char(seed_value) + elif random_val == 3: + seed_value = self.__flip_random_char(seed_value) + + return seed_value \ No newline at end of file diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/PowerSchedule.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/PowerSchedule.py deleted file mode 100644 index ff85b12d..00000000 --- a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/PowerSchedule.py +++ /dev/null @@ -1,42 +0,0 @@ -from typing import List -from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed - -class PowerSchedule: - - def __init__(self): - """initialize PowerSchedule""" - print("Initialize PowerSchedule") - - def assignEnergy(self, population: List[Seed]) -> None: - """Assigns energy to seeds. - - This function takes a population of Seeds and assigns to every seed an energy of 1. - - :param population: List of type Seed. - :type population: list - """ - for seed in population: - seed.energy = 1 - - def normalizedEnergy(self, population: List[Seed]) -> None: - """Normalize energy of all seeds. - - This function takes a population of Seeds and normalizes the energy by dividing the - energy of every seed through sum of all energy values. - - :param population: List of type Seed. - :type population: list - """ - - def choose(self, population: List[Seed]) -> Seed: - """Returns a seed that was selected based on the energy. - - This function takes a population of Seeds and returns and selects a Seed by the energy of the seed. - The higher the energy, the more likely it is that the seed will be selected. - - :param population: List of type Seed. - :type population: list - - :return: Returns a seed. - :rtype: Seed - """ \ No newline at end of file diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py index affee53b..0a6ba6e5 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py @@ -16,8 +16,7 @@ def __init__(self, energy: int = 0, seed_values: list = []): class SeedManager: - counter = -1 - __a = 2 + __power_energy = 2 def __init__(self): """initialize PowerSchedule""" @@ -45,8 +44,6 @@ def select_seed(self, seed_population: List[Seed]) -> Seed: break return seed - #self.counter += 1 - #return seed_population[self.counter] def adjust_energy(self, seed: Seed, branch_dict: dict, hashed_branch: str): """Adjusts the energy of a given seed. @@ -68,7 +65,7 @@ def adjust_energy(self, seed: Seed, branch_dict: dict, hashed_branch: str): :rtype: Seed """ number_path_exercised = branch_dict[hashed_branch] - seed.energy = 1 / number_path_exercised + seed.energy = 1 / (number_path_exercised ** self.__power_energy) def get_normalized_energy(self, seed_population: List[Seed]) -> list: total_energy = 0 diff --git a/custom_components/test/fuzzing/grey_box/test.py b/custom_components/test/fuzzing/grey_box/test.py deleted file mode 100644 index aa02ac3a..00000000 --- a/custom_components/test/fuzzing/grey_box/test.py +++ /dev/null @@ -1,11 +0,0 @@ -import hashlib - -def hash_string_md5(input_string: str) -> str: - md5_hash = hashlib.md5() - md5_hash.update(input_string.encode('utf-8')) - return md5_hash.hexdigest() - -# Beispiel: -input_string = "Hello, world!" -hashed_string = hash_string_md5(input_string) -print(f"Der MD5-Hash von '{input_string}' ist: {hashed_string}") diff --git a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py index f727ac1b..21038952 100644 --- a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py +++ b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py @@ -62,7 +62,7 @@ def crashme(s: str) -> None: print("\n##### Execute Tests #####\n") -test_results = grey_box_runner.run(crashme, seed_population, 7) +test_results = grey_box_runner.run(crashme, seed_population, 10) print("\n##### Test restults #####\n") print(f"Tests passed: {test_results['passed_tests']}") diff --git a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers_complex.py b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers_complex.py new file mode 100644 index 00000000..f8d385d0 --- /dev/null +++ b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers_complex.py @@ -0,0 +1,120 @@ +from custom_components.test.fuzzing.fuzzer_utils.GreyBoxFuzzer import GreyBoxFuzzer +from custom_components.test.fuzzing.fuzzer_utils.GreyBoxRunner import GreyBoxRunner +from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed + +from custom_components.loxone.helpers import ( + map_range, + hass_to_lox, + lox_to_hass, + lox2lox_mapped, + lox2hass_mapped, + to_hass_color_temp, + to_loxone_color_temp, + get_room_name_from_room_uuid, + get_cat_name_from_cat_uuid, + add_room_and_cat_to_value_values, + get_miniserver_type, + get_all, +) + +# Function to test the grey box fuzzer +def complex_function(input_string: str) -> int: + result = 0 + length = len(input_string) + + # Grundlegende Bedingung auf Länge + if length > 10: + result += 1 + if length > 20: + result += 1 + if input_string.startswith("A"): + result += 1 + if input_string.endswith("Z"): + result += 1 + else: + result -= 1 + + # Bedingung auf spezifische Zeichen + if 'a' in input_string: + result += 2 + if input_string.count('a') > 3: + result += 3 + if 'aaa' in input_string: + result += 5 + else: + result -= 1 + else: + result -= 2 + + # Schleifen und verschachtelte Bedingungen + vowels = "aeiou" + consonants = "bcdfghjklmnpqrstvwxyz" + + for i, char in enumerate(input_string): + if char in vowels: + result += 1 + elif char in consonants: + result += 2 + else: + result -= 1 + + # Verschachtelte Schleifen + for j in range(i, length): + if input_string[j] == char: + result += 1 + else: + result -= 1 + + # Noch eine Ebene der Verschachtelung + if j % 2 == 0: + result += 2 + else: + result -= 2 + + # Weitere komplexe Bedingungen + if 'xyz' in input_string: + result += 10 + if input_string.isdigit(): + result -= 10 + if input_string.isalpha(): + result += 20 + + # Substrings und Indizes + if "fuzz" in input_string: + index = input_string.find("fuzz") + if index % 2 == 0: + result += 30 + else: + result -= 30 + + # Rückgabe des Ergebnisses + return result + + + +grey_box_fuzzer = GreyBoxFuzzer() +grey_box_runner = GreyBoxRunner() + +# seed specification + +amount_seeds = 10 +seed_template = ["STRING"] +seed_specification = ['r'] + + +# create a population with fuzzer +seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 100) + +# Print seeds in population +print("##### Population #####") +for i in seed_population: + print(i.seed_values) + +print("\n##### Execute Tests #####\n") + +test_results = grey_box_runner.run(complex_function, seed_population, 2000) + +print("\n##### Test restults #####\n") +print(f"Tests passed: {test_results['passed_tests']}") +print(f"Tests failed: {test_results['failed_tests']}") + From 29c02c73038c358c0cb8646ae0595b8561485f83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6gel?= <hoegma01@thu.de> Date: Wed, 26 Jun 2024 09:32:52 +0200 Subject: [PATCH 07/19] Fix new branch counter Fixed the bug, that the amount of branches that was covert was smaller than the amount of hashes were stored in the hash_dict. --- .coverage | Bin 53248 -> 0 bytes .gitignore | 4 +- .../test/fuzzing/fuzzer_overview.puml | 1 - .../fuzzing/fuzzer_utils/GreyBoxRunner.py | 39 ++++++++++-------- 4 files changed, 25 insertions(+), 19 deletions(-) delete mode 100644 .coverage diff --git a/.coverage b/.coverage deleted file mode 100644 index 0a027e526b0e2b154f46ef762231d7ac618edce7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53248 zcmeI)&u`mg7zc1WY2zeK<)LY{qO|IJfv#HHDFTET2k1tHCMIp89Ty@zCwbEvV!N}Q zwVi}eI;2TR{0E5tfg2Jhgt%})h!YYQgpejpzzK0+2cGA(-85Y@P1-~|_*zZ++J4{H z@B4Xw#Y^YatC!qZicS!EPAs0+PH4KWy(WaFX=Qp%(kt5x3XEkt^jqH-9u+KWOBcSL zG~d=Lg-<l|-O0=5V&&7x=O(_dc%_dgJ~NzBi%wvJ00bZaf&aHa|7yjss#SgWgV<?y zWgI#!87A+O7v5S~URx1s%jYkxh~$_!T^6)0E{bIl23ulNhN9zkrEvYW+j3&pUl;L) z%wqQ<+2)0gj?q%b5tmeY4%u#-QpIweve*n=&k1+L`*LSNMM$p}%k4NhK$%E)-RBtM zbeeo#gt8+;>9=H*l;X78o-fbe{;+IVCr|2kq^gq>w&-VKs5W$9P?d`v%BU5DZ7zK? zbo|zaj248mSHo67<=l>w+J=F5HV#DB^;I?f$c^2=7jj#+da-ODTEPWI%QnK>UmMEn zI#=j=k}|#4KAl10LsF*E)5IMroF8PYgM@r(O03H4_q-;Nzt{6Nxp62(a^W}_m2y<t zI7ln|$Zc~-TMp_w)SMYPw`h{8=esw0a<B0h#31E@s)ipd2(AS6fM+KP*2^b#je4cr zj&5}6^Oo)O;y~@&)cAIt-YOqGQ#7pES^drp)lrzoZn|-_7bp#Nn!Up#x(=To-EVl5 z40jS9Mcu9lI%>#pYLPS!_2&I!=4^)HggnqhkVd4E9%*mm!crMTvl}#r`<m)8k)#1a zb3qoZ)AL(AR3ffJE4V2`XI(C(XOn5+s5=sMoqDNYt;~+<I(uEfuI~lQ{c6FmYBhbg zq`FbsTInxis0(E+dB`-Ho${m;^}=i-dDtYGr5MbT*%(RBP<0lKg0);5Rh=}RT~7nW zzA<K4r%vhpWJK`1#INz8O7M}v5IC?9JbLjrop?h$Sav>41)m!)Sg)KKRd6nXUFY}0 z?RRpj$aktr-=j&NU-_XT^N~T3-;_IxH=QocCYz2M(vYP&I9UM%M{GM3*=Yv7SoL4t zRo#=<o<Vt&3<mp|J;j<=(P?;J38KiQWtEq$=v;PG%xyRk&qC3hXa+%7I)1V?P>PEY zUDJ((D#L@T(yN0!q8-j-{pOr1Q?|J9G`~iJE*oDI@I1$Fr|T6j!%40*_w(T7>m-v1 zB@}ZsC?gu82lC7^P0#X-mF2&*NhX}D=d_dI&W*A!$E^+dKz7M$^tuzv*!85~D5@~3 zV{JK+XoZqa(pow{mw#9{;MTJ1{LXjDMgAz)pVbZP%o)AkO9x{z&)5+y>YkGoJKfC< zb&>-o2mR$TO}O-FXMaCoyL}wIn_Ph>x1<G|i9!}D{j5`-=No+8{9B_RY!H9|1Rwwb z2tWV=5P$##AOHaf{PzU%dQLa^{y%5_teJn%8#V|)00Izz00bZa0SG_<0uX=z1fEWT zN-jTRW`E*QH}!nYO8*GprKJ~_Uf@6S&zW~M^RD^l(^)~38Uhf200bZa0SG_<0uX=z z1Rwx`OrVmV(X)RA$d&W8Qu>bo$>0Bnni*y>@f!jVfB*y_009U<00Izz00bZafyWo< zPv~RXZf)gU<C^bY>w1m)(yH`i6w9!&8g$|<da#PUO3&{J-FU}dqZ9NbpvD^^J<`BF zANakfab@RHuuachX|#G#9C$W8Rbi8M{WxmG6ur^u-MYnT)<d~tH-l|;)Xw(oz_&MK zmmZcB(eo1c`+wbxH2T2?0SG_<0uX=z1Rwwb2tWV=5P-mw6X;Kj?bb@i<o+Me|9f%` zi<}?;0SG_<0uX=z1Rwwb2tWV=$50@z=e5b?`~UlzdH)!e5JiFj1Rwwb2tWV=5P$## zAOHafK;W?j^7+XUfB&zYc<ii@0R$ib0SG_<0uX=z1Rwwb2tWV=$5?>B|Hu9RF>W9f z4FV8=00bZa0SG_<0uX=z1R(GP1o->^qWOhp{$>7d{%ZbY{$PG<enkhcK>z{}fB*y_ z009U<00Izz00ba#<N`)<yj0Tl*?Wee0yDElK?SB~jBypHO&en>P^}qx6|kyCP6ehc zLsx-&Q~dpZ(fpk6|9>-oF@H3_GruvvqyyL>009U<00Izz00bZa0SG_<0uVSlfl`6* z`{~BNn1<-iKMhUK6w**_dOQtPYh!81s^-(sl$9&0TuS$*@c;jht|@V42tWV=5P$## RAOHafKmY;|fWW^c@DHvKtCj!& diff --git a/.gitignore b/.gitignore index 66bcefe4..b0df99ae 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,6 @@ custom_components/loxone/homeassistant .vscode -.DS_Store \ No newline at end of file +.DS_Store + +.coverage \ No newline at end of file diff --git a/custom_components/test/fuzzing/fuzzer_overview.puml b/custom_components/test/fuzzing/fuzzer_overview.puml index c0a1ad99..980126e9 100644 --- a/custom_components/test/fuzzing/fuzzer_overview.puml +++ b/custom_components/test/fuzzing/fuzzer_overview.puml @@ -106,7 +106,6 @@ class "GreyBoxFuzzer" as GBFuzzer << class >> { class "GreyBoxRunner" as GBRunner << class >> { - __seed_manager = SeedManager() - __mutator = Mutator() - - __branch_dict = {} -- - __init__(self) : void + run(self, function: Callable, seed_population: List[Seed], amount_runs: int = 10000) : list diff --git a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py index a9c434cd..d834a7a2 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py +++ b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py @@ -15,7 +15,7 @@ class GreyBoxRunner(Runner): __logger = None __seed_manager = None __mutator = None - __branch_dict = {} + branch_dict = {} def __init__(self): """constructor""" @@ -40,7 +40,7 @@ def run(self, function: Callable, seed_population: List[Seed], amount_runs: int the key 'failed_tests' contains the number of failed tests. :rtype: dict """ - coverages_seen = set() + branch_dict = {} branch_counter = 0 sig = inspect.signature(function) @@ -73,31 +73,34 @@ def run(self, function: Callable, seed_population: List[Seed], amount_runs: int data = cov.get_data() filename = next(iter(data.measured_files())) branch_covered = data.arcs(filename) - new_branches = set(branch_covered) - coverages_seen - coverages_seen.update(branch_covered) + + # Create hash of branch + print(f"Branch: {branch_covered}") + hashed_branch = self.__hash_md5(str(branch_covered)) + print(f"Hashed branch: {hashed_branch}") - if new_branches: - self.__logger.debug(f"Newly covered branches: {new_branches}") - print(f"Test {generation}, seed_value: {seed.seed_values}, Newly covered branches: {new_branches}") + # Check if a new branch was covered + if hashed_branch not in self.branch_dict: + self.__logger.debug(f"Newly covered branches: {branch_covered}") + print(f"Test {generation}, seed_value: {seed.seed_values}, Newly covered branches: {branch_covered}") branch_counter += 1 else: print(f"Test {generation}, seed_value: {seed.seed_values}, No newly covered branches") - # Create hash and store it in dict - hashed_branch = self.__hash_md5(str(branch_covered)) - print(f"Hashed branch: {hashed_branch}") - self.__store_hashed_branch(hashed_branch) + # store hash in branch_dict + branch_dict = self.__store_hashed_branch(hashed_branch, branch_dict) + # Adjust energy of seed print(f"Energy before: {seed.energy}") - self.__seed_manager.adjust_energy(seed, self.__branch_dict, hashed_branch) + self.__seed_manager.adjust_energy(seed, self.branch_dict, hashed_branch) print(f"Energy after: {seed.energy}\n") # Mutate seed values self.__mutator.mutate_grey_box_fuzzer(seed) print("\n##### Hashed branches #####\n") - print(f"Branch_dict: {self.__branch_dict}") + print(f"Branch_dict: {self.branch_dict}") print("\n##### Covert branches #####\n") print(f"In total there were {branch_counter} branches discovered ") @@ -108,8 +111,10 @@ def __hash_md5(self, branch_covered: str) -> str: md5_hash.update(branch_covered.encode('utf-8')) return md5_hash.hexdigest() - def __store_hashed_branch(self, hashed_branch: str): - if hashed_branch in self.__branch_dict: - self.__branch_dict[hashed_branch] += 1 + def __store_hashed_branch(self, hashed_branch: str, branch_dict: dict) -> dict: + if hashed_branch in self.branch_dict: + self.branch_dict[hashed_branch] += 1 else: - self.__branch_dict[hashed_branch] = 1 + self.branch_dict[hashed_branch] = 1 + + return branch_dict From a68a17f237c83ce55e2c3335d1cca034f0eee220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6gel?= <hoegma01@thu.de> Date: Sun, 16 Jun 2024 00:49:08 +0200 Subject: [PATCH 08/19] Add skeletton code of GreyBoxFuzzer --- .../fuzzing/fuzzer_utils/GreyBoxFuzzer.py | 29 +++ .../fuzzer_utils/fuzzer_tools/Mutator.py | 204 ++++++++++++++++++ .../fuzzer_tools/PowerSchedule.py | 42 ++++ .../fuzzing/fuzzer_utils/fuzzer_tools/Seed.py | 36 ++++ 4 files changed, 311 insertions(+) create mode 100644 custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py create mode 100644 custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py create mode 100644 custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/PowerSchedule.py create mode 100644 custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py diff --git a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py new file mode 100644 index 00000000..1838b26e --- /dev/null +++ b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py @@ -0,0 +1,29 @@ +from typing import Callable + +class GreyBoxFuzzer: + """GreyBox fuzzer class, inherits from the abstract fuzzer class.""" + + def __init__(self): + """initialize GreyBoxFuzzer""" + print("Initialize GreyBoxFuzzer") + + def fuzz(self, seed_template: list, function: Callable, rounds: int = 1): + """The function returns a list of the number of passed and failed tests. + The seed is changed randomly in any number of rounds (defined by rounds). + + :param seed_template: The seed_template is a list of input types for the function. + The entries must correspond to the valid function parameters of the function to be tested. + e.g.: ["INT", "FLOAT", "STRING", "BOOLEAN"] + :type seed_template: list + :param function: The function to test + :type function: Callable + :param rounds: SSpecifies how often the function should be tested with different inputs. The default is 1. + :type rounds: int + + :return: Returns a list indicating how many tests were successful and how many failed. + :rtype: list + """ + print("Fuzzing...") + + + diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py new file mode 100644 index 00000000..e679485c --- /dev/null +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py @@ -0,0 +1,204 @@ +from random import random +import math + +class Mutator: + def __init__(self): + """initialize Mutator""" + print("Initialize Mutator") + + def delete_random_char(self, string: str) -> str: + """Returns string with a random character deleted. + + This function takes a string `string` as input and returns a new string + with one random character removed from it. + + :param string: Any string from which a character is to be removed. + :type string: str + + :return: Returns the input string `string` with one randomly chosen character deleted. + :rtype: str + """ + if string == "": + # If the string is empty, there's no character to delete, so return the empty string. + return string + + # Generate a random integer position within the range of the string's indices. + pos = random.randint(0, len(string) - 1) + + # Create a new string by excluding the character at the random position. + # This is done by concatenating the substring before the random position and + # the substring after the random position. + return string[:pos] + string[pos + 1 :] + + def insert_random_char(self, string: str) -> str: + """Returns string with a random character inserted. + + This function takes a string `string` as input and returns a new string + with a random character inserted at a random position within the string. + + :param string: Any string where a character is to be inserted. + :type string: str + + :return: Returns the input string `string` with one randomly chosen character inserted at a random position. + :rtype: str + """ + # Generate a random position within the range of the string's length (including the end of the string). + pos = random.randint(0, len(string)) + + # Generate a random character from the ASCII range 32 to 126 (printable characters). + random_character = chr(random.randrange(32, 127)) + + # Create a new string by inserting the random character at the random position. + # This is done by concatenating the substring before the random position, the random character, + # and the substring after the random position. + return string[:pos] + random_character + string[pos:] + + def flip_random_char(self, string: str) -> str: + """Returns string with a random bit flipped in a random position. + + This function takes a string `string` as input and returns a new string + where one randomly chosen character has one of its bits flipped + at a random bit position. + + :param string: Any string where a character's bit is to be flipped. + :type string: str + + :return: Returns the input string `string` with one character's bit flipped at a random position. + :rtype: str + """ + if string == "": + # If the string is empty, there's no character to flip, so return the empty string. + return string + + # Generate a random integer position within the range of the string's indices. + pos = random.randint(0, len(string) - 1) + + # Get the character at the randomly chosen position. + c = string[pos] + + # Generate a random bit position between 0 and 6 (since we are assuming 7-bit ASCII characters). + bit = 1 << random.randint(0, 6) + + # Flip the bit at the generated bit position using XOR. + new_c = chr(ord(c) ^ bit) + + # Create a new string by replacing the character at the random position with the new character. + # This is done by concatenating the substring before the random position, the new character, + # and the substring after the random position. + return string[:pos] + new_c + string[pos + 1 :] + + def get_random_float(self) -> float: + """Returns a random float value modified by a randomly chosen multiplier. + + This function generates a random float value between 0.0 and 1.0, and then + multiplies it by a randomly selected value from the list `self.__multiplier`. + + :return: A random positiv float value. + :rtype: float + """ + # Generate a random float between 0.0 and 1.0. + random_float = random.random() + + # Multiply the random float by a randomly chosen multiplier from the list `self.__multiplier`. + random_float *= random.choice(self.__multiplier) + + # Return the modified random float. + return random_float + + def check_inf(self, number: float) -> float: + """Checks if the number is infinite and replaces it with a random value if true. + + This function takes a floating-point number `number` as input. If the number is + positive or negative infinity, it replaces the number with a random value between + 0.0 and 1.0. It also logs this replacement. + + :param number: The number to check for infinity. + :type number: float + + :return: Returns the original number if it is not finite; otherwise, returns a random value between 0.0 and 1.0. + :rtype: float + """ + if math.isinf(number): + # If the number is infinite, replace it with a random value between 0.0 and 1.0. + number = random.random() + self.__logger.debug( + "The return value would be - or + INF, set it to a random value between 0.0 and 1.0" + ) + + # Return the potentially modified number. + return number + + def add_random_number(self, number: float) -> float: + """Returns the input number with a random float added. + + This function takes a floating-point number `number` as input and adds + a random float to it. The random float is obtained from the private method + `__get_random_float`. + + :param number: The number to which a random float will be added. + :type number: float + + :return: Returns the input number `number` with an added random float, + after ensuring the result is not infinite using the `__check_inf` method. + :rtype: float + """ + number += self.__get_random_float() + + # Check if the resulting number is infinite. + return self.__check_inf(number) + + def sub_random_number(self, number: float) -> float: + """Subtracts a random float from the given number. + + This function takes a float `number` as input and subtracts a randomly + generated float from it. The resulting number is then checked for + infinity values using the `__check_inf` method. + + :param number: The input number from which a random float will be subtracted. + :type number: float + + :return: Returns the resulting number after subtracting a random float and checking for infinity. + :rtype: float + """ + number -= self.__get_random_float() + + # Check if the resulting number is infinite. + return self.__check_inf(number) + + def mult_random_number(self, number: float) -> float: + """Returns the result of multiplying the input number by a random float. + + This function takes a floating-point number `number` as input and returns + a new floating-point number which is the result of multiplying the input + number by a randomly generated float. It also checks if the result is + infinite. + + :param number: A floating-point number to be multiplied by a random float. + :type number: float + + :return: Returns the input number multiplied by a random float, + after checking if the result is infinite. + :rtype: float + """ + number *= self.__get_random_float() + + # Check if the resulting number is infinite. + return self.__check_inf(number) + + def div_random_number(self, number: float) -> float: + """Divides the input number by a randomly generated float. + + This function takes a float `number` as input and divides it by + a random float generated by the `__get_random_float` method. It then + returns the result of this division after checking for infinity. + + :param number: The float number to be divided. + :type number: float + + :return: Returns the input number divided by a random float. + :rtype: float + """ + number /= self.__get_random_float() + + # Check if the resulting number is infinite. + return self.__check_inf(number) \ No newline at end of file diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/PowerSchedule.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/PowerSchedule.py new file mode 100644 index 00000000..ff85b12d --- /dev/null +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/PowerSchedule.py @@ -0,0 +1,42 @@ +from typing import List +from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed + +class PowerSchedule: + + def __init__(self): + """initialize PowerSchedule""" + print("Initialize PowerSchedule") + + def assignEnergy(self, population: List[Seed]) -> None: + """Assigns energy to seeds. + + This function takes a population of Seeds and assigns to every seed an energy of 1. + + :param population: List of type Seed. + :type population: list + """ + for seed in population: + seed.energy = 1 + + def normalizedEnergy(self, population: List[Seed]) -> None: + """Normalize energy of all seeds. + + This function takes a population of Seeds and normalizes the energy by dividing the + energy of every seed through sum of all energy values. + + :param population: List of type Seed. + :type population: list + """ + + def choose(self, population: List[Seed]) -> Seed: + """Returns a seed that was selected based on the energy. + + This function takes a population of Seeds and returns and selects a Seed by the energy of the seed. + The higher the energy, the more likely it is that the seed will be selected. + + :param population: List of type Seed. + :type population: list + + :return: Returns a seed. + :rtype: Seed + """ \ No newline at end of file diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py new file mode 100644 index 00000000..cfd65e05 --- /dev/null +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py @@ -0,0 +1,36 @@ +from typing import List + +class Seed: + + energy = 0 + seed_values = [] + + def __init__(self, energy: int = 0, seed_values: list = []): + """initialize PowerSchedule""" + self.energy = energy + self.seed_values = seed_values + + +class SeedManager: + + def __init__(self): + """initialize PowerSchedule""" + + def create_random_seed_population(self, seed_template: list, amount_seeds: int) -> List[Seed]: + """Returns a population of seeds with random values specified by the seed_template. + + This function takes a list 'seed_template' an creates random seeds based on the seed template. + The number of random seeds is specified by 'amount_seeds'. A list of the random seeds is returned. + + :param seed_template: A list of the data types of the seeds. + :type seed_template: list + + :param amount_seeds: Amount of random seeds which will be created. + :type amount_seeds: int + + :return: Returns a list of random seed objects. + :rtype: list + """ + + + From b164bce607810f419ada7ecd58e5bdc3558288a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6gel?= <hoegma01@thu.de> Date: Sun, 16 Jun 2024 13:39:45 +0200 Subject: [PATCH 09/19] Extended GreyBoxFuzzers + GreyBoxFuzzer tools - Added DataTypeCreator: Necessary for creating certain data types - Extended SeedManager: Population can be created --- .../fuzzing/fuzzer_utils/GreyBoxFuzzer.py | 10 +- .../fuzzer_tools/DataTypeCreator.py | 149 ++++++++++++++++++ .../fuzzing/fuzzer_utils/fuzzer_tools/Seed.py | 81 ++++++++++ .../grey_box/test_grey_box_on_helpers.py | 54 +++++++ 4 files changed, 291 insertions(+), 3 deletions(-) create mode 100644 custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py create mode 100644 custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py diff --git a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py index 1838b26e..42918382 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py +++ b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py @@ -1,13 +1,15 @@ -from typing import Callable +from custom_components.test.fuzzing.fuzzer_utils.Fuzzer import Fuzzer +from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed +from typing import Callable, List -class GreyBoxFuzzer: +class GreyBoxFuzzer(Fuzzer): """GreyBox fuzzer class, inherits from the abstract fuzzer class.""" def __init__(self): """initialize GreyBoxFuzzer""" print("Initialize GreyBoxFuzzer") - def fuzz(self, seed_template: list, function: Callable, rounds: int = 1): + def fuzz(self, seed_population: List[Seed], seed_template: list, function: Callable, rounds: int = 1): """The function returns a list of the number of passed and failed tests. The seed is changed randomly in any number of rounds (defined by rounds). @@ -23,6 +25,8 @@ def fuzz(self, seed_template: list, function: Callable, rounds: int = 1): :return: Returns a list indicating how many tests were successful and how many failed. :rtype: list """ + for seed in seed_population: + print(seed.seed_values) print("Fuzzing...") diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py new file mode 100644 index 00000000..83daa818 --- /dev/null +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py @@ -0,0 +1,149 @@ +import random +import string + +class DataTypeCreator: + + def __init__(self): + """initialize DataTypeCreator""" + + def create_int(self, amount_digits: int) -> int: + """Returns an int value with a certain number of digits. + + This function takes a value 'amount_digits' and returns an integer with this amount of digits. + + :param amount_digits: Amount of digits the integer should have + :type amount_digits: int + + :return: Returns an integer with a certain amount of digits. + :rtype: int + """ + seed_value = '' + for digit in range(amount_digits): + if digit == 0: + # Decide if negative of positive int + rand_val = random.randint(0,1) + if rand_val == 0: + seed_value += '-' + # First digit should not be a 0 + rand_val = str(random.randint(1,9)) + seed_value += rand_val + else: + rand_val = str(random.randint(0,9)) + seed_value += rand_val + + # cast to int type and append to seed + if digit == amount_digits-1: + return int(seed_value) + + def create_uint(self, amount_digits: int) -> int: + """Returns an uint value with a certain number of digits. + + This function takes a value 'amount_digits' and returns an unsigned integer with this amount of digits. + + :param amount_digits: Amount of digits the unsigned integer should have + :type amount_digits: uint + + :return: Returns an unsigned integer with a certain amount of digits. + :rtype: int + """ + seed_value = '' + for digit in range(amount_digits): + if digit == 0: + # First digit should not be a 0 + rand_val = str(random.randint(1,9)) + seed_value += rand_val + else: + rand_val = str(random.randint(0,9)) + seed_value += rand_val + + # cast to int type and append to seed + if digit == amount_digits-1: + return int(seed_value) + + def create_float(self, amount_digits: int) -> int: + """Returns an int value with a certain number of digits. + + This function takes a value 'amount_digits' and returns an integer with this amount of digits. + + :param amount_digits: Amount of digits the integer should have + :type amount_digits: int + + :return: Returns an integer with a certain amount of digits. + :rtype: int + """ + print("create float") + + def create_string_only_letters(self, amount_chars: int) -> int: + """Returns an string with a certain number of chars. + + This function takes a value 'amount_chars' and returns an string with this amount of chars. + + :param amount_chars: Amount of chars the string should have + :type amount_chars: int + + :return: Returns an string with a certain amount of chars. + :rtype: string + """ + seed_value = '' + for character in range(amount_chars): + random_letter = random.choice(string.ascii_letters) + + seed_value += random_letter + + # cast to int type and append to seed + if character == amount_chars-1: + return seed_value + + def create_string_special_characters(self, amount_chars: int) -> int: + """Returns an string with a certain number of chars. + + This function takes a value 'amount_chars' and returns an string with this amount of chars. + The string includes uppercase and lowercase letters and special charakters. + Special charakters = "!@#$%^&*()_+-=[]{}|;:',.<>?/`~". + + :param amount_chars: Amount of chars the string should have + :type amount_chars: int + + :return: Returns an string with a certain amount of chars. + :rtype: string + """ + special_characters = "!@#$%^&*()_+-=[]{}|;:',.<>?/`~" + seed_value = '' + for character in range(amount_chars): + rand_value = random.randint(0,4) + if rand_value == 0: + random_letter = random.choice(special_characters) + else: + random_letter = random.choice(string.ascii_letters) + + seed_value += random_letter + + # cast to int type and append to seed + if character == amount_chars-1: + return seed_value + + def create_random_string(self, amount_chars: int) -> int: + """Returns an string with a certain number of chars. + + This function takes a value 'amount_chars' and returns an string with this amount of chars. + + :param amount_chars: Amount of chars the string should have + :type amount_chars: int + + :return: Returns an string with a certain amount of chars. + :rtype: string + """ + + def create_byte(self, amount_digits: int) -> int: + """Returns an int value with a certain number of digits. + + This function takes a value 'amount_digits' and returns an integer with this amount of digits. + + :param amount_digits: Amount of digits the integer should have + :type amount_digits: int + + :return: Returns an integer with a certain amount of digits. + :rtype: int + """ + print("create byte") + diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py index cfd65e05..0585920c 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py @@ -1,4 +1,8 @@ from typing import List +from custom_components.test.fuzzing.fuzzer_utils.ValuePoolFuzzer import ValuePoolFuzzer +from custom_components.test.fuzzing.fuzzer_utils.ParamRunner import ParamRunner +from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.DataTypeCreator import DataTypeCreator +import random class Seed: @@ -13,6 +17,10 @@ def __init__(self, energy: int = 0, seed_values: list = []): class SeedManager: + __value_pool_fuzzer = ValuePoolFuzzer() + __param_runner = ParamRunner() + __data_type_creator = DataTypeCreator() + def __init__(self): """initialize PowerSchedule""" @@ -31,6 +39,79 @@ def create_random_seed_population(self, seed_template: list, amount_seeds: int) :return: Returns a list of random seed objects. :rtype: list """ + param_combi = random.randint(1, len(seed_template)) + param_set = self.__value_pool_fuzzer.fuzz(len(seed_template),seed_template, param_combi) + param_set = self.__param_runner.limit_param_set(param_set, amount_seeds) + + seed_population = [] + for param in param_set: + seed = Seed(energy=1, seed_values=param) + seed_population.append(seed) + + return seed_population + + def create_specific_seed_population(self, seed_template: list, seed_specification: list, amount_seeds: int) -> List[Seed]: + """Returns a population of seeds with specific values based on the seed template and seed specifiction. + + This function takes two list 'seed_template' and 'seed_specification' and creates seeds. + The number of specific seeds is specified by 'amount_seeds'. A list of the random seeds is returned. + + :param seed_template: A list of the data types of the seeds. + Supported data types: "INT", "UINT", "FLOAT", "STRING", "BOOL", "BYTE", "LIST", "DICT", "DATE", + E.g.: ["STRING", "INT", "INT"] + :type seed_template: list + + :param seed_specification: A list that provides the number of digits for each data type in seed_template. + If a random data type is to be initialised anyway, this must be marked with an 'r'. + E.g.: [5, 2, 'r'] + :type seed_specification: list + + :param amount_seeds: Amount of specific seeds which will be created. + :type amount_seeds: int + + :return: Returns a list of specific seed objects. + :rtype: list + """ + param_combi = random.randint(1, len(seed_template)) + + # Throw exception if seed_specification and seed_template aren't the same length + if len(seed_template) != len(seed_specification): + raise ValueError("Length of seed_template and seed_specification must be the same length.") + + seed_population = [] + + for _ in range(amount_seeds): + param_set = [] + for seed_spec, data_type in zip(seed_specification, seed_template): + if data_type == "INT": + param_set.append(self.__data_type_creator.create_int(seed_spec)) + elif data_type == "UINT": + param_set.append(self.__data_type_creator.create_uint(seed_spec)) + elif data_type == "FLOAT": + print("create_float") + elif data_type == "STRING": + rand_val = random.randint(0,1) + if rand_val == 0: + param_set.append(self.__data_type_creator.create_string_only_letters(seed_spec)) + elif rand_val == 1: + param_set.append(self.__data_type_creator.create_string_special_characters(seed_spec)) + elif data_type == "BOOL": + param_set.append(random.choice([True, False])) + elif data_type == "BYTE": + print("create_byte") + elif data_type == "LIST": + print("create_list") + elif data_type == "DICT": + print("create_dict") + elif data_type == "DATE": + print("create_date") + + seed = Seed(1, param_set) + seed_population.append(seed) + + return seed_population + + diff --git a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py new file mode 100644 index 00000000..81de8a4c --- /dev/null +++ b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py @@ -0,0 +1,54 @@ +from custom_components.test.fuzzing.fuzzer_utils.GreyBoxFuzzer import GreyBoxFuzzer +from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import SeedManager, Seed +from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.DataTypeCreator import DataTypeCreator + +from custom_components.loxone.helpers import ( + map_range, + hass_to_lox, + lox_to_hass, + lox2lox_mapped, + lox2hass_mapped, + to_hass_color_temp, + to_loxone_color_temp, + get_room_name_from_room_uuid, + get_cat_name_from_cat_uuid, + add_room_and_cat_to_value_values, + get_miniserver_type, + get_all, +) + +# Function to test the grey box fuzzer +def crashme(s: str) -> None: + cnt = 0 + if len(s) > 0 and s[0] == 'b': + cnt += 1 + if len(s) > 1 and s[1] == 'a': + cnt += 1 + if len(s) > 2 and s[2] == 'd': + cnt += 1 + if len(s) > 3 and s[3] == '!': + cnt += 1 + if cnt >= 3: + raise Exception() + + +seed_manager = SeedManager() +grey_box_fuzzer = GreyBoxFuzzer() + +# seed specification + +seed_template = ["STRING"] +seed_specification = [4] +amount_seeds = 10 + +# create a population + +#seed_population = seed_manager.create_random_seed_population(seed_template, 3) +seed_population = seed_manager.create_specific_seed_population(seed_template,seed_specification,amount_seeds) + +# Print seeds in population +for i in seed_population: + print(i.seed_values) + + +grey_box_fuzzer.fuzz(seed_population, seed_template, crashme,10) From f527ea07054dc4371e59bb75fac17c8f90916033 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6gel?= <hoegma01@thu.de> Date: Sun, 16 Jun 2024 14:28:29 +0200 Subject: [PATCH 10/19] Extend DataTypeCreator - Extended methods for creating random int, uint, string so that random values can be created --- .../fuzzing/fuzzer_utils/GreyBoxFuzzer.py | 3 +- .../fuzzer_tools/DataTypeCreator.py | 78 +++++++++++-------- .../fuzzing/fuzzer_utils/fuzzer_tools/Seed.py | 12 ++- .../grey_box/test_grey_box_on_helpers.py | 5 +- 4 files changed, 60 insertions(+), 38 deletions(-) diff --git a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py index 42918382..3d8ac6a4 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py +++ b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py @@ -25,8 +25,7 @@ def fuzz(self, seed_population: List[Seed], seed_template: list, function: Calla :return: Returns a list indicating how many tests were successful and how many failed. :rtype: list """ - for seed in seed_population: - print(seed.seed_values) + print("Fuzzing...") diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py index 83daa818..43b5764f 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py @@ -3,10 +3,13 @@ class DataTypeCreator: + MAX_INT = (1 << 31) - 1 + MAX_UINT = (1 << 32) - 1 + def __init__(self): """initialize DataTypeCreator""" - def create_int(self, amount_digits: int) -> int: + def create_int(self, amount_digits: int = 10, random_creation: bool = True) -> int: """Returns an int value with a certain number of digits. This function takes a value 'amount_digits' and returns an integer with this amount of digits. @@ -14,28 +17,35 @@ def create_int(self, amount_digits: int) -> int: :param amount_digits: Amount of digits the integer should have :type amount_digits: int + :param random_creation: + :type random_creation: boolean + :return: Returns an integer with a certain amount of digits. :rtype: int """ - seed_value = '' - for digit in range(amount_digits): - if digit == 0: - # Decide if negative of positive int - rand_val = random.randint(0,1) - if rand_val == 0: - seed_value += '-' - # First digit should not be a 0 - rand_val = str(random.randint(1,9)) - seed_value += rand_val - else: - rand_val = str(random.randint(0,9)) - seed_value += rand_val - - # cast to int type and append to seed - if digit == amount_digits-1: - return int(seed_value) + if random_creation == True: + random_seed_value = random.randint(-self.MAX_INT, self.MAX_INT) + return random_seed_value + else: + seed_value = '' + for digit in range(amount_digits): + if digit == 0: + # Decide if negative of positive int + rand_val = random.randint(0,1) + if rand_val == 0: + seed_value += '-' + # First digit should not be a 0 + rand_val = str(random.randint(1,9)) + seed_value += rand_val + else: + rand_val = str(random.randint(0,9)) + seed_value += rand_val + + # cast to int type and append to seed + if digit == amount_digits-1: + return int(seed_value) - def create_uint(self, amount_digits: int) -> int: + def create_uint(self, amount_digits: int = 10, random_creation: bool = True) -> int: """Returns an uint value with a certain number of digits. This function takes a value 'amount_digits' and returns an unsigned integer with this amount of digits. @@ -46,19 +56,23 @@ def create_uint(self, amount_digits: int) -> int: :return: Returns an unsigned integer with a certain amount of digits. :rtype: int """ - seed_value = '' - for digit in range(amount_digits): - if digit == 0: - # First digit should not be a 0 - rand_val = str(random.randint(1,9)) - seed_value += rand_val - else: - rand_val = str(random.randint(0,9)) - seed_value += rand_val - - # cast to int type and append to seed - if digit == amount_digits-1: - return int(seed_value) + if random_creation == True: + random_seed_value = random.randint(0, self.MAX_UINT) + return random_seed_value + else: + seed_value = '' + for digit in range(amount_digits): + if digit == 0: + # First digit should not be a 0 + rand_val = str(random.randint(1,9)) + seed_value += rand_val + else: + rand_val = str(random.randint(0,9)) + seed_value += rand_val + + # cast to int type and append to seed + if digit == amount_digits-1: + return int(seed_value) def create_float(self, amount_digits: int) -> int: """Returns an int value with a certain number of digits. diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py index 0585920c..64b4e6f6 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py @@ -16,7 +16,8 @@ def __init__(self, energy: int = 0, seed_values: list = []): class SeedManager: - + RANGE_RANDOM_INT = 9 + RANGE_RANDOM_STRING = 100 __value_pool_fuzzer = ValuePoolFuzzer() __param_runner = ParamRunner() __data_type_creator = DataTypeCreator() @@ -84,12 +85,19 @@ def create_specific_seed_population(self, seed_template: list, seed_specificatio param_set = [] for seed_spec, data_type in zip(seed_specification, seed_template): if data_type == "INT": - param_set.append(self.__data_type_creator.create_int(seed_spec)) + if seed_spec == 'r': + param_set.append(self.__data_type_creator.create_int(seed_spec,True)) + else: + param_set.append(self.__data_type_creator.create_int(seed_spec,False)) elif data_type == "UINT": + if seed_spec == 'r': + seed_spec = random.randint(1, self.RANGE_RANDOM_INT) param_set.append(self.__data_type_creator.create_uint(seed_spec)) elif data_type == "FLOAT": print("create_float") elif data_type == "STRING": + if seed_spec == 'r': + seed_spec = random.randint(1, self.RANGE_RANDOM_STRING) rand_val = random.randint(0,1) if rand_val == 0: param_set.append(self.__data_type_creator.create_string_only_letters(seed_spec)) diff --git a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py index 81de8a4c..33370867 100644 --- a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py +++ b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py @@ -34,11 +34,12 @@ def crashme(s: str) -> None: seed_manager = SeedManager() grey_box_fuzzer = GreyBoxFuzzer() +data_type = DataTypeCreator() # seed specification -seed_template = ["STRING"] -seed_specification = [4] +seed_template = ["STRING", "INT"] +seed_specification = ['r', 'r'] amount_seeds = 10 # create a population From 379966470c0419a5b438740766fa9721e20db171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6gel?= <hoegma01@thu.de> Date: Mon, 24 Jun 2024 23:27:29 +0200 Subject: [PATCH 11/19] Add branch coverage to GreyBoxFuzzer It is now possible to track the branch coverage of a test function. --- .coverage | Bin 0 -> 53248 bytes .../fuzzing/fuzzer_utils/GreyBoxFuzzer.py | 71 ++++++++++-- .../fuzzing/fuzzer_utils/GreyBoxRunner.py | 78 +++++++++++++ .../fuzzing/fuzzer_utils/fuzzer_tools/Seed.py | 104 ++---------------- .../grey_box/test_grey_box_on_helpers.py | 34 ++++-- 5 files changed, 177 insertions(+), 110 deletions(-) create mode 100644 .coverage create mode 100644 custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py diff --git a/.coverage b/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..e31f6ae8427feeee31e12da6bf66f97b970931d4 GIT binary patch literal 53248 zcmeI)L2TPp7zc1WY2zkM_F7Gwmu6Ys1-fc&rwR~a9AFz2nwYe~I(DGKbCQ?VEU`P= zS=&iyYKJrd;si(F2uH*Xi4#IxxFEy{i3>tVlPchZIIsiXdwy=3t(_)q(mM9Hn)I{% z-t&7uzxNz3ozJ~`+KUBm1!2dH`BTP-VVcH^oEwI*hh8OmrHe(uK)OP|%`Ndx;T~i9 z<hLdEx>3x1V6Zn!XV_Hn<I<DEKNdUr_l7^U+<b#}V1WPxAOL~KTVU<gqGgxM=K9;Q zTW^avbQ>Z}-b*K6n>{`^%jb@tI6cdgZTxV7(=s*1kMl5C=1U^vEw3%O=Qq8E8+-l& zj~7Lnx*Lh6ywK4$y41;p(<;A=9Ir{aVzEF)EQMaj4OjUku{x;|=%dAAB~CU_A;Mem zWeR>+XP@AqXo*nx4G|@^IP5hi3KQ3_6)b!2Uh{@fZF0i~{S5cDhBow?a;ilIHG;4y zYhMptzp*HyN$zg8un|x_SK_3#VbD1m2fXe1s+oS|#a`fZu_7AXSTwh7;G{%L7sA`x z8Y=6eY|zCdr#{veonGc`a_ZzdbBikH2PtbWqdYWaR(1BfojQ@f)$J_FlS4U@1INKm zId{q%2fDHMd~UWmm%aA(oz9e8p3x*%*Y_@W#pcPM<h`7esu_MT$z>y`2Rt#Hv!B~* z8q_PrN_4qRpSK*h8wYCLp_A{-&|C4H(ILyO)yx~0RY#FLPTh;6%^=^`X*M_S&~@bg zo%@X(CH<X5j-qy(2Q4*ZWNx0E9O}(m$IQ_b!wuy?<AF{{HGQbPO;(ob!0YXx-rv_$ zkBI~g5Sk0pWUa2>kV7SsZD<5nMCdMvX}vd@79Ms-qOLQO&)KuJox09u7jR}a!@^oQ zXW5mCxt>?uNS`hJ%j)Yw>6zSS8cla~gd26kbRxOiB$?*u&64RD(R-*lQ&!GCUfHQR zI-N74gP}ERz_R!4GuM(4A?GFe8tiL?+|nBYw_OPCz4}XTyvXmYyWC6_A0N!w&+pr* z;<5_PjC{{se<P!+e50)NJ2dIbSGKRpa!aquuZY#DD{h-+lO@*+X~@zXoLm98OxScO zv0D$ivFg8aRdr8!?djDwDWJEW+Ec0tm7Iq6*&vEMx~$5}R&*@g%EuSoNX|mhIIjmm zTeyC5ZJ->dB08oQaaD&quSzfU%7|8F84Fj&Rh`m{i=5`?XwaqOi-Jza^_%*7B`?EC zsWkV?!6}cEOd^z#kJF%xXo%icW?HD;D`%{<{OKh!k)?LrW-{F6N!e23@}jt-xa4Z| zk{gTI>j*BBsLH60wd_W`5enK#*V2jc?A^M7JX_9;e3y60L-M2CTFtcVBS*}&t{#lZ zJmW-kQSZ2Ewe@PIuan%iv)5nF(1c5$cDD8tX}5(#t|mv2lUs5I>xn{|N`KZVOvoF2 zll^PZ4;BbO00Izz00bZa0SG_<0uX=z1pa>lSu<l=^8P==el^%%^o9ij5P$##AOHaf zKmY;|fB*y_0D(tSpqR;4S^6g)GtA6ZZ2d<7&rUxx{d9p^m0>pxc9Z@6Xf_bFh5!U0 z009U<00Izz00bZa0SG`K6)0w_X8NxHnL@Ub*Z&BR{QZAuurN)D-w=QR1Rwwb2tWV= z5P$##AOHaf+`quuusL9?S7wjRpZC4<?autn^f}QHQ7ppwb3rR!rU$Dy=ji!8p%<?@ zbF_n=1T_D0NRKpdP6U28nm@aGI#{9SuFN;OQ5<v}daA+_t@?2^A5-%AR`=>vnP(vs zt4=*wQCppK%?W&GQMBn{NfA9SL4N;lvdEwxED(SI1Rwwb2tWV=5P$##AOHafJUD^1 z;equ^ewW<;<N1FN?!=-b2tWV=5P$##AOHafKmY;|fWR&k$eLNBlzjhx%V4*5VGU6w z2tWV=5P$##AOHafKmY;|fB*#UTOgY)<>mMP#>jmag#sV|0SG_<0uX=z1Rwwb2tWV= z5ZJ{6^80_>|L@`xgsMRR0uX=z1Rwwb2tWV=5P$##9)N)S{(qD$80;VRC;OfK!hT}k zvv1f}><ji8`-FYO-e>D<l`T;cED(SI1Rwwb2tWV=5P$##AOHaf++DyL9n9xVv&O9v z74Qv<so+q}+M|N%A*-Z<gH@}jg33W_SOsGhtDu7Nn3Y$7UABf)P}{I96;x|hP6d^! zHK>Ae#Trn-fwGlVfqlTrs9?WsnJU=WFTejEWf$cA{~zo(_Bs2R?)raVAF_AZckCML zvai`C_9eT(+`Bgrdm#V;2tWV=5P$##AOHafKmY=dp+G(-Z};hbe^f_&V?;-XYD`Df zLwj^|uv*el<zP`qW0heYmB$J?vdeiL)i#E7M0fr=s#J42Dpv+|bf7$-Bl|#BNBix} mkg8vPV?X}?|6|xY(PRif00Izz00bZa0SG_<0uXrY1pWiq<i0-u literal 0 HcmV?d00001 diff --git a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py index 3d8ac6a4..8d6b8d5c 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py +++ b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py @@ -1,26 +1,42 @@ from custom_components.test.fuzzing.fuzzer_utils.Fuzzer import Fuzzer from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed +from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.DataTypeCreator import DataTypeCreator from typing import Callable, List +import random class GreyBoxFuzzer(Fuzzer): """GreyBox fuzzer class, inherits from the abstract fuzzer class.""" + RANGE_RANDOM_INT = 9 + RANGE_RANDOM_STRING = 100 + __data_type_creator = DataTypeCreator() + def __init__(self): """initialize GreyBoxFuzzer""" print("Initialize GreyBoxFuzzer") - def fuzz(self, seed_population: List[Seed], seed_template: list, function: Callable, rounds: int = 1): - """The function returns a list of the number of passed and failed tests. - The seed is changed randomly in any number of rounds (defined by rounds). + def fuzz(self, + seed_template: list, + seed_specification: list = None, + amount_seeds: int = 20) -> List[Seed]: + """Returns a population of seeds with specific values based on the seed template and seed specifiction. + + This function takes two lists 'seed_template' and 'seed_specification' and creates seeds. + The number of seeds is specified by 'amount_seeds'. A list of the random seeds is returned. :param seed_template: The seed_template is a list of input types for the function. The entries must correspond to the valid function parameters of the function to be tested. e.g.: ["INT", "FLOAT", "STRING", "BOOLEAN"] :type seed_template: list - :param function: The function to test - :type function: Callable - :param rounds: SSpecifies how often the function should be tested with different inputs. The default is 1. - :type rounds: int + + :param seed_specification: A list that provides the number of digits for each data type in seed_template. + If a random data type is to be initialised anyway, this must be marked with an 'r'. + Defalault value ist random for every data type. + E.g.: [5, 2, 'r'] + :type seed_specification: list + + :param amount_seeds: Amount of seeds which will be created. + :type amount_seeds: int :return: Returns a list indicating how many tests were successful and how many failed. :rtype: list @@ -28,5 +44,46 @@ def fuzz(self, seed_population: List[Seed], seed_template: list, function: Calla print("Fuzzing...") + # Throw exception if seed_specification and seed_template aren't the same length + if len(seed_template) != len(seed_specification): + raise ValueError("Length of seed_template and seed_specification must be the same length.") + + seed_population = [] + for _ in range(amount_seeds): + param_set = [] + for seed_spec, data_type in zip(seed_specification, seed_template): + if data_type == "INT": + if seed_spec == 'r': + param_set.append(self.__data_type_creator.create_int(seed_spec,True)) + else: + param_set.append(self.__data_type_creator.create_int(seed_spec,False)) + elif data_type == "UINT": + if seed_spec == 'r': + seed_spec = random.randint(1, self.RANGE_RANDOM_INT) + param_set.append(self.__data_type_creator.create_uint(seed_spec)) + elif data_type == "FLOAT": + print("create_float") + elif data_type == "STRING": + if seed_spec == 'r': + seed_spec = random.randint(1, self.RANGE_RANDOM_STRING) + rand_val = random.randint(0,1) + if rand_val == 0: + param_set.append(self.__data_type_creator.create_string_only_letters(seed_spec)) + elif rand_val == 1: + param_set.append(self.__data_type_creator.create_string_special_characters(seed_spec)) + elif data_type == "BOOL": + param_set.append(random.choice([True, False])) + elif data_type == "BYTE": + print("create_byte") + elif data_type == "LIST": + print("create_list") + elif data_type == "DICT": + print("create_dict") + elif data_type == "DATE": + print("create_date") + + seed = Seed(1, param_set) + seed_population.append(seed) + return seed_population \ No newline at end of file diff --git a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py new file mode 100644 index 00000000..0641d3df --- /dev/null +++ b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py @@ -0,0 +1,78 @@ +import logging +import inspect +import coverage +from typing import Callable, List + +from custom_components.test.fuzzing.fuzzer_utils.Runner import Runner +from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed, SeedManager + + +class GreyBoxRunner(Runner): + """Greybox runner class, inherits from the abstract runner class.""" + + __logger = None + __seed_manager = None + + def __init__(self): + """constructor""" + self.__logger = logging.getLogger(__name__) + self.__seed_manager = SeedManager() + + def run(self, function: Callable, seed_population: List[Seed], amount_runs: int = 10000) -> list: + """Executes all transferred parameter sets for the transferred function. + + :param function: The passed function which is to be fuzzed. + :type function: Callable + + :param seed_population: A list with seeds. A seed is a set of parameters. + :type seed_population: list + + :param amount_runs: The number of times the function is to be tested. + :param amount_runs: int + + :return: Returns a dict with 2 keys, + the key 'passed_tests' contains the number of passed tests, + the key 'failed_tests' contains the number of failed tests. + :rtype: dict + """ + coverages_seen = set() + cov = coverage.Coverage(branch=True) + + sig = inspect.signature(function) + num_params = len(sig.parameters) + self.__logger.debug(f"The given functions needs {str(num_params)} parameters") + + test_results = { + "passed_tests": 0, + "failed_tests": 0, + } + + for generation in range(0, amount_runs): + seed = self.__seed_manager.select_seed(seed_population) + cov.start() + try: + function(*seed.seed_values) + cov.stop() + cov.save() + test_results["passed_tests"] += 1 + self.__logger.debug(f"Test {generation} passed with parameters: {seed.seed_values}") + except Exception as e: + cov.stop() + cov.save() + test_results["failed_tests"] += 1 + self.__logger.error(f"Test {generation} failed with parameters: {seed.seed_values}.") + self.__logger.error(f"Exception: {e}") + + data = cov.get_data() + filename = next(iter(data.measured_files())) + branches_covered = data.arcs(filename) + + new_branches = set(branches_covered) - coverages_seen + coverages_seen.update(branches_covered) + + if new_branches: + self.__logger.debug(f"Newly Covered Branches: {new_branches}") + print(f"Test {generation}: Newly Covered Branches: {new_branches}") + + + return test_results \ No newline at end of file diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py index 64b4e6f6..73006d5a 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py @@ -16,108 +16,26 @@ def __init__(self, energy: int = 0, seed_values: list = []): class SeedManager: - RANGE_RANDOM_INT = 9 - RANGE_RANDOM_STRING = 100 - __value_pool_fuzzer = ValuePoolFuzzer() - __param_runner = ParamRunner() - __data_type_creator = DataTypeCreator() + counter = -1 def __init__(self): """initialize PowerSchedule""" - def create_random_seed_population(self, seed_template: list, amount_seeds: int) -> List[Seed]: - """Returns a population of seeds with random values specified by the seed_template. + def select_seed(self, seed_population: List[Seed]) -> Seed: + """Selects a seed based on their energy. - This function takes a list 'seed_template' an creates random seeds based on the seed template. - The number of random seeds is specified by 'amount_seeds'. A list of the random seeds is returned. + This function selects a seed. + The higher the energy of a seed, the more likely it is that a seed will be selected. - :param seed_template: A list of the data types of the seeds. - :type seed_template: list + :param seed_population: A list with seeds. A seed is a set of parameters. + :type seed_population: list - :param amount_seeds: Amount of random seeds which will be created. - :type amount_seeds: int - - :return: Returns a list of random seed objects. - :rtype: list - """ - param_combi = random.randint(1, len(seed_template)) - param_set = self.__value_pool_fuzzer.fuzz(len(seed_template),seed_template, param_combi) - param_set = self.__param_runner.limit_param_set(param_set, amount_seeds) - - seed_population = [] - for param in param_set: - seed = Seed(energy=1, seed_values=param) - seed_population.append(seed) - - return seed_population - - def create_specific_seed_population(self, seed_template: list, seed_specification: list, amount_seeds: int) -> List[Seed]: - """Returns a population of seeds with specific values based on the seed template and seed specifiction. - - This function takes two list 'seed_template' and 'seed_specification' and creates seeds. - The number of specific seeds is specified by 'amount_seeds'. A list of the random seeds is returned. - - :param seed_template: A list of the data types of the seeds. - Supported data types: "INT", "UINT", "FLOAT", "STRING", "BOOL", "BYTE", "LIST", "DICT", "DATE", - E.g.: ["STRING", "INT", "INT"] - :type seed_template: list - - :param seed_specification: A list that provides the number of digits for each data type in seed_template. - If a random data type is to be initialised anyway, this must be marked with an 'r'. - E.g.: [5, 2, 'r'] - :type seed_specification: list - - :param amount_seeds: Amount of specific seeds which will be created. - :type amount_seeds: int - - :return: Returns a list of specific seed objects. - :rtype: list + :return: Returns a single seed. + :rtype: Seed """ - param_combi = random.randint(1, len(seed_template)) - - # Throw exception if seed_specification and seed_template aren't the same length - if len(seed_template) != len(seed_specification): - raise ValueError("Length of seed_template and seed_specification must be the same length.") - - seed_population = [] - - for _ in range(amount_seeds): - param_set = [] - for seed_spec, data_type in zip(seed_specification, seed_template): - if data_type == "INT": - if seed_spec == 'r': - param_set.append(self.__data_type_creator.create_int(seed_spec,True)) - else: - param_set.append(self.__data_type_creator.create_int(seed_spec,False)) - elif data_type == "UINT": - if seed_spec == 'r': - seed_spec = random.randint(1, self.RANGE_RANDOM_INT) - param_set.append(self.__data_type_creator.create_uint(seed_spec)) - elif data_type == "FLOAT": - print("create_float") - elif data_type == "STRING": - if seed_spec == 'r': - seed_spec = random.randint(1, self.RANGE_RANDOM_STRING) - rand_val = random.randint(0,1) - if rand_val == 0: - param_set.append(self.__data_type_creator.create_string_only_letters(seed_spec)) - elif rand_val == 1: - param_set.append(self.__data_type_creator.create_string_special_characters(seed_spec)) - elif data_type == "BOOL": - param_set.append(random.choice([True, False])) - elif data_type == "BYTE": - print("create_byte") - elif data_type == "LIST": - print("create_list") - elif data_type == "DICT": - print("create_dict") - elif data_type == "DATE": - print("create_date") - - seed = Seed(1, param_set) - seed_population.append(seed) + self.counter += 1 + return seed_population[self.counter] - return seed_population diff --git a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py index 33370867..ed7e7a17 100644 --- a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py +++ b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py @@ -1,6 +1,6 @@ from custom_components.test.fuzzing.fuzzer_utils.GreyBoxFuzzer import GreyBoxFuzzer -from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import SeedManager, Seed -from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.DataTypeCreator import DataTypeCreator +from custom_components.test.fuzzing.fuzzer_utils.GreyBoxRunner import GreyBoxRunner +from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed from custom_components.loxone.helpers import ( map_range, @@ -32,24 +32,38 @@ def crashme(s: str) -> None: raise Exception() -seed_manager = SeedManager() grey_box_fuzzer = GreyBoxFuzzer() -data_type = DataTypeCreator() +grey_box_runner = GreyBoxRunner() # seed specification -seed_template = ["STRING", "INT"] -seed_specification = ['r', 'r'] amount_seeds = 10 +seed_template = ["STRING"] +seed_specification = ['r'] -# create a population -#seed_population = seed_manager.create_random_seed_population(seed_template, 3) -seed_population = seed_manager.create_specific_seed_population(seed_template,seed_specification,amount_seeds) +# create a population with fuzzer +#seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 20) + +seed_1 = Seed(1, ["bear"]) +seed_2 = Seed(1, ["rats"]) +seed_3 = Seed(1, ["rods"]) +seed_4 = Seed(1, ["hii!"]) +seed_5 = Seed(1, ["deer"]) +seed_6 = Seed(1, ["lol!"]) +seed_7 = Seed(1, ["bad!"]) + +seed_population = [seed_1, seed_2, seed_3, seed_4, seed_5, seed_6, seed_7] # Print seeds in population +print("##### Population #####") for i in seed_population: print(i.seed_values) +print("\n##### Execute Tests #####\n") + +test_results = grey_box_runner.run(crashme, seed_population, 7) -grey_box_fuzzer.fuzz(seed_population, seed_template, crashme,10) +print("\n##### Test restults #####\n") +print(f"Tests passed: {test_results['passed_tests']}") +print(f"Tests failed: {test_results['failed_tests']}") From 03570ab84c5ceaf16b195d2d960af1de337c0add Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6gel?= <hoegma01@thu.de> Date: Tue, 25 Jun 2024 20:21:56 +0200 Subject: [PATCH 12/19] Adjusted selection of seeds Seeds will now be selected by the energy of the seed. --- .coverage | Bin 53248 -> 53248 bytes .../fuzzing/fuzzer_utils/GreyBoxRunner.py | 44 +++++++++++--- .../fuzzing/fuzzer_utils/fuzzer_tools/Seed.py | 55 +++++++++++++++++- .../test/fuzzing/grey_box/test.py | 11 ++++ .../grey_box/test_grey_box_on_helpers.py | 4 +- 5 files changed, 102 insertions(+), 12 deletions(-) create mode 100644 custom_components/test/fuzzing/grey_box/test.py diff --git a/.coverage b/.coverage index e31f6ae8427feeee31e12da6bf66f97b970931d4..88c58686d2bd01ad0e1666e005cff02f3cfbc2c3 100644 GIT binary patch delta 195 zcmZozz}&Ead4e<}*F+g-My`zsOXiDk@b6&Y|IYu8|0(|+{>%KQ_z&{$*eocpfZvRr zgO!t$kx~9NJ3E*mC(q6XX2{C1vw|7Yvg|BihNLt*GngSRIr-gsF`%IvL59BNf6RZA z|2+Ru{@wf=frifHx8`I6nF%zM1IhxL3T4U4u|ZkVvaC>+q%;eZB`&%7&3+L90FyW* Aq5uE@ delta 404 zcmXZXEl<Nx7{zg#t$o(L^t1_CdwY9(t8jb-6dz(1)C2~DKy|?p3}jfs1Od4$Gb9Lt zn86@WBshX9%woBF^0=Qk;(sQy)@0VY)LwK|?0eDT;O=%O!FUUQSmPU?c*iSV@Q69? zaf54I;0&iY#(0^;d#EY#!_f0=MKPml2#nHF#4sfd7=@ueLl@Lz<eIt+l~c$_6?GW4 zAz~Oy0Ye+|8A?-|k-2e;m1fiReCWbQoA6(J;Q=2m_YG5=V}%+gc)}4D7~#-`@1hQa zO}7hAL{$@uN(+%GBT-=nB3<-Fx$cQn-W8=P6xpUDau-C}1frqRzA#g5QJO7(p10$F DKRP!< diff --git a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py index 0641d3df..22ff3acb 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py +++ b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py @@ -1,6 +1,7 @@ import logging import inspect import coverage +import hashlib from typing import Callable, List from custom_components.test.fuzzing.fuzzer_utils.Runner import Runner @@ -12,6 +13,7 @@ class GreyBoxRunner(Runner): __logger = None __seed_manager = None + __branch_dict = {} def __init__(self): """constructor""" @@ -36,7 +38,7 @@ def run(self, function: Callable, seed_population: List[Seed], amount_runs: int :rtype: dict """ coverages_seen = set() - cov = coverage.Coverage(branch=True) + sig = inspect.signature(function) num_params = len(sig.parameters) @@ -49,6 +51,7 @@ def run(self, function: Callable, seed_population: List[Seed], amount_runs: int for generation in range(0, amount_runs): seed = self.__seed_manager.select_seed(seed_population) + cov = coverage.Coverage(branch=True) cov.start() try: function(*seed.seed_values) @@ -63,16 +66,41 @@ def run(self, function: Callable, seed_population: List[Seed], amount_runs: int self.__logger.error(f"Test {generation} failed with parameters: {seed.seed_values}.") self.__logger.error(f"Exception: {e}") + # check branch coverage data = cov.get_data() filename = next(iter(data.measured_files())) - branches_covered = data.arcs(filename) - - new_branches = set(branches_covered) - coverages_seen - coverages_seen.update(branches_covered) + branch_covered = data.arcs(filename) + new_branches = set(branch_covered) - coverages_seen + coverages_seen.update(branch_covered) if new_branches: - self.__logger.debug(f"Newly Covered Branches: {new_branches}") - print(f"Test {generation}: Newly Covered Branches: {new_branches}") + self.__logger.debug(f"Newly covered branches: {new_branches}") + print(f"Test {generation}, seed_value: {seed.seed_values}, Newly covered branches: {new_branches}") + else: + print(f"Test {generation}, seed_value: {seed.seed_values}, No newly covered branches") + + # Create hashes + hashed_branch = self.__hash_md5(str(branch_covered)) + print(f"Hashed branch: {hashed_branch}") + self.__store_hashed_branch(hashed_branch) + + # Adjust energy of seed + print(f"Energy before: {seed.energy}") + self.__seed_manager.adjust_energy(seed, self.__branch_dict, hashed_branch) + print(f"Energy after: {seed.energy}\n") + print("\n##### Hashed branches #####\n") + print(f"Branch_dict: {self.__branch_dict}") - return test_results \ No newline at end of file + return test_results + + def __hash_md5(self, branch_covered: str) -> str: + md5_hash = hashlib.md5() + md5_hash.update(branch_covered.encode('utf-8')) + return md5_hash.hexdigest() + + def __store_hashed_branch(self, hashed_branch: str): + if hashed_branch in self.__branch_dict: + self.__branch_dict[hashed_branch] += 1 + else: + self.__branch_dict[hashed_branch] = 1 diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py index 73006d5a..affee53b 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py @@ -17,6 +17,7 @@ def __init__(self, energy: int = 0, seed_values: list = []): class SeedManager: counter = -1 + __a = 2 def __init__(self): """initialize PowerSchedule""" @@ -33,8 +34,58 @@ def select_seed(self, seed_population: List[Seed]) -> Seed: :return: Returns a single seed. :rtype: Seed """ - self.counter += 1 - return seed_population[self.counter] + #print(f"seed population: {seed_population}") + normalized_energy = self.get_normalized_energy(seed_population) + #print(f"normalized energy: {normalized_energy}") + random_value = random.uniform(0,1) + #print(f"random_value: {random_value}") + for index, normalized_energy_val in enumerate(normalized_energy): + if random_value <= normalized_energy_val: + seed = seed_population[index] + break + + return seed + #self.counter += 1 + #return seed_population[self.counter] + + def adjust_energy(self, seed: Seed, branch_dict: dict, hashed_branch: str): + """Adjusts the energy of a given seed. + + This function changes the energy of a seed based on how many times the branch was executed. + The formula for the adustment is: e = 1 / number_path_exercised + The number_path_exercised is the number of the how many times the path was seen in total. + + :param seed: A seed with a value and energy attribute. + :type seed: Seed + + :param branch_dict: A dictionary with hashes of the paths and a value of how many times the path was exercised. + :type branch_dict: dict + + :param hashed_branch: A hash of a path. + :type hashed_branch: str + + :return: Returns a single seed. + :rtype: Seed + """ + number_path_exercised = branch_dict[hashed_branch] + seed.energy = 1 / number_path_exercised + + def get_normalized_energy(self, seed_population: List[Seed]) -> list: + total_energy = 0 + for seed in seed_population: + total_energy += seed.energy + + normalized_energy = [] + + for index, seed in enumerate(seed_population): + if index == 0: + normalized_energy.append(seed.energy / total_energy) + else: + normalized_energy.append(normalized_energy[index-1] + (seed.energy / total_energy)) + + return normalized_energy + + diff --git a/custom_components/test/fuzzing/grey_box/test.py b/custom_components/test/fuzzing/grey_box/test.py new file mode 100644 index 00000000..aa02ac3a --- /dev/null +++ b/custom_components/test/fuzzing/grey_box/test.py @@ -0,0 +1,11 @@ +import hashlib + +def hash_string_md5(input_string: str) -> str: + md5_hash = hashlib.md5() + md5_hash.update(input_string.encode('utf-8')) + return md5_hash.hexdigest() + +# Beispiel: +input_string = "Hello, world!" +hashed_string = hash_string_md5(input_string) +print(f"Der MD5-Hash von '{input_string}' ist: {hashed_string}") diff --git a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py index ed7e7a17..f727ac1b 100644 --- a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py +++ b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py @@ -47,9 +47,9 @@ def crashme(s: str) -> None: seed_1 = Seed(1, ["bear"]) seed_2 = Seed(1, ["rats"]) -seed_3 = Seed(1, ["rods"]) +seed_3 = Seed(1, ["code"]) seed_4 = Seed(1, ["hii!"]) -seed_5 = Seed(1, ["deer"]) +seed_5 = Seed(1, ["beer"]) seed_6 = Seed(1, ["lol!"]) seed_7 = Seed(1, ["bad!"]) From 254c77b58e966d82ed1f2d4e35735c9269766533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6gel?= <hoegma01@thu.de> Date: Wed, 26 Jun 2024 09:59:50 +0200 Subject: [PATCH 13/19] Adjust uml diagramm --- .coverage | Bin 53248 -> 53248 bytes .../test/fuzzing/fuzzer_overview.puml | 96 ++++++++++++++ .../fuzzing/fuzzer_utils/GreyBoxFuzzer.py | 12 +- .../fuzzing/fuzzer_utils/GreyBoxRunner.py | 13 +- .../fuzzer_tools/DataTypeCreator.py | 10 +- .../fuzzer_utils/fuzzer_tools/Mutator.py | 60 +++++++-- .../fuzzer_tools/PowerSchedule.py | 42 ------ .../fuzzing/fuzzer_utils/fuzzer_tools/Seed.py | 7 +- .../test/fuzzing/grey_box/test.py | 11 -- .../grey_box/test_grey_box_on_helpers.py | 2 +- .../test_grey_box_on_helpers_complex.py | 120 ++++++++++++++++++ 11 files changed, 290 insertions(+), 83 deletions(-) delete mode 100644 custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/PowerSchedule.py delete mode 100644 custom_components/test/fuzzing/grey_box/test.py create mode 100644 custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers_complex.py diff --git a/.coverage b/.coverage index 88c58686d2bd01ad0e1666e005cff02f3cfbc2c3..0a027e526b0e2b154f46ef762231d7ac618edce7 100644 GIT binary patch delta 63 zcmZozz}&EadBchIBC_)AY^<D|jEpj}?5tphw9Mu^>!0z90cAMZKr%onHYf`ycVoYX F006UG3`GC{ delta 63 zcmZozz}&EadBchIB69NVY^<D|jEu5!?5tphwCv_P>!0z90cAMZKr%onHYf`ycVoYX F006X13{3z4 diff --git a/custom_components/test/fuzzing/fuzzer_overview.puml b/custom_components/test/fuzzing/fuzzer_overview.puml index 334f93ea..b3d3749d 100644 --- a/custom_components/test/fuzzing/fuzzer_overview.puml +++ b/custom_components/test/fuzzing/fuzzer_overview.puml @@ -97,6 +97,69 @@ class "MutationalFuzzer" as MFuzzer << class >> { ''''''''''''''''''''''''''''''''''''''' 'Runner'''''''''''''''''''''''''''''''' ''''''''''''''''''''''''''''''''''''''' +''''''''''''''''''''''''''''''''''''''' +class "GreyBoxFuzzer" as GBFuzzer << class >> { + - __RANGE_RANDOM_INT: int = 9 + - __RANGE_RANDOM_STRING: int = 100 + - __data_type_creator: DataTypeCreator + -- + - __init__(self) : void + + fuzz(self, seed_template: list, seed_specification,: list = None, amount_seeds: int = 100) : list[Seed] +} +''''''''''''''''''''''''''''''''''''''' +class "GreyBoxRunner" as GBRunner << class >> { + - __seed_manager = SeedManager() + - __mutator = Mutator() + - __branch_dict = {} + -- + - __init__(self) : void + + run(self, function: Callable, seed_population: List[Seed], amount_runs: int = 10000) : list + - __hash_md5(self, branch_covered: str) : str + - __store_hashed_branch(self, hashed_branch: str) : void +} +''''''''''''''''''''''''''''''''''''''' +class "SeedManager" as SeedManager << class >> { + - __power_energy: int = 2 + -- + - __init__(self) : void + + select_seed(self, seed_population: List[Seed]) : Seed + + adjust_energy(self, seed: Seed, branch_dict: dict, hashed_branch: str) : void + + get_normalized_energy(self, seed_population: List[Seed]) : list +} +''''''''''''''''''''''''''''''''''''''' +class "Mutator" as Mutator << class >> { + -- + - __init__(self) : void + + mutate_grey_box_fuzzer(self, seed: Seed) : void + - __delete_random_char(self, s: str) : str + - __insert_random_char(self, s: str) : str + - __flip_random_char(self, s: str) : str + - __get_random_float(self) : float + - __check_inf(self, number: float) : float + - __add_random_number(self, number: float) : float + - __sub_random_number(self, number: float) : float + - __mult_random_number(self, number: float) : float + - __div_random_number(self, number: float) : float + - __grey_mutate_string(self, seed_value: str) : str +} +''''''''''''''''''''''''''''''''''''''' +class "Seed" as Seed << class >> { + - energy: int = 0 + - seed_values: list = [] + -- + - __init__(self, energy: int = 0, seed_values: list = []) : void +} +''''''''''''''''''''''''''''''''''''''' +class "DataTypeCreator" as DataTypeCreator << class >> { + - __MAX_INT: int = (1 << 31) - 1 + - __MAX_UINT = (1 << 32) - 1 + -- + - __init__(self) : void + - create_int(self, amount_digits: int = 10, random_creation: bool = True) : int + - create_uint(self, amount_digits: int = 10, random_creation: bool = True) : int + - create_string_special_characters(self, amount_chars: int) : str +} +''''''''''''''''''''''''''''''''''''''' abstract class "Runner" as runner << abstract class >> { -- - __init__(self) : void @@ -150,6 +213,21 @@ entity "test_mut_on_helpers.py" as test_MUT << test cases >>{ test_get_miniserver_type() : None } ''''''''''''''''''''''''''''''''''''''' +entity "test_grey_box_on_helpers.py" as test_GBox << test case >>{ + logger + grey_box_fuzzer = GreyBoxFuzzer + grey_box_runner = GreyBoxRunner + -- + +} +''''''''''''''''''''''''''''''''''''''' +entity "test_grey_box_on_helpers_complex.py" as test_GBox_complex << test case >>{ + logger + grey_box_fuzzer = GreyBoxFuzzer + grey_box_runner = GreyBoxRunner + -- +} +''''''''''''''''''''''''''''''''''''''' 'Fuzzer'''''''''''''''''''''''''''''''' ''''''''''''''''''''''''''''''''''''''' fuzzer <|-- VPFuzzer : inherits from < @@ -158,10 +236,21 @@ GrFuzzer <--* VPFuzzer: has access to < gr_pool <-- VPFuzzer: imports < fuzzer <|-- GFuzzer : inherits from < fuzzer <|-- MFuzzer : inherits from < +GBFuzzer o-- DataTypeCreator : aggregates > +GBFuzzer --> Seed: uses > ''''''''''''''''''''''''''''''''''''''' 'Runner'''''''''''''''''''''''''''''''' ''''''''''''''''''''''''''''''''''''''' runner <|-- PRunner : inherits from < +GBRunner o-- Mutator : aggregates > +GBRunner o-- SeedManager : aggregates > +GBRunner --> Seed: uses > +''''''''''''''''''''''''''''''''''''''' +'Other classes''''''''''''''''''''''''' +''''''''''''''''''''''''''''''''''''''' +Mutator --> Seed: uses > +SeedManager --> Seed: uses > +DataTypeCreator --> Seed: uses > ''''''''''''''''''''''''''''''''''''''' 'Testcases''''''''''''''''''''''''''''' ''''''''''''''''''''''''''''''''''''''' @@ -174,4 +263,11 @@ GFuzzer <--* test_GEN : needs a < ''''''''''''''''''''''''''''''''''''''' MFuzzer "1"<--* test_MUT : needs a < PRunner "1"<--* test_MUT : needs a < +GrFuzzer "1"<--* test_MUT : needs a < +ipv4 "1"<--* test_MUT : needs a < +''''''''''''''''''''''''''''''''''''''' +GBFuzzer "1"<--* test_GBox : needs a < +GBFuzzer "1"<--* test_GBox_complex : needs a < +GBRunner "1"<--* test_GBox : needs a < +GBRunner "1"<--* test_GBox_complex : needs a < @enduml \ No newline at end of file diff --git a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py index 8d6b8d5c..5f2377c0 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py +++ b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py @@ -1,14 +1,14 @@ from custom_components.test.fuzzing.fuzzer_utils.Fuzzer import Fuzzer from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.DataTypeCreator import DataTypeCreator -from typing import Callable, List +from typing import List import random class GreyBoxFuzzer(Fuzzer): """GreyBox fuzzer class, inherits from the abstract fuzzer class.""" - RANGE_RANDOM_INT = 9 - RANGE_RANDOM_STRING = 100 + __RANGE_RANDOM_INT = 9 + __RANGE_RANDOM_STRING = 100 __data_type_creator = DataTypeCreator() def __init__(self): @@ -18,7 +18,7 @@ def __init__(self): def fuzz(self, seed_template: list, seed_specification: list = None, - amount_seeds: int = 20) -> List[Seed]: + amount_seeds: int = 100) -> List[Seed]: """Returns a population of seeds with specific values based on the seed template and seed specifiction. This function takes two lists 'seed_template' and 'seed_specification' and creates seeds. @@ -60,13 +60,13 @@ def fuzz(self, param_set.append(self.__data_type_creator.create_int(seed_spec,False)) elif data_type == "UINT": if seed_spec == 'r': - seed_spec = random.randint(1, self.RANGE_RANDOM_INT) + seed_spec = random.randint(1, self.__RANGE_RANDOM_INT) param_set.append(self.__data_type_creator.create_uint(seed_spec)) elif data_type == "FLOAT": print("create_float") elif data_type == "STRING": if seed_spec == 'r': - seed_spec = random.randint(1, self.RANGE_RANDOM_STRING) + seed_spec = random.randint(1, self.__RANGE_RANDOM_STRING) rand_val = random.randint(0,1) if rand_val == 0: param_set.append(self.__data_type_creator.create_string_only_letters(seed_spec)) diff --git a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py index 22ff3acb..a9c434cd 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py +++ b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py @@ -6,6 +6,7 @@ from custom_components.test.fuzzing.fuzzer_utils.Runner import Runner from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed, SeedManager +from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Mutator import Mutator class GreyBoxRunner(Runner): @@ -13,12 +14,14 @@ class GreyBoxRunner(Runner): __logger = None __seed_manager = None + __mutator = None __branch_dict = {} def __init__(self): """constructor""" self.__logger = logging.getLogger(__name__) self.__seed_manager = SeedManager() + self.__mutator = Mutator() def run(self, function: Callable, seed_population: List[Seed], amount_runs: int = 10000) -> list: """Executes all transferred parameter sets for the transferred function. @@ -38,7 +41,7 @@ def run(self, function: Callable, seed_population: List[Seed], amount_runs: int :rtype: dict """ coverages_seen = set() - + branch_counter = 0 sig = inspect.signature(function) num_params = len(sig.parameters) @@ -76,10 +79,11 @@ def run(self, function: Callable, seed_population: List[Seed], amount_runs: int if new_branches: self.__logger.debug(f"Newly covered branches: {new_branches}") print(f"Test {generation}, seed_value: {seed.seed_values}, Newly covered branches: {new_branches}") + branch_counter += 1 else: print(f"Test {generation}, seed_value: {seed.seed_values}, No newly covered branches") - # Create hashes + # Create hash and store it in dict hashed_branch = self.__hash_md5(str(branch_covered)) print(f"Hashed branch: {hashed_branch}") self.__store_hashed_branch(hashed_branch) @@ -89,8 +93,13 @@ def run(self, function: Callable, seed_population: List[Seed], amount_runs: int self.__seed_manager.adjust_energy(seed, self.__branch_dict, hashed_branch) print(f"Energy after: {seed.energy}\n") + # Mutate seed values + self.__mutator.mutate_grey_box_fuzzer(seed) + print("\n##### Hashed branches #####\n") print(f"Branch_dict: {self.__branch_dict}") + print("\n##### Covert branches #####\n") + print(f"In total there were {branch_counter} branches discovered ") return test_results diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py index 43b5764f..edee5551 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py @@ -3,8 +3,8 @@ class DataTypeCreator: - MAX_INT = (1 << 31) - 1 - MAX_UINT = (1 << 32) - 1 + __MAX_INT = (1 << 31) - 1 + __MAX_UINT = (1 << 32) - 1 def __init__(self): """initialize DataTypeCreator""" @@ -24,7 +24,7 @@ def create_int(self, amount_digits: int = 10, random_creation: bool = True) -> i :rtype: int """ if random_creation == True: - random_seed_value = random.randint(-self.MAX_INT, self.MAX_INT) + random_seed_value = random.randint(-self.__MAX_INT, self.__MAX_INT) return random_seed_value else: seed_value = '' @@ -57,7 +57,7 @@ def create_uint(self, amount_digits: int = 10, random_creation: bool = True) -> :rtype: int """ if random_creation == True: - random_seed_value = random.randint(0, self.MAX_UINT) + random_seed_value = random.randint(0, self.__MAX_UINT) return random_seed_value else: seed_value = '' @@ -108,7 +108,7 @@ def create_string_only_letters(self, amount_chars: int) -> int: if character == amount_chars-1: return seed_value - def create_string_special_characters(self, amount_chars: int) -> int: + def create_string_special_characters(self, amount_chars: int) -> str: """Returns an string with a certain number of chars. This function takes a value 'amount_chars' and returns an string with this amount of chars. diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py index e679485c..bc94bf0a 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py @@ -1,12 +1,26 @@ +from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed from random import random +import random import math class Mutator: def __init__(self): """initialize Mutator""" - print("Initialize Mutator") - def delete_random_char(self, string: str) -> str: + def mutate_grey_box_fuzzer(self, seed: Seed): + """Mutates all seed values. + + This function takes a seed and mutates all seed values of it. + + :param seed: A seed consists of a list of seed_values. + :type seed: Seed + """ + for index, seed_value in enumerate(seed.seed_values): + if isinstance(seed_value, str): + seed.seed_values[index] = self.__grey_mutate_string(seed_value) + + + def __delete_random_char(self, string: str) -> str: """Returns string with a random character deleted. This function takes a string `string` as input and returns a new string @@ -30,7 +44,7 @@ def delete_random_char(self, string: str) -> str: # the substring after the random position. return string[:pos] + string[pos + 1 :] - def insert_random_char(self, string: str) -> str: + def __insert_random_char(self, string: str) -> str: """Returns string with a random character inserted. This function takes a string `string` as input and returns a new string @@ -53,7 +67,7 @@ def insert_random_char(self, string: str) -> str: # and the substring after the random position. return string[:pos] + random_character + string[pos:] - def flip_random_char(self, string: str) -> str: + def __flip_random_char(self, string: str) -> str: """Returns string with a random bit flipped in a random position. This function takes a string `string` as input and returns a new string @@ -87,7 +101,7 @@ def flip_random_char(self, string: str) -> str: # and the substring after the random position. return string[:pos] + new_c + string[pos + 1 :] - def get_random_float(self) -> float: + def __get_random_float(self) -> float: """Returns a random float value modified by a randomly chosen multiplier. This function generates a random float value between 0.0 and 1.0, and then @@ -105,7 +119,7 @@ def get_random_float(self) -> float: # Return the modified random float. return random_float - def check_inf(self, number: float) -> float: + def __check_inf(self, number: float) -> float: """Checks if the number is infinite and replaces it with a random value if true. This function takes a floating-point number `number` as input. If the number is @@ -128,7 +142,7 @@ def check_inf(self, number: float) -> float: # Return the potentially modified number. return number - def add_random_number(self, number: float) -> float: + def __add_random_number(self, number: float) -> float: """Returns the input number with a random float added. This function takes a floating-point number `number` as input and adds @@ -147,7 +161,7 @@ def add_random_number(self, number: float) -> float: # Check if the resulting number is infinite. return self.__check_inf(number) - def sub_random_number(self, number: float) -> float: + def __sub_random_number(self, number: float) -> float: """Subtracts a random float from the given number. This function takes a float `number` as input and subtracts a randomly @@ -165,7 +179,7 @@ def sub_random_number(self, number: float) -> float: # Check if the resulting number is infinite. return self.__check_inf(number) - def mult_random_number(self, number: float) -> float: + def __mult_random_number(self, number: float) -> float: """Returns the result of multiplying the input number by a random float. This function takes a floating-point number `number` as input and returns @@ -185,7 +199,7 @@ def mult_random_number(self, number: float) -> float: # Check if the resulting number is infinite. return self.__check_inf(number) - def div_random_number(self, number: float) -> float: + def __div_random_number(self, number: float) -> float: """Divides the input number by a randomly generated float. This function takes a float `number` as input and divides it by @@ -201,4 +215,28 @@ def div_random_number(self, number: float) -> float: number /= self.__get_random_float() # Check if the resulting number is infinite. - return self.__check_inf(number) \ No newline at end of file + return self.__check_inf(number) + + def __grey_mutate_string(self, seed_value: str) -> str: + """Mutates a string random. + + This function takes a string and applies different mutations on it. + 1. delete random char + 2. insert random char + 3. flip random char + + :param seed_value: A string which should be mutated. + :type seed_value: str + + :return: Returns the mutated seed value. + :rtype: str + """ + random_val = random.choice([1, 2, 3]) + if random_val == 1: + seed_value = self.__delete_random_char(seed_value) + elif random_val == 2: + seed_value = self.__insert_random_char(seed_value) + elif random_val == 3: + seed_value = self.__flip_random_char(seed_value) + + return seed_value \ No newline at end of file diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/PowerSchedule.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/PowerSchedule.py deleted file mode 100644 index ff85b12d..00000000 --- a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/PowerSchedule.py +++ /dev/null @@ -1,42 +0,0 @@ -from typing import List -from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed - -class PowerSchedule: - - def __init__(self): - """initialize PowerSchedule""" - print("Initialize PowerSchedule") - - def assignEnergy(self, population: List[Seed]) -> None: - """Assigns energy to seeds. - - This function takes a population of Seeds and assigns to every seed an energy of 1. - - :param population: List of type Seed. - :type population: list - """ - for seed in population: - seed.energy = 1 - - def normalizedEnergy(self, population: List[Seed]) -> None: - """Normalize energy of all seeds. - - This function takes a population of Seeds and normalizes the energy by dividing the - energy of every seed through sum of all energy values. - - :param population: List of type Seed. - :type population: list - """ - - def choose(self, population: List[Seed]) -> Seed: - """Returns a seed that was selected based on the energy. - - This function takes a population of Seeds and returns and selects a Seed by the energy of the seed. - The higher the energy, the more likely it is that the seed will be selected. - - :param population: List of type Seed. - :type population: list - - :return: Returns a seed. - :rtype: Seed - """ \ No newline at end of file diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py index affee53b..0a6ba6e5 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py @@ -16,8 +16,7 @@ def __init__(self, energy: int = 0, seed_values: list = []): class SeedManager: - counter = -1 - __a = 2 + __power_energy = 2 def __init__(self): """initialize PowerSchedule""" @@ -45,8 +44,6 @@ def select_seed(self, seed_population: List[Seed]) -> Seed: break return seed - #self.counter += 1 - #return seed_population[self.counter] def adjust_energy(self, seed: Seed, branch_dict: dict, hashed_branch: str): """Adjusts the energy of a given seed. @@ -68,7 +65,7 @@ def adjust_energy(self, seed: Seed, branch_dict: dict, hashed_branch: str): :rtype: Seed """ number_path_exercised = branch_dict[hashed_branch] - seed.energy = 1 / number_path_exercised + seed.energy = 1 / (number_path_exercised ** self.__power_energy) def get_normalized_energy(self, seed_population: List[Seed]) -> list: total_energy = 0 diff --git a/custom_components/test/fuzzing/grey_box/test.py b/custom_components/test/fuzzing/grey_box/test.py deleted file mode 100644 index aa02ac3a..00000000 --- a/custom_components/test/fuzzing/grey_box/test.py +++ /dev/null @@ -1,11 +0,0 @@ -import hashlib - -def hash_string_md5(input_string: str) -> str: - md5_hash = hashlib.md5() - md5_hash.update(input_string.encode('utf-8')) - return md5_hash.hexdigest() - -# Beispiel: -input_string = "Hello, world!" -hashed_string = hash_string_md5(input_string) -print(f"Der MD5-Hash von '{input_string}' ist: {hashed_string}") diff --git a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py index f727ac1b..21038952 100644 --- a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py +++ b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py @@ -62,7 +62,7 @@ def crashme(s: str) -> None: print("\n##### Execute Tests #####\n") -test_results = grey_box_runner.run(crashme, seed_population, 7) +test_results = grey_box_runner.run(crashme, seed_population, 10) print("\n##### Test restults #####\n") print(f"Tests passed: {test_results['passed_tests']}") diff --git a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers_complex.py b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers_complex.py new file mode 100644 index 00000000..f8d385d0 --- /dev/null +++ b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers_complex.py @@ -0,0 +1,120 @@ +from custom_components.test.fuzzing.fuzzer_utils.GreyBoxFuzzer import GreyBoxFuzzer +from custom_components.test.fuzzing.fuzzer_utils.GreyBoxRunner import GreyBoxRunner +from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed + +from custom_components.loxone.helpers import ( + map_range, + hass_to_lox, + lox_to_hass, + lox2lox_mapped, + lox2hass_mapped, + to_hass_color_temp, + to_loxone_color_temp, + get_room_name_from_room_uuid, + get_cat_name_from_cat_uuid, + add_room_and_cat_to_value_values, + get_miniserver_type, + get_all, +) + +# Function to test the grey box fuzzer +def complex_function(input_string: str) -> int: + result = 0 + length = len(input_string) + + # Grundlegende Bedingung auf Länge + if length > 10: + result += 1 + if length > 20: + result += 1 + if input_string.startswith("A"): + result += 1 + if input_string.endswith("Z"): + result += 1 + else: + result -= 1 + + # Bedingung auf spezifische Zeichen + if 'a' in input_string: + result += 2 + if input_string.count('a') > 3: + result += 3 + if 'aaa' in input_string: + result += 5 + else: + result -= 1 + else: + result -= 2 + + # Schleifen und verschachtelte Bedingungen + vowels = "aeiou" + consonants = "bcdfghjklmnpqrstvwxyz" + + for i, char in enumerate(input_string): + if char in vowels: + result += 1 + elif char in consonants: + result += 2 + else: + result -= 1 + + # Verschachtelte Schleifen + for j in range(i, length): + if input_string[j] == char: + result += 1 + else: + result -= 1 + + # Noch eine Ebene der Verschachtelung + if j % 2 == 0: + result += 2 + else: + result -= 2 + + # Weitere komplexe Bedingungen + if 'xyz' in input_string: + result += 10 + if input_string.isdigit(): + result -= 10 + if input_string.isalpha(): + result += 20 + + # Substrings und Indizes + if "fuzz" in input_string: + index = input_string.find("fuzz") + if index % 2 == 0: + result += 30 + else: + result -= 30 + + # Rückgabe des Ergebnisses + return result + + + +grey_box_fuzzer = GreyBoxFuzzer() +grey_box_runner = GreyBoxRunner() + +# seed specification + +amount_seeds = 10 +seed_template = ["STRING"] +seed_specification = ['r'] + + +# create a population with fuzzer +seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 100) + +# Print seeds in population +print("##### Population #####") +for i in seed_population: + print(i.seed_values) + +print("\n##### Execute Tests #####\n") + +test_results = grey_box_runner.run(complex_function, seed_population, 2000) + +print("\n##### Test restults #####\n") +print(f"Tests passed: {test_results['passed_tests']}") +print(f"Tests failed: {test_results['failed_tests']}") + From 4baa12d35f622e537858d8ad07c3d7153dfdad58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6gel?= <hoegma01@thu.de> Date: Wed, 26 Jun 2024 09:32:52 +0200 Subject: [PATCH 14/19] Fix new branch counter Fixed the bug, that the amount of branches that was covert was smaller than the amount of hashes were stored in the hash_dict. --- .coverage | Bin 53248 -> 0 bytes .gitignore | 4 +- .../test/fuzzing/fuzzer_overview.puml | 1 - .../fuzzing/fuzzer_utils/GreyBoxRunner.py | 39 ++++++++++-------- 4 files changed, 25 insertions(+), 19 deletions(-) delete mode 100644 .coverage diff --git a/.coverage b/.coverage deleted file mode 100644 index 0a027e526b0e2b154f46ef762231d7ac618edce7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53248 zcmeI)&u`mg7zc1WY2zeK<)LY{qO|IJfv#HHDFTET2k1tHCMIp89Ty@zCwbEvV!N}Q zwVi}eI;2TR{0E5tfg2Jhgt%})h!YYQgpejpzzK0+2cGA(-85Y@P1-~|_*zZ++J4{H z@B4Xw#Y^YatC!qZicS!EPAs0+PH4KWy(WaFX=Qp%(kt5x3XEkt^jqH-9u+KWOBcSL zG~d=Lg-<l|-O0=5V&&7x=O(_dc%_dgJ~NzBi%wvJ00bZaf&aHa|7yjss#SgWgV<?y zWgI#!87A+O7v5S~URx1s%jYkxh~$_!T^6)0E{bIl23ulNhN9zkrEvYW+j3&pUl;L) z%wqQ<+2)0gj?q%b5tmeY4%u#-QpIweve*n=&k1+L`*LSNMM$p}%k4NhK$%E)-RBtM zbeeo#gt8+;>9=H*l;X78o-fbe{;+IVCr|2kq^gq>w&-VKs5W$9P?d`v%BU5DZ7zK? zbo|zaj248mSHo67<=l>w+J=F5HV#DB^;I?f$c^2=7jj#+da-ODTEPWI%QnK>UmMEn zI#=j=k}|#4KAl10LsF*E)5IMroF8PYgM@r(O03H4_q-;Nzt{6Nxp62(a^W}_m2y<t zI7ln|$Zc~-TMp_w)SMYPw`h{8=esw0a<B0h#31E@s)ipd2(AS6fM+KP*2^b#je4cr zj&5}6^Oo)O;y~@&)cAIt-YOqGQ#7pES^drp)lrzoZn|-_7bp#Nn!Up#x(=To-EVl5 z40jS9Mcu9lI%>#pYLPS!_2&I!=4^)HggnqhkVd4E9%*mm!crMTvl}#r`<m)8k)#1a zb3qoZ)AL(AR3ffJE4V2`XI(C(XOn5+s5=sMoqDNYt;~+<I(uEfuI~lQ{c6FmYBhbg zq`FbsTInxis0(E+dB`-Ho${m;^}=i-dDtYGr5MbT*%(RBP<0lKg0);5Rh=}RT~7nW zzA<K4r%vhpWJK`1#INz8O7M}v5IC?9JbLjrop?h$Sav>41)m!)Sg)KKRd6nXUFY}0 z?RRpj$aktr-=j&NU-_XT^N~T3-;_IxH=QocCYz2M(vYP&I9UM%M{GM3*=Yv7SoL4t zRo#=<o<Vt&3<mp|J;j<=(P?;J38KiQWtEq$=v;PG%xyRk&qC3hXa+%7I)1V?P>PEY zUDJ((D#L@T(yN0!q8-j-{pOr1Q?|J9G`~iJE*oDI@I1$Fr|T6j!%40*_w(T7>m-v1 zB@}ZsC?gu82lC7^P0#X-mF2&*NhX}D=d_dI&W*A!$E^+dKz7M$^tuzv*!85~D5@~3 zV{JK+XoZqa(pow{mw#9{;MTJ1{LXjDMgAz)pVbZP%o)AkO9x{z&)5+y>YkGoJKfC< zb&>-o2mR$TO}O-FXMaCoyL}wIn_Ph>x1<G|i9!}D{j5`-=No+8{9B_RY!H9|1Rwwb z2tWV=5P$##AOHaf{PzU%dQLa^{y%5_teJn%8#V|)00Izz00bZa0SG_<0uX=z1fEWT zN-jTRW`E*QH}!nYO8*GprKJ~_Uf@6S&zW~M^RD^l(^)~38Uhf200bZa0SG_<0uX=z z1Rwx`OrVmV(X)RA$d&W8Qu>bo$>0Bnni*y>@f!jVfB*y_009U<00Izz00bZafyWo< zPv~RXZf)gU<C^bY>w1m)(yH`i6w9!&8g$|<da#PUO3&{J-FU}dqZ9NbpvD^^J<`BF zANakfab@RHuuachX|#G#9C$W8Rbi8M{WxmG6ur^u-MYnT)<d~tH-l|;)Xw(oz_&MK zmmZcB(eo1c`+wbxH2T2?0SG_<0uX=z1Rwwb2tWV=5P-mw6X;Kj?bb@i<o+Me|9f%` zi<}?;0SG_<0uX=z1Rwwb2tWV=$50@z=e5b?`~UlzdH)!e5JiFj1Rwwb2tWV=5P$## zAOHafK;W?j^7+XUfB&zYc<ii@0R$ib0SG_<0uX=z1Rwwb2tWV=$5?>B|Hu9RF>W9f z4FV8=00bZa0SG_<0uX=z1R(GP1o->^qWOhp{$>7d{%ZbY{$PG<enkhcK>z{}fB*y_ z009U<00Izz00ba#<N`)<yj0Tl*?Wee0yDElK?SB~jBypHO&en>P^}qx6|kyCP6ehc zLsx-&Q~dpZ(fpk6|9>-oF@H3_GruvvqyyL>009U<00Izz00bZa0SG_<0uVSlfl`6* z`{~BNn1<-iKMhUK6w**_dOQtPYh!81s^-(sl$9&0TuS$*@c;jht|@V42tWV=5P$## RAOHafKmY;|fWW^c@DHvKtCj!& diff --git a/.gitignore b/.gitignore index 66bcefe4..b0df99ae 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,6 @@ custom_components/loxone/homeassistant .vscode -.DS_Store \ No newline at end of file +.DS_Store + +.coverage \ No newline at end of file diff --git a/custom_components/test/fuzzing/fuzzer_overview.puml b/custom_components/test/fuzzing/fuzzer_overview.puml index b3d3749d..8a09f488 100644 --- a/custom_components/test/fuzzing/fuzzer_overview.puml +++ b/custom_components/test/fuzzing/fuzzer_overview.puml @@ -110,7 +110,6 @@ class "GreyBoxFuzzer" as GBFuzzer << class >> { class "GreyBoxRunner" as GBRunner << class >> { - __seed_manager = SeedManager() - __mutator = Mutator() - - __branch_dict = {} -- - __init__(self) : void + run(self, function: Callable, seed_population: List[Seed], amount_runs: int = 10000) : list diff --git a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py index a9c434cd..d834a7a2 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py +++ b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py @@ -15,7 +15,7 @@ class GreyBoxRunner(Runner): __logger = None __seed_manager = None __mutator = None - __branch_dict = {} + branch_dict = {} def __init__(self): """constructor""" @@ -40,7 +40,7 @@ def run(self, function: Callable, seed_population: List[Seed], amount_runs: int the key 'failed_tests' contains the number of failed tests. :rtype: dict """ - coverages_seen = set() + branch_dict = {} branch_counter = 0 sig = inspect.signature(function) @@ -73,31 +73,34 @@ def run(self, function: Callable, seed_population: List[Seed], amount_runs: int data = cov.get_data() filename = next(iter(data.measured_files())) branch_covered = data.arcs(filename) - new_branches = set(branch_covered) - coverages_seen - coverages_seen.update(branch_covered) + + # Create hash of branch + print(f"Branch: {branch_covered}") + hashed_branch = self.__hash_md5(str(branch_covered)) + print(f"Hashed branch: {hashed_branch}") - if new_branches: - self.__logger.debug(f"Newly covered branches: {new_branches}") - print(f"Test {generation}, seed_value: {seed.seed_values}, Newly covered branches: {new_branches}") + # Check if a new branch was covered + if hashed_branch not in self.branch_dict: + self.__logger.debug(f"Newly covered branches: {branch_covered}") + print(f"Test {generation}, seed_value: {seed.seed_values}, Newly covered branches: {branch_covered}") branch_counter += 1 else: print(f"Test {generation}, seed_value: {seed.seed_values}, No newly covered branches") - # Create hash and store it in dict - hashed_branch = self.__hash_md5(str(branch_covered)) - print(f"Hashed branch: {hashed_branch}") - self.__store_hashed_branch(hashed_branch) + # store hash in branch_dict + branch_dict = self.__store_hashed_branch(hashed_branch, branch_dict) + # Adjust energy of seed print(f"Energy before: {seed.energy}") - self.__seed_manager.adjust_energy(seed, self.__branch_dict, hashed_branch) + self.__seed_manager.adjust_energy(seed, self.branch_dict, hashed_branch) print(f"Energy after: {seed.energy}\n") # Mutate seed values self.__mutator.mutate_grey_box_fuzzer(seed) print("\n##### Hashed branches #####\n") - print(f"Branch_dict: {self.__branch_dict}") + print(f"Branch_dict: {self.branch_dict}") print("\n##### Covert branches #####\n") print(f"In total there were {branch_counter} branches discovered ") @@ -108,8 +111,10 @@ def __hash_md5(self, branch_covered: str) -> str: md5_hash.update(branch_covered.encode('utf-8')) return md5_hash.hexdigest() - def __store_hashed_branch(self, hashed_branch: str): - if hashed_branch in self.__branch_dict: - self.__branch_dict[hashed_branch] += 1 + def __store_hashed_branch(self, hashed_branch: str, branch_dict: dict) -> dict: + if hashed_branch in self.branch_dict: + self.branch_dict[hashed_branch] += 1 else: - self.__branch_dict[hashed_branch] = 1 + self.branch_dict[hashed_branch] = 1 + + return branch_dict From 9a2244849d31bf3b3bf036383bfdc00a93b98771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6gel?= <hoegma01@thu.de> Date: Tue, 2 Jul 2024 16:40:07 +0200 Subject: [PATCH 15/19] Change in GreyBoxRunner - Mutate before running tests - If new path was discovered -> insert seed to seed pool -> point of interest --- .../test/fuzzing/fuzzer_utils/GreyBoxRunner.py | 12 ++++++++++-- .../test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py | 3 ++- .../fuzzing/grey_box/test_grey_box_on_helpers.py | 14 +++++++------- custom_components/test/fuzzing/readme.md | 1 + 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py index d834a7a2..dbdd5a16 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py +++ b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py @@ -53,7 +53,12 @@ def run(self, function: Callable, seed_population: List[Seed], amount_runs: int } for generation in range(0, amount_runs): + # get seed for the test seed = self.__seed_manager.select_seed(seed_population) + + # Mutate seed values + self.__mutator.mutate_grey_box_fuzzer(seed) + cov = coverage.Coverage(branch=True) cov.start() try: @@ -83,6 +88,7 @@ def run(self, function: Callable, seed_population: List[Seed], amount_runs: int if hashed_branch not in self.branch_dict: self.__logger.debug(f"Newly covered branches: {branch_covered}") print(f"Test {generation}, seed_value: {seed.seed_values}, Newly covered branches: {branch_covered}") + seed_population.append(seed) branch_counter += 1 else: print(f"Test {generation}, seed_value: {seed.seed_values}, No newly covered branches") @@ -96,13 +102,15 @@ def run(self, function: Callable, seed_population: List[Seed], amount_runs: int self.__seed_manager.adjust_energy(seed, self.branch_dict, hashed_branch) print(f"Energy after: {seed.energy}\n") - # Mutate seed values - self.__mutator.mutate_grey_box_fuzzer(seed) + print("\n##### Hashed branches #####\n") print(f"Branch_dict: {self.branch_dict}") print("\n##### Covert branches #####\n") print(f"In total there were {branch_counter} branches discovered ") + print("Population") + for s in seed_population: + print(f"{s.seed_values}") return test_results diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py index 0a6ba6e5..5ed7b8ff 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py @@ -3,6 +3,7 @@ from custom_components.test.fuzzing.fuzzer_utils.ParamRunner import ParamRunner from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.DataTypeCreator import DataTypeCreator import random +import copy class Seed: @@ -40,7 +41,7 @@ def select_seed(self, seed_population: List[Seed]) -> Seed: #print(f"random_value: {random_value}") for index, normalized_energy_val in enumerate(normalized_energy): if random_value <= normalized_energy_val: - seed = seed_population[index] + seed = copy.deepcopy(seed_population[index]) break return seed diff --git a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py index 21038952..24142e07 100644 --- a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py +++ b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py @@ -46,14 +46,14 @@ def crashme(s: str) -> None: #seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 20) seed_1 = Seed(1, ["bear"]) -seed_2 = Seed(1, ["rats"]) -seed_3 = Seed(1, ["code"]) -seed_4 = Seed(1, ["hii!"]) -seed_5 = Seed(1, ["beer"]) -seed_6 = Seed(1, ["lol!"]) -seed_7 = Seed(1, ["bad!"]) +#seed_2 = Seed(1, ["rats"]) +#seed_3 = Seed(1, ["code"]) +#seed_4 = Seed(1, ["hii!"]) +#seed_5 = Seed(1, ["beer"]) +#seed_6 = Seed(1, ["lol!"]) +#seed_7 = Seed(1, ["bad!"]) -seed_population = [seed_1, seed_2, seed_3, seed_4, seed_5, seed_6, seed_7] +seed_population = [seed_1]#, seed_2, seed_3, seed_4, seed_5, seed_6, seed_7] # Print seeds in population print("##### Population #####") diff --git a/custom_components/test/fuzzing/readme.md b/custom_components/test/fuzzing/readme.md index 5aa57411..9d240cac 100644 --- a/custom_components/test/fuzzing/readme.md +++ b/custom_components/test/fuzzing/readme.md @@ -34,6 +34,7 @@ pip install pytest pip install pytest-timeout pip install homeassistant pip install numpy +pip install coverage ``` 4. you maybe have to tell python were to find the `PyLoxone` project From 62074fb37c0de72cb10f21b72c8c51493b3fd23b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6gel?= <hoegma01@thu.de> Date: Tue, 2 Jul 2024 18:19:12 +0200 Subject: [PATCH 16/19] Create test_case --- .../fuzzing/fuzzer_utils/GreyBoxRunner.py | 90 +++---- .../fuzzing/fuzzer_utils/MutationalFuzzer.py | 6 +- .../fuzzer_utils/fuzzer_tools/Mutator.py | 242 +++--------------- .../fuzzing/fuzzer_utils/fuzzer_tools/Seed.py | 13 - .../grey_box/htest_grey_box_on_helpers.py | 70 +++++ ...y => htest_grey_box_on_helpers_complex.py} | 0 .../grey_box/test_grey_box_on_helpers.py | 68 ++--- 7 files changed, 172 insertions(+), 317 deletions(-) create mode 100644 custom_components/test/fuzzing/grey_box/htest_grey_box_on_helpers.py rename custom_components/test/fuzzing/grey_box/{test_grey_box_on_helpers_complex.py => htest_grey_box_on_helpers_complex.py} (100%) diff --git a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py index 29935da4..cdfbc2ea 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py +++ b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py @@ -2,6 +2,7 @@ import inspect import coverage import hashlib +import random from typing import Callable, List from custom_components.test.fuzzing.fuzzer_utils.Runner import Runner @@ -15,7 +16,7 @@ class GreyBoxRunner(Runner): __logger = None __seed_manager = None __mutator = None - branch_dict = {} + path_dict = {} def __init__(self): """constructor""" @@ -40,8 +41,8 @@ def run(self, function: Callable, seed_population: List[Seed], amount_runs: int the key 'failed_tests' contains the number of failed tests. :rtype: dict """ - branch_dict = {} - branch_counter = 0 + path_dict = {} + path_counter = 0 sig = inspect.signature(function) num_params = len(sig.parameters) @@ -53,16 +54,12 @@ def run(self, function: Callable, seed_population: List[Seed], amount_runs: int } for generation in range(0, amount_runs): -<<<<<<< HEAD # get seed for the test seed = self.__seed_manager.select_seed(seed_population) # Mutate seed values self.__mutator.mutate_grey_box_fuzzer(seed) -======= - seed = self.__seed_manager.select_seed(seed_population) ->>>>>>> 29c02c73038c358c0cb8646ae0595b8561485f83 cov = coverage.Coverage(branch=True) cov.start() try: @@ -78,66 +75,61 @@ def run(self, function: Callable, seed_population: List[Seed], amount_runs: int self.__logger.error(f"Test {generation} failed with parameters: {seed.seed_values}.") self.__logger.error(f"Exception: {e}") - # check branch coverage + # check path coverage data = cov.get_data() filename = next(iter(data.measured_files())) - branch_covered = data.arcs(filename) + path_covered = data.arcs(filename) - # Create hash of branch - print(f"Branch: {branch_covered}") - hashed_branch = self.__hash_md5(str(branch_covered)) - print(f"Hashed branch: {hashed_branch}") + # Create hash of path + ###################print(f"path: {path_covered}") + hashed_path = self.__hash_md5(str(path_covered)) + ###################print(f"Hashed path: {hashed_path}") - # Check if a new branch was covered - if hashed_branch not in self.branch_dict: - self.__logger.debug(f"Newly covered branches: {branch_covered}") - print(f"Test {generation}, seed_value: {seed.seed_values}, Newly covered branches: {branch_covered}") -<<<<<<< HEAD + # Check if a new path was covered + if hashed_path not in self.path_dict: + self.__logger.debug(f"Newly covered pathes: {path_covered}") + #print(f"Test {generation}, seed_value: {seed.seed_values}, Newly covered pathes: {path_covered}") seed_population.append(seed) -======= ->>>>>>> 29c02c73038c358c0cb8646ae0595b8561485f83 - branch_counter += 1 - else: - print(f"Test {generation}, seed_value: {seed.seed_values}, No newly covered branches") - - # store hash in branch_dict - branch_dict = self.__store_hashed_branch(hashed_branch, branch_dict) - + path_counter += 1 + + # store hash in path_dict + path_dict = self.__store_hashed_path(hashed_path, path_dict) + # Adjust energy of seed - print(f"Energy before: {seed.energy}") - self.__seed_manager.adjust_energy(seed, self.branch_dict, hashed_branch) - print(f"Energy after: {seed.energy}\n") + ###################print(f"Energy before: {seed.energy}") + self.__seed_manager.adjust_energy(seed, self.path_dict, hashed_path) + ###################print(f"Energy after: {seed.energy}\n") -<<<<<<< HEAD -======= - # Mutate seed values - self.__mutator.mutate_grey_box_fuzzer(seed) ->>>>>>> 29c02c73038c358c0cb8646ae0595b8561485f83 - print("\n##### Hashed branches #####\n") - print(f"Branch_dict: {self.branch_dict}") - print("\n##### Covert branches #####\n") - print(f"In total there were {branch_counter} branches discovered ") -<<<<<<< HEAD + #print("\n##### Hashed pathes #####\n") + #print(f"path_dict: {self.path_dict}") + ###################print("\n##### Covert pathes #####\n") + self.__logger.debug("\n##### Covert pathes #####\n") + ###################print(f"In total there were {path_counter} pathes discovered") + self.__logger.debug(f"In total there were {path_counter} pathes discovered") + print("Population") for s in seed_population: print(f"{s.seed_values}") -======= ->>>>>>> 29c02c73038c358c0cb8646ae0595b8561485f83 return test_results - def __hash_md5(self, branch_covered: str) -> str: + def __hash_md5(self, path_covered: str) -> str: md5_hash = hashlib.md5() - md5_hash.update(branch_covered.encode('utf-8')) + md5_hash.update(path_covered.encode('utf-8')) return md5_hash.hexdigest() - def __store_hashed_branch(self, hashed_branch: str, branch_dict: dict) -> dict: - if hashed_branch in self.branch_dict: - self.branch_dict[hashed_branch] += 1 + def __store_hashed_path(self, hashed_path: str, path_dict: dict) -> dict: + if hashed_path in self.path_dict: + self.path_dict[hashed_path] += 1 else: - self.branch_dict[hashed_branch] = 1 + self.path_dict[hashed_path] = 1 - return branch_dict + return path_dict + + + + + diff --git a/custom_components/test/fuzzing/fuzzer_utils/MutationalFuzzer.py b/custom_components/test/fuzzing/fuzzer_utils/MutationalFuzzer.py index d7beb87b..5ea79c4d 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/MutationalFuzzer.py +++ b/custom_components/test/fuzzing/fuzzer_utils/MutationalFuzzer.py @@ -20,7 +20,7 @@ def __init__(self): self.__multiplier.append(i) i *= 10 - def __delete_random_char(self, string: str) -> str: + def delete_random_char(self, string: str) -> str: """Returns string with a random character deleted. This function takes a string `string` as input and returns a new string @@ -44,7 +44,7 @@ def __delete_random_char(self, string: str) -> str: # the substring after the random position. return string[:pos] + string[pos + 1 :] - def __insert_random_char(self, string: str) -> str: + def insert_random_char(self, string: str) -> str: """Returns string with a random character inserted. This function takes a string `string` as input and returns a new string @@ -67,7 +67,7 @@ def __insert_random_char(self, string: str) -> str: # and the substring after the random position. return string[:pos] + random_character + string[pos:] - def __flip_random_char(self, string: str) -> str: + def flip_random_char(self, string: str) -> str: """Returns string with a random bit flipped in a random position. This function takes a string `string` as input and returns a new string diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py index bc94bf0a..9365b154 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py @@ -1,226 +1,62 @@ from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed +from custom_components.test.fuzzing.fuzzer_utils.MutationalFuzzer import MutationalBlackBoxFuzzer + from random import random import random -import math class Mutator: + + __mutationalFuzzer = None + def __init__(self): """initialize Mutator""" + self.__mutationalFuzzer = MutationalBlackBoxFuzzer() def mutate_grey_box_fuzzer(self, seed: Seed): - """Mutates all seed values. + """Mutates one of the seed values. - This function takes a seed and mutates all seed values of it. + This function takes a seed and mutates one of the seed values of it. :param seed: A seed consists of a list of seed_values. :type seed: Seed """ - for index, seed_value in enumerate(seed.seed_values): - if isinstance(seed_value, str): - seed.seed_values[index] = self.__grey_mutate_string(seed_value) - - - def __delete_random_char(self, string: str) -> str: - """Returns string with a random character deleted. - - This function takes a string `string` as input and returns a new string - with one random character removed from it. - - :param string: Any string from which a character is to be removed. - :type string: str - - :return: Returns the input string `string` with one randomly chosen character deleted. - :rtype: str - """ - if string == "": - # If the string is empty, there's no character to delete, so return the empty string. - return string + amount_values = len(seed.seed_values) + random_index = random.choice(range(0,amount_values)) + seed_value = seed.seed_values[random_index] - # Generate a random integer position within the range of the string's indices. - pos = random.randint(0, len(string) - 1) + if isinstance(seed_value, str): + seed.seed_values[random_index] = self.__mutate_string(seed_value) + if isinstance(seed_value, int): + seed.seed_values[random_index] = self.__mutate_int(seed_value) - # Create a new string by excluding the character at the random position. - # This is done by concatenating the substring before the random position and - # the substring after the random position. - return string[:pos] + string[pos + 1 :] - - def __insert_random_char(self, string: str) -> str: - """Returns string with a random character inserted. - - This function takes a string `string` as input and returns a new string - with a random character inserted at a random position within the string. - - :param string: Any string where a character is to be inserted. - :type string: str - - :return: Returns the input string `string` with one randomly chosen character inserted at a random position. - :rtype: str - """ - # Generate a random position within the range of the string's length (including the end of the string). - pos = random.randint(0, len(string)) - - # Generate a random character from the ASCII range 32 to 126 (printable characters). - random_character = chr(random.randrange(32, 127)) - - # Create a new string by inserting the random character at the random position. - # This is done by concatenating the substring before the random position, the random character, - # and the substring after the random position. - return string[:pos] + random_character + string[pos:] - - def __flip_random_char(self, string: str) -> str: - """Returns string with a random bit flipped in a random position. + def __mutate_string(self, seed_value: str) -> str: + """Mutates a string random. - This function takes a string `string` as input and returns a new string - where one randomly chosen character has one of its bits flipped - at a random bit position. + This function takes a string and applies different mutations on it. + 1. delete random char + 2. insert random char + 3. flip random char - :param string: Any string where a character's bit is to be flipped. - :type string: str + :param seed_value: A string which should be mutated. + :type seed_value: str - :return: Returns the input string `string` with one character's bit flipped at a random position. + :return: Returns the mutated seed value. :rtype: str """ - if string == "": - # If the string is empty, there's no character to flip, so return the empty string. - return string - - # Generate a random integer position within the range of the string's indices. - pos = random.randint(0, len(string) - 1) - - # Get the character at the randomly chosen position. - c = string[pos] - - # Generate a random bit position between 0 and 6 (since we are assuming 7-bit ASCII characters). - bit = 1 << random.randint(0, 6) - - # Flip the bit at the generated bit position using XOR. - new_c = chr(ord(c) ^ bit) - - # Create a new string by replacing the character at the random position with the new character. - # This is done by concatenating the substring before the random position, the new character, - # and the substring after the random position. - return string[:pos] + new_c + string[pos + 1 :] - - def __get_random_float(self) -> float: - """Returns a random float value modified by a randomly chosen multiplier. - - This function generates a random float value between 0.0 and 1.0, and then - multiplies it by a randomly selected value from the list `self.__multiplier`. - - :return: A random positiv float value. - :rtype: float - """ - # Generate a random float between 0.0 and 1.0. - random_float = random.random() - - # Multiply the random float by a randomly chosen multiplier from the list `self.__multiplier`. - random_float *= random.choice(self.__multiplier) - - # Return the modified random float. - return random_float - - def __check_inf(self, number: float) -> float: - """Checks if the number is infinite and replaces it with a random value if true. - - This function takes a floating-point number `number` as input. If the number is - positive or negative infinity, it replaces the number with a random value between - 0.0 and 1.0. It also logs this replacement. - - :param number: The number to check for infinity. - :type number: float - - :return: Returns the original number if it is not finite; otherwise, returns a random value between 0.0 and 1.0. - :rtype: float - """ - if math.isinf(number): - # If the number is infinite, replace it with a random value between 0.0 and 1.0. - number = random.random() - self.__logger.debug( - "The return value would be - or + INF, set it to a random value between 0.0 and 1.0" - ) - - # Return the potentially modified number. - return number - - def __add_random_number(self, number: float) -> float: - """Returns the input number with a random float added. - - This function takes a floating-point number `number` as input and adds - a random float to it. The random float is obtained from the private method - `__get_random_float`. - - :param number: The number to which a random float will be added. - :type number: float - - :return: Returns the input number `number` with an added random float, - after ensuring the result is not infinite using the `__check_inf` method. - :rtype: float - """ - number += self.__get_random_float() - - # Check if the resulting number is infinite. - return self.__check_inf(number) - - def __sub_random_number(self, number: float) -> float: - """Subtracts a random float from the given number. - - This function takes a float `number` as input and subtracts a randomly - generated float from it. The resulting number is then checked for - infinity values using the `__check_inf` method. - - :param number: The input number from which a random float will be subtracted. - :type number: float - - :return: Returns the resulting number after subtracting a random float and checking for infinity. - :rtype: float - """ - number -= self.__get_random_float() - - # Check if the resulting number is infinite. - return self.__check_inf(number) - - def __mult_random_number(self, number: float) -> float: - """Returns the result of multiplying the input number by a random float. - - This function takes a floating-point number `number` as input and returns - a new floating-point number which is the result of multiplying the input - number by a randomly generated float. It also checks if the result is - infinite. - - :param number: A floating-point number to be multiplied by a random float. - :type number: float - - :return: Returns the input number multiplied by a random float, - after checking if the result is infinite. - :rtype: float - """ - number *= self.__get_random_float() - - # Check if the resulting number is infinite. - return self.__check_inf(number) - - def __div_random_number(self, number: float) -> float: - """Divides the input number by a randomly generated float. - - This function takes a float `number` as input and divides it by - a random float generated by the `__get_random_float` method. It then - returns the result of this division after checking for infinity. - - :param number: The float number to be divided. - :type number: float - - :return: Returns the input number divided by a random float. - :rtype: float - """ - number /= self.__get_random_float() - - # Check if the resulting number is infinite. - return self.__check_inf(number) + random_val = random.choice([1, 2, 3]) + if random_val == 1: + seed_value = self.__mutationalFuzzer.delete_random_char(seed_value) + elif random_val == 2: + seed_value = self.__mutationalFuzzer.insert_random_char(seed_value) + elif random_val == 3: + seed_value = self.__mutationalFuzzer.flip_random_char(seed_value) + + return seed_value - def __grey_mutate_string(self, seed_value: str) -> str: - """Mutates a string random. + def __mutate_int(self, seed_value: str) -> str: + """Mutates an integer random. - This function takes a string and applies different mutations on it. + This function takes an integer and applies different mutations on it. 1. delete random char 2. insert random char 3. flip random char @@ -233,10 +69,10 @@ def __grey_mutate_string(self, seed_value: str) -> str: """ random_val = random.choice([1, 2, 3]) if random_val == 1: - seed_value = self.__delete_random_char(seed_value) + seed_value = self.__mutationalFuzzer.delete_random_char(seed_value) elif random_val == 2: - seed_value = self.__insert_random_char(seed_value) + seed_value = self.__mutationalFuzzer.insert_random_char(seed_value) elif random_val == 3: - seed_value = self.__flip_random_char(seed_value) + seed_value = self.__mutationalFuzzer.flip_random_char(seed_value) return seed_value \ No newline at end of file diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py index aaf2ce7e..d100eaf5 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Seed.py @@ -1,12 +1,6 @@ from typing import List -from custom_components.test.fuzzing.fuzzer_utils.ValuePoolFuzzer import ValuePoolFuzzer -from custom_components.test.fuzzing.fuzzer_utils.ParamRunner import ParamRunner -from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.DataTypeCreator import DataTypeCreator import random -<<<<<<< HEAD import copy -======= ->>>>>>> 29c02c73038c358c0cb8646ae0595b8561485f83 class Seed: @@ -37,18 +31,11 @@ def select_seed(self, seed_population: List[Seed]) -> Seed: :return: Returns a single seed. :rtype: Seed """ - #print(f"seed population: {seed_population}") normalized_energy = self.get_normalized_energy(seed_population) - #print(f"normalized energy: {normalized_energy}") random_value = random.uniform(0,1) - #print(f"random_value: {random_value}") for index, normalized_energy_val in enumerate(normalized_energy): if random_value <= normalized_energy_val: -<<<<<<< HEAD seed = copy.deepcopy(seed_population[index]) -======= - seed = seed_population[index] ->>>>>>> 29c02c73038c358c0cb8646ae0595b8561485f83 break return seed diff --git a/custom_components/test/fuzzing/grey_box/htest_grey_box_on_helpers.py b/custom_components/test/fuzzing/grey_box/htest_grey_box_on_helpers.py new file mode 100644 index 00000000..a94d71b1 --- /dev/null +++ b/custom_components/test/fuzzing/grey_box/htest_grey_box_on_helpers.py @@ -0,0 +1,70 @@ +from custom_components.test.fuzzing.fuzzer_utils.GreyBoxFuzzer import GreyBoxFuzzer +from custom_components.test.fuzzing.fuzzer_utils.GreyBoxRunner import GreyBoxRunner +from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed + +from custom_components.loxone.helpers import ( + map_range, + hass_to_lox, + lox_to_hass, + lox2lox_mapped, + lox2hass_mapped, + to_hass_color_temp, + to_loxone_color_temp, + get_room_name_from_room_uuid, + get_cat_name_from_cat_uuid, + add_room_and_cat_to_value_values, + get_miniserver_type, + get_all, +) + +# Function to test the grey box fuzzer +def crashme(s: str) -> None: + cnt = 0 + if len(s) > 0 and s[0] == 'b': + cnt += 1 + if len(s) > 1 and s[1] == 'a': + cnt += 1 + if len(s) > 2 and s[2] == 'd': + cnt += 1 + if len(s) > 3 and s[3] == '!': + cnt += 1 + if cnt >= 3: + raise Exception() + + +grey_box_fuzzer = GreyBoxFuzzer() +grey_box_runner = GreyBoxRunner() + +# seed specification + +amount_seeds = 10 +seed_template = ["STRING"] +seed_specification = [4] + + +# create a population with fuzzer +seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 10) + +#seed_1 = Seed(1, ["bear"]) +#seed_2 = Seed(1, ["rats"]) +#seed_3 = Seed(1, ["code"]) +#seed_4 = Seed(1, ["hii!"]) +#seed_5 = Seed(1, ["beer"]) +#seed_6 = Seed(1, ["lol!"]) +#seed_7 = Seed(1, ["bad!"]) + +#seed_population = [seed_1]#, seed_2, seed_3, seed_4, seed_5, seed_6, seed_7] + +# Print seeds in population +print("##### Population #####") +for i in seed_population: + print(i.seed_values) + +print("\n##### Execute Tests #####\n") + +test_results = grey_box_runner.run(crashme, seed_population, 10000) + + +print("\n##### Test restults #####\n") +print(f"Tests passed: {test_results['passed_tests']}") +print(f"Tests failed: {test_results['failed_tests']}") diff --git a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers_complex.py b/custom_components/test/fuzzing/grey_box/htest_grey_box_on_helpers_complex.py similarity index 100% rename from custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers_complex.py rename to custom_components/test/fuzzing/grey_box/htest_grey_box_on_helpers_complex.py diff --git a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py index 1ba079e9..d2a6ae59 100644 --- a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py +++ b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py @@ -1,6 +1,7 @@ from custom_components.test.fuzzing.fuzzer_utils.GreyBoxFuzzer import GreyBoxFuzzer from custom_components.test.fuzzing.fuzzer_utils.GreyBoxRunner import GreyBoxRunner -from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed +import pytest +import logging from custom_components.loxone.helpers import ( map_range, @@ -10,15 +11,19 @@ lox2hass_mapped, to_hass_color_temp, to_loxone_color_temp, - get_room_name_from_room_uuid, - get_cat_name_from_cat_uuid, - add_room_and_cat_to_value_values, get_miniserver_type, - get_all, ) +logger = logging.getLogger(__name__) +grey_box_fuzzer: GreyBoxFuzzer = GreyBoxFuzzer() +grey_box_runner: GreyBoxRunner = GreyBoxRunner() + + + + + # Function to test the grey box fuzzer -def crashme(s: str) -> None: +def demo_function(s: str) -> None: cnt = 0 if len(s) > 0 and s[0] == 'b': cnt += 1 @@ -32,49 +37,14 @@ def crashme(s: str) -> None: raise Exception() -grey_box_fuzzer = GreyBoxFuzzer() -grey_box_runner = GreyBoxRunner() - -# seed specification - -amount_seeds = 10 -seed_template = ["STRING"] -seed_specification = ['r'] - - -# create a population with fuzzer -#seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 20) - -seed_1 = Seed(1, ["bear"]) -<<<<<<< HEAD -#seed_2 = Seed(1, ["rats"]) -#seed_3 = Seed(1, ["code"]) -#seed_4 = Seed(1, ["hii!"]) -#seed_5 = Seed(1, ["beer"]) -#seed_6 = Seed(1, ["lol!"]) -#seed_7 = Seed(1, ["bad!"]) - -seed_population = [seed_1]#, seed_2, seed_3, seed_4, seed_5, seed_6, seed_7] -======= -seed_2 = Seed(1, ["rats"]) -seed_3 = Seed(1, ["code"]) -seed_4 = Seed(1, ["hii!"]) -seed_5 = Seed(1, ["beer"]) -seed_6 = Seed(1, ["lol!"]) -seed_7 = Seed(1, ["bad!"]) - -seed_population = [seed_1, seed_2, seed_3, seed_4, seed_5, seed_6, seed_7] ->>>>>>> 29c02c73038c358c0cb8646ae0595b8561485f83 - -# Print seeds in population -print("##### Population #####") -for i in seed_population: - print(i.seed_values) +@pytest.mark.skipif(False, reason="Not skiped!") +def test_crashme() -> None: + logger.info("Start of test_crashme() test.") + seed_template = ["STRING"] + seed_specification = [4] -print("\n##### Execute Tests #####\n") -test_results = grey_box_runner.run(crashme, seed_population, 10) + seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 1) + result = grey_box_runner.run(demo_function, seed_population, 10) -print("\n##### Test restults #####\n") -print(f"Tests passed: {test_results['passed_tests']}") -print(f"Tests failed: {test_results['failed_tests']}") + assert result["failed_tests"] == 0 From a6a05c439155372b4be00ab8bdd50485703d728c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6gel?= <hoegma01@thu.de> Date: Tue, 2 Jul 2024 21:49:58 +0200 Subject: [PATCH 17/19] Add test cases, Change in mutation - Added test cases of helpers.py for greybox fuzzing - Mutation of str, int, float is now made with mutational blackbox fuzzer --- .../fuzzing/fuzzer_utils/GreyBoxFuzzer.py | 15 ++- .../fuzzing/fuzzer_utils/GreyBoxRunner.py | 16 --- .../fuzzing/fuzzer_utils/MutationalFuzzer.py | 6 +- .../fuzzer_tools/DataTypeCreator.py | 73 ++--------- .../fuzzer_utils/fuzzer_tools/Mutator.py | 56 +------- .../grey_box/example1_grey_box_fuzzer.py | 71 +++++++++++ ...helpers.py => example2_grey_box_fuzzer.py} | 12 +- .../htest_grey_box_on_helpers_complex.py | 120 ------------------ .../grey_box/test_grey_box_on_helpers.py | 118 ++++++++++++++++- 9 files changed, 214 insertions(+), 273 deletions(-) create mode 100644 custom_components/test/fuzzing/grey_box/example1_grey_box_fuzzer.py rename custom_components/test/fuzzing/grey_box/{htest_grey_box_on_helpers.py => example2_grey_box_fuzzer.py} (81%) delete mode 100644 custom_components/test/fuzzing/grey_box/htest_grey_box_on_helpers_complex.py diff --git a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py index 5f2377c0..5b139ba8 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py +++ b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py @@ -41,8 +41,6 @@ def fuzz(self, :return: Returns a list indicating how many tests were successful and how many failed. :rtype: list """ - - print("Fuzzing...") # Throw exception if seed_specification and seed_template aren't the same length if len(seed_template) != len(seed_specification): @@ -58,12 +56,13 @@ def fuzz(self, param_set.append(self.__data_type_creator.create_int(seed_spec,True)) else: param_set.append(self.__data_type_creator.create_int(seed_spec,False)) - elif data_type == "UINT": - if seed_spec == 'r': - seed_spec = random.randint(1, self.__RANGE_RANDOM_INT) - param_set.append(self.__data_type_creator.create_uint(seed_spec)) + elif data_type == "FLOAT": - print("create_float") + if seed_spec == 'r': + param_set.append(self.__data_type_creator.create_float(seed_spec,True)) + else: + param_set.append(self.__data_type_creator.create_float(seed_spec,False)) + elif data_type == "STRING": if seed_spec == 'r': seed_spec = random.randint(1, self.__RANGE_RANDOM_STRING) @@ -72,8 +71,10 @@ def fuzz(self, param_set.append(self.__data_type_creator.create_string_only_letters(seed_spec)) elif rand_val == 1: param_set.append(self.__data_type_creator.create_string_special_characters(seed_spec)) + elif data_type == "BOOL": param_set.append(random.choice([True, False])) + elif data_type == "BYTE": print("create_byte") elif data_type == "LIST": diff --git a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py index cdfbc2ea..c37c9c8b 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py +++ b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py @@ -2,7 +2,6 @@ import inspect import coverage import hashlib -import random from typing import Callable, List from custom_components.test.fuzzing.fuzzer_utils.Runner import Runner @@ -81,14 +80,11 @@ def run(self, function: Callable, seed_population: List[Seed], amount_runs: int path_covered = data.arcs(filename) # Create hash of path - ###################print(f"path: {path_covered}") hashed_path = self.__hash_md5(str(path_covered)) - ###################print(f"Hashed path: {hashed_path}") # Check if a new path was covered if hashed_path not in self.path_dict: self.__logger.debug(f"Newly covered pathes: {path_covered}") - #print(f"Test {generation}, seed_value: {seed.seed_values}, Newly covered pathes: {path_covered}") seed_population.append(seed) path_counter += 1 @@ -97,22 +93,10 @@ def run(self, function: Callable, seed_population: List[Seed], amount_runs: int path_dict = self.__store_hashed_path(hashed_path, path_dict) # Adjust energy of seed - ###################print(f"Energy before: {seed.energy}") self.__seed_manager.adjust_energy(seed, self.path_dict, hashed_path) - ###################print(f"Energy after: {seed.energy}\n") - - - #print("\n##### Hashed pathes #####\n") - #print(f"path_dict: {self.path_dict}") - ###################print("\n##### Covert pathes #####\n") self.__logger.debug("\n##### Covert pathes #####\n") - ###################print(f"In total there were {path_counter} pathes discovered") self.__logger.debug(f"In total there were {path_counter} pathes discovered") - - print("Population") - for s in seed_population: - print(f"{s.seed_values}") return test_results diff --git a/custom_components/test/fuzzing/fuzzer_utils/MutationalFuzzer.py b/custom_components/test/fuzzing/fuzzer_utils/MutationalFuzzer.py index 5ea79c4d..d7beb87b 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/MutationalFuzzer.py +++ b/custom_components/test/fuzzing/fuzzer_utils/MutationalFuzzer.py @@ -20,7 +20,7 @@ def __init__(self): self.__multiplier.append(i) i *= 10 - def delete_random_char(self, string: str) -> str: + def __delete_random_char(self, string: str) -> str: """Returns string with a random character deleted. This function takes a string `string` as input and returns a new string @@ -44,7 +44,7 @@ def delete_random_char(self, string: str) -> str: # the substring after the random position. return string[:pos] + string[pos + 1 :] - def insert_random_char(self, string: str) -> str: + def __insert_random_char(self, string: str) -> str: """Returns string with a random character inserted. This function takes a string `string` as input and returns a new string @@ -67,7 +67,7 @@ def insert_random_char(self, string: str) -> str: # and the substring after the random position. return string[:pos] + random_character + string[pos:] - def flip_random_char(self, string: str) -> str: + def __flip_random_char(self, string: str) -> str: """Returns string with a random bit flipped in a random position. This function takes a string `string` as input and returns a new string diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py index edee5551..4d161538 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py @@ -17,7 +17,7 @@ def create_int(self, amount_digits: int = 10, random_creation: bool = True) -> i :param amount_digits: Amount of digits the integer should have :type amount_digits: int - :param random_creation: + :param random_creation: True: The int will be created random. False: The int will be amount_digits long. :type random_creation: boolean :return: Returns an integer with a certain amount of digits. @@ -44,48 +44,22 @@ def create_int(self, amount_digits: int = 10, random_creation: bool = True) -> i # cast to int type and append to seed if digit == amount_digits-1: return int(seed_value) - - def create_uint(self, amount_digits: int = 10, random_creation: bool = True) -> int: - """Returns an uint value with a certain number of digits. - - This function takes a value 'amount_digits' and returns an unsigned integer with this amount of digits. - :param amount_digits: Amount of digits the unsigned integer should have - :type amount_digits: uint - - :return: Returns an unsigned integer with a certain amount of digits. - :rtype: int - """ - if random_creation == True: - random_seed_value = random.randint(0, self.__MAX_UINT) - return random_seed_value - else: - seed_value = '' - for digit in range(amount_digits): - if digit == 0: - # First digit should not be a 0 - rand_val = str(random.randint(1,9)) - seed_value += rand_val - else: - rand_val = str(random.randint(0,9)) - seed_value += rand_val - - # cast to int type and append to seed - if digit == amount_digits-1: - return int(seed_value) - - def create_float(self, amount_digits: int) -> int: + def create_float(self, amount_digits: int, random_creation: bool = True) -> int: """Returns an int value with a certain number of digits. - This function takes a value 'amount_digits' and returns an integer with this amount of digits. + This function takes a value 'amount_digits' and returns an float with this amount of digits. - :param amount_digits: Amount of digits the integer should have + :param amount_digits: Amount of digits the float should have :type amount_digits: int - :return: Returns an integer with a certain amount of digits. - :rtype: int + :param random_creation: True: The float will be created random. False: The float will be amount_digits long. + :type random_creation: boolean + + :return: Returns an float with a certain amount of digits. + :rtype: float """ - print("create float") + return random.uniform(-1000,1000) def create_string_only_letters(self, amount_chars: int) -> int: """Returns an string with a certain number of chars. @@ -95,6 +69,9 @@ def create_string_only_letters(self, amount_chars: int) -> int: :param amount_chars: Amount of chars the string should have :type amount_chars: int + :param random_creation: True: The string will be created random. False: The string will be amount_digits long. + :type random_creation: boolean + :return: Returns an string with a certain amount of chars. :rtype: string """ @@ -135,29 +112,5 @@ def create_string_special_characters(self, amount_chars: int) -> str: # cast to int type and append to seed if character == amount_chars-1: return seed_value - - def create_random_string(self, amount_chars: int) -> int: - """Returns an string with a certain number of chars. - This function takes a value 'amount_chars' and returns an string with this amount of chars. - - :param amount_chars: Amount of chars the string should have - :type amount_chars: int - - :return: Returns an string with a certain amount of chars. - :rtype: string - """ - - def create_byte(self, amount_digits: int) -> int: - """Returns an int value with a certain number of digits. - - This function takes a value 'amount_digits' and returns an integer with this amount of digits. - - :param amount_digits: Amount of digits the integer should have - :type amount_digits: int - - :return: Returns an integer with a certain amount of digits. - :rtype: int - """ - print("create byte") diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py index 9365b154..8d662f4e 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py @@ -1,7 +1,5 @@ from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed from custom_components.test.fuzzing.fuzzer_utils.MutationalFuzzer import MutationalBlackBoxFuzzer - -from random import random import random class Mutator: @@ -23,56 +21,6 @@ def mutate_grey_box_fuzzer(self, seed: Seed): amount_values = len(seed.seed_values) random_index = random.choice(range(0,amount_values)) seed_value = seed.seed_values[random_index] + seed.seed_values[random_index] = self.__mutationalFuzzer.fuzz([seed_value],2)[1][0] - if isinstance(seed_value, str): - seed.seed_values[random_index] = self.__mutate_string(seed_value) - if isinstance(seed_value, int): - seed.seed_values[random_index] = self.__mutate_int(seed_value) - - def __mutate_string(self, seed_value: str) -> str: - """Mutates a string random. - - This function takes a string and applies different mutations on it. - 1. delete random char - 2. insert random char - 3. flip random char - - :param seed_value: A string which should be mutated. - :type seed_value: str - - :return: Returns the mutated seed value. - :rtype: str - """ - random_val = random.choice([1, 2, 3]) - if random_val == 1: - seed_value = self.__mutationalFuzzer.delete_random_char(seed_value) - elif random_val == 2: - seed_value = self.__mutationalFuzzer.insert_random_char(seed_value) - elif random_val == 3: - seed_value = self.__mutationalFuzzer.flip_random_char(seed_value) - - return seed_value - - def __mutate_int(self, seed_value: str) -> str: - """Mutates an integer random. - - This function takes an integer and applies different mutations on it. - 1. delete random char - 2. insert random char - 3. flip random char - - :param seed_value: A string which should be mutated. - :type seed_value: str - - :return: Returns the mutated seed value. - :rtype: str - """ - random_val = random.choice([1, 2, 3]) - if random_val == 1: - seed_value = self.__mutationalFuzzer.delete_random_char(seed_value) - elif random_val == 2: - seed_value = self.__mutationalFuzzer.insert_random_char(seed_value) - elif random_val == 3: - seed_value = self.__mutationalFuzzer.flip_random_char(seed_value) - - return seed_value \ No newline at end of file + print(seed.seed_values) \ No newline at end of file diff --git a/custom_components/test/fuzzing/grey_box/example1_grey_box_fuzzer.py b/custom_components/test/fuzzing/grey_box/example1_grey_box_fuzzer.py new file mode 100644 index 00000000..97a1687f --- /dev/null +++ b/custom_components/test/fuzzing/grey_box/example1_grey_box_fuzzer.py @@ -0,0 +1,71 @@ +from custom_components.test.fuzzing.fuzzer_utils.GreyBoxFuzzer import GreyBoxFuzzer +from custom_components.test.fuzzing.fuzzer_utils.GreyBoxRunner import GreyBoxRunner +from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed + +from custom_components.loxone.helpers import ( + map_range, + hass_to_lox, + lox_to_hass, + lox2lox_mapped, + lox2hass_mapped, + to_hass_color_temp, + to_loxone_color_temp, + get_room_name_from_room_uuid, + get_cat_name_from_cat_uuid, + add_room_and_cat_to_value_values, + get_miniserver_type, + get_all, +) + +def path_coverage_function(f: float, s: str, i: int) -> str: + if f > 0.0: + if len(s) > 5: + if i % 2 == 0: + return "Path 1: f > 0.0, len(s) > 5, i is even" + else: + return "Path 2: f > 0.0, len(s) > 5, i is odd" + else: + if i % 2 == 0: + return "Path 3: f > 0.0, len(s) <= 5, i is even" + else: + return "Path 4: f > 0.0, len(s) <= 5, i is odd" + else: + if len(s) > 5: + if i % 2 == 0: + return "Path 5: f <= 0.0, len(s) > 5, i is even" + else: + return "Path 6: f <= 0.0, len(s) > 5, i is odd" + else: + if i % 2 == 0: + return "Path 7: f <= 0.0, len(s) <= 5, i is even" + else: + raise Exception() + + + +grey_box_fuzzer = GreyBoxFuzzer() +grey_box_runner = GreyBoxRunner() + +# seed specification + +amount_seeds = 10 +seed_template = ["FLOAT", "STRING", "INT"] +seed_specification = ['r','r','r'] + + +# create a population with fuzzer +seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 10) + +# Print seeds in population +print("##### Population #####") +for i in seed_population: + print(i.seed_values) + +print("\n##### Execute Tests #####\n") + +test_results = grey_box_runner.run(path_coverage_function, seed_population, 2000) + +print("\n##### Test restults #####\n") +print(f"Tests passed: {test_results['passed_tests']}") +print(f"Tests failed: {test_results['failed_tests']}") + diff --git a/custom_components/test/fuzzing/grey_box/htest_grey_box_on_helpers.py b/custom_components/test/fuzzing/grey_box/example2_grey_box_fuzzer.py similarity index 81% rename from custom_components/test/fuzzing/grey_box/htest_grey_box_on_helpers.py rename to custom_components/test/fuzzing/grey_box/example2_grey_box_fuzzer.py index a94d71b1..21c2bc9e 100644 --- a/custom_components/test/fuzzing/grey_box/htest_grey_box_on_helpers.py +++ b/custom_components/test/fuzzing/grey_box/example2_grey_box_fuzzer.py @@ -45,16 +45,6 @@ def crashme(s: str) -> None: # create a population with fuzzer seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 10) -#seed_1 = Seed(1, ["bear"]) -#seed_2 = Seed(1, ["rats"]) -#seed_3 = Seed(1, ["code"]) -#seed_4 = Seed(1, ["hii!"]) -#seed_5 = Seed(1, ["beer"]) -#seed_6 = Seed(1, ["lol!"]) -#seed_7 = Seed(1, ["bad!"]) - -#seed_population = [seed_1]#, seed_2, seed_3, seed_4, seed_5, seed_6, seed_7] - # Print seeds in population print("##### Population #####") for i in seed_population: @@ -62,7 +52,7 @@ def crashme(s: str) -> None: print("\n##### Execute Tests #####\n") -test_results = grey_box_runner.run(crashme, seed_population, 10000) +test_results = grey_box_runner.run(crashme, seed_population, 10) print("\n##### Test restults #####\n") diff --git a/custom_components/test/fuzzing/grey_box/htest_grey_box_on_helpers_complex.py b/custom_components/test/fuzzing/grey_box/htest_grey_box_on_helpers_complex.py deleted file mode 100644 index f8d385d0..00000000 --- a/custom_components/test/fuzzing/grey_box/htest_grey_box_on_helpers_complex.py +++ /dev/null @@ -1,120 +0,0 @@ -from custom_components.test.fuzzing.fuzzer_utils.GreyBoxFuzzer import GreyBoxFuzzer -from custom_components.test.fuzzing.fuzzer_utils.GreyBoxRunner import GreyBoxRunner -from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed - -from custom_components.loxone.helpers import ( - map_range, - hass_to_lox, - lox_to_hass, - lox2lox_mapped, - lox2hass_mapped, - to_hass_color_temp, - to_loxone_color_temp, - get_room_name_from_room_uuid, - get_cat_name_from_cat_uuid, - add_room_and_cat_to_value_values, - get_miniserver_type, - get_all, -) - -# Function to test the grey box fuzzer -def complex_function(input_string: str) -> int: - result = 0 - length = len(input_string) - - # Grundlegende Bedingung auf Länge - if length > 10: - result += 1 - if length > 20: - result += 1 - if input_string.startswith("A"): - result += 1 - if input_string.endswith("Z"): - result += 1 - else: - result -= 1 - - # Bedingung auf spezifische Zeichen - if 'a' in input_string: - result += 2 - if input_string.count('a') > 3: - result += 3 - if 'aaa' in input_string: - result += 5 - else: - result -= 1 - else: - result -= 2 - - # Schleifen und verschachtelte Bedingungen - vowels = "aeiou" - consonants = "bcdfghjklmnpqrstvwxyz" - - for i, char in enumerate(input_string): - if char in vowels: - result += 1 - elif char in consonants: - result += 2 - else: - result -= 1 - - # Verschachtelte Schleifen - for j in range(i, length): - if input_string[j] == char: - result += 1 - else: - result -= 1 - - # Noch eine Ebene der Verschachtelung - if j % 2 == 0: - result += 2 - else: - result -= 2 - - # Weitere komplexe Bedingungen - if 'xyz' in input_string: - result += 10 - if input_string.isdigit(): - result -= 10 - if input_string.isalpha(): - result += 20 - - # Substrings und Indizes - if "fuzz" in input_string: - index = input_string.find("fuzz") - if index % 2 == 0: - result += 30 - else: - result -= 30 - - # Rückgabe des Ergebnisses - return result - - - -grey_box_fuzzer = GreyBoxFuzzer() -grey_box_runner = GreyBoxRunner() - -# seed specification - -amount_seeds = 10 -seed_template = ["STRING"] -seed_specification = ['r'] - - -# create a population with fuzzer -seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 100) - -# Print seeds in population -print("##### Population #####") -for i in seed_population: - print(i.seed_values) - -print("\n##### Execute Tests #####\n") - -test_results = grey_box_runner.run(complex_function, seed_population, 2000) - -print("\n##### Test restults #####\n") -print(f"Tests passed: {test_results['passed_tests']}") -print(f"Tests failed: {test_results['failed_tests']}") - diff --git a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py index d2a6ae59..b0700115 100644 --- a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py +++ b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py @@ -1,5 +1,6 @@ from custom_components.test.fuzzing.fuzzer_utils.GreyBoxFuzzer import GreyBoxFuzzer from custom_components.test.fuzzing.fuzzer_utils.GreyBoxRunner import GreyBoxRunner +from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed import pytest import logging @@ -42,9 +43,122 @@ def test_crashme() -> None: logger.info("Start of test_crashme() test.") seed_template = ["STRING"] seed_specification = [4] + seed_population: list[Seed] + result: dict + seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 20) + result = grey_box_runner.run(demo_function, seed_population, 1000) - seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 1) - result = grey_box_runner.run(demo_function, seed_population, 10) + assert result["failed_tests"] == 0 + + +@pytest.mark.skipif(False, reason="Not skiped!") +def test_map_range() -> None: + logger.info("Start of test_map_range() test.") + seed_template = ["FLOAT", "FLOAT", "FLOAT", "FLOAT", "FLOAT"] + seed_specification = ['r', 'r', 'r', 'r', 'r'] + seed_population: list[Seed] + result: dict + + seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 20) + result = grey_box_runner.run(map_range, seed_population, 1000) + + assert result["failed_tests"] == 0 + + +@pytest.mark.skipif(False, reason="Not skiped!") +def test_hass_to_lox() -> None: + logger.info("Start of test_hass_to_lox() test.") + seed_template = ["FLOAT"] + seed_specification = ['r'] + seed_population: list[Seed] + result: dict + + seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 20) + result = grey_box_runner.run(hass_to_lox, seed_population, 1000) + + assert result["failed_tests"] == 0 + + +@pytest.mark.skipif(False, reason="Not skiped!") +def test_lox_to_hass() -> None: + logger.info("Start of test_lox_to_hass() test.") + seed_template = ["FLOAT"] + seed_specification = ['r'] + seed_population: list[Seed] + result: dict + + seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 20) + result = grey_box_runner.run(lox_to_hass, seed_population, 1000) + + assert result["failed_tests"] == 0 + + +@pytest.mark.skipif(False, reason="Not skiped!") +def test_lox2lox_mapped() -> None: + logger.info("Start of test_lox2lox_mapped() test.") + seed_template = ["FLOAT", "FLOAT", "FLOAT"] + seed_specification = ['r', 'r', 'r'] + seed_population: list[Seed] + result: dict + + seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 20) + result = grey_box_runner.run(lox2lox_mapped, seed_population, 1000) + + assert result["failed_tests"] == 0 + + +@pytest.mark.skipif(False, reason="Not skiped!") +def test_lox2hass_mapped() -> None: + logger.info("Start of test_lox2hass_mapped() test.") + seed_template = ["FLOAT", "FLOAT", "FLOAT"] + seed_specification = ['r', 'r', 'r'] + seed_population: list[Seed] + result: dict + + seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 20) + result = grey_box_runner.run(lox2hass_mapped, seed_population, 1000) + + assert result["failed_tests"] == 0 + + +@pytest.mark.skipif(False, reason="Not skiped!") +def test_to_hass_color_temp() -> None: + logger.info("Start of test_to_hass_color_temp() test.") + seed_template = ["FLOAT"] + seed_specification = ['r'] + seed_population: list[Seed] + result: dict + + seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 20) + result = grey_box_runner.run(to_hass_color_temp, seed_population, 1000) + + assert result["failed_tests"] == 0 + + +@pytest.mark.skipif(False, reason="Not skiped!") +def test_to_loxone_color_temp() -> None: + logger.info("Start of test_to_loxone_color_temp() test.") + seed_template = ["FLOAT"] + seed_specification = ['r'] + seed_population: list[Seed] + result: dict + + seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 20) + result = grey_box_runner.run(to_loxone_color_temp, seed_population, 1000) + + assert result["failed_tests"] == 0 + + +@pytest.mark.skipif(False, reason="Not skiped!") +def test_get_miniserver_type() -> None: + logger.info("Start of test_get_miniserver_type() test.") + seed_template = ["INT"] + seed_specification = ['r'] + seed_population: list[Seed] + result: dict + + seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 20) + result = grey_box_runner.run(get_miniserver_type, seed_population, 1000) assert result["failed_tests"] == 0 From 1aedcc02d181e8ed2e1604bd5dfb565151a66ed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6gel?= <hoegma01@thu.de> Date: Tue, 2 Jul 2024 22:10:51 +0200 Subject: [PATCH 18/19] Delete Mutator - Deleted mutator and shiftet mutation method to GreyBoxRunner -> Mutator class unnecessary with one method in it - Adjusted uml diagram --- .../test/fuzzing/fuzzer_overview.puml | 31 ++----------------- .../fuzzing/fuzzer_utils/GreyBoxFuzzer.py | 2 +- .../fuzzing/fuzzer_utils/GreyBoxRunner.py | 23 +++++++++++--- .../fuzzer_tools/DataTypeCreator.py | 1 - .../fuzzer_utils/fuzzer_tools/Mutator.py | 26 ---------------- .../grey_box/test_grey_box_on_helpers.py | 18 +++++------ 6 files changed, 31 insertions(+), 70 deletions(-) delete mode 100644 custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py diff --git a/custom_components/test/fuzzing/fuzzer_overview.puml b/custom_components/test/fuzzing/fuzzer_overview.puml index 0c057b1a..05c72765 100644 --- a/custom_components/test/fuzzing/fuzzer_overview.puml +++ b/custom_components/test/fuzzing/fuzzer_overview.puml @@ -119,6 +119,7 @@ class "GreyBoxRunner" as GBRunner << class >> { + run(self, function: Callable, seed_population: List[Seed], amount_runs: int = 10000) : list - __hash_md5(self, branch_covered: str) : str - __store_hashed_branch(self, hashed_branch: str) : void + - __mutate(self, seed: Seed) : void } ''''''''''''''''''''''''''''''''''''''' class "SeedManager" as SeedManager << class >> { @@ -130,22 +131,6 @@ class "SeedManager" as SeedManager << class >> { + get_normalized_energy(self, seed_population: List[Seed]) : list } ''''''''''''''''''''''''''''''''''''''' -class "Mutator" as Mutator << class >> { - -- - - __init__(self) : void - + mutate_grey_box_fuzzer(self, seed: Seed) : void - - __delete_random_char(self, s: str) : str - - __insert_random_char(self, s: str) : str - - __flip_random_char(self, s: str) : str - - __get_random_float(self) : float - - __check_inf(self, number: float) : float - - __add_random_number(self, number: float) : float - - __sub_random_number(self, number: float) : float - - __mult_random_number(self, number: float) : float - - __div_random_number(self, number: float) : float - - __grey_mutate_string(self, seed_value: str) : str -} -''''''''''''''''''''''''''''''''''''''' class "Seed" as Seed << class >> { - energy: int = 0 - seed_values: list = [] @@ -159,7 +144,8 @@ class "DataTypeCreator" as DataTypeCreator << class >> { -- - __init__(self) : void - create_int(self, amount_digits: int = 10, random_creation: bool = True) : int - - create_uint(self, amount_digits: int = 10, random_creation: bool = True) : int + - create_float(self, amount_digits: int = 10, random_creation: bool = True) : float + - create_string_only_letters(self, amount_chars: int) : int - create_string_special_characters(self, amount_chars: int) : str } ''''''''''''''''''''''''''''''''''''''' @@ -239,13 +225,6 @@ entity "test_grey_box_on_helpers.py" as test_GBox << test case >>{ } ''''''''''''''''''''''''''''''''''''''' -entity "test_grey_box_on_helpers_complex.py" as test_GBox_complex << test case >>{ - logger - grey_box_fuzzer = GreyBoxFuzzer - grey_box_runner = GreyBoxRunner - -- -} -''''''''''''''''''''''''''''''''''''''' 'Fuzzer'''''''''''''''''''''''''''''''' ''''''''''''''''''''''''''''''''''''''' fuzzer <|-- VPFuzzer : inherits from < @@ -260,13 +239,11 @@ GBFuzzer --> Seed: uses > 'Runner'''''''''''''''''''''''''''''''' ''''''''''''''''''''''''''''''''''''''' runner <|-- PRunner : inherits from < -GBRunner o-- Mutator : aggregates > GBRunner o-- SeedManager : aggregates > GBRunner --> Seed: uses > ''''''''''''''''''''''''''''''''''''''' 'Other classes''''''''''''''''''''''''' ''''''''''''''''''''''''''''''''''''''' -Mutator --> Seed: uses > SeedManager --> Seed: uses > DataTypeCreator --> Seed: uses > ''''''''''''''''''''''''''''''''''''''' @@ -285,7 +262,5 @@ GrFuzzer "1"<--* test_MUT : needs a < ipv4 "1"<--* test_MUT : needs a < ''''''''''''''''''''''''''''''''''''''' GBFuzzer "1"<--* test_GBox : needs a < -GBFuzzer "1"<--* test_GBox_complex : needs a < GBRunner "1"<--* test_GBox : needs a < -GBRunner "1"<--* test_GBox_complex : needs a < @enduml \ No newline at end of file diff --git a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py index 5b139ba8..45c84e9a 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py +++ b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py @@ -7,7 +7,6 @@ class GreyBoxFuzzer(Fuzzer): """GreyBox fuzzer class, inherits from the abstract fuzzer class.""" - __RANGE_RANDOM_INT = 9 __RANGE_RANDOM_STRING = 100 __data_type_creator = DataTypeCreator() @@ -26,6 +25,7 @@ def fuzz(self, :param seed_template: The seed_template is a list of input types for the function. The entries must correspond to the valid function parameters of the function to be tested. + Valid inputs are "INT", "FLOAT", "STRING" and "BOOLEAN". e.g.: ["INT", "FLOAT", "STRING", "BOOLEAN"] :type seed_template: list diff --git a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py index c37c9c8b..5aef74e8 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py +++ b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxRunner.py @@ -2,26 +2,26 @@ import inspect import coverage import hashlib +import random from typing import Callable, List from custom_components.test.fuzzing.fuzzer_utils.Runner import Runner from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed, SeedManager -from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Mutator import Mutator - +from custom_components.test.fuzzing.fuzzer_utils.MutationalFuzzer import MutationalBlackBoxFuzzer class GreyBoxRunner(Runner): """Greybox runner class, inherits from the abstract runner class.""" __logger = None __seed_manager = None - __mutator = None + __mutationalFuzzer = None path_dict = {} def __init__(self): """constructor""" self.__logger = logging.getLogger(__name__) self.__seed_manager = SeedManager() - self.__mutator = Mutator() + self.__mutationalFuzzer = MutationalBlackBoxFuzzer() def run(self, function: Callable, seed_population: List[Seed], amount_runs: int = 10000) -> list: """Executes all transferred parameter sets for the transferred function. @@ -57,7 +57,7 @@ def run(self, function: Callable, seed_population: List[Seed], amount_runs: int seed = self.__seed_manager.select_seed(seed_population) # Mutate seed values - self.__mutator.mutate_grey_box_fuzzer(seed) + self.__mutate(seed) cov = coverage.Coverage(branch=True) cov.start() @@ -113,6 +113,19 @@ def __store_hashed_path(self, hashed_path: str, path_dict: dict) -> dict: return path_dict + def __mutate(self, seed: Seed): + """Mutates one of the seed values. + + This function takes a seed and mutates one of the seed values of it. + + :param seed: A seed consists of a list of seed_values. + :type seed: Seed + """ + amount_values = len(seed.seed_values) + random_index = random.choice(range(0,amount_values)) + seed_value = seed.seed_values[random_index] + seed.seed_values[random_index] = self.__mutationalFuzzer.fuzz([seed_value],2)[1][0] + diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py index 4d161538..323bb598 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py +++ b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/DataTypeCreator.py @@ -4,7 +4,6 @@ class DataTypeCreator: __MAX_INT = (1 << 31) - 1 - __MAX_UINT = (1 << 32) - 1 def __init__(self): """initialize DataTypeCreator""" diff --git a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py b/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py deleted file mode 100644 index 8d662f4e..00000000 --- a/custom_components/test/fuzzing/fuzzer_utils/fuzzer_tools/Mutator.py +++ /dev/null @@ -1,26 +0,0 @@ -from custom_components.test.fuzzing.fuzzer_utils.fuzzer_tools.Seed import Seed -from custom_components.test.fuzzing.fuzzer_utils.MutationalFuzzer import MutationalBlackBoxFuzzer -import random - -class Mutator: - - __mutationalFuzzer = None - - def __init__(self): - """initialize Mutator""" - self.__mutationalFuzzer = MutationalBlackBoxFuzzer() - - def mutate_grey_box_fuzzer(self, seed: Seed): - """Mutates one of the seed values. - - This function takes a seed and mutates one of the seed values of it. - - :param seed: A seed consists of a list of seed_values. - :type seed: Seed - """ - amount_values = len(seed.seed_values) - random_index = random.choice(range(0,amount_values)) - seed_value = seed.seed_values[random_index] - seed.seed_values[random_index] = self.__mutationalFuzzer.fuzz([seed_value],2)[1][0] - - print(seed.seed_values) \ No newline at end of file diff --git a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py index b0700115..09ed027b 100644 --- a/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py +++ b/custom_components/test/fuzzing/grey_box/test_grey_box_on_helpers.py @@ -47,7 +47,7 @@ def test_crashme() -> None: result: dict seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 20) - result = grey_box_runner.run(demo_function, seed_population, 1000) + result = grey_box_runner.run(demo_function, seed_population, 100) assert result["failed_tests"] == 0 @@ -61,7 +61,7 @@ def test_map_range() -> None: result: dict seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 20) - result = grey_box_runner.run(map_range, seed_population, 1000) + result = grey_box_runner.run(map_range, seed_population, 100) assert result["failed_tests"] == 0 @@ -75,7 +75,7 @@ def test_hass_to_lox() -> None: result: dict seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 20) - result = grey_box_runner.run(hass_to_lox, seed_population, 1000) + result = grey_box_runner.run(hass_to_lox, seed_population, 100) assert result["failed_tests"] == 0 @@ -89,7 +89,7 @@ def test_lox_to_hass() -> None: result: dict seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 20) - result = grey_box_runner.run(lox_to_hass, seed_population, 1000) + result = grey_box_runner.run(lox_to_hass, seed_population, 100) assert result["failed_tests"] == 0 @@ -103,7 +103,7 @@ def test_lox2lox_mapped() -> None: result: dict seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 20) - result = grey_box_runner.run(lox2lox_mapped, seed_population, 1000) + result = grey_box_runner.run(lox2lox_mapped, seed_population, 100) assert result["failed_tests"] == 0 @@ -117,7 +117,7 @@ def test_lox2hass_mapped() -> None: result: dict seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 20) - result = grey_box_runner.run(lox2hass_mapped, seed_population, 1000) + result = grey_box_runner.run(lox2hass_mapped, seed_population, 100) assert result["failed_tests"] == 0 @@ -131,7 +131,7 @@ def test_to_hass_color_temp() -> None: result: dict seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 20) - result = grey_box_runner.run(to_hass_color_temp, seed_population, 1000) + result = grey_box_runner.run(to_hass_color_temp, seed_population, 100) assert result["failed_tests"] == 0 @@ -145,7 +145,7 @@ def test_to_loxone_color_temp() -> None: result: dict seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 20) - result = grey_box_runner.run(to_loxone_color_temp, seed_population, 1000) + result = grey_box_runner.run(to_loxone_color_temp, seed_population, 100) assert result["failed_tests"] == 0 @@ -159,6 +159,6 @@ def test_get_miniserver_type() -> None: result: dict seed_population = grey_box_fuzzer.fuzz(seed_template, seed_specification, 20) - result = grey_box_runner.run(get_miniserver_type, seed_population, 1000) + result = grey_box_runner.run(get_miniserver_type, seed_population, 100) assert result["failed_tests"] == 0 From 9fb7310236af63c0fcbdd1c6e24fc9f0be5f4453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6gel?= <hoegma01@thu.de> Date: Tue, 2 Jul 2024 22:32:16 +0200 Subject: [PATCH 19/19] Change in GreyBoxFuzzer - Deleted unnecessary functionality --- .../test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py index 45c84e9a..c5bffaa3 100644 --- a/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py +++ b/custom_components/test/fuzzing/fuzzer_utils/GreyBoxFuzzer.py @@ -71,18 +71,9 @@ def fuzz(self, param_set.append(self.__data_type_creator.create_string_only_letters(seed_spec)) elif rand_val == 1: param_set.append(self.__data_type_creator.create_string_special_characters(seed_spec)) - + elif data_type == "BOOL": param_set.append(random.choice([True, False])) - - elif data_type == "BYTE": - print("create_byte") - elif data_type == "LIST": - print("create_list") - elif data_type == "DICT": - print("create_dict") - elif data_type == "DATE": - print("create_date") seed = Seed(1, param_set) seed_population.append(seed)