Commit 2b66833c authored by Peter Kotwicz's avatar Peter Kotwicz Committed by Commit Bot

[Android] Rewrite Chrome strict mode handling

This CL:
- Fixes strict mode detection on Android P
- Imports list of "unfixable strict mode violations" from other google
  apps
- Avoids reporting strict mode violation (uploading stack trace) or
  preforming penalty if the strict mode violation is whitelisted.

BUG=1038384

Change-Id: Ib125d08104225af0e1dafb1be1afc5ef8d8e128e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2258578Reviewed-by: default avatarAlexander Cooper <alcooper@chromium.org>
Reviewed-by: default avatarTed Choc <tedchoc@chromium.org>
Reviewed-by: default avatarYaron Friedman <yfriedman@chromium.org>
Reviewed-by: default avatarAndrew Grieve <agrieve@chromium.org>
Reviewed-by: default avatarPeter Wen <wnwen@chromium.org>
Commit-Queue: Peter Kotwicz <pkotwicz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#787921}
parent 6db4e07c
...@@ -419,6 +419,7 @@ android_library("chrome_java") { ...@@ -419,6 +419,7 @@ android_library("chrome_java") {
"//components/signin/core/browser/android:java", "//components/signin/core/browser/android:java",
"//components/signin/public/android:java", "//components/signin/public/android:java",
"//components/spellcheck/browser/android:java", "//components/spellcheck/browser/android:java",
"//components/strictmode/android:java",
"//components/subresource_filter/android:java", "//components/subresource_filter/android:java",
"//components/sync/android:sync_java", "//components/sync/android:sync_java",
"//components/sync/protocol:protocol_java", "//components/sync/protocol:protocol_java",
...@@ -1068,6 +1069,7 @@ android_library("chrome_test_java") { ...@@ -1068,6 +1069,7 @@ android_library("chrome_test_java") {
"//components/signin/core/browser/android:signin_java_test_support", "//components/signin/core/browser/android:signin_java_test_support",
"//components/signin/core/browser/android:signin_javatests", "//components/signin/core/browser/android:signin_javatests",
"//components/signin/public/android:java", "//components/signin/public/android:java",
"//components/strictmode/android:javatests",
"//components/sync:sync_java_test_support", "//components/sync:sync_java_test_support",
"//components/sync/android:sync_java", "//components/sync/android:sync_java",
"//components/sync/protocol:protocol_java", "//components/sync/protocol:protocol_java",
......
...@@ -40,6 +40,7 @@ include_rules = [ ...@@ -40,6 +40,7 @@ include_rules = [
"+components/signin/core/browser/android", "+components/signin/core/browser/android",
"+components/signin/public/android", "+components/signin/public/android",
"+components/spellcheck/browser", "+components/spellcheck/browser",
"+components/strictmode/android",
"+components/subresource_filter/android", "+components/subresource_filter/android",
"+components/translate/content/android", "+components/translate/content/android",
"+components/user_prefs/android", "+components/user_prefs/android",
......
...@@ -4,10 +4,10 @@ ...@@ -4,10 +4,10 @@
package org.chromium.chrome.browser; package org.chromium.chrome.browser;
import android.app.ApplicationErrorReport;
import android.os.Build; import android.os.Build;
import android.os.Looper; import android.os.Looper;
import android.os.StrictMode; import android.os.StrictMode;
import android.text.TextUtils;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
...@@ -18,8 +18,13 @@ import org.chromium.base.Log; ...@@ -18,8 +18,13 @@ import org.chromium.base.Log;
import org.chromium.base.ThreadUtils; import org.chromium.base.ThreadUtils;
import org.chromium.base.library_loader.LibraryLoader; import org.chromium.base.library_loader.LibraryLoader;
import org.chromium.chrome.browser.flags.ChromeSwitches; import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.components.strictmode.KnownViolations;
import org.chromium.components.strictmode.StrictModePolicyViolation;
import org.chromium.components.strictmode.ThreadStrictModeInterceptor;
import org.chromium.components.strictmode.Violation;
import java.lang.reflect.Field; import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
...@@ -34,29 +39,10 @@ public class ChromeStrictMode { ...@@ -34,29 +39,10 @@ public class ChromeStrictMode {
private static final double MAX_UPLOADS_PER_SESSION = 3; private static final double MAX_UPLOADS_PER_SESSION = 3;
private static boolean sIsStrictModeAlreadyConfigured; private static boolean sIsStrictModeAlreadyConfigured;
private static List<Object> sCachedStackTraces = private static List<Violation> sCachedViolations =
Collections.synchronizedList(new ArrayList<Object>()); Collections.synchronizedList(new ArrayList<>());
private static AtomicInteger sNumUploads = new AtomicInteger(); private static AtomicInteger sNumUploads = new AtomicInteger();
private static class SnoopingArrayList<T> extends ArrayList<T> {
@Override
public void clear() {
for (int i = 0; i < size(); i++) {
// It is likely that we have at most one violation pass this check each time around.
if (Math.random() < UPLOAD_PROBABILITY) {
// Ensure that we do not upload too many StrictMode violations in any single
// session. To prevent races, we allow sNumUploads to increase beyond the
// limit, but just skip actually uploading the stack trace then.
if (sNumUploads.getAndAdd(1) >= MAX_UPLOADS_PER_SESSION) {
break;
}
sCachedStackTraces.add(get(i));
}
}
super.clear();
}
}
/** /**
* Always process the violation on the UI thread. This ensures other crash reports are not * Always process the violation on the UI thread. This ensures other crash reports are not
* corrupted. Since each individual user has a very small chance of uploading each violation, * corrupted. Since each individual user has a very small chance of uploading each violation,
...@@ -65,45 +51,36 @@ public class ChromeStrictMode { ...@@ -65,45 +51,36 @@ public class ChromeStrictMode {
* @param violationInfo The violation info from the StrictMode violation in question. * @param violationInfo The violation info from the StrictMode violation in question.
*/ */
@UiThread @UiThread
private static void reportStrictModeViolation(Object violationInfo) { private static void reportStrictModeViolation(Violation violation) {
try { StringWriter stackTraceWriter = new StringWriter();
Field crashInfoField = violationInfo.getClass().getField("crashInfo"); new StrictModePolicyViolation(violation).printStackTrace(new PrintWriter(stackTraceWriter));
ApplicationErrorReport.CrashInfo crashInfo = String stackTrace = stackTraceWriter.toString();
(ApplicationErrorReport.CrashInfo) crashInfoField.get(violationInfo); if (TextUtils.isEmpty(stackTrace)) {
String stackTrace = crashInfo.stackTrace; Log.d(TAG, "StrictMode violation stack trace was empty.");
if (stackTrace == null) { } else {
Log.d(TAG, "StrictMode violation stack trace was null."); Log.d(TAG, "Upload stack trace: " + stackTrace);
} else { JavaExceptionReporter.reportStackTrace(stackTrace);
Log.d(TAG, "Upload stack trace: " + stackTrace);
JavaExceptionReporter.reportStackTrace(stackTrace);
}
} catch (Exception e) {
// Ignore all exceptions.
Log.d(TAG, "Could not handle observed StrictMode violation.", e);
} }
} }
/** /**
* Replace Android OS's StrictMode.violationsBeingTimed with a custom ArrayList acting as an * Add custom {@link ThreadStrictModeInterceptor} penalty which records strict mode violations.
* observer into violation stack traces. Set up an idle handler so StrictMode violations that * Set up an idle handler so StrictMode violations that occur on startup are not ignored.
* occur on startup are not ignored.
*/ */
@SuppressWarnings({"unchecked", "rawtypes" })
@UiThread @UiThread
private static void initializeStrictModeWatch() { private static void initializeStrictModeWatch(
try { ThreadStrictModeInterceptor.Builder threadInterceptor) {
Field violationsBeingTimedField = threadInterceptor.setCustomPenalty(violation -> {
StrictMode.class.getDeclaredField("violationsBeingTimed"); if (Math.random() < UPLOAD_PROBABILITY) {
violationsBeingTimedField.setAccessible(true); // Ensure that we do not upload too many StrictMode violations in any single
ThreadLocal<ArrayList> violationsBeingTimed = // session. To prevent races, we allow sNumUploads to increase beyond the limit, but
(ThreadLocal<ArrayList>) violationsBeingTimedField.get(null); // just skip actually uploading the stack trace then.
ArrayList replacementList = new SnoopingArrayList(); if (sNumUploads.getAndAdd(1) < MAX_UPLOADS_PER_SESSION) {
violationsBeingTimed.set(replacementList); sCachedViolations.add(violation);
} catch (Exception e) { }
// Terminate watch if any exceptions are raised. }
Log.w(TAG, "Could not initialize StrictMode watch.", e); });
return;
}
sNumUploads.set(0); sNumUploads.set(0);
// Delay handling StrictMode violations during initialization until the main loop is idle. // Delay handling StrictMode violations during initialization until the main loop is idle.
Looper.myQueue().addIdleHandler(() -> { Looper.myQueue().addIdleHandler(() -> {
...@@ -111,14 +88,14 @@ public class ChromeStrictMode { ...@@ -111,14 +88,14 @@ public class ChromeStrictMode {
if (!LibraryLoader.getInstance().isInitialized()) return true; if (!LibraryLoader.getInstance().isInitialized()) return true;
// Check again next time if no more cached stack traces to upload, and we have not // Check again next time if no more cached stack traces to upload, and we have not
// reached the max number of uploads for this session. // reached the max number of uploads for this session.
if (sCachedStackTraces.isEmpty()) { if (sCachedViolations.isEmpty()) {
// TODO(wnwen): Add UMA count when this happens. // TODO(wnwen): Add UMA count when this happens.
// In case of races, continue checking an extra time (equal condition). // In case of races, continue checking an extra time (equal condition).
return sNumUploads.get() <= MAX_UPLOADS_PER_SESSION; return sNumUploads.get() <= MAX_UPLOADS_PER_SESSION;
} }
// Since this is the only place we are removing elements, no need for additional // Since this is the only place we are removing elements, no need for additional
// synchronization to ensure it is still non-empty. // synchronization to ensure it is still non-empty.
reportStrictModeViolation(sCachedStackTraces.remove(0)); reportStrictModeViolation(sCachedViolations.remove(0));
return true; return true;
}); });
} }
...@@ -139,14 +116,12 @@ public class ChromeStrictMode { ...@@ -139,14 +116,12 @@ public class ChromeStrictMode {
} }
} }
private static void addDefaultPenalties(StrictMode.ThreadPolicy.Builder threadPolicy, private static void addDefaultThreadPenalties(StrictMode.ThreadPolicy.Builder threadPolicy) {
StrictMode.VmPolicy.Builder vmPolicy) {
threadPolicy.penaltyLog().penaltyFlashScreen().penaltyDeathOnNetwork(); threadPolicy.penaltyLog().penaltyFlashScreen().penaltyDeathOnNetwork();
vmPolicy.penaltyLog();
} }
private static void addThreadDeathPenalty(StrictMode.ThreadPolicy.Builder threadPolicy) { private static void addDefaultVmPenalties(StrictMode.VmPolicy.Builder vmPolicy) {
threadPolicy.penaltyDeath(); vmPolicy.penaltyLog();
} }
private static void addVmDeathPenalty(StrictMode.VmPolicy.Builder vmPolicy) { private static void addVmDeathPenalty(StrictMode.VmPolicy.Builder vmPolicy) {
...@@ -188,25 +163,28 @@ public class ChromeStrictMode { ...@@ -188,25 +163,28 @@ public class ChromeStrictMode {
new StrictMode.ThreadPolicy.Builder(StrictMode.getThreadPolicy()); new StrictMode.ThreadPolicy.Builder(StrictMode.getThreadPolicy());
StrictMode.VmPolicy.Builder vmPolicy = StrictMode.VmPolicy.Builder vmPolicy =
new StrictMode.VmPolicy.Builder(StrictMode.getVmPolicy()); new StrictMode.VmPolicy.Builder(StrictMode.getVmPolicy());
ThreadStrictModeInterceptor.Builder threadInterceptor =
new ThreadStrictModeInterceptor.Builder();
turnOnDetection(threadPolicy, vmPolicy);
if (shouldApplyPenalties) { if (shouldApplyPenalties) {
turnOnDetection(threadPolicy, vmPolicy); addDefaultVmPenalties(vmPolicy);
addDefaultPenalties(threadPolicy, vmPolicy);
if ("death".equals(commandLine.getSwitchValue(ChromeSwitches.STRICT_MODE))) { if ("death".equals(commandLine.getSwitchValue(ChromeSwitches.STRICT_MODE))) {
addThreadDeathPenalty(threadPolicy); threadInterceptor.replaceAllPenaltiesWithDeathPenalty();
addVmDeathPenalty(vmPolicy); addVmDeathPenalty(vmPolicy);
} else if ("testing".equals(commandLine.getSwitchValue(ChromeSwitches.STRICT_MODE))) { } else if ("testing".equals(commandLine.getSwitchValue(ChromeSwitches.STRICT_MODE))) {
addThreadDeathPenalty(threadPolicy); threadInterceptor.replaceAllPenaltiesWithDeathPenalty();
// Currently VmDeathPolicy kills the process, and is not visible on bot test output. // Currently VmDeathPolicy kills the process, and is not visible on bot test output.
} else {
addDefaultThreadPenalties(threadPolicy);
} }
} }
if (enableStrictModeWatch) { if (enableStrictModeWatch) {
turnOnDetection(threadPolicy, vmPolicy); initializeStrictModeWatch(threadInterceptor);
initializeStrictModeWatch();
} }
StrictMode.setThreadPolicy(threadPolicy.build()); KnownViolations.addExemptions(threadInterceptor);
threadInterceptor.build().install(threadPolicy.build());
StrictMode.setVmPolicy(vmPolicy.build()); StrictMode.setVmPolicy(vmPolicy.build());
} }
} }
file://build/config/android/OWNERS
# COMPONENT: Build
# Copyright 2020 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//build/config/android/rules.gni")
android_library("java") {
sources = [
"java/src/org/chromium/components/strictmode/KnownViolations.java",
"java/src/org/chromium/components/strictmode/ReflectiveThreadStrictModeInterceptor.java",
"java/src/org/chromium/components/strictmode/StrictModePolicyViolation.java",
"java/src/org/chromium/components/strictmode/ThreadStrictModeInterceptor.java",
"java/src/org/chromium/components/strictmode/ThreadStrictModeInterceptorP.java",
"java/src/org/chromium/components/strictmode/Violation.java",
]
deps = [ "//base:base_java" ]
}
android_library("javatests") {
testonly = true
sources = [ "javatests/src/org/chromium/components/strictmode/ThreadStrictModeInterceptorTest.java" ]
deps = [
":java",
"//base:base_java_test_support",
"//content/public/test/android:content_java_test_support",
"//third_party/android_deps:androidx_appcompat_appcompat_java",
"//third_party/android_deps:androidx_core_core_java",
"//third_party/android_support_test_runner:runner_java",
"//third_party/junit",
]
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.components.strictmode;
import static org.chromium.components.strictmode.Violation.DETECT_DISK_IO;
import static org.chromium.components.strictmode.Violation.DETECT_DISK_READ;
import static org.chromium.components.strictmode.Violation.DETECT_DISK_WRITE;
import static org.chromium.components.strictmode.Violation.DETECT_RESOURCE_MISMATCH;
import android.os.Build;
import java.util.Locale;
/**
* Collection of known unfixable StrictMode violations. This list should stay in sync with the
* list for other apps (http://go/chrome-known-violations-upstream). Add Chrome-specific exemptions
* to {@link ChromeStrictMode}.
*/
public final class KnownViolations {
public static ThreadStrictModeInterceptor.Builder addExemptions(
ThreadStrictModeInterceptor.Builder builder) {
applyManufacturer(builder);
applyVendor(builder);
applyPlatform(builder);
return builder;
}
private static void applyManufacturer(ThreadStrictModeInterceptor.Builder exemptions) {
String manufacturer = Build.MANUFACTURER.toLowerCase(Locale.US);
String model = Build.MODEL.toLowerCase(Locale.US);
switch (manufacturer) {
case "samsung":
exemptions.ignoreExternalMethod(DETECT_DISK_READ | DETECT_DISK_WRITE,
"android.util.GeneralUtil#isSupportedGloveModeInternal");
exemptions.ignoreExternalMethod(
DETECT_DISK_READ, "android.graphics.Typeface#SetAppTypeFace");
exemptions.ignoreExternalMethod(
DETECT_DISK_READ, "android.graphics.Typeface#setAppTypeFace");
exemptions.ignoreExternalMethod(DETECT_DISK_READ,
"android.app.ApplicationPackageManager#queryIntentActivities");
exemptions.ignoreExternalMethod(
DETECT_DISK_READ, "android.app.ActivityThread#parseCSCAppResource");
exemptions.ignoreExternalMethod(
DETECT_DISK_READ, "android.app.ActivityThread#performLaunchActivity");
exemptions.ignoreExternalMethod(DETECT_DISK_READ,
"com.samsung.android.knox.custom.ProKioskManager#getProKioskState");
if (model.equals("sm-g9350")) {
exemptions.ignoreExternalMethod(
DETECT_DISK_WRITE, "android.content.res.Resources#loadDrawable");
}
if (model.equals("sm-j700f") && Build.VERSION.SDK_INT == 23) {
exemptions.ignoreExternalMethod(
DETECT_DISK_IO, "android.content.res.Resources#loadDrawable");
exemptions.ignoreExternalMethod(
DETECT_DISK_WRITE, "android.app.ActivityThread#performLaunchActivity");
}
break;
case "oneplus":
exemptions.ignoreExternalMethod(DETECT_DISK_READ | DETECT_DISK_WRITE,
"com.android.server.am.ActivityManagerService#checkProcessExist");
break;
case "vivo":
exemptions.ignoreExternalMethod(
DETECT_DISK_READ, "android.content.res.VivoResources#loadThemeValues");
break;
case "xiaomi":
exemptions.ignoreExternalMethod(
DETECT_DISK_READ, "com.android.internal.policy.PhoneWindow#getDecorView");
exemptions.ignoreExternalMethod(
DETECT_DISK_WRITE, "miui.content.res.ThemeResourcesSystem#checkUpdate");
exemptions.ignoreExternalMethod(
DETECT_DISK_READ, "android.util.BoostFramework#<init>");
break;
default:
// fall through
}
}
private static void applyVendor(ThreadStrictModeInterceptor.Builder exemptions) {
exemptions.ignoreExternalMethod(DETECT_DISK_READ, "com.qualcomm.qti.Performance#<clinit>");
}
private static void applyPlatform(ThreadStrictModeInterceptor.Builder exemptions) {
exemptions.ignoreExternalMethod(
DETECT_DISK_READ, "com.android.messageformat.MessageFormat#formatNamedArgs");
exemptions.ignoreExternalMethod(
DETECT_RESOURCE_MISMATCH, "com.android.internal.widget.SwipeDismissLayout#init");
exemptions.ignoreExternalMethod(DETECT_DISK_IO, "java.lang.ThreadGroup#uncaughtException");
exemptions.ignoreExternalMethod(DETECT_DISK_IO, "android.widget.VideoView#openVideo");
exemptions.ignoreExternalMethod(DETECT_DISK_IO,
"com.android.server.inputmethod.InputMethodManagerService#startInputOrWindowGainedFocus");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
exemptions.ignoreExternalMethod(DETECT_DISK_WRITE,
"com.android.server.clipboard.HostClipboardMonitor#setHostClipboard");
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
exemptions.ignoreExternalMethod(
DETECT_DISK_WRITE, "android.content.ClipboardManager#setPrimaryClip");
}
}
private KnownViolations() {}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.components.strictmode;
import android.app.ApplicationErrorReport;
import android.os.StrictMode;
import android.os.StrictMode.ThreadPolicy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.chromium.base.Consumer;
import org.chromium.base.Function;
import org.chromium.base.Log;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* StrictMode whitelist installer.
*
* <p><b>How this works:</b><br>
* When StrictMode is enabled without the death penalty, it queues up all ThreadPolicy violations
* into a ThreadLocal ArrayList, and then posts a Runnable to the start of the Looper queue to
* process them. This is done in order to set a cap to the number of logged/handled violations per
* event loop, and avoid overflowing the log buffer or other penalty handlers with violations. <br>
* Because the violations are queued into a ThreadLocal ArrayList, they must be queued on the
* offending thread, and thus the offending stack frame will exist in the stack trace. The
* whitelisting mechanism works by using reflection to set a custom ArrayList into the ThreadLocal.
* When StrictMode is adding a new item to the ArrayList, our custom ArrayList checks the stack
* trace for any whitelisted frames, and if one is found, no-ops the addition. Then, when the
* processing runnable executes, it sees there are no items, and no-ops. <br>
* However, if the death penalty is enabled, the concern about handling too many violations no
* longer exists (since death will occur after the first one), so the queue is bypassed, and death
* occurs instantly without allowing the whitelisting system to intercept it. In order to retain the
* death penalty, the whitelisting mechanism itself can be configured to execute the death penalty
* after the first non-whitelisted violation.
*/
final class ReflectiveThreadStrictModeInterceptor implements ThreadStrictModeInterceptor {
private static final String TAG = "ThreadStrictMode";
@NonNull
private final List<Function<Violation, Integer>> mWhitelistEntries;
@Nullable
private final Consumer mCustomPenalty;
ReflectiveThreadStrictModeInterceptor(
@NonNull List<Function<Violation, Integer>> whitelistEntries,
@Nullable Consumer customPenalty) {
mWhitelistEntries = whitelistEntries;
mCustomPenalty = customPenalty;
}
@Override
public void install(ThreadPolicy detectors) {
interceptWithReflection();
StrictMode.setThreadPolicy(new ThreadPolicy.Builder(detectors).penaltyLog().build());
}
private void interceptWithReflection() {
ThreadLocal<ArrayList<Object>> violationsBeingTimed;
try {
violationsBeingTimed = getViolationsBeingTimed();
} catch (Exception e) {
throw new RuntimeException(null, e);
}
violationsBeingTimed.get().clear();
violationsBeingTimed.set(new ArrayList<Object>() {
@Override
public boolean add(Object o) {
int violationType = getViolationType(o);
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
Violation violation =
new Violation(violationType, Arrays.copyOf(stackTrace, stackTrace.length));
if (violationType != Violation.DETECT_UNKNOWN
&& violation.isInWhitelist(mWhitelistEntries)) {
return true;
}
if (mCustomPenalty != null) {
mCustomPenalty.accept(violation);
}
return super.add(o);
}
});
}
@SuppressWarnings({"unchecked"})
private static ThreadLocal<ArrayList<Object>> getViolationsBeingTimed()
throws IllegalAccessException, NoSuchFieldException {
Field violationTimingField = StrictMode.class.getDeclaredField("violationsBeingTimed");
violationTimingField.setAccessible(true);
return (ThreadLocal<ArrayList<Object>>) violationTimingField.get(null);
}
/** @param o {@code android.os.StrictMode.ViolationInfo} */
@SuppressWarnings({"unchecked", "PrivateApi"})
private static int getViolationType(Object o) {
try {
Class<?> violationInfo = Class.forName("android.os.StrictMode$ViolationInfo");
Field crashInfoField = violationInfo.getDeclaredField("crashInfo");
crashInfoField.setAccessible(true);
ApplicationErrorReport.CrashInfo crashInfo =
(ApplicationErrorReport.CrashInfo) crashInfoField.get(o);
Method parseViolationFromMessage =
StrictMode.class.getDeclaredMethod("parseViolationFromMessage", String.class);
parseViolationFromMessage.setAccessible(true);
int mask = (int) parseViolationFromMessage.invoke(
null /* static */, crashInfo.exceptionMessage);
return mask & Violation.DETECT_ALL_KNOWN;
} catch (Exception e) {
Log.e(TAG, "Unable to get violation.", e);
return Violation.DETECT_UNKNOWN;
}
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.components.strictmode;
/** Dummy exception thrown for the custom death penalty. */
public final class StrictModePolicyViolation extends Error {
public StrictModePolicyViolation(Violation v) {
super(v.violationString());
if (v.stackTrace().length == 0) {
super.fillInStackTrace();
} else {
setStackTrace(v.stackTrace());
}
}
@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.components.strictmode;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.StrictMode.ThreadPolicy;
import androidx.annotation.Nullable;
import org.chromium.base.Consumer;
import org.chromium.base.Function;
import java.util.ArrayList;
import java.util.List;
/** Installs a whitelist configuration for StrictMode's ThreadPolicy feature. */
public interface ThreadStrictModeInterceptor {
/**
* Install this interceptor and it's whitelists.
*
* Pre-P, this uses reflection.
*/
void install(ThreadPolicy detectors);
/**
* Builds a configuration for StrictMode enforcement.
*
* The API (but not the implementation) should stay in sync with the API used by
* 'KnownViolations' for other apps (http://go/chrome-known-violations-upstream).
*/
final class Builder {
private final List<Function<Violation, Integer>> mWhitelistEntries = new ArrayList<>();
private @Nullable Consumer<Violation> mCustomPenalty;
/**
* Ignore a violation that occurs outside of your app.
*
* @param violationType A mask containing one or more of the DETECT_* constants.
* @param classNameWithMethod The name of the class and method to ignore StrictMode
* violations
* in. The format must be "package.Class#method", for example,
* "com.google.foo.ThreadStrictModeInterceptor#addAllowedMethod".
*/
public Builder ignoreExternalMethod(int violationType, final String classNameWithMethod) {
String[] parts = classNameWithMethod.split("#");
String className = parts[0];
String methodName = parts[1];
mWhitelistEntries.add(violation -> {
if ((violation.violationType() & violationType) == 0) {
return null;
}
for (StackTraceElement frame : violation.stackTrace()) {
if (frame.getClassName().equals(className)
&& frame.getMethodName().equals(methodName)) {
return violationType;
}
}
return null;
});
return this;
}
/**
* Ignore a violation that occurs inside your app.
*
* @param violationType A mask containing one or more of the DETECT_* constants.
* @param classNameWithMethod The name of the class and method to ignore StrictMode
* violations
* in. The format must be "package.Class#method", for example,
* "com.google.foo.StrictModeWhitelist#addAllowedMethod".
*/
public Builder addAllowedMethod(int violationType, final String classNameWithMethod) {
return ignoreExternalMethod(violationType, classNameWithMethod);
}
/** Set the custom penalty that will be notified when an unwhitelisted violation occurs. */
public Builder setCustomPenalty(Consumer<Violation> penalty) {
mCustomPenalty = penalty;
return this;
}
/**
* Replaces all penalties with the death penalty.
*
* <p>Installing whitelists requires that StrictMode does not have the death penalty. If
* your app requires the death penalty, you can set this, which will attempt to emulate the
* system behavior if possible.
*
* <p>Death is not guaranteed, since it relies on reflection to work.
*/
public Builder replaceAllPenaltiesWithDeathPenalty() {
mCustomPenalty = info -> {
StrictModePolicyViolation toThrow = new StrictModePolicyViolation(info);
// Post task so that no one has a chance to catch the thrown exception.
new Handler(Looper.getMainLooper()).post(() -> { throw toThrow; });
};
return this;
}
/** Make immutable. */
public ThreadStrictModeInterceptor build() {
if (Build.VERSION.SDK_INT >= 28) {
return new ThreadStrictModeInterceptorP(mWhitelistEntries, mCustomPenalty);
} else {
return new ReflectiveThreadStrictModeInterceptor(mWhitelistEntries, mCustomPenalty);
}
}
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.components.strictmode;
import android.annotation.TargetApi;
import android.os.StrictMode;
import android.os.StrictMode.ThreadPolicy;
import android.os.strictmode.DiskReadViolation;
import android.os.strictmode.DiskWriteViolation;
import android.os.strictmode.ResourceMismatchViolation;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.chromium.base.Consumer;
import org.chromium.base.Function;
import java.util.List;
/**
* Android P+ {@ThreadStrictModeInterceptor} implementation.
*/
@TargetApi(28)
final class ThreadStrictModeInterceptorP implements ThreadStrictModeInterceptor {
@NonNull
private final List<Function<Violation, Integer>> mWhitelistEntries;
@Nullable
private final Consumer mCustomPenalty;
ThreadStrictModeInterceptorP(@NonNull List<Function<Violation, Integer>> whitelistEntries,
@Nullable Consumer customPenalty) {
mWhitelistEntries = whitelistEntries;
mCustomPenalty = customPenalty;
}
@Override
@TargetApi(28)
public void install(ThreadPolicy threadPolicy) {
StrictMode.OnThreadViolationListener listener = v -> {
handleThreadViolation(new Violation(computeType(v), v.getStackTrace()));
};
ThreadPolicy.Builder builder = new ThreadPolicy.Builder(threadPolicy);
builder.penaltyListener(Runnable::run, listener);
StrictMode.setThreadPolicy(builder.build());
}
private void handleThreadViolation(Violation violation) {
if (violation.isInWhitelist(mWhitelistEntries)) {
return;
}
if (mCustomPenalty != null) {
mCustomPenalty.accept(violation);
}
}
private static int computeType(android.os.strictmode.Violation violation) {
if (violation instanceof DiskReadViolation) {
return Violation.DETECT_DISK_READ;
} else if (violation instanceof DiskWriteViolation) {
return Violation.DETECT_DISK_WRITE;
} else if (violation instanceof ResourceMismatchViolation) {
return Violation.DETECT_RESOURCE_MISMATCH;
} else {
return Violation.DETECT_UNKNOWN;
}
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.components.strictmode;
import org.chromium.base.Function;
import java.util.List;
/** Violation that occurred. */
public class Violation {
public static final int DETECT_UNKNOWN = 0x00;
// Taken from android.os.StrictMode
public static final int DETECT_DISK_WRITE = 0x01;
public static final int DETECT_DISK_READ = 0x02;
// In almost all cases that writes needs to be masked, so do reads. Simplify that case.
public static final int DETECT_DISK_IO = DETECT_DISK_WRITE | DETECT_DISK_READ;
// Package since apps should always fix this.
static final int DETECT_RESOURCE_MISMATCH = 0x10;
// Bitmask with all of the thread strict mode violations that we are interested in.
public static final int DETECT_ALL_KNOWN = DETECT_DISK_IO | DETECT_RESOURCE_MISMATCH;
private final int mViolationType;
private final StackTraceElement[] mStackTrace;
public Violation(int violationType, StackTraceElement[] stackTrace) {
mViolationType = violationType;
mStackTrace = stackTrace;
}
boolean isInWhitelist(List<Function<Violation, Integer>> whitelist) {
for (Function<Violation, Integer> whitelistEntry : whitelist) {
Integer mask = whitelistEntry.apply(this);
if (mask != null && (~mask & violationType()) == 0) {
return true;
}
}
return false;
}
/**
* Type of violation occurred, bitmask containing 0 or more of the DETECT_* constants from AOSP.
*/
public int violationType() {
return mViolationType;
}
public StackTraceElement[] stackTrace() {
return mStackTrace;
}
/**
* A human readable string describing the type of violation that occurred. If multiple
* violations occurred, this will only contain one of them.
*/
public final String violationString() {
int type = violationType();
if ((type & DETECT_DISK_WRITE) != 0) {
return "DISK_WRITE";
} else if ((type & DETECT_DISK_READ) != 0) {
return "DISK_READ";
} else if ((type & DETECT_RESOURCE_MISMATCH) != 0) {
return "RESOURCE_MISMATCH";
}
return "UNKNOWN";
}
}
include_rules = [
"+content/public/test/android",
]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.components.strictmode;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.os.StrictMode;
import android.os.StrictMode.ThreadPolicy;
import android.support.test.InstrumentationRegistry;
import androidx.core.content.ContextCompat;
import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import java.io.File;
import java.io.FileOutputStream;
/**
* Tests for {@link ThreadStrictModeInterceptor}.
*/
@RunWith(BaseJUnit4ClassRunner.class)
public class ThreadStrictModeInterceptorTest {
/**
* Test that the penalty is not notified about whitelisted strict mode exceptions.
*/
@Test
@SmallTest
public void testWhitelisted() {
CallbackHelper strictModeDetector = new CallbackHelper();
ThreadStrictModeInterceptor.Builder threadInterceptor =
new ThreadStrictModeInterceptor.Builder();
threadInterceptor.addAllowedMethod(Violation.DETECT_DISK_IO,
"org.chromium.components.strictmode.ThreadStrictModeInterceptorTest#doDiskWrite");
TestThreadUtils.runOnUiThreadBlocking(() -> {
installThreadInterceptor(threadInterceptor, strictModeDetector);
doDiskWrite();
});
// Wait for any tasks posted to the main thread by android.os.StrictMode to complete.
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
assertEquals(0, strictModeDetector.getCallCount());
}
/**
* Test that the penalty is notified about non-whitelisted strict mode exceptions.
*/
@Test
@SmallTest
public void testNotWhitelisted() {
CallbackHelper strictModeDetector = new CallbackHelper();
ThreadStrictModeInterceptor.Builder threadInterceptor =
new ThreadStrictModeInterceptor.Builder();
TestThreadUtils.runOnUiThreadBlocking(() -> {
installThreadInterceptor(threadInterceptor, strictModeDetector);
doDiskWrite();
});
// Wait for any tasks posted to the main thread by android.os.StrictMode to complete.
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
assertTrue(strictModeDetector.getCallCount() >= 1);
}
private void doDiskWrite() {
File dataDir = ContextCompat.getDataDir(InstrumentationRegistry.getTargetContext());
File prefsDir = new File(dataDir, "shared_prefs");
File outFile = new File(prefsDir, "random.txt");
try (FileOutputStream out = new FileOutputStream(outFile)) {
out.write(1);
} catch (Exception e) {
}
}
private void installThreadInterceptor(ThreadStrictModeInterceptor.Builder threadInterceptor,
CallbackHelper strictModeDetector) {
ThreadPolicy.Builder threadPolicy =
new ThreadPolicy.Builder(StrictMode.getThreadPolicy()).penaltyLog().detectAll();
threadInterceptor.setCustomPenalty(violation -> { strictModeDetector.notifyCalled(); });
threadInterceptor.build().install(threadPolicy.build());
}
}
...@@ -5,13 +5,13 @@ ...@@ -5,13 +5,13 @@
package org.chromium.device.vr; package org.chromium.device.vr;
import android.content.Context; import android.content.Context;
import android.os.StrictMode;
import android.view.Display; import android.view.Display;
import com.google.vr.cardboard.DisplaySynchronizer; import com.google.vr.cardboard.DisplaySynchronizer;
import com.google.vr.ndk.base.GvrApi; import com.google.vr.ndk.base.GvrApi;
import org.chromium.base.ContextUtils; import org.chromium.base.ContextUtils;
import org.chromium.base.StrictModeContext;
import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace; import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeMethods; import org.chromium.base.annotations.NativeMethods;
...@@ -34,20 +34,20 @@ public class NonPresentingGvrContext { ...@@ -34,20 +34,20 @@ public class NonPresentingGvrContext {
mNativeGvrDevice = nativeGvrDevice; mNativeGvrDevice = nativeGvrDevice;
Context context = ContextUtils.getApplicationContext(); Context context = ContextUtils.getApplicationContext();
Display display = DisplayAndroidManager.getDefaultDisplayForContext(context); Display display = DisplayAndroidManager.getDefaultDisplayForContext(context);
mDisplaySynchronizer = new DisplaySynchronizer(context, display) {
@Override try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
public void onConfigurationChanged() { mDisplaySynchronizer = new DisplaySynchronizer(context, display) {
super.onConfigurationChanged(); @Override
onDisplayConfigurationChanged(); public void onConfigurationChanged() {
} super.onConfigurationChanged();
}; onDisplayConfigurationChanged();
}
};
}
// Creating the GvrApi can sometimes create the Daydream config file. // Creating the GvrApi can sometimes create the Daydream config file.
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); try (StrictModeContext ignored = StrictModeContext.allowDiskWrites()) {
try {
mGvrApi = new GvrApi(context, mDisplaySynchronizer); mGvrApi = new GvrApi(context, mDisplaySynchronizer);
} finally {
StrictMode.setThreadPolicy(oldPolicy);
} }
resume(); resume();
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment