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

Fix RecursionError: Limit depth of nested Objects when generating fuzz parameters #89

Merged
merged 1 commit into from
Feb 23, 2024
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ optional arguments:
--ignore-exceptions Ignores all exceptions raised during fuzzing (aka.
only fails when vulnerabilities are found).
--disable-unicode Disable unicode characters in fuzzing, only use ASCII.
--depth DEPTH Maximum depth for generating nested fuzz parameters.

```

Expand Down
4 changes: 4 additions & 0 deletions fuzz_lightyear/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ENABLE_COLOR = True
MAX_FUZZ_DEPTH = 6
SEED_RANDOM_LENGTH = 128
UNICODE_ENABLED = True
24 changes: 21 additions & 3 deletions fuzz_lightyear/fuzzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def _fuzz_parameter(
parameter: Dict[str, Any],
operation_id: str = None,
required: bool = False,
depth: int = 0,
) -> SearchStrategy:
"""
:param required: for object types, the required parameter is in a
Expand Down Expand Up @@ -78,7 +79,12 @@ def _fuzz_parameter(
}
fuzz_fn = mapping[_type]
if fuzz_fn in (_fuzz_object, _fuzz_array):
strategy = fuzz_fn(parameter, operation_id, required=required) # type: ignore
strategy = fuzz_fn(
parameter,
operation_id,
depth + 1,
required=required,
) # type: ignore
else:
strategy = fuzz_fn(parameter, required=required) # type: ignore

Expand Down Expand Up @@ -168,19 +174,22 @@ def _fuzz_boolean(
def _fuzz_array(
parameter: Dict[str, Any],
operation_id: str = None,
depth: int = 0,
required: bool = False,
) -> SearchStrategy:
item = parameter['items']
required = parameter.get('required', required)

# TODO: Handle `oneOf`
strategy = st.lists(
elements=_fuzz_parameter(item, operation_id, required=required),
elements=_fuzz_parameter(item, operation_id, required=required, depth=depth + 1),
min_size=parameter.get(
'minItems',
0 if not required else 1,
),
max_size=parameter.get('maxItems', None),
max_size=(0
if depth > get_settings().max_fuzz_depth
else parameter.get('maxItems', None)),
)
if not required:
return st.one_of(st.none(), strategy)
Expand All @@ -191,10 +200,18 @@ def _fuzz_array(
def _fuzz_object(
parameter: Dict[str, Any],
operation_id: str = None,
depth: int = 0,
**kwargs: Any,
) -> SearchStrategy:
# TODO: Handle `additionalProperties`
output = {}

if (
depth > get_settings().max_fuzz_depth or
'properties' not in parameter
):
return st.none()

for name, specification in parameter['properties'].items():
try:
strategy = _get_strategy_from_factory(
Expand Down Expand Up @@ -224,6 +241,7 @@ def _fuzz_object(
specification,
operation_id,
bool(required),
depth + 1,
)

return st.fixed_dictionaries(output)
Expand Down
5 changes: 5 additions & 0 deletions fuzz_lightyear/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from hypothesis.errors import NonInteractiveExampleWarning
from swagger_spec_validator.common import SwaggerValidationError # type: ignore

from .config import MAX_FUZZ_DEPTH
from .datastore import get_excluded_operations
from .datastore import get_non_vulnerable_operations
from .datastore import get_setup_fixtures
Expand Down Expand Up @@ -56,6 +57,7 @@ def main(argv: Optional[List[str]] = None) -> int:
seed=args.seed,
ignore_exceptions=args.ignore_exceptions,
disable_unicode=args.disable_unicode,
max_fuzz_depth=args.depth,
)

outputter.show_results()
Expand Down Expand Up @@ -112,6 +114,7 @@ def run_tests(
seed: int = None,
ignore_exceptions: bool = False,
disable_unicode: bool = False,
max_fuzz_depth: int = MAX_FUZZ_DEPTH,
) -> ResultFormatter:
"""
:param tests: list of tests to run.
Expand All @@ -121,12 +124,14 @@ def run_tests(
:param seed: used for random generation of test input
:param ignore_exceptions: if True, ignores HTTP exceptions to requests.
:param disable_unicode: if True, only use ASCII characters to fuzz strings
:param max_fuzz_depth: used for preventing recursion Objects with self reference
"""
if seed is not None:
get_settings().seed = seed
Chandra158 marked this conversation as resolved.
Show resolved Hide resolved
if disable_unicode:
get_settings().unicode_enabled = False

get_settings().max_fuzz_depth = max_fuzz_depth
jpdakran marked this conversation as resolved.
Show resolved Hide resolved
outputter = ResultFormatter()
for result in generate_sequences(
n=iterations,
Expand Down
12 changes: 9 additions & 3 deletions fuzz_lightyear/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@

from hypothesis import core

from fuzz_lightyear.config import ENABLE_COLOR
from fuzz_lightyear.config import MAX_FUZZ_DEPTH
from fuzz_lightyear.config import SEED_RANDOM_LENGTH
from fuzz_lightyear.config import UNICODE_ENABLED


class Settings:
def __init__(self) -> None:
self.seed = random.getrandbits(128) # type: int
self.unicode_enabled = True # type: bool
self.enable_color = True # type: bool
self.seed = random.getrandbits(SEED_RANDOM_LENGTH) # type: int
self.unicode_enabled = UNICODE_ENABLED # type: bool
self.enable_color = ENABLE_COLOR # type: bool
self.max_fuzz_depth = MAX_FUZZ_DEPTH # type: int

@property
def seed(self) -> int:
Expand Down
7 changes: 7 additions & 0 deletions fuzz_lightyear/usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@ def parse_args(argv: Optional[List[str]] = None) -> argparse.Namespace:
),
)

parser.add_argument(
'--depth',
default=6,
jpdakran marked this conversation as resolved.
Show resolved Hide resolved
type=int,
help='Maximum depth for generating nested fuzz parameters.',
)

return parser.parse_args(argv)


Expand Down
Loading