Coverage for /pythoncovmergedfiles/medio/medio/src/json_differential_fuzzer.py: 30%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

44 statements  

1###### Coverage stub 

2import atexit 

3import coverage 

4cov = coverage.coverage(data_file='.coverage', cover_pylib=True) 

5cov.start() 

6# Register an exist handler that will print coverage 

7def exit_handler(): 

8 cov.stop() 

9 cov.save() 

10atexit.register(exit_handler) 

11####### End of coverage stub 

12#!/usr/bin/python3 

13 

14# Copyright 2020 Google LLC 

15# 

16# Licensed under the Apache License, Version 2.0 (the "License"); 

17# you may not use this file except in compliance with the License. 

18# You may obtain a copy of the License at 

19# 

20# http://www.apache.org/licenses/LICENSE-2.0 

21# 

22# Unless required by applicable law or agreed to in writing, software 

23# distributed under the License is distributed on an "AS IS" BASIS, 

24# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

25# See the License for the specific language governing permissions and 

26# limitations under the License. 

27""" An example native JSON vs uJSON differential fuzzer. 

28 

29This fuzzer looks for differences between the built-in json library and the 

30native ujson library. The ujson library should be built for coverage (see 

31build_install_ujson.sh), and the Python fuzzer should be executed under ASAN. 

32As an example: 

33 LD_PRELOAD="/usr/lib/llvm-9/lib/clang/9.0.1/lib/linux/libclang_rt.asan-x86_64.so 

34 $(python3 -c "import atheris; print(atheris.path())")" python3 

35 ./json_differential_fuzzer.py -detect_leaks=0 

36 

37This fuzzer has found a bug with inconsistent handling of integers with 

38too-high magnitude. uJSON sometimes refuses to process numbers that are too far 

39from 0 with "Value is too big!" or the equivalent for values that are too 

40negative. However, other times it happily processes them with two's compliment 

41mod. As an example, it refuses to parse "-9223372036854775809" (the first 

42integer not representable in a 64-bit signed number) with "Value is too small"; 

43but it will happily parse "-80888888888888888888", a significantly more negative 

44number. However, it parses it as -9223372036854775808. The JSON spec 

45(https://tools.ietf.org/html/rfc7159#section-6) "allows implementations to set 

46limits on the range and precision of numbers accepted", so failing to parse 

47values that are too big or too small is techincally fine; however, 

48misinterpreting them is not. 

49""" 

50 

51import atheris 

52import sys 

53 

54with atheris.instrument_imports(): 

55 import json 

56 import ujson 

57 

58 

59@atheris.instrument_func 

60def ClearAllIntegers(data): 

61 """Used to prevent known bug; sets all integers in data recursively to 0.""" 

62 if type(data) == int: 

63 return 0 

64 if type(data) == list: 

65 for i in range(0, len(data)): 

66 data[i] = ClearAllIntegers(data[i]) 

67 if type(data) == dict: 

68 for k, v in data: 

69 data[k] = ClearAllIntegers(v) 

70 return data 

71 

72 

73@atheris.instrument_func 

74def TestOneInput(input_bytes): 

75 fdp = atheris.FuzzedDataProvider(input_bytes) 

76 original = fdp.ConsumeUnicode(sys.maxsize) 

77 

78 try: 

79 ujson_data = ujson.loads(original) 

80 json_data = json.loads(original) 

81 except Exception as e: 

82 # It would be interesting to enforce that if one of the libraries throws an 

83 # exception, the other does too. However, uJSON accepts many invalid inputs 

84 # that are uninteresting, such as "00". So, that is not done. 

85 return 

86 

87 # Uncomment these lines to ignore the errors described in the docstring of 

88 # this file. 

89 # json_data = ClearAllIntegers(json_data) 

90 # ujson_data = ClearAllIntegers(ujson_data) 

91 

92 json_dumped = json.dumps(json_data) 

93 ujson_dumped = json.dumps(ujson_data) 

94 

95 if json_dumped != ujson_dumped: 

96 raise RuntimeError( 

97 "Decoding/encoding disagreement!\nInput: %s\nJSON data: %s\nuJSON data: %s\nJSON-dumped: %s\nuJSON-dumped: %s\n" 

98 % (original, json_data, ujson_data, json_dumped, ujson_dumped)) 

99 

100 

101def main(): 

102 atheris.Setup(sys.argv, TestOneInput) 

103 atheris.Fuzz() 

104 

105 

106if __name__ == "__main__": 

107 main()