| #!/usr/bin/env python2 |
| # -*- coding: utf-8 -*- |
| # Copyright 2018 The ChromiumOS Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Autotriager.""" |
| |
| from __future__ import print_function |
| |
| import argparse |
| import base64 |
| import pickle |
| import sys |
| |
| import simpledb |
| |
| import config |
| import utils |
| |
| |
| REPORT_TEMPLATE = """ |
| Upstream commit is %s ("%s"). |
| |
| %s |
| %s |
| |
| Syzkaller URL with a similar report is at %s |
| """ |
| |
| |
| class AutoTriager(object): |
| """Autotriager triages syzkaller bugs on issuetracker. |
| |
| AutoTriager correlates information from issuetracker and |
| syzweb to determine fixes for issuetracker bugs. |
| """ |
| |
| def __init__(self): |
| self._use_mst = False |
| self.it_db = simpledb.SimpleDB(config.ISSUETRACKER_DB) |
| self.syz_db = simpledb.SimpleDB(config.SYZWEB_DB) |
| self.linux_db = simpledb.SimpleDB(config.SRC_LINUX_DB) |
| self.cros_dbs = ( |
| simpledb.SimpleDB(config.SRC_V414_DB), |
| simpledb.SimpleDB(config.SRC_V44_DB), |
| simpledb.SimpleDB(config.SRC_V318_DB), |
| simpledb.SimpleDB(config.SRC_V314_DB), |
| simpledb.SimpleDB(config.SRC_V310_DB), |
| simpledb.SimpleDB(config.SRC_V38_DB), |
| ) |
| |
| try: |
| self.triaged_bugs = open("triaged_bugs").readlines() |
| self.triaged_bugs = [i.strip() for i in self.triaged_bugs] |
| except IOError as _: |
| self.triaged_bugs = [] |
| |
| try: |
| contents = open("blacklistfns").readlines() |
| contents = [i for i in contents if not i.startswith("#")] |
| self.blacklistfns = set([i.strip() for i in contents]) |
| except IOError as _: |
| self.blacklistfns = [] |
| |
| try: |
| contents = open("known_mismatch", "r").readlines() |
| contents = [i.strip().split() for i in contents] |
| self.known_mismatch = {i[0]: i[1] for i in contents} |
| except IOError as _: |
| self.known_mismatch = {} |
| |
| print("[+] Autotriager initialized.") |
| |
| def use_mst(self): |
| """Set Autotriager to use stacktrace matching.""" |
| self._use_mst = True |
| |
| def is_triaged(self, bugid): |
| """Returns true if bugid if listed as already triaged.""" |
| return bugid in self.triaged_bugs |
| |
| def is_mismatch(self, bugid, url): |
| """Returns true if |url| is known to not be the fix for |bugid|.""" |
| return self.known_mismatch.get(bugid, "") == url |
| |
| def generate_report(self, cid, cmsg, url): |
| """Generate a report with commit information. |
| |
| Generate a report indicating which kernels the commit is |
| present in, and which syzweb report was used to find the commit. |
| """ |
| patch_status = [0] * len(self.cros_dbs) |
| for i, crosdb in enumerate(self.cros_dbs): |
| if crosdb.find_one(title=cmsg): |
| patch_status[i] = 1 |
| continue |
| |
| VERSIONS = ["v4.14", "v4.4", "v3.18", "v3.14", "v3.10", "v3.8"] |
| present = [VERSIONS[i] for i, j in enumerate(patch_status) if j] |
| not_present = [VERSIONS[i] for i, j in enumerate(patch_status) if not j] |
| |
| present = ",".join(present) |
| not_present = ",".join(not_present) |
| |
| if present: |
| present = "This patch is present in " + present |
| if not_present: |
| not_present = "This patch is not present in " + not_present |
| |
| url = "https://syzkaller.appspot.com" + url |
| report = REPORT_TEMPLATE % (cid[:12], cmsg, present, not_present, url) |
| |
| utils.print_report(report) |
| |
| def clear_blacklistedfns(self, st): |
| """Filter functions that are blacklisted.""" |
| return set(i for i in st if i not in self.blacklistfns) |
| |
| def matchstacktrace(self, itbug_sts, syz_st): |
| """Match stacktraces obtained from syzweb and issuetracker.""" |
| if not self._use_mst: |
| return True |
| |
| count = 0 |
| itbug_sts = pickle.loads(base64.b64decode(itbug_sts)) |
| for itbug_st in itbug_sts: |
| count = 0 |
| itbug_st = self.clear_blacklistedfns(itbug_st) |
| if len(itbug_st) < 3: |
| continue |
| for fn in itbug_st: |
| if fn in syz_st: |
| count += 1 |
| if count == len(itbug_st): |
| print("Match found using :-") |
| print(itbug_st) |
| return True |
| return False |
| |
| def add_to_triaged(self, bugid): |
| """Add |bugid| to the list of triaged bugs.""" |
| self.triaged_bugs.append(bugid) |
| open("triaged_bugs", "a").write(bugid + "\n") |
| |
| def add_mismatch(self, bugid, syzurl): |
| """Avoid false positives in the future with |bugid| and |syzurl|.""" |
| self.known_mismatch[bugid] = syzurl |
| open("known_mismatch", "a").write("%s %s\n" % (bugid, syzurl)) |
| |
| def _triage(self, title): |
| """Correlate issuetracker against syzweb for a bug |title|.""" |
| it_bugs = self.it_db.find(title=title) |
| syz_bugs = self.syz_db.find(title=title) |
| |
| for itbug in it_bugs: |
| for syzbug in syz_bugs: |
| if not self.matchstacktrace( |
| itbug["stacktrace"], syzbug["stacktrace"] |
| ): |
| continue |
| |
| if self.is_triaged(itbug["bugid"]): |
| continue |
| |
| if self.is_mismatch(itbug["bugid"], syzbug["url"]): |
| continue |
| |
| utils.hit_summary( |
| itbug["bugid"], syzbug["url"], syzbug["commitmsg"] |
| ) |
| |
| if utils.interact("Generate report? [y/N] - "): |
| cid = self.linux_db.find_one(title=syzbug["commitmsg"]) |
| cid = cid["commitid"] if cid else "" |
| self.generate_report( |
| cid, syzbug["commitmsg"], syzbug["url"] |
| ) |
| self.add_to_triaged(itbug["bugid"]) |
| |
| else: |
| self.add_mismatch(itbug["bugid"], syzbug["url"]) |
| |
| utils.endbanner() |
| |
| def triage(self): |
| """Start autotriage using local caches.""" |
| common_titles = set() |
| for it_bug in self.it_db.distinct("title"): |
| it_title = it_bug["title"] |
| for match_syzbug in self.syz_db.find(title=it_title): |
| common_titles.add(match_syzbug["title"]) |
| print("[+] %d common titles found" % (len(common_titles))) |
| |
| for title in common_titles: |
| self._triage(title) |
| print("[+] Autotriager done.") |
| |
| |
| def get_parser(): |
| """Create and return an ArgumentParser instance.""" |
| parser = argparse.ArgumentParser(description=__doc__) |
| parser.add_argument( |
| "--mst", |
| action="store_true", |
| help="match stacktraces to determine if " |
| "the issuetracker bug and syzweb fix " |
| "match", |
| ) |
| return parser |
| |
| |
| def main(argv): |
| """main.""" |
| a = AutoTriager() |
| parser = get_parser() |
| opts = parser.parse_args(argv) |
| |
| if opts.mst: |
| a.use_mst() |
| |
| a.triage() |
| return 0 |
| |
| |
| if __name__ == "__main__": |
| sys.exit(main(sys.argv[1:])) |