• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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