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