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