style-checker: Add UAF to the list of security words to warn about.
[WebKit-https.git] / Tools / Scripts / webkitpy / style / checkers / changelog.py
index be279a7..399dacf 100644 (file)
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-#
 # Copyright (C) 2011 Patrick Gansterer <paroga@paroga.com>
 #
 # Redistribution and use in source and binary forms, with or without
 
 """Checks WebKit style for ChangeLog files."""
 
-import re
-from common import TabChecker
-from webkitpy.common.net.bugzilla import parse_bug_id_from_changelog
+from sys import maxsize
+from webkitpy.common.checkout.changelog import parse_bug_id_from_changelog
+from webkitpy.style.checkers.common import TabChecker, match, search, searchIgnorecase
 
 
 class ChangeLogChecker(object):
-
     """Processes text lines for checking style."""
 
-    def __init__(self, file_path, handle_style_error):
+    categories = set(['changelog/bugnumber', 'changelog/filechangedescriptionwhitespace'])
+
+    def __init__(self, file_path, handle_style_error, should_line_be_checked):
         self.file_path = file_path
         self.handle_style_error = handle_style_error
+        self.should_line_be_checked = should_line_be_checked
         self._tab_checker = TabChecker(file_path, handle_style_error)
 
-    def check_entry(self, entry_line_number, entry_lines):
+    def check_entry(self, first_line_checked, entry_lines):
+        if not entry_lines:
+            return
         for line in entry_lines:
             if parse_bug_id_from_changelog(line):
                 break
-            if re.search("Unreviewed", line, re.IGNORECASE):
+            if searchIgnorecase("Unreviewed", line):
                 break
-            if re.search("build", line, re.IGNORECASE) and re.search("fix", line, re.IGNORECASE):
+            if searchIgnorecase("build", line) and searchIgnorecase("fix", line):
                 break
         else:
-            self.handle_style_error(entry_line_number + 1,
+            self.handle_style_error(first_line_checked,
                                     "changelog/bugnumber", 5,
                                     "ChangeLog entry has no bug number")
+        # check file change descriptions for style violations
+        line_no = first_line_checked - 1
+        for line in entry_lines:
+            line_no = line_no + 1
+            # filter file change descriptions
+            if not match('\s*\*\s', line):
+                continue
+            if search(':\s*$', line) or search(':\s', line):
+                continue
+            self.handle_style_error(line_no,
+                                    "changelog/filechangedescriptionwhitespace", 5,
+                                    "Need whitespace between colon and description")
+
+        # check for a lingering "No new tests (OOPS!)." left over from prepare-changeLog.
+        line_no = first_line_checked - 1
+        for line in entry_lines:
+            line_no = line_no + 1
+            if match('\s*No new tests \(OOPS!\)\.$', line):
+                self.handle_style_error(line_no,
+                                        "changelog/nonewtests", 5,
+                                        "You should remove the 'No new tests' and either add and list tests, or explain why no new tests were possible.")
+
+        self.check_for_unwanted_security_phrases(first_line_checked, entry_lines)
 
     def check(self, lines):
         self._tab_checker.check(lines)
-        entry_line_number = 0
+        first_line_checked = 0
         entry_lines = []
-        started_at_first_line = False
-
-        for line_number, line in enumerate(lines):
-            if re.match("^\d{4}-\d{2}-\d{2}", line):
-                if line_number:
-                    self.check_entry(entry_line_number, entry_lines)
-                else:
-                    started_at_first_line = True
-                entry_line_number = line_number
-                entry_lines = []
 
+        for line_index, line in enumerate(lines):
+            if not self.should_line_be_checked(line_index + 1):
+                # If we transitioned from finding changed lines to
+                # unchanged lines, then we are done.
+                if first_line_checked:
+                    break
+                continue
+            if not first_line_checked:
+                first_line_checked = line_index + 1
             entry_lines.append(line)
 
-        if started_at_first_line:
-            self.check_entry(entry_line_number, entry_lines)
+        self.check_entry(first_line_checked, entry_lines)
+
+    def contains_phrase_in_first_line_or_across_two_lines(self, phrase, line1, line2):
+        return searchIgnorecase(phrase, line1) or ((not searchIgnorecase(phrase, line2)) and searchIgnorecase(phrase, line1 + " " + line2))
+
+    def check_for_unwanted_security_phrases(self, first_line_checked, lines):
+        unwanted_security_phrases = [
+            "arbitrary code execution", "buffer overflow", "buffer overrun",
+            "buffer underrun", "dangling pointer", "double free", "fuzzer", "fuzzing", "fuzz test",
+            "invalid cast", "jsfunfuzz", "malicious", "memory corruption", "security bug",
+            "security flaw", "use after free", "use-after-free", "UAF", "UXSS",
+            "WTFCrashWithSecurityImplication",
+            "spoof",  # Captures spoof, spoofed, spoofing
+            "vulnerab",  # Captures vulnerable, vulnerability, vulnerabilities
+        ]
+
+        lines_with_single_spaces = []
+        for line in lines:
+            lines_with_single_spaces.append(" ".join(line.split()))
+
+        found_unwanted_security_phrases = []
+        last_index = len(lines_with_single_spaces) - 1
+        first_line_number_with_unwanted_phrase = maxsize
+        for unwanted_phrase in unwanted_security_phrases:
+            for line_index, line in enumerate(lines_with_single_spaces):
+                next_line = "" if line_index >= last_index else lines_with_single_spaces[line_index + 1]
+                if self.contains_phrase_in_first_line_or_across_two_lines(unwanted_phrase, line, next_line):
+                    found_unwanted_security_phrases.append(unwanted_phrase)
+                    first_line_number_with_unwanted_phrase = min(first_line_number_with_unwanted_phrase, first_line_checked + line_index)
+
+        if len(found_unwanted_security_phrases) > 0:
+            self.handle_style_error(first_line_number_with_unwanted_phrase,
+                                    "changelog/unwantedsecurityterms", 3,
+                                    "Please consider whether the use of security-sensitive phrasing could help someone exploit WebKit: {}".format(", ".join(found_unwanted_security_phrases)))