blob: b3977944cbe78f1caa2736c021992088d3edd3cc [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.compatibility.common.util;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.math.BigInteger;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/** Contains helper functions and shared constants for crash parsing. */
public class CrashUtils {
// used to only detect actual addresses instead of nullptr and other unlikely values
public static final BigInteger MIN_CRASH_ADDR = new BigInteger("8000", 16);
// Matches the end of a crash
public static final Pattern sEndofCrashPattern =
Pattern.compile("DEBUG\\s+?:\\s+?backtrace:");
public static final String DEVICE_PATH = "/data/local/tmp/CrashParserResults/";
public static final String LOCK_FILENAME = "lockFile.loc";
public static final String UPLOAD_REQUEST = "Please upload a result file to stagefright";
public static final Pattern sUploadRequestPattern =
Pattern.compile(UPLOAD_REQUEST);
public static final String NEW_TEST_ALERT = "New test starting with name: ";
public static final Pattern sNewTestPattern =
Pattern.compile(NEW_TEST_ALERT + "(\\w+?)\\(.*?\\)");
public static final String SIGNAL = "signal";
public static final String NAME = "name";
public static final String PROCESS = "process";
public static final String PID = "pid";
public static final String TID = "tid";
public static final String FAULT_ADDRESS = "faultaddress";
// Matches the smallest blob that has the appropriate header and footer
private static final Pattern sCrashBlobPattern =
Pattern.compile("DEBUG\\s+?:( [*]{3})+?.*?DEBUG\\s+?:\\s+?backtrace:", Pattern.DOTALL);
// Matches process id and name line and captures them
private static final Pattern sPidtidNamePattern =
Pattern.compile("pid: (\\d+?), tid: (\\d+?), name: ([^\\s]+?\\s+?)*?>>> (.*?) <<<");
// Matches fault address and signal type line
private static final Pattern sFaultLinePattern =
Pattern.compile(
"\\w+? \\d+? \\((.*?)\\), code -*?\\d+? \\(.*?\\), fault addr "
+ "(?:0x(\\p{XDigit}+)|-+)");
// Matches the abort message line if it contains CHECK_
private static Pattern sAbortMessageCheckPattern =
Pattern.compile("(?i)Abort message.*?CHECK_");
public static final String SIGSEGV = "SIGSEGV";
public static final String SIGBUS = "SIGBUS";
public static final String SIGABRT = "SIGABRT";
/**
* returns the filename of the process.
* e.g. "/system/bin/mediaserver" returns "mediaserver"
*/
public static String getProcessFileName(JSONObject crash) throws JSONException {
return new File(crash.getString(PROCESS)).getName();
}
/**
* Determines if the given input has a {@link com.android.compatibility.common.util.Crash} that
* should fail an sts test
*
* @param crashes list of crashes to check
* @param config crash detection configuration object
* @return if a crash is serious enough to fail an sts test
*/
public static boolean securityCrashDetected(JSONArray crashes, Config config) {
return matchSecurityCrashes(crashes, config).length() > 0;
}
public static BigInteger getBigInteger(JSONObject source, String name) throws JSONException {
if (source.isNull(name)) {
return null;
}
String intString = source.getString(name);
BigInteger value = null;
try {
value = new BigInteger(intString, 16);
} catch (NumberFormatException e) {}
return value;
}
/**
* Determines which given inputs have a {@link com.android.compatibility.common.util.Crash} that
* should fail an sts test
*
* @param crashes list of crashes to check
* @param config crash detection configuration object
* @return the list of crashes serious enough to fail an sts test
*/
public static JSONArray matchSecurityCrashes(JSONArray crashes, Config config) {
JSONArray securityCrashes = new JSONArray();
for (int i = 0; i < crashes.length(); i++) {
try {
JSONObject crash = crashes.getJSONObject(i);
// match process patterns
if (!matchesAny(getProcessFileName(crash), config.processPatterns)) {
continue;
}
// match signal
String crashSignal = crash.getString(SIGNAL);
if (!config.signals.contains(crashSignal)) {
continue;
}
// if check specified, reject crash if address is unlikely to be security-related
if (config.checkMinAddress) {
BigInteger faultAddress = getBigInteger(crash, FAULT_ADDRESS);
if (faultAddress != null
&& faultAddress.compareTo(config.minCrashAddress) < 0) {
continue;
}
}
securityCrashes.put(crash);
} catch (JSONException e) {}
}
return securityCrashes;
}
/**
* returns true if the input matches any of the patterns.
*/
private static boolean matchesAny(String input, Collection<Pattern> patterns) {
for (Pattern p : patterns) {
if (p.matcher(input).matches()) {
return true;
}
}
return false;
}
/** Adds all crashes found in the input as JSONObjects to the given JSONArray */
public static JSONArray addAllCrashes(String input, JSONArray crashes) {
Matcher crashBlobFinder = sCrashBlobPattern.matcher(input);
while (crashBlobFinder.find()) {
String crashStr = crashBlobFinder.group(0);
int tid = 0;
int pid = 0;
BigInteger faultAddress = null;
String name = null;
String process = null;
String signal = null;
Matcher pidtidNameMatcher = sPidtidNamePattern.matcher(crashStr);
if (pidtidNameMatcher.find()) {
try {
pid = Integer.parseInt(pidtidNameMatcher.group(1));
} catch (NumberFormatException e) {}
try {
tid = Integer.parseInt(pidtidNameMatcher.group(2));
} catch (NumberFormatException e) {}
name = pidtidNameMatcher.group(3).trim();
process = pidtidNameMatcher.group(4).trim();
}
Matcher faultLineMatcher = sFaultLinePattern.matcher(crashStr);
if (faultLineMatcher.find()) {
signal = faultLineMatcher.group(1);
String faultAddrMatch = faultLineMatcher.group(2);
if (faultAddrMatch != null) {
try {
faultAddress = new BigInteger(faultAddrMatch, 16);
} catch (NumberFormatException e) {}
}
}
if (!sAbortMessageCheckPattern.matcher(crashStr).find()) {
try {
JSONObject crash = new JSONObject();
crash.put(PID, pid);
crash.put(TID, tid);
crash.put(NAME, name);
crash.put(PROCESS, process);
crash.put(FAULT_ADDRESS,
faultAddress == null ? null : faultAddress.toString(16));
crash.put(SIGNAL, signal);
crashes.put(crash);
} catch (JSONException e) {}
}
}
return crashes;
}
public static class Config {
private boolean checkMinAddress;
private BigInteger minCrashAddress;
private List<String> signals;
private List<Pattern> processPatterns;
public Config() {
checkMinAddress = true;
minCrashAddress = MIN_CRASH_ADDR;
setSignals(SIGSEGV, SIGBUS);
processPatterns = new ArrayList();
}
public Config setMinAddress(BigInteger minCrashAddress) {
this.minCrashAddress = minCrashAddress;
return this;
}
public Config checkMinAddress(boolean checkMinAddress) {
this.checkMinAddress = checkMinAddress;
return this;
}
public Config setSignals(String... signals) {
this.signals = new ArrayList(Arrays.asList(signals));
return this;
}
public Config appendSignals(String... signals) {
Collections.addAll(this.signals, signals);
return this;
}
public Config setProcessPatterns(String... processPatternStrings) {
Pattern[] processPatterns = new Pattern[processPatternStrings.length];
for (int i = 0; i < processPatternStrings.length; i++) {
processPatterns[i] = Pattern.compile(processPatternStrings[i]);
}
return setProcessPatterns(processPatterns);
}
public Config setProcessPatterns(Pattern... processPatterns) {
this.processPatterns = new ArrayList(Arrays.asList(processPatterns));
return this;
}
public List<Pattern> getProcessPatterns() {
return Collections.unmodifiableList(processPatterns);
}
public Config appendProcessPatterns(Pattern... processPatterns) {
Collections.addAll(this.processPatterns, processPatterns);
return this;
}
}
}