| // Copyright 2015 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.chrome.browser; |
| |
| import android.os.Build; |
| import android.os.Looper; |
| import android.os.StrictMode; |
| import android.text.TextUtils; |
| |
| import androidx.annotation.UiThread; |
| |
| import org.chromium.base.BuildConfig; |
| import org.chromium.base.CommandLine; |
| import org.chromium.base.JavaExceptionReporter; |
| import org.chromium.base.Log; |
| import org.chromium.base.ThreadUtils; |
| import org.chromium.base.library_loader.LibraryLoader; |
| import org.chromium.chrome.browser.flags.ChromeSwitches; |
| import org.chromium.chrome.browser.version.ChromeVersionInfo; |
| 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.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| /** |
| * Initialize application-level StrictMode reporting. |
| */ |
| public class ChromeStrictMode { |
| private static final String TAG = "ChromeStrictMode"; |
| private static final double UPLOAD_PROBABILITY = 0.01; |
| private static final double MAX_UPLOADS_PER_SESSION = 3; |
| |
| private static boolean sIsStrictModeAlreadyConfigured; |
| private static List<Violation> sCachedViolations = |
| Collections.synchronizedList(new ArrayList<>()); |
| private static AtomicInteger sNumUploads = new AtomicInteger(); |
| |
| /** |
| * 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, |
| * and we have a hard cap of 3 per session, this will not affect performance too much. |
| * |
| * @param violationInfo The violation info from the StrictMode violation in question. |
| */ |
| @UiThread |
| private static void reportStrictModeViolation(Violation violation) { |
| StringWriter stackTraceWriter = new StringWriter(); |
| new StrictModePolicyViolation(violation).printStackTrace(new PrintWriter(stackTraceWriter)); |
| String stackTrace = stackTraceWriter.toString(); |
| if (TextUtils.isEmpty(stackTrace)) { |
| Log.d(TAG, "StrictMode violation stack trace was empty."); |
| } else { |
| Log.d(TAG, "Upload stack trace: " + stackTrace); |
| JavaExceptionReporter.reportStackTrace(stackTrace); |
| } |
| } |
| |
| /** |
| * Add custom {@link ThreadStrictModeInterceptor} penalty which records strict mode violations. |
| * Set up an idle handler so StrictMode violations that occur on startup are not ignored. |
| */ |
| @UiThread |
| private static void initializeStrictModeWatch( |
| ThreadStrictModeInterceptor.Builder threadInterceptor) { |
| threadInterceptor.setCustomPenalty(violation -> { |
| 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) { |
| sCachedViolations.add(violation); |
| } |
| } |
| }); |
| |
| sNumUploads.set(0); |
| // Delay handling StrictMode violations during initialization until the main loop is idle. |
| Looper.myQueue().addIdleHandler(() -> { |
| // Will retry if the native library has not been initialized. |
| if (!LibraryLoader.getInstance().isInitialized()) return true; |
| // 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. |
| if (sCachedViolations.isEmpty()) { |
| // TODO(wnwen): Add UMA count when this happens. |
| // In case of races, continue checking an extra time (equal condition). |
| return sNumUploads.get() <= MAX_UPLOADS_PER_SESSION; |
| } |
| // Since this is the only place we are removing elements, no need for additional |
| // synchronization to ensure it is still non-empty. |
| reportStrictModeViolation(sCachedViolations.remove(0)); |
| return true; |
| }); |
| } |
| |
| private static void turnOnDetection(StrictMode.ThreadPolicy.Builder threadPolicy, |
| StrictMode.VmPolicy.Builder vmPolicy) { |
| threadPolicy.detectAll(); |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { |
| vmPolicy.detectAll(); |
| } else { |
| // Explicitly enable detection of all violations except file URI leaks, as that |
| // results in false positives when file URI intents are passed between Chrome |
| // activities in separate processes. See http://crbug.com/508282#c11. |
| vmPolicy.detectActivityLeaks() |
| .detectLeakedClosableObjects() |
| .detectLeakedRegistrationObjects() |
| .detectLeakedSqlLiteObjects(); |
| } |
| } |
| |
| private static void addDefaultThreadPenalties(StrictMode.ThreadPolicy.Builder threadPolicy) { |
| threadPolicy.penaltyLog().penaltyFlashScreen().penaltyDeathOnNetwork(); |
| } |
| |
| private static void addDefaultVmPenalties(StrictMode.VmPolicy.Builder vmPolicy) { |
| vmPolicy.penaltyLog(); |
| } |
| |
| private static void addVmDeathPenalty(StrictMode.VmPolicy.Builder vmPolicy) { |
| vmPolicy.penaltyDeath(); |
| } |
| |
| /** |
| * Turn on StrictMode detection based on build and command-line switches. |
| */ |
| @UiThread |
| // FindBugs doesn't like conditionals with compile time results |
| public static void configureStrictMode() { |
| assert ThreadUtils.runningOnUiThread(); |
| if (sIsStrictModeAlreadyConfigured) { |
| return; |
| } |
| sIsStrictModeAlreadyConfigured = true; |
| |
| // Check is present so that proguard deletes the strict mode detection code for non-local |
| // non-dev-channel release builds. This is needed because ChromeVersionInfo#isLocalBuild() |
| // is not listed with -assumenosideeffects in the proguard configuration |
| if (!ChromeStrictModeSwitch.ALLOW_STRICT_MODE_CHECKING) return; |
| |
| CommandLine commandLine = CommandLine.getInstance(); |
| boolean shouldApplyPenalties = BuildConfig.DCHECK_IS_ON || ChromeVersionInfo.isLocalBuild() |
| || commandLine.hasSwitch(ChromeSwitches.STRICT_MODE); |
| |
| // Enroll 1% of dev sessions into StrictMode watch. This is done client-side rather than |
| // through finch because this decision is as early as possible in the browser initialization |
| // process. We need to detect early start-up StrictMode violations before loading native and |
| // before warming the SharedPreferences (that is a violation in an of itself). We will |
| // closely monitor this on dev channel. |
| boolean enableStrictModeWatch = |
| (ChromeVersionInfo.isLocalBuild() && !BuildConfig.DCHECK_IS_ON) |
| || (ChromeVersionInfo.isDevBuild() && Math.random() < UPLOAD_PROBABILITY); |
| if (!shouldApplyPenalties && !enableStrictModeWatch) return; |
| |
| StrictMode.ThreadPolicy.Builder threadPolicy = |
| new StrictMode.ThreadPolicy.Builder(StrictMode.getThreadPolicy()); |
| StrictMode.VmPolicy.Builder vmPolicy = |
| new StrictMode.VmPolicy.Builder(StrictMode.getVmPolicy()); |
| ThreadStrictModeInterceptor.Builder threadInterceptor = |
| new ThreadStrictModeInterceptor.Builder(); |
| turnOnDetection(threadPolicy, vmPolicy); |
| |
| if (shouldApplyPenalties) { |
| addDefaultVmPenalties(vmPolicy); |
| if ("death".equals(commandLine.getSwitchValue(ChromeSwitches.STRICT_MODE))) { |
| threadInterceptor.replaceAllPenaltiesWithDeathPenalty(); |
| addVmDeathPenalty(vmPolicy); |
| } else if ("testing".equals(commandLine.getSwitchValue(ChromeSwitches.STRICT_MODE))) { |
| threadInterceptor.replaceAllPenaltiesWithDeathPenalty(); |
| // Currently VmDeathPolicy kills the process, and is not visible on bot test output. |
| } else { |
| addDefaultThreadPenalties(threadPolicy); |
| } |
| } |
| if (enableStrictModeWatch) { |
| initializeStrictModeWatch(threadInterceptor); |
| } |
| |
| addExemptions(threadInterceptor); |
| threadInterceptor.build().install(threadPolicy.build()); |
| StrictMode.setVmPolicy(vmPolicy.build()); |
| } |
| |
| /** |
| * Add exemptions which should only be used for Chrome. Exemptions which also apply to WebLayer |
| * and WebView should be in org.chromium.components.strictmode.KnownViolations. |
| */ |
| private static void addChromeOnlyExemptions( |
| ThreadStrictModeInterceptor.Builder threadInterceptor) { |
| // Ignore strict mode violations due to SharedPreferences. |
| threadInterceptor.ignoreExternalClass( |
| Violation.DETECT_DISK_IO, "android.content.SharedPreferences"); |
| threadInterceptor.ignoreExternalMethod( |
| Violation.DETECT_DISK_IO, "android.app.ContextImpl#getSharedPreferences"); |
| |
| // Ignore strict mode violations due to xposed. |
| threadInterceptor.ignoreExternalPackage( |
| Violation.DETECT_ALL_KNOWN, "re.dobv.android.xposed"); |
| |
| // crbug.com/1121181 |
| if (Build.MANUFACTURER.toLowerCase(Locale.US).equals("samsung")) { |
| threadInterceptor.ignoreExternalMethod(Violation.DETECT_DISK_READ, |
| "android.net.ConnectivityManager#registerDefaultNetworkCallback"); |
| } |
| } |
| |
| public static void addExemptions(ThreadStrictModeInterceptor.Builder threadInterceptor) { |
| KnownViolations.addExemptions(threadInterceptor); |
| addChromeOnlyExemptions(threadInterceptor); |
| |
| // WebView code must be strict mode clean on all devices. Since Chrome uploads strict mode |
| // violations but WebView does not, detect strict mode violations which originate from |
| // org.chromium.content in Chrome for all devices in order to provide extra strict mode |
| // coverage for webview. Ignore for now strict mode violations in other packages for |
| // non-Nexus, non-Pixel devices to improve the signal to noise ratio. |
| String lowercaseModel = Build.MODEL.toLowerCase(Locale.US); |
| if (!lowercaseModel.contains("nexus") && !lowercaseModel.contains("pixel")) { |
| threadInterceptor.onlyDetectViolationsForPackage("org.chromium.content"); |
| } |
| } |
| } |