diff --git a/fuzzing/fuzz-targets/fuzz_submodule.py b/fuzzing/fuzz-targets/fuzz_submodule.py index ddcbaa00f..92b569949 100644 --- a/fuzzing/fuzz-targets/fuzz_submodule.py +++ b/fuzzing/fuzz-targets/fuzz_submodule.py @@ -3,14 +3,23 @@ import os import tempfile from configparser import ParsingError -from utils import is_expected_exception_message +from utils import is_expected_exception_message, get_max_filename_length +from git import Repo, GitCommandError, InvalidGitRepositoryError -if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): +if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): # pragma: no cover path_to_bundled_git_binary = os.path.abspath(os.path.join(os.path.dirname(__file__), "git")) os.environ["GIT_PYTHON_GIT_EXECUTABLE"] = path_to_bundled_git_binary -with atheris.instrument_imports(): - from git import Repo, GitCommandError, InvalidGitRepositoryError +if not sys.warnoptions: # pragma: no cover + # The warnings filter below can be overridden by passing the -W option + # to the Python interpreter command line or setting the `PYTHONWARNINGS` environment variable. + import warnings + import logging + + # Fuzzing data causes some modules to generate a large number of warnings + # which are not usually interesting and make the test output hard to read, so we ignore them. + warnings.simplefilter("ignore") + logging.getLogger().setLevel(logging.ERROR) def TestOneInput(data): @@ -42,12 +51,12 @@ def TestOneInput(data): writer.release() submodule.update(init=fdp.ConsumeBool(), dry_run=fdp.ConsumeBool(), force=fdp.ConsumeBool()) - submodule_repo = submodule.module() - new_file_path = os.path.join( - submodule_repo.working_tree_dir, - f"new_file_{fdp.ConsumeUnicodeNoSurrogates(fdp.ConsumeIntInRange(1, 512))}", + + new_file_name = fdp.ConsumeUnicodeNoSurrogates( + fdp.ConsumeIntInRange(1, max(1, get_max_filename_length(submodule_repo.working_tree_dir))) ) + new_file_path = os.path.join(submodule_repo.working_tree_dir, new_file_name) with open(new_file_path, "wb") as new_file: new_file.write(fdp.ConsumeBytes(fdp.ConsumeIntInRange(1, 512))) submodule_repo.index.add([new_file_path]) @@ -67,16 +76,24 @@ def TestOneInput(data): ) repo.index.commit(f"Removed submodule {submodule_name}") - except (ParsingError, GitCommandError, InvalidGitRepositoryError, FileNotFoundError, BrokenPipeError): + except ( + ParsingError, + GitCommandError, + InvalidGitRepositoryError, + FileNotFoundError, + FileExistsError, + IsADirectoryError, + NotADirectoryError, + BrokenPipeError, + ): return -1 - except (ValueError, OSError) as e: + except ValueError as e: expected_messages = [ "SHA is empty", "Reference at", "embedded null byte", "This submodule instance does not exist anymore", "cmd stdin was empty", - "File name too long", ] if is_expected_exception_message(e, expected_messages): return -1 @@ -85,6 +102,7 @@ def TestOneInput(data): def main(): + atheris.instrument_all() atheris.Setup(sys.argv, TestOneInput) atheris.Fuzz() diff --git a/fuzzing/fuzz-targets/utils.py b/fuzzing/fuzz-targets/utils.py index 42faa8eb0..f522d2959 100644 --- a/fuzzing/fuzz-targets/utils.py +++ b/fuzzing/fuzz-targets/utils.py @@ -1,4 +1,5 @@ import atheris # pragma: no cover +import os # pragma: no cover from typing import List # pragma: no cover @@ -20,3 +21,17 @@ def is_expected_exception_message(exception: Exception, error_message_list: List if error.lower() in exception_message: return True return False + + +@atheris.instrument_func +def get_max_filename_length(path: str) -> int: # pragma: no cover + """ + Get the maximum filename length for the filesystem containing the given path. + + Args: + path (str): The path to check the filesystem for. + + Returns: + int: The maximum filename length. + """ + return os.pathconf(path, "PC_NAME_MAX")