update.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. #! /usr/bin/env python
  2. # Copyright (c) 2016 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. """Script for updating AFL. Also updates AFL version in README.chromium.
  6. """
  7. import argparse
  8. import cStringIO
  9. import datetime
  10. import os
  11. import re
  12. import subprocess
  13. import sys
  14. import tarfile
  15. import urllib2
  16. VERSION_REGEX = r'(?P<version>([0-9]*[.])?[0-9]+b)'
  17. PATH_REGEX = r'(afl-)' + VERSION_REGEX
  18. class ChromiumReadme(object):
  19. """Class that handles reading from and updating the README.chromium"""
  20. README_FILE_PATH = 'third_party/afl/README.chromium'
  21. README_VERSION_REGEX = r'Version: ' + VERSION_REGEX
  22. def __init__(self):
  23. """
  24. Inits the ChromiumReadme.
  25. """
  26. with open(self.README_FILE_PATH) as readme_file_handle:
  27. self.readme_contents = readme_file_handle.read()
  28. def get_current_version(self):
  29. """
  30. Get the current version of AFL according to the README.chromium
  31. """
  32. match = re.search(self.README_VERSION_REGEX, self.readme_contents)
  33. if not match:
  34. raise Exception('Could not determine current AFL version')
  35. return match.groupdict()['version']
  36. def update(self, new_version):
  37. """
  38. Update the readme to reflect the new version that has been downloaded.
  39. """
  40. new_readme = self.readme_contents
  41. subsitutions = [(VERSION_REGEX, new_version), # Update the version.
  42. (r'Date: .*',
  43. 'Date: ' + datetime.date.today().strftime("%B %d, %Y")),
  44. # Update the Local Modifications.
  45. (PATH_REGEX + r'/', 'afl-' + new_version + '/')]
  46. for regex, replacement in subsitutions:
  47. new_readme = re.subn(regex, replacement, new_readme, 1)[0]
  48. self.readme_contents = new_readme
  49. with open(self.README_FILE_PATH, 'w+') as readme_file_handle:
  50. readme_file_handle.write(self.readme_contents)
  51. class AflTarball(object):
  52. """
  53. Class that handles the afl-latest.tgz tarball.
  54. """
  55. # Regexes that match files that we don't want to extract.
  56. # Note that you should add these removals to "Local Modifications" in
  57. # the README.chromium.
  58. UNWANTED_FILE_REGEX = '|'.join([
  59. r'(.*\.elf)', # presubmit complains these aren't marked executable.
  60. r'(.*others/elf)', # We don't need this if we have no elfs.
  61. # checkdeps complains about #includes.
  62. r'(.*afl-llvm-pass\.so\.cc)',
  63. r'(.*argv.*)', # Delete the demo's directory as well.
  64. r'(.*dictionaries.*)', # Including these make builds fail.
  65. ])
  66. AFL_SRC_DIR = 'third_party/afl/src'
  67. def __init__(self, version):
  68. """
  69. Init this AFL tarball.
  70. """
  71. release_name = 'afl-{0}'.format(version)
  72. filename = '{0}.tgz'.format(release_name)
  73. # Note: lcamtuf.coredump.cx does not support TLS connections. The "http://"
  74. # protocol is intentional.
  75. self.url = "http://lcamtuf.coredump.cx/afl/releases/{0}".format(filename)
  76. self.tarball = None
  77. self.real_version = version if version != 'latest' else None
  78. def download(self):
  79. """Download the tarball version from
  80. http://lcamtuf.coredump.cx/afl/releases/
  81. """
  82. tarball_contents = urllib2.urlopen(self.url).read()
  83. tarball_file = cStringIO.StringIO(tarball_contents)
  84. self.tarball = tarfile.open(fileobj=tarball_file, mode="r:gz")
  85. if self.real_version is None:
  86. regex_match = re.search(VERSION_REGEX, self.tarball.members[0].path)
  87. self.real_version = regex_match.groupdict()['version']
  88. def extract(self):
  89. """
  90. Extract the files and folders from the tarball we have downloaded while
  91. skipping unwanted ones.
  92. """
  93. for member in self.tarball.getmembers():
  94. member.path = re.sub(PATH_REGEX, self.AFL_SRC_DIR, member.path)
  95. if re.match(self.UNWANTED_FILE_REGEX, member.path):
  96. print 'skipping unwanted file: {0}'.format(member.path)
  97. continue
  98. self.tarball.extract(member)
  99. def version_to_float(version):
  100. """
  101. Convert version string to float.
  102. """
  103. if version.endswith('b'):
  104. return float(version[:-1])
  105. return float(version)
  106. def apply_patches():
  107. afl_dir = os.path.join('third_party', 'afl')
  108. patch_dir = os.path.join(afl_dir, 'patches')
  109. src_dir = os.path.join(afl_dir, 'src')
  110. for patch_file in os.listdir(patch_dir):
  111. subprocess.check_output(
  112. ['patch', '-i',
  113. os.path.join('..', 'patches', patch_file)], cwd=src_dir)
  114. def update_afl(new_version):
  115. """
  116. Update this version of AFL to newer version, new_version.
  117. """
  118. readme = ChromiumReadme()
  119. old_version = readme.get_current_version()
  120. if new_version != 'latest':
  121. new_float = version_to_float(new_version)
  122. assert version_to_float(old_version) < new_float, (
  123. 'Trying to update from version {0} to {1}'.format(old_version,
  124. new_version))
  125. # Extract the tarball.
  126. tarball = AflTarball(new_version)
  127. tarball.download()
  128. tarball.extract()
  129. apply_patches()
  130. readme.update(tarball.real_version)
  131. def main():
  132. """
  133. Update AFL if possible.
  134. """
  135. parser = argparse.ArgumentParser('Update AFL.')
  136. parser.add_argument('version', metavar='version', default='latest', nargs='?',
  137. help='(optional) Version to update AFL to.')
  138. args = parser.parse_args()
  139. version = args.version
  140. if version != 'latest' and not version.endswith('b'):
  141. version += 'b'
  142. in_correct_directory = (os.path.basename(os.getcwd()) == 'src' and
  143. os.path.exists('third_party'))
  144. assert in_correct_directory, (
  145. '{0} must be run from the repo\'s root'.format(sys.argv[0]))
  146. update_afl(version)
  147. print ("Run git diff third_party/afl/src/docs/ChangeLog to see changes to AFL"
  148. " since the last roll")
  149. if __name__ == '__main__':
  150. main()