1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.compatibility.common.util; 18 19 import java.io.File; 20 import java.util.Arrays; 21 import java.util.Collection; 22 import java.util.Collections; 23 import java.util.List; 24 import java.util.regex.Matcher; 25 import java.util.regex.Pattern; 26 import java.math.BigInteger; 27 import org.json.JSONArray; 28 import org.json.JSONException; 29 import org.json.JSONObject; 30 31 /** Contains helper functions and shared constants for crash parsing. */ 32 public class CrashUtils { 33 // used to only detect actual addresses instead of nullptr and other unlikely values 34 public static final BigInteger MIN_CRASH_ADDR = new BigInteger("8000", 16); 35 // Matches the end of a crash 36 public static final Pattern sEndofCrashPattern = 37 Pattern.compile("DEBUG\\s+?:\\s+?backtrace:"); 38 public static final String DEVICE_PATH = "/data/local/tmp/CrashParserResults/"; 39 public static final String LOCK_FILENAME = "lockFile.loc"; 40 public static final String UPLOAD_REQUEST = "Please upload a result file to stagefright"; 41 public static final Pattern sUploadRequestPattern = 42 Pattern.compile(UPLOAD_REQUEST); 43 public static final String NEW_TEST_ALERT = "New test starting with name: "; 44 public static final Pattern sNewTestPattern = 45 Pattern.compile(NEW_TEST_ALERT + "(\\w+?)\\(.*?\\)"); 46 public static final String SIGNAL = "signal"; 47 public static final String NAME = "name"; 48 public static final String PROCESS = "process"; 49 public static final String PID = "pid"; 50 public static final String TID = "tid"; 51 public static final String FAULT_ADDRESS = "faultaddress"; 52 // Matches the smallest blob that has the appropriate header and footer 53 private static final Pattern sCrashBlobPattern = 54 Pattern.compile("DEBUG\\s+?:( [*]{3})+?.*?DEBUG\\s+?:\\s+?backtrace:", Pattern.DOTALL); 55 // Matches process id and name line and captures them 56 private static final Pattern sPidtidNamePattern = 57 Pattern.compile("pid: (\\d+?), tid: (\\d+?), name: ([^\\s]+?\\s+?)*?>>> (.*?) <<<"); 58 // Matches fault address and signal type line 59 private static final Pattern sFaultLinePattern = 60 Pattern.compile( 61 "\\w+? \\d+? \\((.*?)\\), code -*?\\d+? \\(.*?\\), fault addr " 62 + "(?:0x(\\p{XDigit}+)|-+)"); 63 // Matches the abort message line if it contains CHECK_ 64 private static Pattern sAbortMessageCheckPattern = 65 Pattern.compile("(?i)Abort message.*?CHECK_"); 66 67 public static final String SIGSEGV = "SIGSEGV"; 68 public static final String SIGBUS = "SIGBUS"; 69 public static final String SIGABRT = "SIGABRT"; 70 71 /** 72 * returns the filename of the process. 73 * e.g. "/system/bin/mediaserver" returns "mediaserver" 74 */ getProcessFileName(JSONObject crash)75 public static String getProcessFileName(JSONObject crash) throws JSONException { 76 return new File(crash.getString(PROCESS)).getName(); 77 } 78 79 /** 80 * Determines if the given input has a {@link com.android.compatibility.common.util.Crash} that 81 * should fail an sts test 82 * 83 * @param crashes list of crashes to check 84 * @param config crash detection configuration object 85 * @return if a crash is serious enough to fail an sts test 86 */ securityCrashDetected(JSONArray crashes, Config config)87 public static boolean securityCrashDetected(JSONArray crashes, Config config) { 88 return matchSecurityCrashes(crashes, config).length() > 0; 89 } 90 getBigInteger(JSONObject source, String name)91 public static BigInteger getBigInteger(JSONObject source, String name) throws JSONException { 92 if (source.isNull(name)) { 93 return null; 94 } 95 String intString = source.getString(name); 96 BigInteger value = null; 97 try { 98 value = new BigInteger(intString, 16); 99 } catch (NumberFormatException e) {} 100 return value; 101 } 102 103 /** 104 * Determines which given inputs have a {@link com.android.compatibility.common.util.Crash} that 105 * should fail an sts test 106 * 107 * @param crashes list of crashes to check 108 * @param config crash detection configuration object 109 * @return the list of crashes serious enough to fail an sts test 110 */ matchSecurityCrashes(JSONArray crashes, Config config)111 public static JSONArray matchSecurityCrashes(JSONArray crashes, Config config) { 112 JSONArray securityCrashes = new JSONArray(); 113 for (int i = 0; i < crashes.length(); i++) { 114 try { 115 JSONObject crash = crashes.getJSONObject(i); 116 117 // match process patterns 118 if (!matchesAny(getProcessFileName(crash), config.processPatterns)) { 119 continue; 120 } 121 122 // match signal 123 String crashSignal = crash.getString(SIGNAL); 124 if (!config.signals.contains(crashSignal)) { 125 continue; 126 } 127 128 // if check specified, reject crash if address is unlikely to be security-related 129 if (config.checkMinAddress) { 130 BigInteger faultAddress = getBigInteger(crash, FAULT_ADDRESS); 131 if (faultAddress != null 132 && faultAddress.compareTo(config.minCrashAddress) < 0) { 133 continue; 134 } 135 } 136 securityCrashes.put(crash); 137 } catch (JSONException e) {} 138 } 139 return securityCrashes; 140 } 141 142 /** 143 * returns true if the input matches any of the patterns. 144 */ matchesAny(String input, Collection<Pattern> patterns)145 private static boolean matchesAny(String input, Collection<Pattern> patterns) { 146 for (Pattern p : patterns) { 147 if (p.matcher(input).matches()) { 148 return true; 149 } 150 } 151 return false; 152 } 153 154 /** Adds all crashes found in the input as JSONObjects to the given JSONArray */ addAllCrashes(String input, JSONArray crashes)155 public static JSONArray addAllCrashes(String input, JSONArray crashes) { 156 Matcher crashBlobFinder = sCrashBlobPattern.matcher(input); 157 while (crashBlobFinder.find()) { 158 String crashStr = crashBlobFinder.group(0); 159 int tid = 0; 160 int pid = 0; 161 BigInteger faultAddress = null; 162 String name = null; 163 String process = null; 164 String signal = null; 165 166 Matcher pidtidNameMatcher = sPidtidNamePattern.matcher(crashStr); 167 if (pidtidNameMatcher.find()) { 168 try { 169 pid = Integer.parseInt(pidtidNameMatcher.group(1)); 170 } catch (NumberFormatException e) {} 171 try { 172 tid = Integer.parseInt(pidtidNameMatcher.group(2)); 173 } catch (NumberFormatException e) {} 174 name = pidtidNameMatcher.group(3).trim(); 175 process = pidtidNameMatcher.group(4).trim(); 176 } 177 178 Matcher faultLineMatcher = sFaultLinePattern.matcher(crashStr); 179 if (faultLineMatcher.find()) { 180 signal = faultLineMatcher.group(1); 181 String faultAddrMatch = faultLineMatcher.group(2); 182 if (faultAddrMatch != null) { 183 try { 184 faultAddress = new BigInteger(faultAddrMatch, 16); 185 } catch (NumberFormatException e) {} 186 } 187 } 188 if (!sAbortMessageCheckPattern.matcher(crashStr).find()) { 189 try { 190 JSONObject crash = new JSONObject(); 191 crash.put(PID, pid); 192 crash.put(TID, tid); 193 crash.put(NAME, name); 194 crash.put(PROCESS, process); 195 crash.put(FAULT_ADDRESS, 196 faultAddress == null ? null : faultAddress.toString(16)); 197 crash.put(SIGNAL, signal); 198 crashes.put(crash); 199 } catch (JSONException e) {} 200 } 201 } 202 return crashes; 203 } 204 205 public static class Config { 206 private boolean checkMinAddress = true; 207 private BigInteger minCrashAddress = MIN_CRASH_ADDR; 208 private List<String> signals = Arrays.asList(SIGSEGV, SIGBUS); 209 private List<Pattern> processPatterns = Collections.emptyList(); 210 setMinAddress(BigInteger minCrashAddress)211 public Config setMinAddress(BigInteger minCrashAddress) { 212 this.minCrashAddress = minCrashAddress; 213 return this; 214 } 215 checkMinAddress(boolean checkMinAddress)216 public Config checkMinAddress(boolean checkMinAddress) { 217 this.checkMinAddress = checkMinAddress; 218 return this; 219 } 220 setSignals(String... signals)221 public Config setSignals(String... signals) { 222 this.signals = Arrays.asList(signals); 223 return this; 224 } 225 appendSignals(String... signals)226 public Config appendSignals(String... signals) { 227 Collections.addAll(this.signals, signals); 228 return this; 229 } 230 setProcessPatterns(String... processPatternStrings)231 public Config setProcessPatterns(String... processPatternStrings) { 232 Pattern[] processPatterns = new Pattern[processPatternStrings.length]; 233 for (int i = 0; i < processPatternStrings.length; i++) { 234 processPatterns[i] = Pattern.compile(processPatternStrings[i]); 235 } 236 return setProcessPatterns(processPatterns); 237 } 238 setProcessPatterns(Pattern... processPatterns)239 public Config setProcessPatterns(Pattern... processPatterns) { 240 this.processPatterns = Arrays.asList(processPatterns); 241 return this; 242 } 243 getProcessPatterns()244 public List<Pattern> getProcessPatterns() { 245 return Collections.unmodifiableList(processPatterns); 246 } 247 appendProcessPatterns(Pattern... processPatterns)248 public Config appendProcessPatterns(Pattern... processPatterns) { 249 Collections.addAll(this.processPatterns, processPatterns); 250 return this; 251 } 252 } 253 } 254