SConscript 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. # Run a fuzz test to verify robustness against corrupted/malicious data.
  2. import sys
  3. import time
  4. import zipfile
  5. import random
  6. import subprocess
  7. Import("env", "malloc_env")
  8. def set_pkgname(src, dst, pkgname):
  9. data = open(str(src)).read()
  10. placeholder = '// package name placeholder'
  11. assert placeholder in data
  12. data = data.replace(placeholder, 'package %s;' % pkgname)
  13. open(str(dst), 'w').write(data)
  14. # We want both pointer and static versions of the AllTypes message
  15. # Prefix them with package name.
  16. env.Command("alltypes_static.proto", "#alltypes/alltypes.proto",
  17. lambda target, source, env: set_pkgname(source[0], target[0], 'alltypes_static'))
  18. env.Command("alltypes_pointer.proto", "#alltypes/alltypes.proto",
  19. lambda target, source, env: set_pkgname(source[0], target[0], 'alltypes_pointer'))
  20. env.NanopbProto(["alltypes_pointer", "alltypes_pointer.options"])
  21. env.NanopbProto(["alltypes_static", "alltypes_static.options"])
  22. # Do the same for proto3 versions
  23. env.Command("alltypes_proto3_static.proto", "#alltypes_proto3/alltypes.proto",
  24. lambda target, source, env: set_pkgname(source[0], target[0], 'alltypes_proto3_static'))
  25. env.Command("alltypes_proto3_pointer.proto", "#alltypes_proto3/alltypes.proto",
  26. lambda target, source, env: set_pkgname(source[0], target[0], 'alltypes_proto3_pointer'))
  27. env.NanopbProto(["alltypes_proto3_pointer", "alltypes_proto3_pointer.options"])
  28. env.NanopbProto(["alltypes_proto3_static", "alltypes_proto3_static.options"])
  29. # And also a callback version
  30. env.Command("alltypes_callback.proto", "#alltypes/alltypes.proto",
  31. lambda target, source, env: set_pkgname(source[0], target[0], 'alltypes_callback'))
  32. env.NanopbProto(["alltypes_callback", "alltypes_callback.options"])
  33. common_objs = [env.Object("random_data.c"),
  34. env.Object("validation.c"),
  35. env.Object("flakystream.c"),
  36. env.Object("alltypes_pointer.pb.c"),
  37. env.Object("alltypes_static.pb.c"),
  38. env.Object("alltypes_callback.pb.c"),
  39. env.Object("alltypes_proto3_pointer.pb.c"),
  40. env.Object("alltypes_proto3_static.pb.c"),
  41. "$COMMON/malloc_wrappers.o"]
  42. objs_malloc = ["$COMMON/pb_encode_with_malloc.o",
  43. "$COMMON/pb_decode_with_malloc.o",
  44. "$COMMON/pb_common_with_malloc.o"] + common_objs
  45. objs_static = ["$COMMON/pb_encode.o",
  46. "$COMMON/pb_decode.o",
  47. "$COMMON/pb_common.o"] + common_objs
  48. fuzz = malloc_env.Program(["fuzztest.c"] + objs_malloc)
  49. # Run the stand-alone fuzz tester
  50. seed = int(time.time())
  51. if env.get('EMBEDDED'):
  52. iterations = 100
  53. else:
  54. iterations = 1000
  55. env.RunTest(fuzz, ARGS = [str(seed), str(iterations)])
  56. generate_message = malloc_env.Program(["generate_message.c"] + objs_static)
  57. # Test the message generator
  58. env.RunTest(generate_message, ARGS = [str(seed)])
  59. env.RunTest("generate_message.output.fuzzed", [fuzz, "generate_message.output"])
  60. # Run against the latest corpus from ossfuzz
  61. # This allows quick testing against regressions and also lets us more
  62. # completely test slow embedded targets. To reduce runtime, only a subset
  63. # of the corpus is fuzzed each time.
  64. def run_against_corpus(target, source, env):
  65. corpus = zipfile.ZipFile(str(source[1]), 'r')
  66. count = 0
  67. args = [str(source[0])]
  68. if "TEST_RUNNER" in env:
  69. args = [env["TEST_RUNNER"]] + args
  70. if "FUZZTEST_CORPUS_SAMPLESIZE" in env:
  71. samplesize = int(env["FUZZTEST_CORPUS_SAMPLESIZE"])
  72. elif env.get('EMBEDDED'):
  73. samplesize = 100
  74. else:
  75. samplesize = 4096
  76. files = [n for n in corpus.namelist() if not n.endswith('/')]
  77. files = random.sample(files, min(samplesize, len(files)))
  78. for filename in files:
  79. sys.stdout.write("Fuzzing: %5d/%5d: %-40.40s\r" % (count, len(files), filename))
  80. sys.stdout.flush()
  81. count += 1
  82. maxsize = env.get('CPPDEFINES', {}).get('FUZZTEST_BUFSIZE', 256*1024)
  83. data_in = corpus.read(filename)[:maxsize]
  84. try:
  85. process = subprocess.Popen(args, stdin=subprocess.PIPE,
  86. stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  87. stdout, stderr = process.communicate(input = data_in)
  88. result = process.wait()
  89. except OSError as e:
  90. if e.errno == 22:
  91. print("Warning: OSError 22 when running with input " + filename)
  92. result = process.wait()
  93. else:
  94. raise
  95. if result != 0:
  96. stdout += stderr
  97. print(stdout)
  98. print('\033[31m[FAIL]\033[0m Program ' + str(args) + ' returned ' + str(result) + ' with input ' + filename + ' from ' + str(source[1]))
  99. return result
  100. open(str(target[0]), 'w').write(str(count))
  101. print('\033[32m[ OK ]\033[0m Ran ' + str(args) + " against " + str(source[1]) + " (" + str(count) + " entries)")
  102. env.Command("corpus.zip.fuzzed", [fuzz, "corpus.zip"], run_against_corpus)
  103. env.Command("regressions.zip.fuzzed", [fuzz, "regressions.zip"], run_against_corpus)
  104. # Build separate fuzzers for each test case.
  105. # Having them separate speeds up control flow based fuzzer engines.
  106. # These are mainly used by oss-fuzz project.
  107. env_proto2_static = env.Clone()
  108. env_proto2_static.Append(CPPDEFINES = {'FUZZTEST_PROTO2_STATIC': '1'})
  109. env_proto2_static.Program("fuzztest_proto2_static",
  110. [env_proto2_static.Object("fuzztest_proto2_static.o", "fuzztest.c")] + objs_static)
  111. env_proto2_pointer = malloc_env.Clone()
  112. env_proto2_pointer.Append(CPPDEFINES = {'FUZZTEST_PROTO2_POINTER': '1'})
  113. env_proto2_pointer.Program("fuzztest_proto2_pointer",
  114. [env_proto2_pointer.Object("fuzztest_proto2_pointer.o", "fuzztest.c")] + objs_malloc)
  115. env_proto3_static = env.Clone()
  116. env_proto3_static.Append(CPPDEFINES = {'FUZZTEST_PROTO3_STATIC': '1'})
  117. env_proto3_static.Program("fuzztest_proto3_static",
  118. [env_proto3_static.Object("fuzztest_proto3_static.o", "fuzztest.c")] + objs_static)
  119. env_proto3_pointer = malloc_env.Clone()
  120. env_proto3_pointer.Append(CPPDEFINES = {'FUZZTEST_PROTO3_POINTER': '1'})
  121. env_proto3_pointer.Program("fuzztest_proto3_pointer",
  122. [env_proto3_pointer.Object("fuzztest_proto3_pointer.o", "fuzztest.c")] + objs_malloc)
  123. env_io_errors = malloc_env.Clone()
  124. env_io_errors.Append(CPPDEFINES = {'FUZZTEST_IO_ERRORS': '1'})
  125. env_io_errors.Program("fuzztest_io_errors",
  126. [env_io_errors.Object("fuzztest_io_errors.o", "fuzztest.c")] + objs_malloc)