• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python3
2
3# Copyright 2021 Zac Hatfield-Dodds
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""This fuzzer is an example harness using Hypothesis for structured inputs.
18
19It would be possible, though more difficult, to write this test in terms
20of Atheris' `FuzzedDataProvider` instead of Hypothesis strategies.
21
22As well as defining structured inputs however, the call to
23`test_ujson_roundtrip()` will replay, deduplicate, and minimize any known
24failing examples from previous runs - which is great when debugging.
25Hypothesis uses a separate cache to Atheris/LibFuzzer seeds, so this is
26strictly complementary to your traditional fuzzing workflow.
27
28For more details on Hypothesis, see:
29https://hypothesis.readthedocs.io/en/latest/data.html
30https://hypothesis.readthedocs.io/en/latest/details.html#use-with-external-fuzzers
31"""
32
33import sys
34import atheris
35import ujson
36from hypothesis import given, strategies as st
37
38# We could define all these inline within the call to @given(),
39# but it's a bit easier to read if we name them here instead.
40JSON_ATOMS = st.one_of(
41    st.none(),
42    st.booleans(),
43    st.integers(min_value=-(2 ** 63), max_value=2 ** 63 - 1),
44    st.floats(allow_nan=False, allow_infinity=False),
45    st.text(),
46)
47JSON_OBJECTS = st.recursive(
48    base=JSON_ATOMS,
49    extend=lambda inner: st.lists(inner) | st.dictionaries(st.text(), inner),
50)
51UJSON_ENCODE_KWARGS = {
52    "ensure_ascii": st.booleans(),
53    "encode_html_chars": st.booleans(),
54    "escape_forward_slashes": st.booleans(),
55    "sort_keys": st.booleans(),
56    "indent": st.integers(0, 20),
57}
58
59
60@given(obj=JSON_OBJECTS, kwargs=st.fixed_dictionaries(UJSON_ENCODE_KWARGS))
61@atheris.instrument_func
62def test_ujson_roundtrip(obj, kwargs):
63    """Check that all JSON objects round-trip regardless of other options."""
64    assert obj == ujson.decode(ujson.encode(obj, **kwargs))
65
66
67if __name__ == "__main__":
68    # Running `pytest hypothesis_structured_fuzzer.py` will replay, deduplicate,
69    # and minimize any failures discovered by earlier runs or by OSS-Fuzz, or
70    # briefly search for new failures if none are known.
71    # Or, when running via OSS-Fuzz, we'll execute it via the fuzzing hook:
72    atheris.Setup(sys.argv, atheris.instrument_func(test_ujson_roundtrip.hypothesis.fuzz_one_input))
73    atheris.Fuzz()
74