Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add graceful handling of expected exceptions in fuzz_submodule.py #1922

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 29 additions & 11 deletions fuzzing/fuzz-targets/fuzz_submodule.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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])
Expand All @@ -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
Expand All @@ -85,6 +102,7 @@ def TestOneInput(data):


def main():
atheris.instrument_all()
atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()

Expand Down
15 changes: 15 additions & 0 deletions fuzzing/fuzz-targets/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import atheris # pragma: no cover
import os # pragma: no cover
from typing import List # pragma: no cover


Expand All @@ -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")
Loading