• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python3
2
3# Copyright 2020 Google LLC
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""" An example native JSON vs uJSON differential fuzzer.
17
18This fuzzer looks for differences between the built-in json library and the
19native ujson library. The ujson library should be built for coverage (see
20build_install_ujson.sh), and the Python fuzzer should be executed under ASAN.
21As an example:
22    LD_PRELOAD="/usr/lib/llvm-9/lib/clang/9.0.1/lib/linux/libclang_rt.asan-x86_64.so
23    $(python3 -c "import atheris; print(atheris.path())")" python3
24    ./json_differential_fuzzer.py -detect_leaks=0
25
26This fuzzer has found a bug with inconsistent handling of integers with
27too-high magnitude. uJSON sometimes refuses to process numbers that are too far
28from 0 with "Value is too big!" or the equivalent for values that are too
29negative. However, other times it happily processes them with two's compliment
30mod. As an example, it refuses to parse "-9223372036854775809" (the first
31integer not representable in a 64-bit signed number) with "Value is too small";
32but it will happily parse "-80888888888888888888", a significantly more negative
33number. However, it parses it as -9223372036854775808. The JSON spec
34(https://tools.ietf.org/html/rfc7159#section-6) "allows implementations to set
35limits on the range and precision of numbers accepted", so failing to parse
36values that are too big or too small is techincally fine; however,
37misinterpreting them is not.
38"""
39
40import atheris
41import sys
42
43with atheris.instrument_imports():
44  import json
45  import ujson
46
47
48@atheris.instrument_func
49def ClearAllIntegers(data):
50  """Used to prevent known bug; sets all integers in data recursively to 0."""
51  if type(data) == int:
52    return 0
53  if type(data) == list:
54    for i in range(0, len(data)):
55      data[i] = ClearAllIntegers(data[i])
56  if type(data) == dict:
57    for k, v in data:
58      data[k] = ClearAllIntegers(v)
59  return data
60
61
62@atheris.instrument_func
63def TestOneInput(input_bytes):
64  fdp = atheris.FuzzedDataProvider(input_bytes)
65  original = fdp.ConsumeUnicode(sys.maxsize)
66
67  try:
68    ujson_data = ujson.loads(original)
69    json_data = json.loads(original)
70  except Exception as e:
71    # It would be interesting to enforce that if one of the libraries throws an
72    # exception, the other does too. However, uJSON accepts many invalid inputs
73    # that are uninteresting, such as "00". So, that is not done.
74    return
75
76  # Uncomment these lines to ignore the errors described in the docstring of
77  # this file.
78  # json_data = ClearAllIntegers(json_data)
79  # ujson_data = ClearAllIntegers(ujson_data)
80
81  json_dumped = json.dumps(json_data)
82  ujson_dumped = json.dumps(ujson_data)
83
84  if json_dumped != ujson_dumped:
85    raise RuntimeError(
86        "Decoding/encoding disagreement!\nInput: %s\nJSON data: %s\nuJSON data: %s\nJSON-dumped: %s\nuJSON-dumped: %s\n"
87        % (original, json_data, ujson_data, json_dumped, ujson_dumped))
88
89
90def main():
91  atheris.Setup(sys.argv, TestOneInput)
92  atheris.Fuzz()
93
94
95if __name__ == "__main__":
96  main()
97