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.ArrayList; 21 import java.util.Arrays; 22 import java.util.Collection; 23 import java.util.Collections; 24 import java.util.List; 25 import java.util.regex.Matcher; 26 import java.util.regex.Pattern; 27 import java.util.stream.Collectors; 28 import java.util.stream.Stream; 29 import java.math.BigInteger; 30 import org.json.JSONArray; 31 import org.json.JSONException; 32 import org.json.JSONObject; 33 34 /** Contains helper functions and shared constants for crash parsing. */ 35 public class CrashUtils { 36 // used to only detect actual addresses instead of nullptr and other unlikely values 37 public static final BigInteger MIN_CRASH_ADDR = new BigInteger("8000", 16); 38 // Matches the end of a crash 39 public static final Pattern sEndofCrashPattern = 40 Pattern.compile("DEBUG\\s+?:\\s+?backtrace:"); 41 public static final String DEVICE_PATH = "/data/local/tmp/CrashParserResults/"; 42 public static final String LOCK_FILENAME = "lockFile.loc"; 43 public static final String UPLOAD_REQUEST = "Please upload a result file to stagefright"; 44 public static final Pattern sUploadRequestPattern = 45 Pattern.compile(UPLOAD_REQUEST); 46 public static final String NEW_TEST_ALERT = "New test starting with name: "; 47 public static final Pattern sNewTestPattern = 48 Pattern.compile(NEW_TEST_ALERT + "(\\w+?)\\(.*?\\)"); 49 public static final String SIGNAL = "signal"; 50 public static final String ABORT_MESSAGE = "abortmessage"; 51 public static final String NAME = "name"; 52 public static final String PROCESS = "process"; 53 public static final String PID = "pid"; 54 public static final String TID = "tid"; 55 public static final String FAULT_ADDRESS = "faultaddress"; 56 // Matches the smallest blob that has the appropriate header and footer 57 private static final Pattern sCrashBlobPattern = 58 Pattern.compile("DEBUG\\s+?:( [*]{3})+?.*?DEBUG\\s+?:\\s+?backtrace:", Pattern.DOTALL); 59 // Matches process id and name line and captures them 60 private static final Pattern sPidtidNamePattern = 61 Pattern.compile("pid: (\\d+?), tid: (\\d+?), name: ([^\\s]+?\\s+?)*?>>> (.*?) <<<"); 62 // Matches fault address and signal type line 63 private static final Pattern sFaultLinePattern = 64 Pattern.compile( 65 "\\w+? \\d+? \\((.*?)\\), code -*?\\d+? \\(.*?\\), fault addr " 66 + "(?:0x(\\p{XDigit}+)|-+)"); 67 // Matches the abort message line 68 private static Pattern sAbortMessagePattern = 69 Pattern.compile("(?i)Abort message: (.*)"); 70 71 public static final String SIGSEGV = "SIGSEGV"; 72 public static final String SIGBUS = "SIGBUS"; 73 public static final String SIGABRT = "SIGABRT"; 74 75 /** 76 * returns the filename of the process. 77 * e.g. "/system/bin/mediaserver" returns "mediaserver" 78 */ getProcessFileName(JSONObject crash)79 public static String getProcessFileName(JSONObject crash) throws JSONException { 80 return new File(crash.getString(PROCESS)).getName(); 81 } 82 83 /** 84 * Determines if the given input has a {@link com.android.compatibility.common.util.Crash} that 85 * should fail an sts test 86 * 87 * @param crashes list of crashes to check 88 * @param config crash detection configuration object 89 * @return if a crash is serious enough to fail an sts test 90 */ securityCrashDetected(JSONArray crashes, Config config)91 public static boolean securityCrashDetected(JSONArray crashes, Config config) { 92 return matchSecurityCrashes(crashes, config).length() > 0; 93 } 94 getBigInteger(JSONObject source, String name)95 public static BigInteger getBigInteger(JSONObject source, String name) throws JSONException { 96 if (source.isNull(name)) { 97 return null; 98 } 99 String intString = source.getString(name); 100 BigInteger value = null; 101 try { 102 value = new BigInteger(intString, 16); 103 } catch (NumberFormatException e) {} 104 return value; 105 } 106 107 /** 108 * Determines which given inputs have a {@link com.android.compatibility.common.util.Crash} that 109 * should fail an sts test 110 * 111 * @param crashes list of crashes to check 112 * @param config crash detection configuration object 113 * @return the list of crashes serious enough to fail an sts test 114 */ matchSecurityCrashes(JSONArray crashes, Config config)115 public static JSONArray matchSecurityCrashes(JSONArray crashes, Config config) { 116 JSONArray securityCrashes = new JSONArray(); 117 for (int i = 0; i < crashes.length(); i++) { 118 try { 119 JSONObject crash = crashes.getJSONObject(i); 120 121 // match process patterns 122 if (!matchesAny(getProcessFileName(crash), config.processPatterns)) { 123 continue; 124 } 125 126 // match signal 127 String crashSignal = crash.getString(SIGNAL); 128 if (!config.signals.contains(crashSignal)) { 129 continue; 130 } 131 132 if (crash.has(ABORT_MESSAGE)) { 133 String crashAbortMessage = crash.getString(ABORT_MESSAGE); 134 if (!config.abortMessageIncludes.isEmpty()) { 135 if (!config.abortMessageIncludes.stream() 136 .filter(p -> p.matcher(crashAbortMessage).find()) 137 .findFirst() 138 .isPresent()) { 139 continue; 140 } 141 } 142 if (config.abortMessageExcludes.stream() 143 .filter(p -> p.matcher(crashAbortMessage).find()) 144 .findFirst() 145 .isPresent()) { 146 continue; 147 } 148 } 149 150 // if check specified, reject crash if address is unlikely to be security-related 151 if (config.checkMinAddress) { 152 BigInteger faultAddress = getBigInteger(crash, FAULT_ADDRESS); 153 if (faultAddress != null 154 && faultAddress.compareTo(config.minCrashAddress) < 0) { 155 continue; 156 } 157 } 158 securityCrashes.put(crash); 159 } catch (JSONException e) {} 160 } 161 return securityCrashes; 162 } 163 164 /** 165 * returns true if the input matches any of the patterns. 166 */ matchesAny(String input, Collection<Pattern> patterns)167 private static boolean matchesAny(String input, Collection<Pattern> patterns) { 168 for (Pattern p : patterns) { 169 if (p.matcher(input).matches()) { 170 return true; 171 } 172 } 173 return false; 174 } 175 176 /** Adds all crashes found in the input as JSONObjects to the given JSONArray */ addAllCrashes(String input, JSONArray crashes)177 public static JSONArray addAllCrashes(String input, JSONArray crashes) { 178 Matcher crashBlobFinder = sCrashBlobPattern.matcher(input); 179 while (crashBlobFinder.find()) { 180 String crashStr = crashBlobFinder.group(0); 181 int tid = 0; 182 int pid = 0; 183 BigInteger faultAddress = null; 184 String name = null; 185 String process = null; 186 String signal = null; 187 String abortMessage = null; 188 189 Matcher pidtidNameMatcher = sPidtidNamePattern.matcher(crashStr); 190 if (pidtidNameMatcher.find()) { 191 try { 192 pid = Integer.parseInt(pidtidNameMatcher.group(1)); 193 } catch (NumberFormatException e) {} 194 try { 195 tid = Integer.parseInt(pidtidNameMatcher.group(2)); 196 } catch (NumberFormatException e) {} 197 name = pidtidNameMatcher.group(3).trim(); 198 process = pidtidNameMatcher.group(4).trim(); 199 } 200 201 Matcher faultLineMatcher = sFaultLinePattern.matcher(crashStr); 202 if (faultLineMatcher.find()) { 203 signal = faultLineMatcher.group(1); 204 String faultAddrMatch = faultLineMatcher.group(2); 205 if (faultAddrMatch != null) { 206 try { 207 faultAddress = new BigInteger(faultAddrMatch, 16); 208 } catch (NumberFormatException e) {} 209 } 210 } 211 212 Matcher abortMessageMatcher = sAbortMessagePattern.matcher(crashStr); 213 if (abortMessageMatcher.find()) { 214 abortMessage = abortMessageMatcher.group(1); 215 } 216 217 try { 218 JSONObject crash = new JSONObject(); 219 crash.put(PID, pid); 220 crash.put(TID, tid); 221 crash.put(NAME, name); 222 crash.put(PROCESS, process); 223 crash.put(FAULT_ADDRESS, 224 faultAddress == null ? null : faultAddress.toString(16)); 225 crash.put(SIGNAL, signal); 226 crash.put(ABORT_MESSAGE, abortMessage); 227 crashes.put(crash); 228 } catch (JSONException e) {} 229 } 230 return crashes; 231 } 232 233 public static class Config { 234 private boolean checkMinAddress; 235 private BigInteger minCrashAddress; 236 private List<String> signals; 237 private List<Pattern> processPatterns; 238 private List<Pattern> abortMessageIncludes; 239 private List<Pattern> abortMessageExcludes; 240 Config()241 public Config() { 242 checkMinAddress = true; 243 minCrashAddress = MIN_CRASH_ADDR; 244 setSignals(SIGSEGV, SIGBUS); 245 abortMessageIncludes = new ArrayList<>(); 246 setAbortMessageExcludes("CHECK_", "CANNOT LINK EXECUTABLE"); 247 processPatterns = new ArrayList(); 248 } 249 setMinAddress(BigInteger minCrashAddress)250 public Config setMinAddress(BigInteger minCrashAddress) { 251 this.minCrashAddress = minCrashAddress; 252 return this; 253 } 254 checkMinAddress(boolean checkMinAddress)255 public Config checkMinAddress(boolean checkMinAddress) { 256 this.checkMinAddress = checkMinAddress; 257 return this; 258 } 259 setSignals(String... signals)260 public Config setSignals(String... signals) { 261 this.signals = new ArrayList(Arrays.asList(signals)); 262 return this; 263 } 264 appendSignals(String... signals)265 public Config appendSignals(String... signals) { 266 Collections.addAll(this.signals, signals); 267 return this; 268 } 269 setAbortMessageIncludes(String... abortMessages)270 public Config setAbortMessageIncludes(String... abortMessages) { 271 this.abortMessageIncludes = new ArrayList<>(toPatterns(abortMessages)); 272 return this; 273 } 274 setAbortMessageIncludes(Pattern... abortMessages)275 public Config setAbortMessageIncludes(Pattern... abortMessages) { 276 this.abortMessageIncludes = new ArrayList<>(Arrays.asList(abortMessages)); 277 return this; 278 } 279 appendAbortMessageIncludes(String... abortMessages)280 public Config appendAbortMessageIncludes(String... abortMessages) { 281 this.abortMessageIncludes.addAll(toPatterns(abortMessages)); 282 return this; 283 } 284 appendAbortMessageIncludes(Pattern... abortMessages)285 public Config appendAbortMessageIncludes(Pattern... abortMessages) { 286 Collections.addAll(this.abortMessageIncludes, abortMessages); 287 return this; 288 } 289 setAbortMessageExcludes(String... abortMessages)290 public Config setAbortMessageExcludes(String... abortMessages) { 291 this.abortMessageExcludes = new ArrayList<>(toPatterns(abortMessages)); 292 return this; 293 } 294 setAbortMessageExcludes(Pattern... abortMessages)295 public Config setAbortMessageExcludes(Pattern... abortMessages) { 296 this.abortMessageExcludes = new ArrayList<>(Arrays.asList(abortMessages)); 297 return this; 298 } 299 appendAbortMessageExcludes(String... abortMessages)300 public Config appendAbortMessageExcludes(String... abortMessages) { 301 this.abortMessageExcludes.addAll(toPatterns(abortMessages)); 302 return this; 303 } 304 appendAbortMessageExcludes(Pattern... abortMessages)305 public Config appendAbortMessageExcludes(Pattern... abortMessages) { 306 Collections.addAll(this.abortMessageExcludes, abortMessages); 307 return this; 308 } 309 310 setProcessPatterns(String... processPatternStrings)311 public Config setProcessPatterns(String... processPatternStrings) { 312 this.processPatterns = new ArrayList<>(toPatterns(processPatternStrings)); 313 return this; 314 } 315 setProcessPatterns(Pattern... processPatterns)316 public Config setProcessPatterns(Pattern... processPatterns) { 317 this.processPatterns = new ArrayList(Arrays.asList(processPatterns)); 318 return this; 319 } 320 getProcessPatterns()321 public List<Pattern> getProcessPatterns() { 322 return Collections.unmodifiableList(processPatterns); 323 } 324 appendProcessPatterns(String... processPatternStrings)325 public Config appendProcessPatterns(String... processPatternStrings) { 326 this.processPatterns.addAll(toPatterns(processPatternStrings)); 327 return this; 328 } 329 appendProcessPatterns(Pattern... processPatterns)330 public Config appendProcessPatterns(Pattern... processPatterns) { 331 Collections.addAll(this.processPatterns, processPatterns); 332 return this; 333 } 334 } 335 toPatterns(String... patternStrings)336 private static List<Pattern> toPatterns(String... patternStrings) { 337 return Stream.of(patternStrings).map(Pattern::compile).collect(Collectors.toList()); 338 } 339 } 340