opencas-test-framework/test_tools/peach_fuzzer/peach_fuzzer.py
Katarzyna Treder b2b20cb714 Fix parameter type in peach fuzzer get_fuzzed_command method
Signed-off-by: Katarzyna Treder <katarzyna.treder@h-partners.com>
2024-10-15 11:12:38 +02:00

213 lines
8.9 KiB
Python

#
# Copyright(c) 2021 Intel Corporation
# Copyright(c) 2024 Huawei Technologies Co., Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#
import os
import base64
import posixpath
import random
import tempfile
import lxml.etree as etree
from collections import namedtuple
from core.test_run import TestRun
from test_tools import fs_utils
from test_tools.fs_utils import create_directory, check_if_file_exists, write_file
from test_utils import os_utils
class PeachFuzzer:
"""
API to work with Peach Fuzzer tool in Test-Framework.
Peach Fuzzer is used only for generating fuzzed values that later are used in Test-Framework
in order to execute fuzzed CLI commands or to prepare fuzzed config files.
"""
peach_fuzzer_3_0_url = "https://sourceforge.net/projects/peachfuzz/files/Peach/3.0/" \
"peach-3.0.202-linux-x86_64-release.zip"
base_dir = "/root/Fuzzy"
peach_dir = "peach-3.0.202-linux-x86_64-release"
xml_config_template = os.path.join(os.path.dirname(__file__), "config_template.xml")
xml_config_file = posixpath.join(base_dir, "fuzzerConfig.xml")
xml_namespace = "http://peachfuzzer.com/2012/Peach"
fuzzy_output_file = posixpath.join(base_dir, "fuzzedParams.txt")
tested_param_placeholder = b"{param}"
# escape backslash first, so it doesn't interfere with escaping other characters
escape_chars = '\\\n"\'&|;()`<>$! '
@classmethod
def get_fuzzed_command(cls, command_template: str, count: int):
"""
Generate command with fuzzed parameter provided on command_template.
:param command_template: string with command to be executed.
parameter to be replaced with fuzzed string has to be tested_param_placeholder
:param count: amount of fuzzed commands to generate
:returns: named tuple with fuzzed param and CLI ready to be executed with Test-Framework
executors. Param is returned in order to implement correct values checkers in the tests
"""
TestRun.LOGGER.info(f"Try to get commands with fuzzed parameters")
FuzzedCommand = namedtuple('FuzzedCommand', ['param', 'command'])
command_template = command_template.encode("ascii")
if cls.tested_param_placeholder not in command_template:
TestRun.block("No param placeholder is found in command template!")
for fuzzed_parameter in cls.generate_peach_fuzzer_parameters(count):
yield FuzzedCommand(
fuzzed_parameter,
command_template.replace(
cls.tested_param_placeholder,
fuzzed_parameter
)
)
@classmethod
def generate_peach_fuzzer_parameters(cls, count: int):
"""
Generate fuzzed parameter according to Peach Fuzzer XML config
Fuzzed parameter later can be used for either generating cli command or config.
:param count: amount of fuzzed strings to generate
:returns: fuzzed value in byte string
"""
if not cls._is_installed():
TestRun.LOGGER.info("Try to install Peach Fuzzer")
cls._install()
if not cls._is_xml_config_prepared():
TestRun.block("No Peach Fuzzer XML config needed to generate fuzzed values was found!")
fs_utils.remove(cls.fuzzy_output_file, force=True, ignore_errors=True)
TestRun.LOGGER.info(f"Generate {count} unique fuzzed values")
cmd = f"cd {cls.base_dir}; {cls.peach_dir}/peach --range 0,{count - 1} " \
f"--seed {random.randrange(2 ** 32)} {cls.xml_config_file} > " \
f"{cls.base_dir}/peachOutput.log"
TestRun.executor.run_expect_success(cmd)
if not check_if_file_exists(cls.fuzzy_output_file):
TestRun.block("No expected fuzzy output file was found!")
# process fuzzy output file locally on the controller as it can be very big
local_fuzzy_file = tempfile.NamedTemporaryFile(delete=False)
local_fuzzy_file.close()
TestRun.executor.copy_from(cls.fuzzy_output_file, local_fuzzy_file.name)
with open(local_fuzzy_file.name, "r") as fd:
for fuzzed_param_line in fd:
fuzzed_param_bytes = base64.b64decode(fuzzed_param_line)
fuzzed_param_bytes = cls._escape_special_chars(fuzzed_param_bytes)
yield fuzzed_param_bytes
@classmethod
def generate_config(cls, data_model_config: list):
"""
Generate Peach Fuzzer XML config based on template provided in xml_config_template
and data template passed as an argument.
:param data_model_config: dictionary with config that has to be used for generating
DataModel section in PeachFuzzer XML config. Config can be stored in test in more compact
form, e.g. in yaml, and can be converted to dict just before passing to this function.
Example of such config in yaml:
- name: String
attributes:
name: CacheId
value: '1'
size: '14'
mutable: 'true'
children:
- name: Hint
attributes:
name: NumericalString
value: 'true'
"""
if not os.path.exists(cls.xml_config_template):
TestRun.block("Peach fuzzer xml config template not found!")
root = etree.parse(cls.xml_config_template)
data_model = root.find(f'{{{cls.xml_namespace}}}DataModel[@name="Value"]')
cls.__create_xml_nodes(data_model, data_model_config)
create_directory(cls.base_dir, True)
write_file(cls.xml_config_file, etree.tostring(root, encoding="unicode"))
@classmethod
def copy_config(cls, config_file: str):
"""
Instead of generating config with "generate_config" method, config can be prepared manually
and just passed as is to PeachFuzzer.
:param config_file: Peach Fuzzer XML config to be copied to the DUT
"""
if not os.path.exists(config_file):
TestRun.block("Peach fuzzer xml config to be copied doesn't exist!")
create_directory(cls.base_dir, True)
TestRun.executor.rsync_to(config_file, cls.xml_config_file)
@classmethod
def __create_xml_nodes(cls, xml_node, config):
"""
Create XML code for Peach Fuzzer based on python dict config
"""
for element in config:
new_node = etree.Element(element["name"])
for attr_name, attr_value in element["attributes"].items():
new_node.set(attr_name, attr_value)
if element.get("children"):
cls.__create_xml_nodes(new_node, element.get("children"))
xml_node.append(new_node)
@classmethod
def _install(cls):
"""
Install Peach Fuzzer on the DUT
"""
create_directory(cls.base_dir, True)
peach_archive = os_utils.download_file(
cls.peach_fuzzer_3_0_url, destination_dir=cls.base_dir
)
TestRun.executor.run_expect_success(
f'cd {cls.base_dir} && unzip -u "{peach_archive}"')
if cls._is_installed():
TestRun.LOGGER.info("Peach fuzzer installed successfully")
else:
TestRun.block("Peach fuzzer installation failed!")
@classmethod
def _is_installed(cls):
"""
Check if Peach Fuzzer is installed on the DUT
"""
if not cls._is_mono_installed():
TestRun.block("Mono is not installed, can't continue with Peach Fuzzer!")
if fs_utils.check_if_directory_exists(posixpath.join(cls.base_dir, cls.peach_dir)):
return "Peach" in TestRun.executor.run(
f"cd {cls.base_dir} && {cls.peach_dir}/peach --version").stdout.strip()
else:
return False
@classmethod
def _escape_special_chars(cls, fuzzed_str: bytes):
"""
Escape special chars provided in escape_chars list in the fuzzed string generated by
Peach Fuzzer
Escaping is done for example in order to make fuzzed string executable in Linux CLI
If fuzzed string will be used in other places, escape_chars list may be overwritten.
"""
for i in cls.escape_chars:
i = bytes(i, "utf-8")
if i in fuzzed_str[:]:
fuzzed_str = fuzzed_str.replace(i, b'\\' + i)
return fuzzed_str
@classmethod
def _is_xml_config_prepared(cls):
"""
Check if Peach Fuzzer XML config is present on the DUT
"""
if fs_utils.check_if_file_exists(cls.xml_config_file):
return True
else:
return False
@staticmethod
def _is_mono_installed():
"""
Check if Mono (.NET compatible framework) is installed on the DUT
If it's not, it has to be installed manually.
For RHEL-based OSes it's usually mono-complete package
"""
return TestRun.executor.run("which mono").exit_code == 0