3237 lines
129 KiB
Java
3237 lines
129 KiB
Java
/*
|
|
* Copyright (C) 2010 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 android.os;
|
|
|
|
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
|
|
|
|
import android.animation.ValueAnimator;
|
|
import android.annotation.IntDef;
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
import android.annotation.SystemApi;
|
|
import android.annotation.TestApi;
|
|
import android.app.ActivityManager;
|
|
import android.app.ActivityThread;
|
|
import android.app.IActivityManager;
|
|
import android.app.IUnsafeIntentStrictModeCallback;
|
|
import android.app.compat.CompatChanges;
|
|
import android.compat.annotation.ChangeId;
|
|
import android.compat.annotation.EnabledSince;
|
|
import android.compat.annotation.UnsupportedAppUsage;
|
|
import android.content.BroadcastReceiver;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.ServiceConnection;
|
|
import android.content.pm.ApplicationInfo;
|
|
import android.content.pm.PackageManager;
|
|
import android.content.res.Configuration;
|
|
import android.net.TrafficStats;
|
|
import android.net.Uri;
|
|
import android.os.storage.IStorageManager;
|
|
import android.os.strictmode.CleartextNetworkViolation;
|
|
import android.os.strictmode.ContentUriWithoutPermissionViolation;
|
|
import android.os.strictmode.CredentialProtectedWhileLockedViolation;
|
|
import android.os.strictmode.CustomViolation;
|
|
import android.os.strictmode.DiskReadViolation;
|
|
import android.os.strictmode.DiskWriteViolation;
|
|
import android.os.strictmode.ExplicitGcViolation;
|
|
import android.os.strictmode.FileUriExposedViolation;
|
|
import android.os.strictmode.ImplicitDirectBootViolation;
|
|
import android.os.strictmode.IncorrectContextUseViolation;
|
|
import android.os.strictmode.InstanceCountViolation;
|
|
import android.os.strictmode.IntentReceiverLeakedViolation;
|
|
import android.os.strictmode.LeakedClosableViolation;
|
|
import android.os.strictmode.NetworkViolation;
|
|
import android.os.strictmode.NonSdkApiUsedViolation;
|
|
import android.os.strictmode.ResourceMismatchViolation;
|
|
import android.os.strictmode.ServiceConnectionLeakedViolation;
|
|
import android.os.strictmode.SqliteObjectLeakedViolation;
|
|
import android.os.strictmode.UnbufferedIoViolation;
|
|
import android.os.strictmode.UnsafeIntentLaunchViolation;
|
|
import android.os.strictmode.UntaggedSocketViolation;
|
|
import android.os.strictmode.Violation;
|
|
import android.os.strictmode.WebViewMethodCalledOnWrongThreadViolation;
|
|
import android.util.ArrayMap;
|
|
import android.util.Log;
|
|
import android.util.Printer;
|
|
import android.util.Singleton;
|
|
import android.util.Slog;
|
|
import android.util.SparseLongArray;
|
|
import android.view.IWindowManager;
|
|
|
|
import com.android.internal.annotations.GuardedBy;
|
|
import com.android.internal.os.BackgroundThread;
|
|
import com.android.internal.os.RuntimeInit;
|
|
import com.android.internal.util.FastPrintWriter;
|
|
import com.android.internal.util.HexDump;
|
|
import com.android.internal.util.Preconditions;
|
|
|
|
import dalvik.system.BlockGuard;
|
|
import dalvik.system.CloseGuard;
|
|
import dalvik.system.VMDebug;
|
|
import dalvik.system.VMRuntime;
|
|
|
|
import java.io.PrintWriter;
|
|
import java.io.StringWriter;
|
|
import java.lang.annotation.Retention;
|
|
import java.lang.annotation.RetentionPolicy;
|
|
import java.net.InetAddress;
|
|
import java.net.UnknownHostException;
|
|
import java.util.ArrayDeque;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Deque;
|
|
import java.util.HashMap;
|
|
import java.util.concurrent.Executor;
|
|
import java.util.concurrent.RejectedExecutionException;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
import java.util.function.Consumer;
|
|
|
|
/**
|
|
* StrictMode is a developer tool which detects things you might be doing by accident and brings
|
|
* them to your attention so you can fix them.
|
|
*
|
|
* <p>StrictMode is most commonly used to catch accidental disk or network access on the
|
|
* application's main thread, where UI operations are received and animations take place. Keeping
|
|
* disk and network operations off the main thread makes for much smoother, more responsive
|
|
* applications. By keeping your application's main thread responsive, you also prevent <a
|
|
* href="{@docRoot}guide/practices/design/responsiveness.html">ANR dialogs</a> from being shown to
|
|
* users.
|
|
*
|
|
* <p class="note">Note that even though an Android device's disk is often on flash memory, many
|
|
* devices run a filesystem on top of that memory with very limited concurrency. It's often the case
|
|
* that almost all disk accesses are fast, but may in individual cases be dramatically slower when
|
|
* certain I/O is happening in the background from other processes. If possible, it's best to assume
|
|
* that such things are not fast.
|
|
*
|
|
* <p>Example code to enable from early in your {@link android.app.Application}, {@link
|
|
* android.app.Activity}, or other application component's {@link android.app.Application#onCreate}
|
|
* method:
|
|
*
|
|
* <pre>
|
|
* public void onCreate() {
|
|
* StrictMode.setThreadPolicy(new {@link ThreadPolicy.Builder StrictMode.ThreadPolicy.Builder}()
|
|
* .detectDiskReads()
|
|
* .detectDiskWrites()
|
|
* .detectNetwork() // or .detectAll() for all detectable problems
|
|
* .penaltyLog()
|
|
* .build());
|
|
* StrictMode.setVmPolicy(new {@link VmPolicy.Builder StrictMode.VmPolicy.Builder}()
|
|
* .detectLeakedSqlLiteObjects()
|
|
* .detectLeakedClosableObjects()
|
|
* .penaltyLog()
|
|
* .penaltyDeath()
|
|
* .build());
|
|
* super.onCreate();
|
|
* }
|
|
* </pre>
|
|
*
|
|
* <p>You can decide what should happen when a violation is detected. For example, using {@link
|
|
* ThreadPolicy.Builder#penaltyLog} you can watch the output of <code>adb logcat</code> while you
|
|
* use your application to see the violations as they happen.
|
|
*
|
|
* <p>If you find violations that you feel are problematic, there are a variety of tools to help
|
|
* solve them: threads, {@link android.os.Handler}, {@link android.os.AsyncTask}, {@link
|
|
* android.app.IntentService}, etc. But don't feel compelled to fix everything that StrictMode
|
|
* finds. In particular, many cases of disk access are often necessary during the normal activity
|
|
* lifecycle. Use StrictMode to find things you did by accident. Network requests on the UI thread
|
|
* are almost always a problem, though.
|
|
*
|
|
* <p class="note">StrictMode is not a security mechanism and is not guaranteed to find all disk or
|
|
* network accesses. While it does propagate its state across process boundaries when doing {@link
|
|
* android.os.Binder} calls, it's still ultimately a best effort mechanism. Notably, disk or network
|
|
* access from JNI calls won't necessarily trigger it.
|
|
*/
|
|
@android.ravenwood.annotation.RavenwoodKeepPartialClass
|
|
public final class StrictMode {
|
|
private static final String TAG = "StrictMode";
|
|
private static final boolean LOG_V = Log.isLoggable(TAG, Log.VERBOSE);
|
|
|
|
/**
|
|
* Boolean system property to disable strict mode checks outright. Set this to 'true' to force
|
|
* disable; 'false' has no effect on other enable/disable policy.
|
|
*
|
|
* @hide
|
|
*/
|
|
public static final String DISABLE_PROPERTY = "persist.sys.strictmode.disable";
|
|
|
|
/**
|
|
* The boolean system property to control screen flashes on violations.
|
|
*
|
|
* @hide
|
|
*/
|
|
public static final String VISUAL_PROPERTY = "persist.sys.strictmode.visual";
|
|
|
|
/**
|
|
* Temporary property used to include {@link #DETECT_VM_CLEARTEXT_NETWORK} in {@link
|
|
* VmPolicy.Builder#detectAll()}. Apps can still always opt-into detection using {@link
|
|
* VmPolicy.Builder#detectCleartextNetwork()}.
|
|
*/
|
|
private static final String CLEARTEXT_PROPERTY = "persist.sys.strictmode.clear";
|
|
|
|
/**
|
|
* Quick feature-flag that can be used to disable the defaults provided by {@link
|
|
* #initThreadDefaults(ApplicationInfo)} and {@link #initVmDefaults(ApplicationInfo)}.
|
|
*/
|
|
private static final boolean DISABLE = false;
|
|
|
|
// Only apply VM penalties for the same violation at this interval.
|
|
private static final long MIN_VM_INTERVAL_MS = 1000;
|
|
|
|
// Only log a duplicate stack trace to the logs every second.
|
|
private static final long MIN_LOG_INTERVAL_MS = 1000;
|
|
|
|
// Only show an annoying dialog at most every 30 seconds
|
|
private static final long MIN_DIALOG_INTERVAL_MS = 30000;
|
|
|
|
// Only log a dropbox entry at most every 30 seconds
|
|
private static final long MIN_DROPBOX_INTERVAL_MS = 3000;
|
|
|
|
// How many Span tags (e.g. animations) to report.
|
|
private static final int MAX_SPAN_TAGS = 20;
|
|
|
|
// How many offending stacks to keep track of (and time) per loop
|
|
// of the Looper.
|
|
private static final int MAX_OFFENSES_PER_LOOP = 10;
|
|
|
|
/** @hide */
|
|
@IntDef(flag = true, prefix = { "DETECT_THREAD_", "PENALTY_" }, value = {
|
|
DETECT_THREAD_DISK_WRITE,
|
|
DETECT_THREAD_DISK_READ,
|
|
DETECT_THREAD_NETWORK,
|
|
DETECT_THREAD_CUSTOM,
|
|
DETECT_THREAD_RESOURCE_MISMATCH,
|
|
DETECT_THREAD_UNBUFFERED_IO,
|
|
DETECT_THREAD_EXPLICIT_GC,
|
|
PENALTY_GATHER,
|
|
PENALTY_LOG,
|
|
PENALTY_DIALOG,
|
|
PENALTY_DEATH,
|
|
PENALTY_FLASH,
|
|
PENALTY_DROPBOX,
|
|
PENALTY_DEATH_ON_NETWORK,
|
|
PENALTY_DEATH_ON_CLEARTEXT_NETWORK,
|
|
PENALTY_DEATH_ON_FILE_URI_EXPOSURE,
|
|
})
|
|
@Retention(RetentionPolicy.SOURCE)
|
|
public @interface ThreadPolicyMask {}
|
|
|
|
// Thread policy: bits 0-15
|
|
|
|
/** @hide */
|
|
private static final int DETECT_THREAD_DISK_WRITE = 1 << 0;
|
|
/** @hide */
|
|
private static final int DETECT_THREAD_DISK_READ = 1 << 1;
|
|
/** @hide */
|
|
private static final int DETECT_THREAD_NETWORK = 1 << 2;
|
|
/** @hide */
|
|
private static final int DETECT_THREAD_CUSTOM = 1 << 3;
|
|
/** @hide */
|
|
private static final int DETECT_THREAD_RESOURCE_MISMATCH = 1 << 4;
|
|
/** @hide */
|
|
private static final int DETECT_THREAD_UNBUFFERED_IO = 1 << 5;
|
|
/** @hide */
|
|
private static final int DETECT_THREAD_EXPLICIT_GC = 1 << 6;
|
|
|
|
/** @hide */
|
|
private static final int DETECT_THREAD_ALL = 0x0000ffff;
|
|
|
|
/** @hide */
|
|
@IntDef(flag = true, prefix = { "DETECT_THREAD_", "PENALTY_" }, value = {
|
|
DETECT_VM_CURSOR_LEAKS,
|
|
DETECT_VM_CLOSABLE_LEAKS,
|
|
DETECT_VM_ACTIVITY_LEAKS,
|
|
DETECT_VM_INSTANCE_LEAKS,
|
|
DETECT_VM_REGISTRATION_LEAKS,
|
|
DETECT_VM_FILE_URI_EXPOSURE,
|
|
DETECT_VM_CLEARTEXT_NETWORK,
|
|
DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION,
|
|
DETECT_VM_UNTAGGED_SOCKET,
|
|
DETECT_VM_NON_SDK_API_USAGE,
|
|
DETECT_VM_IMPLICIT_DIRECT_BOOT,
|
|
DETECT_VM_INCORRECT_CONTEXT_USE,
|
|
DETECT_VM_UNSAFE_INTENT_LAUNCH,
|
|
PENALTY_GATHER,
|
|
PENALTY_LOG,
|
|
PENALTY_DIALOG,
|
|
PENALTY_DEATH,
|
|
PENALTY_FLASH,
|
|
PENALTY_DROPBOX,
|
|
PENALTY_DEATH_ON_NETWORK,
|
|
PENALTY_DEATH_ON_CLEARTEXT_NETWORK,
|
|
PENALTY_DEATH_ON_FILE_URI_EXPOSURE,
|
|
})
|
|
@Retention(RetentionPolicy.SOURCE)
|
|
public @interface VmPolicyMask {}
|
|
|
|
// VM policy: bits 0-15
|
|
|
|
/** @hide */
|
|
private static final int DETECT_VM_CURSOR_LEAKS = 1 << 0;
|
|
/** @hide */
|
|
private static final int DETECT_VM_CLOSABLE_LEAKS = 1 << 1;
|
|
/** @hide */
|
|
private static final int DETECT_VM_ACTIVITY_LEAKS = 1 << 2;
|
|
/** @hide */
|
|
private static final int DETECT_VM_INSTANCE_LEAKS = 1 << 3;
|
|
/** @hide */
|
|
private static final int DETECT_VM_REGISTRATION_LEAKS = 1 << 4;
|
|
/** @hide */
|
|
private static final int DETECT_VM_FILE_URI_EXPOSURE = 1 << 5;
|
|
/** @hide */
|
|
private static final int DETECT_VM_CLEARTEXT_NETWORK = 1 << 6;
|
|
/** @hide */
|
|
private static final int DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION = 1 << 7;
|
|
/** @hide */
|
|
private static final int DETECT_VM_UNTAGGED_SOCKET = 1 << 8;
|
|
/** @hide */
|
|
private static final int DETECT_VM_NON_SDK_API_USAGE = 1 << 9;
|
|
/** @hide */
|
|
private static final int DETECT_VM_IMPLICIT_DIRECT_BOOT = 1 << 10;
|
|
/** @hide */
|
|
private static final int DETECT_VM_CREDENTIAL_PROTECTED_WHILE_LOCKED = 1 << 11;
|
|
/** @hide */
|
|
private static final int DETECT_VM_INCORRECT_CONTEXT_USE = 1 << 12;
|
|
/** @hide */
|
|
private static final int DETECT_VM_UNSAFE_INTENT_LAUNCH = 1 << 13;
|
|
|
|
/** @hide */
|
|
private static final int DETECT_VM_ALL = 0x0000ffff;
|
|
|
|
// Penalty policy: bits 16-31
|
|
|
|
/**
|
|
* Non-public penalty mode which overrides all the other penalty bits and signals that we're in
|
|
* a Binder call and we should ignore the other penalty bits and instead serialize back all our
|
|
* offending stack traces to the caller to ultimately handle in the originating process.
|
|
*
|
|
* <p>This must be kept in sync with the constant in libs/binder/Parcel.cpp
|
|
*
|
|
* @hide
|
|
*/
|
|
public static final int PENALTY_GATHER = 1 << 31;
|
|
|
|
/** {@hide} */
|
|
public static final int PENALTY_LOG = 1 << 30;
|
|
/** {@hide} */
|
|
public static final int PENALTY_DIALOG = 1 << 29;
|
|
/** {@hide} */
|
|
public static final int PENALTY_DEATH = 1 << 28;
|
|
/** {@hide} */
|
|
public static final int PENALTY_FLASH = 1 << 27;
|
|
/** {@hide} */
|
|
public static final int PENALTY_DROPBOX = 1 << 26;
|
|
/** {@hide} */
|
|
public static final int PENALTY_DEATH_ON_NETWORK = 1 << 25;
|
|
/** {@hide} */
|
|
public static final int PENALTY_DEATH_ON_CLEARTEXT_NETWORK = 1 << 24;
|
|
/** {@hide} */
|
|
public static final int PENALTY_DEATH_ON_FILE_URI_EXPOSURE = 1 << 23;
|
|
|
|
/** @hide */
|
|
public static final int PENALTY_ALL = 0xffff0000;
|
|
|
|
/** {@hide} */
|
|
public static final int NETWORK_POLICY_ACCEPT = 0;
|
|
/** {@hide} */
|
|
public static final int NETWORK_POLICY_LOG = 1;
|
|
/** {@hide} */
|
|
public static final int NETWORK_POLICY_REJECT = 2;
|
|
|
|
/**
|
|
* Detect explicit calls to {@link Runtime#gc()}.
|
|
*/
|
|
@ChangeId
|
|
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
|
|
static final long DETECT_EXPLICIT_GC = 3400644L;
|
|
|
|
// TODO: wrap in some ImmutableHashMap thing.
|
|
// Note: must be before static initialization of sVmPolicy.
|
|
private static final HashMap<Class, Integer> EMPTY_CLASS_LIMIT_MAP =
|
|
new HashMap<Class, Integer>();
|
|
|
|
/** The current VmPolicy in effect. */
|
|
private static volatile VmPolicy sVmPolicy = VmPolicy.LAX;
|
|
|
|
/** {@hide} */
|
|
@TestApi
|
|
public interface ViolationLogger {
|
|
|
|
/** Called when penaltyLog is enabled and a violation needs logging. */
|
|
void log(ViolationInfo info);
|
|
}
|
|
|
|
private static final ViolationLogger LOGCAT_LOGGER =
|
|
info -> {
|
|
String msg;
|
|
if (info.durationMillis != -1) {
|
|
msg = "StrictMode policy violation; ~duration=" + info.durationMillis + " ms:";
|
|
} else {
|
|
msg = "StrictMode policy violation:";
|
|
}
|
|
Log.d(TAG, msg + " " + info.getStackTrace());
|
|
};
|
|
|
|
private static volatile ViolationLogger sLogger = LOGCAT_LOGGER;
|
|
|
|
private static final ThreadLocal<OnThreadViolationListener> sThreadViolationListener =
|
|
new ThreadLocal<>();
|
|
private static final ThreadLocal<Executor> sThreadViolationExecutor = new ThreadLocal<>();
|
|
|
|
/**
|
|
* When #{@link ThreadPolicy.Builder#penaltyListener} is enabled, the listener is called on the
|
|
* provided executor when a Thread violation occurs.
|
|
*/
|
|
public interface OnThreadViolationListener {
|
|
/** Called on a thread policy violation. */
|
|
void onThreadViolation(Violation v);
|
|
}
|
|
|
|
/**
|
|
* When #{@link VmPolicy.Builder#penaltyListener} is enabled, the listener is called on the
|
|
* provided executor when a VM violation occurs.
|
|
*/
|
|
public interface OnVmViolationListener {
|
|
/** Called on a VM policy violation. */
|
|
void onVmViolation(Violation v);
|
|
}
|
|
|
|
/** {@hide} */
|
|
@TestApi
|
|
public static void setViolationLogger(ViolationLogger listener) {
|
|
if (listener == null) {
|
|
listener = LOGCAT_LOGGER;
|
|
}
|
|
sLogger = listener;
|
|
}
|
|
|
|
/**
|
|
* The number of threads trying to do an async dropbox write. Just to limit ourselves out of
|
|
* paranoia.
|
|
*/
|
|
private static final AtomicInteger sDropboxCallsInFlight = new AtomicInteger(0);
|
|
|
|
/**
|
|
* Callback supplied to dalvik / libcore to get informed of usages of java API that are not
|
|
* a part of the public SDK.
|
|
*/
|
|
private static final Consumer<String> sNonSdkApiUsageConsumer =
|
|
message -> onVmPolicyViolation(new NonSdkApiUsedViolation(message));
|
|
|
|
private StrictMode() {}
|
|
|
|
/**
|
|
* {@link StrictMode} policy applied to a certain thread.
|
|
*
|
|
* <p>The policy is enabled by {@link #setThreadPolicy}. The current policy can be retrieved
|
|
* with {@link #getThreadPolicy}.
|
|
*
|
|
* <p>Note that multiple penalties may be provided and they're run in order from least to most
|
|
* severe (logging before process death, for example). There's currently no mechanism to choose
|
|
* different penalties for different detected actions.
|
|
*/
|
|
public static final class ThreadPolicy {
|
|
/** The lax policy which doesn't catch anything. */
|
|
public static final ThreadPolicy LAX = new ThreadPolicy(0, null, null);
|
|
|
|
@UnsupportedAppUsage
|
|
final @ThreadPolicyMask int mask;
|
|
final OnThreadViolationListener mListener;
|
|
final Executor mCallbackExecutor;
|
|
|
|
private ThreadPolicy(@ThreadPolicyMask int mask, OnThreadViolationListener listener,
|
|
Executor executor) {
|
|
this.mask = mask;
|
|
mListener = listener;
|
|
mCallbackExecutor = executor;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "[StrictMode.ThreadPolicy; mask=" + mask + "]";
|
|
}
|
|
|
|
/**
|
|
* Creates {@link ThreadPolicy} instances. Methods whose names start with {@code detect}
|
|
* specify what problems we should look for. Methods whose names start with {@code penalty}
|
|
* specify what we should do when we detect a problem.
|
|
*
|
|
* <p>You can call as many {@code detect} and {@code penalty} methods as you like. Currently
|
|
* order is insignificant: all penalties apply to all detected problems.
|
|
*
|
|
* <p>For example, detect everything and log anything that's found:
|
|
*
|
|
* <pre>
|
|
* StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder()
|
|
* .detectAll()
|
|
* .penaltyLog()
|
|
* .build();
|
|
* StrictMode.setThreadPolicy(policy);
|
|
* </pre>
|
|
*/
|
|
public static final class Builder {
|
|
private @ThreadPolicyMask int mMask = 0;
|
|
private OnThreadViolationListener mListener;
|
|
private Executor mExecutor;
|
|
|
|
/**
|
|
* Create a Builder that detects nothing and has no violations. (but note that {@link
|
|
* #build} will default to enabling {@link #penaltyLog} if no other penalties are
|
|
* specified)
|
|
*/
|
|
public Builder() {
|
|
mMask = 0;
|
|
}
|
|
|
|
/** Initialize a Builder from an existing ThreadPolicy. */
|
|
public Builder(ThreadPolicy policy) {
|
|
mMask = policy.mask;
|
|
mListener = policy.mListener;
|
|
mExecutor = policy.mCallbackExecutor;
|
|
}
|
|
|
|
/**
|
|
* Detect everything that's potentially suspect.
|
|
*
|
|
* <p>As of the Gingerbread release this includes network and disk operations but will
|
|
* likely expand in future releases.
|
|
*/
|
|
@SuppressWarnings("AndroidFrameworkCompatChange")
|
|
public @NonNull Builder detectAll() {
|
|
detectDiskReads();
|
|
detectDiskWrites();
|
|
detectNetwork();
|
|
|
|
final int targetSdk = VMRuntime.getRuntime().getTargetSdkVersion();
|
|
if (targetSdk >= Build.VERSION_CODES.HONEYCOMB) {
|
|
detectCustomSlowCalls();
|
|
}
|
|
if (targetSdk >= Build.VERSION_CODES.M) {
|
|
detectResourceMismatches();
|
|
}
|
|
if (targetSdk >= Build.VERSION_CODES.O) {
|
|
detectUnbufferedIo();
|
|
}
|
|
if (CompatChanges.isChangeEnabled(DETECT_EXPLICIT_GC)) {
|
|
detectExplicitGc();
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/** Disable the detection of everything. */
|
|
public @NonNull Builder permitAll() {
|
|
return disable(DETECT_THREAD_ALL);
|
|
}
|
|
|
|
/** Enable detection of network operations. */
|
|
public @NonNull Builder detectNetwork() {
|
|
return enable(DETECT_THREAD_NETWORK);
|
|
}
|
|
|
|
/** Disable detection of network operations. */
|
|
public @NonNull Builder permitNetwork() {
|
|
return disable(DETECT_THREAD_NETWORK);
|
|
}
|
|
|
|
/** Enable detection of disk reads. */
|
|
public @NonNull Builder detectDiskReads() {
|
|
return enable(DETECT_THREAD_DISK_READ);
|
|
}
|
|
|
|
/** Disable detection of disk reads. */
|
|
public @NonNull Builder permitDiskReads() {
|
|
return disable(DETECT_THREAD_DISK_READ);
|
|
}
|
|
|
|
/** Enable detection of slow calls. */
|
|
public @NonNull Builder detectCustomSlowCalls() {
|
|
return enable(DETECT_THREAD_CUSTOM);
|
|
}
|
|
|
|
/** Disable detection of slow calls. */
|
|
public @NonNull Builder permitCustomSlowCalls() {
|
|
return disable(DETECT_THREAD_CUSTOM);
|
|
}
|
|
|
|
/** Disable detection of mismatches between defined resource types and getter calls. */
|
|
public @NonNull Builder permitResourceMismatches() {
|
|
return disable(DETECT_THREAD_RESOURCE_MISMATCH);
|
|
}
|
|
|
|
/** Detect unbuffered input/output operations. */
|
|
public @NonNull Builder detectUnbufferedIo() {
|
|
return enable(DETECT_THREAD_UNBUFFERED_IO);
|
|
}
|
|
|
|
/** Disable detection of unbuffered input/output operations. */
|
|
public @NonNull Builder permitUnbufferedIo() {
|
|
return disable(DETECT_THREAD_UNBUFFERED_IO);
|
|
}
|
|
|
|
/**
|
|
* Enables detection of mismatches between defined resource types and getter calls.
|
|
*
|
|
* <p>This helps detect accidental type mismatches and potentially expensive type
|
|
* conversions when obtaining typed resources.
|
|
*
|
|
* <p>For example, a strict mode violation would be thrown when calling {@link
|
|
* android.content.res.TypedArray#getInt(int, int)} on an index that contains a
|
|
* String-type resource. If the string value can be parsed as an integer, this method
|
|
* call will return a value without crashing; however, the developer should format the
|
|
* resource as an integer to avoid unnecessary type conversion.
|
|
*/
|
|
public @NonNull Builder detectResourceMismatches() {
|
|
return enable(DETECT_THREAD_RESOURCE_MISMATCH);
|
|
}
|
|
|
|
/** Enable detection of disk writes. */
|
|
public @NonNull Builder detectDiskWrites() {
|
|
return enable(DETECT_THREAD_DISK_WRITE);
|
|
}
|
|
|
|
/** Disable detection of disk writes. */
|
|
public @NonNull Builder permitDiskWrites() {
|
|
return disable(DETECT_THREAD_DISK_WRITE);
|
|
}
|
|
|
|
/**
|
|
* Detect calls to {@link Runtime#gc()}.
|
|
*/
|
|
public @NonNull Builder detectExplicitGc() {
|
|
return enable(DETECT_THREAD_EXPLICIT_GC);
|
|
}
|
|
|
|
/**
|
|
* Disable detection of calls to {@link Runtime#gc()}.
|
|
*/
|
|
public @NonNull Builder permitExplicitGc() {
|
|
return disable(DETECT_THREAD_EXPLICIT_GC);
|
|
}
|
|
|
|
/**
|
|
* Show an annoying dialog to the developer on detected violations, rate-limited to be
|
|
* only a little annoying.
|
|
*/
|
|
public @NonNull Builder penaltyDialog() {
|
|
return enable(PENALTY_DIALOG);
|
|
}
|
|
|
|
/**
|
|
* Crash the whole process on violation. This penalty runs at the end of all enabled
|
|
* penalties so you'll still get see logging or other violations before the process
|
|
* dies.
|
|
*
|
|
* <p>Unlike {@link #penaltyDeathOnNetwork}, this applies to disk reads, disk writes,
|
|
* and network usage if their corresponding detect flags are set.
|
|
*/
|
|
public @NonNull Builder penaltyDeath() {
|
|
return enable(PENALTY_DEATH);
|
|
}
|
|
|
|
/**
|
|
* Crash the whole process on any network usage. Unlike {@link #penaltyDeath}, this
|
|
* penalty runs <em>before</em> anything else. You must still have called {@link
|
|
* #detectNetwork} to enable this.
|
|
*
|
|
* <p>In the Honeycomb or later SDKs, this is on by default.
|
|
*/
|
|
public @NonNull Builder penaltyDeathOnNetwork() {
|
|
return enable(PENALTY_DEATH_ON_NETWORK);
|
|
}
|
|
|
|
/** Flash the screen during a violation. */
|
|
public @NonNull Builder penaltyFlashScreen() {
|
|
return enable(PENALTY_FLASH);
|
|
}
|
|
|
|
/** Log detected violations to the system log. */
|
|
public @NonNull Builder penaltyLog() {
|
|
return enable(PENALTY_LOG);
|
|
}
|
|
|
|
/**
|
|
* Enable detected violations log a stacktrace and timing data to the {@link
|
|
* android.os.DropBoxManager DropBox} on policy violation. Intended mostly for platform
|
|
* integrators doing beta user field data collection.
|
|
*/
|
|
public @NonNull Builder penaltyDropBox() {
|
|
return enable(PENALTY_DROPBOX);
|
|
}
|
|
|
|
/**
|
|
* Call #{@link OnThreadViolationListener#onThreadViolation(Violation)} on specified
|
|
* executor every violation.
|
|
*/
|
|
public @NonNull Builder penaltyListener(
|
|
@NonNull Executor executor, @NonNull OnThreadViolationListener listener) {
|
|
if (executor == null) {
|
|
throw new NullPointerException("executor must not be null");
|
|
}
|
|
mListener = listener;
|
|
mExecutor = executor;
|
|
return this;
|
|
}
|
|
|
|
/** @removed */
|
|
public @NonNull Builder penaltyListener(
|
|
@NonNull OnThreadViolationListener listener, @NonNull Executor executor) {
|
|
return penaltyListener(executor, listener);
|
|
}
|
|
|
|
private Builder enable(@ThreadPolicyMask int mask) {
|
|
mMask |= mask;
|
|
return this;
|
|
}
|
|
|
|
private Builder disable(@ThreadPolicyMask int mask) {
|
|
mMask &= ~mask;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Construct the ThreadPolicy instance.
|
|
*
|
|
* <p>Note: if no penalties are enabled before calling <code>build</code>, {@link
|
|
* #penaltyLog} is implicitly set.
|
|
*/
|
|
public ThreadPolicy build() {
|
|
// If there are detection bits set but no violation bits
|
|
// set, enable simple logging.
|
|
if (mListener == null
|
|
&& mMask != 0
|
|
&& (mMask
|
|
& (PENALTY_DEATH
|
|
| PENALTY_LOG
|
|
| PENALTY_DROPBOX
|
|
| PENALTY_DIALOG))
|
|
== 0) {
|
|
penaltyLog();
|
|
}
|
|
return new ThreadPolicy(mMask, mListener, mExecutor);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* {@link StrictMode} policy applied to all threads in the virtual machine's process.
|
|
*
|
|
* <p>The policy is enabled by {@link #setVmPolicy}.
|
|
*/
|
|
public static final class VmPolicy {
|
|
/** The lax policy which doesn't catch anything. */
|
|
public static final VmPolicy LAX = new VmPolicy(0, EMPTY_CLASS_LIMIT_MAP, null, null);
|
|
|
|
@UnsupportedAppUsage
|
|
final @VmPolicyMask int mask;
|
|
final OnVmViolationListener mListener;
|
|
final Executor mCallbackExecutor;
|
|
|
|
// Map from class to max number of allowed instances in memory.
|
|
final HashMap<Class, Integer> classInstanceLimit;
|
|
|
|
private VmPolicy(
|
|
@VmPolicyMask int mask,
|
|
HashMap<Class, Integer> classInstanceLimit,
|
|
OnVmViolationListener listener,
|
|
Executor executor) {
|
|
if (classInstanceLimit == null) {
|
|
throw new NullPointerException("classInstanceLimit == null");
|
|
}
|
|
this.mask = mask;
|
|
this.classInstanceLimit = classInstanceLimit;
|
|
mListener = listener;
|
|
mCallbackExecutor = executor;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "[StrictMode.VmPolicy; mask=" + mask + "]";
|
|
}
|
|
|
|
/**
|
|
* Creates {@link VmPolicy} instances. Methods whose names start with {@code detect} specify
|
|
* what problems we should look for. Methods whose names start with {@code penalty} specify
|
|
* what we should do when we detect a problem.
|
|
*
|
|
* <p>You can call as many {@code detect} and {@code penalty} methods as you like. Currently
|
|
* order is insignificant: all penalties apply to all detected problems.
|
|
*
|
|
* <p>For example, detect everything and log anything that's found:
|
|
*
|
|
* <pre>
|
|
* StrictMode.VmPolicy policy = new StrictMode.VmPolicy.Builder()
|
|
* .detectAll()
|
|
* .penaltyLog()
|
|
* .build();
|
|
* StrictMode.setVmPolicy(policy);
|
|
* </pre>
|
|
*/
|
|
public static final class Builder {
|
|
@UnsupportedAppUsage
|
|
private @VmPolicyMask int mMask;
|
|
private OnVmViolationListener mListener;
|
|
private Executor mExecutor;
|
|
|
|
private HashMap<Class, Integer> mClassInstanceLimit; // null until needed
|
|
private boolean mClassInstanceLimitNeedCow = false; // need copy-on-write
|
|
|
|
public Builder() {
|
|
mMask = 0;
|
|
}
|
|
|
|
/** Build upon an existing VmPolicy. */
|
|
public Builder(VmPolicy base) {
|
|
mMask = base.mask;
|
|
mClassInstanceLimitNeedCow = true;
|
|
mClassInstanceLimit = base.classInstanceLimit;
|
|
mListener = base.mListener;
|
|
mExecutor = base.mCallbackExecutor;
|
|
}
|
|
|
|
/**
|
|
* Set an upper bound on how many instances of a class can be in memory at once. Helps
|
|
* to prevent object leaks.
|
|
*/
|
|
public @NonNull Builder setClassInstanceLimit(Class klass, int instanceLimit) {
|
|
if (klass == null) {
|
|
throw new NullPointerException("klass == null");
|
|
}
|
|
if (mClassInstanceLimitNeedCow) {
|
|
if (mClassInstanceLimit.containsKey(klass)
|
|
&& mClassInstanceLimit.get(klass) == instanceLimit) {
|
|
// no-op; don't break COW
|
|
return this;
|
|
}
|
|
mClassInstanceLimitNeedCow = false;
|
|
mClassInstanceLimit = (HashMap<Class, Integer>) mClassInstanceLimit.clone();
|
|
} else if (mClassInstanceLimit == null) {
|
|
mClassInstanceLimit = new HashMap<Class, Integer>();
|
|
}
|
|
mMask |= DETECT_VM_INSTANCE_LEAKS;
|
|
mClassInstanceLimit.put(klass, instanceLimit);
|
|
return this;
|
|
}
|
|
|
|
/** Detect leaks of {@link android.app.Activity} subclasses. */
|
|
public @NonNull Builder detectActivityLeaks() {
|
|
return enable(DETECT_VM_ACTIVITY_LEAKS);
|
|
}
|
|
|
|
/** @hide */
|
|
public @NonNull Builder permitActivityLeaks() {
|
|
synchronized (StrictMode.class) {
|
|
sExpectedActivityInstanceCount.clear();
|
|
}
|
|
return disable(DETECT_VM_ACTIVITY_LEAKS);
|
|
}
|
|
|
|
/**
|
|
* Detect reflective usage of APIs that are not part of the public Android SDK.
|
|
*
|
|
* <p>Note that any non-SDK APIs that this processes accesses before this detection is
|
|
* enabled may not be detected. To ensure that all such API accesses are detected,
|
|
* you should apply this policy as early as possible after process creation.
|
|
*/
|
|
public @NonNull Builder detectNonSdkApiUsage() {
|
|
return enable(DETECT_VM_NON_SDK_API_USAGE);
|
|
}
|
|
|
|
/**
|
|
* Permit reflective usage of APIs that are not part of the public Android SDK. Note
|
|
* that this <b>only</b> affects {@code StrictMode}, the underlying runtime may
|
|
* continue to restrict or warn on access to methods that are not part of the
|
|
* public SDK.
|
|
*/
|
|
public @NonNull Builder permitNonSdkApiUsage() {
|
|
return disable(DETECT_VM_NON_SDK_API_USAGE);
|
|
}
|
|
|
|
/**
|
|
* Detect everything that's potentially suspect.
|
|
*
|
|
* <p>In the Honeycomb release this includes leaks of SQLite cursors, Activities, and
|
|
* other closable objects but will likely expand in future releases.
|
|
*/
|
|
@SuppressWarnings("AndroidFrameworkCompatChange")
|
|
public @NonNull Builder detectAll() {
|
|
detectLeakedSqlLiteObjects();
|
|
|
|
final int targetSdk = VMRuntime.getRuntime().getTargetSdkVersion();
|
|
if (targetSdk >= Build.VERSION_CODES.HONEYCOMB) {
|
|
detectActivityLeaks();
|
|
detectLeakedClosableObjects();
|
|
}
|
|
if (targetSdk >= Build.VERSION_CODES.JELLY_BEAN) {
|
|
detectLeakedRegistrationObjects();
|
|
}
|
|
if (targetSdk >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
|
detectFileUriExposure();
|
|
}
|
|
if (targetSdk >= Build.VERSION_CODES.M) {
|
|
// TODO: always add DETECT_VM_CLEARTEXT_NETWORK once we have
|
|
// facility for apps to mark sockets that should be ignored
|
|
if (SystemProperties.getBoolean(CLEARTEXT_PROPERTY, false)) {
|
|
detectCleartextNetwork();
|
|
}
|
|
}
|
|
if (targetSdk >= Build.VERSION_CODES.O) {
|
|
detectContentUriWithoutPermission();
|
|
detectUntaggedSockets();
|
|
}
|
|
if (targetSdk >= Build.VERSION_CODES.Q) {
|
|
detectCredentialProtectedWhileLocked();
|
|
}
|
|
if (targetSdk >= Build.VERSION_CODES.R) {
|
|
detectIncorrectContextUse();
|
|
}
|
|
if (targetSdk >= Build.VERSION_CODES.S) {
|
|
detectUnsafeIntentLaunch();
|
|
}
|
|
|
|
// TODO: Decide whether to detect non SDK API usage beyond a certain API level.
|
|
// TODO: enable detectImplicitDirectBoot() once system is less noisy
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Detect when an {@link android.database.sqlite.SQLiteCursor} or other SQLite object is
|
|
* finalized without having been closed.
|
|
*
|
|
* <p>You always want to explicitly close your SQLite cursors to avoid unnecessary
|
|
* database contention and temporary memory leaks.
|
|
*/
|
|
public @NonNull Builder detectLeakedSqlLiteObjects() {
|
|
return enable(DETECT_VM_CURSOR_LEAKS);
|
|
}
|
|
|
|
/**
|
|
* Detect when an {@link java.io.Closeable} or other object with an explicit termination
|
|
* method is finalized without having been closed.
|
|
*
|
|
* <p>You always want to explicitly close such objects to avoid unnecessary resources
|
|
* leaks.
|
|
*/
|
|
public @NonNull Builder detectLeakedClosableObjects() {
|
|
return enable(DETECT_VM_CLOSABLE_LEAKS);
|
|
}
|
|
|
|
/**
|
|
* Detect when a {@link BroadcastReceiver} or {@link ServiceConnection} is leaked during
|
|
* {@link Context} teardown.
|
|
*/
|
|
public @NonNull Builder detectLeakedRegistrationObjects() {
|
|
return enable(DETECT_VM_REGISTRATION_LEAKS);
|
|
}
|
|
|
|
/**
|
|
* Detect when the calling application exposes a {@code file://} {@link android.net.Uri}
|
|
* to another app.
|
|
*
|
|
* <p>This exposure is discouraged since the receiving app may not have access to the
|
|
* shared path. For example, the receiving app may not have requested the {@link
|
|
* android.Manifest.permission#READ_EXTERNAL_STORAGE} runtime permission, or the
|
|
* platform may be sharing the {@link android.net.Uri} across user profile boundaries.
|
|
*
|
|
* <p>Instead, apps should use {@code content://} Uris so the platform can extend
|
|
* temporary permission for the receiving app to access the resource.
|
|
*
|
|
* @see androidx.core.content.FileProvider
|
|
* @see Intent#FLAG_GRANT_READ_URI_PERMISSION
|
|
*/
|
|
public @NonNull Builder detectFileUriExposure() {
|
|
return enable(DETECT_VM_FILE_URI_EXPOSURE);
|
|
}
|
|
|
|
/**
|
|
* Detect any network traffic from the calling app which is not wrapped in SSL/TLS. This
|
|
* can help you detect places that your app is inadvertently sending cleartext data
|
|
* across the network.
|
|
*
|
|
* <p>Using {@link #penaltyDeath()} or {@link #penaltyDeathOnCleartextNetwork()} will
|
|
* block further traffic on that socket to prevent accidental data leakage, in addition
|
|
* to crashing your process.
|
|
*
|
|
* <p>Using {@link #penaltyDropBox()} will log the raw contents of the packet that
|
|
* triggered the violation.
|
|
*
|
|
* <p>This inspects both IPv4/IPv6 and TCP/UDP network traffic, but it may be subject to
|
|
* false positives, such as when STARTTLS protocols or HTTP proxies are used.
|
|
*/
|
|
public @NonNull Builder detectCleartextNetwork() {
|
|
return enable(DETECT_VM_CLEARTEXT_NETWORK);
|
|
}
|
|
|
|
/**
|
|
* Detect when the calling application sends a {@code content://} {@link
|
|
* android.net.Uri} to another app without setting {@link
|
|
* Intent#FLAG_GRANT_READ_URI_PERMISSION} or {@link
|
|
* Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
|
|
*
|
|
* <p>Forgetting to include one or more of these flags when sending an intent is
|
|
* typically an app bug.
|
|
*
|
|
* @see Intent#FLAG_GRANT_READ_URI_PERMISSION
|
|
* @see Intent#FLAG_GRANT_WRITE_URI_PERMISSION
|
|
*/
|
|
public @NonNull Builder detectContentUriWithoutPermission() {
|
|
return enable(DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION);
|
|
}
|
|
|
|
/**
|
|
* Detect any sockets in the calling app which have not been tagged using {@link
|
|
* TrafficStats}. Tagging sockets can help you investigate network usage inside your
|
|
* app, such as a narrowing down heavy usage to a specific library or component.
|
|
*
|
|
* <p>This currently does not detect sockets created in native code.
|
|
*
|
|
* @see TrafficStats#setThreadStatsTag(int)
|
|
* @see TrafficStats#tagSocket(java.net.Socket)
|
|
* @see TrafficStats#tagDatagramSocket(java.net.DatagramSocket)
|
|
*/
|
|
public @NonNull Builder detectUntaggedSockets() {
|
|
return enable(DETECT_VM_UNTAGGED_SOCKET);
|
|
}
|
|
|
|
/** @hide */
|
|
public @NonNull Builder permitUntaggedSockets() {
|
|
return disable(DETECT_VM_UNTAGGED_SOCKET);
|
|
}
|
|
|
|
/**
|
|
* Detect any implicit reliance on Direct Boot automatic filtering
|
|
* of {@link PackageManager} values. Violations are only triggered
|
|
* when implicit calls are made while the user is locked.
|
|
* <p>
|
|
* Apps becoming Direct Boot aware need to carefully inspect each
|
|
* query site and explicitly decide which combination of flags they
|
|
* want to use:
|
|
* <ul>
|
|
* <li>{@link PackageManager#MATCH_DIRECT_BOOT_AWARE}
|
|
* <li>{@link PackageManager#MATCH_DIRECT_BOOT_UNAWARE}
|
|
* <li>{@link PackageManager#MATCH_DIRECT_BOOT_AUTO}
|
|
* </ul>
|
|
*/
|
|
public @NonNull Builder detectImplicitDirectBoot() {
|
|
return enable(DETECT_VM_IMPLICIT_DIRECT_BOOT);
|
|
}
|
|
|
|
/** @hide */
|
|
public @NonNull Builder permitImplicitDirectBoot() {
|
|
return disable(DETECT_VM_IMPLICIT_DIRECT_BOOT);
|
|
}
|
|
|
|
/**
|
|
* Detect access to filesystem paths stored in credential protected
|
|
* storage areas while the user is locked.
|
|
* <p>
|
|
* When a user is locked, credential protected storage is
|
|
* unavailable, and files stored in these locations appear to not
|
|
* exist, which can result in subtle app bugs if they assume default
|
|
* behaviors or empty states. Instead, apps should store data needed
|
|
* while a user is locked under device protected storage areas.
|
|
*
|
|
* @see Context#createDeviceProtectedStorageContext()
|
|
*/
|
|
public @NonNull Builder detectCredentialProtectedWhileLocked() {
|
|
return enable(DETECT_VM_CREDENTIAL_PROTECTED_WHILE_LOCKED);
|
|
}
|
|
|
|
/** @hide */
|
|
public @NonNull Builder permitCredentialProtectedWhileLocked() {
|
|
return disable(DETECT_VM_CREDENTIAL_PROTECTED_WHILE_LOCKED);
|
|
}
|
|
|
|
/**
|
|
* Detect attempts to invoke a method on a {@link Context} that is not suited for such
|
|
* operation.
|
|
* <p>An example of this is trying to obtain an instance of UI service (e.g.
|
|
* {@link android.view.WindowManager}) from a non-visual {@link Context}. This is not
|
|
* allowed, since a non-visual {@link Context} is not adjusted to any visual area, and
|
|
* therefore can report incorrect metrics or resources.
|
|
* @see Context#getDisplay()
|
|
* @see Context#getSystemService(String)
|
|
*/
|
|
public @NonNull Builder detectIncorrectContextUse() {
|
|
return enable(DETECT_VM_INCORRECT_CONTEXT_USE);
|
|
}
|
|
|
|
/**
|
|
* Disable detection of incorrect context use.
|
|
*
|
|
* @see #detectIncorrectContextUse()
|
|
*
|
|
* @hide
|
|
*/
|
|
@TestApi
|
|
public @NonNull Builder permitIncorrectContextUse() {
|
|
return disable(DETECT_VM_INCORRECT_CONTEXT_USE);
|
|
}
|
|
|
|
/**
|
|
* Detect when your app sends an unsafe {@link Intent}.
|
|
* <p>
|
|
* Violations may indicate security vulnerabilities in the design of
|
|
* your app, where a malicious app could trick you into granting
|
|
* {@link Uri} permissions or launching unexported components. Here
|
|
* are some typical design patterns that can be used to safely
|
|
* resolve these violations:
|
|
* <ul>
|
|
* <li> If you are sending an implicit intent to an unexported component, you should
|
|
* make it an explicit intent by using {@link Intent#setPackage},
|
|
* {@link Intent#setClassName} or {@link Intent#setComponent}.
|
|
* </li>
|
|
* <li> If you are unparceling and sending an intent from the intent delivered, The
|
|
* ideal approach is to migrate to using a {@link android.app.PendingIntent}, which
|
|
* ensures that your launch is performed using the identity of the original creator,
|
|
* completely avoiding the security issues described above.
|
|
* <li>If using a {@link android.app.PendingIntent} isn't feasible, an
|
|
* alternative approach is to create a brand new {@link Intent} and
|
|
* carefully copy only specific values from the original
|
|
* {@link Intent} after careful validation.
|
|
* </ul>
|
|
* <p>
|
|
* Note that this <em>may</em> detect false-positives if your app
|
|
* sends itself an {@link Intent} which is first routed through the
|
|
* OS, such as using {@link Intent#createChooser}. In these cases,
|
|
* careful inspection is required to determine if the return point
|
|
* into your app is appropriately protected with a signature
|
|
* permission or marked as unexported. If the return point is not
|
|
* protected, your app is likely vulnerable to malicious apps.
|
|
*
|
|
* @see Context#startActivity(Intent)
|
|
* @see Context#startService(Intent)
|
|
* @see Context#bindService(Intent, ServiceConnection, int)
|
|
* @see Context#sendBroadcast(Intent)
|
|
* @see android.app.Activity#setResult(int, Intent)
|
|
*/
|
|
public @NonNull Builder detectUnsafeIntentLaunch() {
|
|
return enable(DETECT_VM_UNSAFE_INTENT_LAUNCH);
|
|
}
|
|
|
|
/**
|
|
* Permit your app to launch any {@link Intent} which originated
|
|
* from outside your app.
|
|
* <p>
|
|
* Disabling this check is <em>strongly discouraged</em>, as
|
|
* violations may indicate security vulnerabilities in the design of
|
|
* your app, where a malicious app could trick you into granting
|
|
* {@link Uri} permissions or launching unexported components.
|
|
*
|
|
* @see #detectUnsafeIntentLaunch()
|
|
*/
|
|
public @NonNull Builder permitUnsafeIntentLaunch() {
|
|
return disable(DETECT_VM_UNSAFE_INTENT_LAUNCH);
|
|
}
|
|
|
|
/**
|
|
* Crashes the whole process on violation. This penalty runs at the end of all enabled
|
|
* penalties so you'll still get your logging or other violations before the process
|
|
* dies.
|
|
*/
|
|
public @NonNull Builder penaltyDeath() {
|
|
return enable(PENALTY_DEATH);
|
|
}
|
|
|
|
/**
|
|
* Crashes the whole process when cleartext network traffic is detected.
|
|
*
|
|
* @see #detectCleartextNetwork()
|
|
*/
|
|
public @NonNull Builder penaltyDeathOnCleartextNetwork() {
|
|
return enable(PENALTY_DEATH_ON_CLEARTEXT_NETWORK);
|
|
}
|
|
|
|
/**
|
|
* Crashes the whole process when a {@code file://} {@link android.net.Uri} is exposed
|
|
* beyond this app.
|
|
*
|
|
* @see #detectFileUriExposure()
|
|
*/
|
|
public @NonNull Builder penaltyDeathOnFileUriExposure() {
|
|
return enable(PENALTY_DEATH_ON_FILE_URI_EXPOSURE);
|
|
}
|
|
|
|
/** Log detected violations to the system log. */
|
|
public @NonNull Builder penaltyLog() {
|
|
return enable(PENALTY_LOG);
|
|
}
|
|
|
|
/**
|
|
* Enable detected violations log a stacktrace and timing data to the {@link
|
|
* android.os.DropBoxManager DropBox} on policy violation. Intended mostly for platform
|
|
* integrators doing beta user field data collection.
|
|
*/
|
|
public @NonNull Builder penaltyDropBox() {
|
|
return enable(PENALTY_DROPBOX);
|
|
}
|
|
|
|
/**
|
|
* Call #{@link OnVmViolationListener#onVmViolation(Violation)} on every violation.
|
|
*/
|
|
public @NonNull Builder penaltyListener(
|
|
@NonNull Executor executor, @NonNull OnVmViolationListener listener) {
|
|
if (executor == null) {
|
|
throw new NullPointerException("executor must not be null");
|
|
}
|
|
mListener = listener;
|
|
mExecutor = executor;
|
|
return this;
|
|
}
|
|
|
|
/** @removed */
|
|
public @NonNull Builder penaltyListener(
|
|
@NonNull OnVmViolationListener listener, @NonNull Executor executor) {
|
|
return penaltyListener(executor, listener);
|
|
}
|
|
|
|
private Builder enable(@VmPolicyMask int mask) {
|
|
mMask |= mask;
|
|
return this;
|
|
}
|
|
|
|
Builder disable(@VmPolicyMask int mask) {
|
|
mMask &= ~mask;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Construct the VmPolicy instance.
|
|
*
|
|
* <p>Note: if no penalties are enabled before calling <code>build</code>, {@link
|
|
* #penaltyLog} is implicitly set.
|
|
*/
|
|
public VmPolicy build() {
|
|
// If there are detection bits set but no violation bits
|
|
// set, enable simple logging.
|
|
if (mListener == null
|
|
&& mMask != 0
|
|
&& (mMask
|
|
& (PENALTY_DEATH
|
|
| PENALTY_LOG
|
|
| PENALTY_DROPBOX
|
|
| PENALTY_DIALOG))
|
|
== 0) {
|
|
penaltyLog();
|
|
}
|
|
return new VmPolicy(
|
|
mMask,
|
|
mClassInstanceLimit != null ? mClassInstanceLimit : EMPTY_CLASS_LIMIT_MAP,
|
|
mListener,
|
|
mExecutor);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Log of strict mode violation stack traces that have occurred during a Binder call, to be
|
|
* serialized back later to the caller via Parcel.writeNoException() (amusingly) where the
|
|
* caller can choose how to react.
|
|
*/
|
|
private static final ThreadLocal<ArrayList<ViolationInfo>> gatheredViolations =
|
|
new ThreadLocal<ArrayList<ViolationInfo>>() {
|
|
@Override
|
|
protected ArrayList<ViolationInfo> initialValue() {
|
|
// Starts null to avoid unnecessary allocations when
|
|
// checking whether there are any violations or not in
|
|
// hasGatheredViolations() below.
|
|
return null;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Sets the policy for what actions on the current thread should be detected, as well as the
|
|
* penalty if such actions occur.
|
|
*
|
|
* <p>Internally this sets a thread-local variable which is propagated across cross-process IPC
|
|
* calls, meaning you can catch violations when a system service or another process accesses the
|
|
* disk or network on your behalf.
|
|
*
|
|
* @param policy the policy to put into place
|
|
*/
|
|
public static void setThreadPolicy(final ThreadPolicy policy) {
|
|
setThreadPolicyMask(policy.mask);
|
|
sThreadViolationListener.set(policy.mListener);
|
|
sThreadViolationExecutor.set(policy.mCallbackExecutor);
|
|
}
|
|
|
|
/** @hide */
|
|
@android.ravenwood.annotation.RavenwoodReplace
|
|
public static void setThreadPolicyMask(@ThreadPolicyMask int threadPolicyMask) {
|
|
// In addition to the Java-level thread-local in Dalvik's
|
|
// BlockGuard, we also need to keep a native thread-local in
|
|
// Binder in order to propagate the value across Binder calls,
|
|
// even across native-only processes. The two are kept in
|
|
// sync via the callback to onStrictModePolicyChange, below.
|
|
setBlockGuardPolicy(threadPolicyMask);
|
|
|
|
// And set the Android native version...
|
|
Binder.setThreadStrictModePolicy(threadPolicyMask);
|
|
}
|
|
|
|
/** @hide */
|
|
public static void setThreadPolicyMask$ravenwood(@ThreadPolicyMask int threadPolicyMask) {
|
|
// Ravenwood currently doesn't support any detection modes
|
|
Preconditions.checkFlagsArgument(threadPolicyMask, 0);
|
|
}
|
|
|
|
// Sets the policy in Dalvik/libcore (BlockGuard)
|
|
private static void setBlockGuardPolicy(@ThreadPolicyMask int threadPolicyMask) {
|
|
if (threadPolicyMask == 0) {
|
|
BlockGuard.setThreadPolicy(BlockGuard.LAX_POLICY);
|
|
return;
|
|
}
|
|
final BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
|
|
final AndroidBlockGuardPolicy androidPolicy;
|
|
if (policy instanceof AndroidBlockGuardPolicy) {
|
|
androidPolicy = (AndroidBlockGuardPolicy) policy;
|
|
} else {
|
|
androidPolicy = THREAD_ANDROID_POLICY.get();
|
|
BlockGuard.setThreadPolicy(androidPolicy);
|
|
}
|
|
androidPolicy.setThreadPolicyMask(threadPolicyMask);
|
|
}
|
|
|
|
private static void setBlockGuardVmPolicy(@VmPolicyMask int vmPolicyMask) {
|
|
// We only need to install BlockGuard for a small subset of VM policies
|
|
vmPolicyMask &= DETECT_VM_CREDENTIAL_PROTECTED_WHILE_LOCKED;
|
|
if (vmPolicyMask != 0) {
|
|
BlockGuard.setVmPolicy(VM_ANDROID_POLICY);
|
|
} else {
|
|
BlockGuard.setVmPolicy(BlockGuard.LAX_VM_POLICY);
|
|
}
|
|
}
|
|
|
|
// Sets up CloseGuard in Dalvik/libcore
|
|
private static void setCloseGuardEnabled(boolean enabled) {
|
|
if (!(CloseGuard.getReporter() instanceof AndroidCloseGuardReporter)) {
|
|
CloseGuard.setReporter(new AndroidCloseGuardReporter());
|
|
}
|
|
CloseGuard.setEnabled(enabled);
|
|
}
|
|
|
|
/**
|
|
* Returns the bitmask of the current thread's policy.
|
|
*
|
|
* @return the bitmask of all the DETECT_* and PENALTY_* bits currently enabled
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage
|
|
@android.ravenwood.annotation.RavenwoodReplace
|
|
public static @ThreadPolicyMask int getThreadPolicyMask() {
|
|
final BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
|
|
if (policy instanceof AndroidBlockGuardPolicy) {
|
|
return ((AndroidBlockGuardPolicy) policy).getThreadPolicyMask();
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/** @hide */
|
|
public static @ThreadPolicyMask int getThreadPolicyMask$ravenwood() {
|
|
// Ravenwood currently doesn't support any detection modes
|
|
return 0;
|
|
}
|
|
|
|
/** Returns the current thread's policy. */
|
|
public static ThreadPolicy getThreadPolicy() {
|
|
// TODO: this was a last minute Gingerbread API change (to
|
|
// introduce VmPolicy cleanly) but this isn't particularly
|
|
// optimal for users who might call this method often. This
|
|
// should be in a thread-local and not allocate on each call.
|
|
return new ThreadPolicy(
|
|
getThreadPolicyMask(),
|
|
sThreadViolationListener.get(),
|
|
sThreadViolationExecutor.get());
|
|
}
|
|
|
|
/**
|
|
* A convenience wrapper that takes the current {@link ThreadPolicy} from {@link
|
|
* #getThreadPolicy}, modifies it to permit both disk reads & writes, and sets the new
|
|
* policy with {@link #setThreadPolicy}, returning the old policy so you can restore it at the
|
|
* end of a block.
|
|
*
|
|
* @return the old policy, to be passed to {@link #setThreadPolicy} to restore the policy at the
|
|
* end of a block
|
|
*/
|
|
public static ThreadPolicy allowThreadDiskWrites() {
|
|
return new ThreadPolicy(
|
|
allowThreadDiskWritesMask(),
|
|
sThreadViolationListener.get(),
|
|
sThreadViolationExecutor.get());
|
|
}
|
|
|
|
/** @hide */
|
|
@android.ravenwood.annotation.RavenwoodKeep
|
|
public static @ThreadPolicyMask int allowThreadDiskWritesMask() {
|
|
int oldPolicyMask = getThreadPolicyMask();
|
|
int newPolicyMask = oldPolicyMask & ~(DETECT_THREAD_DISK_WRITE | DETECT_THREAD_DISK_READ);
|
|
if (newPolicyMask != oldPolicyMask) {
|
|
setThreadPolicyMask(newPolicyMask);
|
|
}
|
|
return oldPolicyMask;
|
|
}
|
|
|
|
/**
|
|
* A convenience wrapper that takes the current {@link ThreadPolicy} from {@link
|
|
* #getThreadPolicy}, modifies it to permit disk reads, and sets the new policy with {@link
|
|
* #setThreadPolicy}, returning the old policy so you can restore it at the end of a block.
|
|
*
|
|
* @return the old policy, to be passed to setThreadPolicy to restore the policy.
|
|
*/
|
|
public static ThreadPolicy allowThreadDiskReads() {
|
|
return new ThreadPolicy(
|
|
allowThreadDiskReadsMask(),
|
|
sThreadViolationListener.get(),
|
|
sThreadViolationExecutor.get());
|
|
}
|
|
|
|
/** @hide */
|
|
@android.ravenwood.annotation.RavenwoodKeep
|
|
public static @ThreadPolicyMask int allowThreadDiskReadsMask() {
|
|
int oldPolicyMask = getThreadPolicyMask();
|
|
int newPolicyMask = oldPolicyMask & ~(DETECT_THREAD_DISK_READ);
|
|
if (newPolicyMask != oldPolicyMask) {
|
|
setThreadPolicyMask(newPolicyMask);
|
|
}
|
|
return oldPolicyMask;
|
|
}
|
|
|
|
/** @hide */
|
|
public static ThreadPolicy allowThreadViolations() {
|
|
ThreadPolicy oldPolicy = getThreadPolicy();
|
|
setThreadPolicyMask(0);
|
|
return oldPolicy;
|
|
}
|
|
|
|
/** @hide */
|
|
public static VmPolicy allowVmViolations() {
|
|
VmPolicy oldPolicy = getVmPolicy();
|
|
sVmPolicy = VmPolicy.LAX;
|
|
return oldPolicy;
|
|
}
|
|
|
|
/**
|
|
* Determine if the given app is "bundled" as part of the system image. These bundled apps are
|
|
* developed in lock-step with the OS, and they aren't updated outside of an OTA, so we want to
|
|
* chase any {@link StrictMode} regressions by enabling detection when running on {@link
|
|
* Build#IS_USERDEBUG} or {@link Build#IS_ENG} builds.
|
|
*
|
|
* <p>Unbundled apps included in the system image are expected to detect and triage their own
|
|
* {@link StrictMode} issues separate from the OS release process, which is why we don't enable
|
|
* them here.
|
|
*
|
|
* @hide
|
|
*/
|
|
public static boolean isBundledSystemApp(ApplicationInfo ai) {
|
|
if (ai == null || ai.packageName == null) {
|
|
// Probably system server
|
|
return true;
|
|
} else if (ai.isSystemApp()) {
|
|
// Ignore unbundled apps living in the wrong namespace
|
|
if (ai.packageName.equals("com.android.vending")
|
|
|| ai.packageName.equals("com.android.chrome")) {
|
|
return false;
|
|
}
|
|
|
|
// Ignore bundled apps that are way too spammy
|
|
// STOPSHIP: burn this list down to zero
|
|
if (ai.packageName.equals("com.android.phone")) {
|
|
return false;
|
|
}
|
|
|
|
if (ai.packageName.equals("android")
|
|
|| ai.packageName.startsWith("android.")
|
|
|| ai.packageName.startsWith("com.android.")) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Initialize default {@link ThreadPolicy} for the current thread.
|
|
*
|
|
* @hide
|
|
*/
|
|
public static void initThreadDefaults(ApplicationInfo ai) {
|
|
final ThreadPolicy.Builder builder = new ThreadPolicy.Builder();
|
|
final int targetSdkVersion =
|
|
(ai != null) ? ai.targetSdkVersion : Build.VERSION_CODES.CUR_DEVELOPMENT;
|
|
|
|
// Starting in HC, we don't allow network usage on the main thread
|
|
if (targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
|
|
builder.detectNetwork();
|
|
builder.penaltyDeathOnNetwork();
|
|
}
|
|
|
|
if (Build.IS_USER || DISABLE || SystemProperties.getBoolean(DISABLE_PROPERTY, false)) {
|
|
// Detect nothing extra
|
|
} else if (Build.IS_USERDEBUG || Build.IS_ENG) {
|
|
// Detect everything in bundled apps
|
|
if (isBundledSystemApp(ai)) {
|
|
builder.detectAll();
|
|
builder.penaltyDropBox();
|
|
if (SystemProperties.getBoolean(VISUAL_PROPERTY, false)) {
|
|
builder.penaltyFlashScreen();
|
|
}
|
|
if (Build.IS_ENG) {
|
|
builder.penaltyLog();
|
|
}
|
|
}
|
|
}
|
|
|
|
setThreadPolicy(builder.build());
|
|
}
|
|
|
|
/**
|
|
* Initialize default {@link VmPolicy} for the current VM.
|
|
*
|
|
* @hide
|
|
*/
|
|
public static void initVmDefaults(ApplicationInfo ai) {
|
|
final VmPolicy.Builder builder = new VmPolicy.Builder();
|
|
final int targetSdkVersion =
|
|
(ai != null) ? ai.targetSdkVersion : Build.VERSION_CODES.CUR_DEVELOPMENT;
|
|
|
|
// Starting in N, we don't allow file:// Uri exposure
|
|
if (targetSdkVersion >= Build.VERSION_CODES.N) {
|
|
builder.detectFileUriExposure();
|
|
builder.penaltyDeathOnFileUriExposure();
|
|
}
|
|
|
|
if (Build.IS_USER || DISABLE || SystemProperties.getBoolean(DISABLE_PROPERTY, false)) {
|
|
// Detect nothing extra
|
|
} else if (Build.IS_USERDEBUG) {
|
|
// Detect everything in bundled apps (except activity leaks, which
|
|
// are expensive to track)
|
|
if (isBundledSystemApp(ai)) {
|
|
builder.detectAll();
|
|
builder.permitActivityLeaks();
|
|
builder.penaltyDropBox();
|
|
}
|
|
} else if (Build.IS_ENG) {
|
|
// Detect everything in bundled apps
|
|
if (isBundledSystemApp(ai)) {
|
|
builder.detectAll();
|
|
builder.penaltyDropBox();
|
|
builder.penaltyLog();
|
|
}
|
|
}
|
|
|
|
setVmPolicy(builder.build());
|
|
}
|
|
|
|
/**
|
|
* Used by the framework to make file usage a fatal error.
|
|
*
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public static void enableDeathOnFileUriExposure() {
|
|
sVmPolicy =
|
|
new VmPolicy(
|
|
sVmPolicy.mask
|
|
| DETECT_VM_FILE_URI_EXPOSURE
|
|
| PENALTY_DEATH_ON_FILE_URI_EXPOSURE,
|
|
sVmPolicy.classInstanceLimit,
|
|
sVmPolicy.mListener,
|
|
sVmPolicy.mCallbackExecutor);
|
|
}
|
|
|
|
/**
|
|
* Used by lame internal apps that haven't done the hard work to get themselves off file:// Uris
|
|
* yet.
|
|
*
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public static void disableDeathOnFileUriExposure() {
|
|
sVmPolicy =
|
|
new VmPolicy(
|
|
sVmPolicy.mask
|
|
& ~(DETECT_VM_FILE_URI_EXPOSURE
|
|
| PENALTY_DEATH_ON_FILE_URI_EXPOSURE),
|
|
sVmPolicy.classInstanceLimit,
|
|
sVmPolicy.mListener,
|
|
sVmPolicy.mCallbackExecutor);
|
|
}
|
|
|
|
@UnsupportedAppUsage
|
|
private static final ThreadLocal<ArrayList<ViolationInfo>> violationsBeingTimed =
|
|
new ThreadLocal<ArrayList<ViolationInfo>>() {
|
|
@Override
|
|
protected ArrayList<ViolationInfo> initialValue() {
|
|
return new ArrayList<ViolationInfo>();
|
|
}
|
|
};
|
|
|
|
// Note: only access this once verifying the thread has a Looper.
|
|
private static final ThreadLocal<Handler> THREAD_HANDLER =
|
|
new ThreadLocal<Handler>() {
|
|
@Override
|
|
protected Handler initialValue() {
|
|
return new Handler();
|
|
}
|
|
};
|
|
|
|
private static final ThreadLocal<AndroidBlockGuardPolicy> THREAD_ANDROID_POLICY =
|
|
new ThreadLocal<AndroidBlockGuardPolicy>() {
|
|
@Override
|
|
protected AndroidBlockGuardPolicy initialValue() {
|
|
return new AndroidBlockGuardPolicy(0);
|
|
}
|
|
};
|
|
|
|
private static boolean tooManyViolationsThisLoop() {
|
|
return violationsBeingTimed.get().size() >= MAX_OFFENSES_PER_LOOP;
|
|
}
|
|
|
|
private static class AndroidBlockGuardPolicy implements BlockGuard.Policy {
|
|
private @ThreadPolicyMask int mThreadPolicyMask;
|
|
|
|
// Map from violation stacktrace hashcode -> uptimeMillis of
|
|
// last violation. No locking needed, as this is only
|
|
// accessed by the same thread.
|
|
/** Temporarily retained; appears to be missing UnsupportedAppUsage annotation */
|
|
private ArrayMap<Integer, Long> mLastViolationTime;
|
|
private SparseLongArray mRealLastViolationTime;
|
|
|
|
public AndroidBlockGuardPolicy(@ThreadPolicyMask int threadPolicyMask) {
|
|
mThreadPolicyMask = threadPolicyMask;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "AndroidBlockGuardPolicy; mPolicyMask=" + mThreadPolicyMask;
|
|
}
|
|
|
|
// Part of BlockGuard.Policy interface:
|
|
public int getPolicyMask() {
|
|
return mThreadPolicyMask;
|
|
}
|
|
|
|
// Part of BlockGuard.Policy interface:
|
|
public void onWriteToDisk() {
|
|
if ((mThreadPolicyMask & DETECT_THREAD_DISK_WRITE) == 0) {
|
|
return;
|
|
}
|
|
if (tooManyViolationsThisLoop()) {
|
|
return;
|
|
}
|
|
startHandlingViolationException(new DiskWriteViolation());
|
|
}
|
|
|
|
// Not part of BlockGuard.Policy; just part of StrictMode:
|
|
void onCustomSlowCall(String name) {
|
|
if ((mThreadPolicyMask & DETECT_THREAD_CUSTOM) == 0) {
|
|
return;
|
|
}
|
|
if (tooManyViolationsThisLoop()) {
|
|
return;
|
|
}
|
|
startHandlingViolationException(new CustomViolation(name));
|
|
}
|
|
|
|
// Not part of BlockGuard.Policy; just part of StrictMode:
|
|
void onResourceMismatch(Object tag) {
|
|
if ((mThreadPolicyMask & DETECT_THREAD_RESOURCE_MISMATCH) == 0) {
|
|
return;
|
|
}
|
|
if (tooManyViolationsThisLoop()) {
|
|
return;
|
|
}
|
|
startHandlingViolationException(new ResourceMismatchViolation(tag));
|
|
}
|
|
|
|
// Not part of BlockGuard.Policy; just part of StrictMode:
|
|
public void onUnbufferedIO() {
|
|
if ((mThreadPolicyMask & DETECT_THREAD_UNBUFFERED_IO) == 0) {
|
|
return;
|
|
}
|
|
if (tooManyViolationsThisLoop()) {
|
|
return;
|
|
}
|
|
startHandlingViolationException(new UnbufferedIoViolation());
|
|
}
|
|
|
|
// Part of BlockGuard.Policy interface:
|
|
public void onReadFromDisk() {
|
|
if ((mThreadPolicyMask & DETECT_THREAD_DISK_READ) == 0) {
|
|
return;
|
|
}
|
|
if (tooManyViolationsThisLoop()) {
|
|
return;
|
|
}
|
|
startHandlingViolationException(new DiskReadViolation());
|
|
}
|
|
|
|
// Part of BlockGuard.Policy interface:
|
|
public void onNetwork() {
|
|
if ((mThreadPolicyMask & DETECT_THREAD_NETWORK) == 0) {
|
|
return;
|
|
}
|
|
if ((mThreadPolicyMask & PENALTY_DEATH_ON_NETWORK) != 0) {
|
|
throw new NetworkOnMainThreadException();
|
|
}
|
|
if (tooManyViolationsThisLoop()) {
|
|
return;
|
|
}
|
|
startHandlingViolationException(new NetworkViolation());
|
|
}
|
|
|
|
// Part of BlockGuard.Policy interface:
|
|
public void onExplicitGc() {
|
|
if ((mThreadPolicyMask & DETECT_THREAD_EXPLICIT_GC) == 0) {
|
|
return;
|
|
}
|
|
if (tooManyViolationsThisLoop()) {
|
|
return;
|
|
}
|
|
startHandlingViolationException(new ExplicitGcViolation());
|
|
}
|
|
|
|
public @ThreadPolicyMask int getThreadPolicyMask() {
|
|
return mThreadPolicyMask;
|
|
}
|
|
|
|
public void setThreadPolicyMask(@ThreadPolicyMask int threadPolicyMask) {
|
|
mThreadPolicyMask = threadPolicyMask;
|
|
}
|
|
|
|
// Start handling a violation that just started and hasn't
|
|
// actually run yet (e.g. no disk write or network operation
|
|
// has yet occurred). This sees if we're in an event loop
|
|
// thread and, if so, uses it to roughly measure how long the
|
|
// violation took.
|
|
void startHandlingViolationException(Violation e) {
|
|
final int penaltyMask = (mThreadPolicyMask & PENALTY_ALL);
|
|
final ViolationInfo info = new ViolationInfo(e, penaltyMask);
|
|
info.violationUptimeMillis = SystemClock.uptimeMillis();
|
|
handleViolationWithTimingAttempt(info);
|
|
}
|
|
|
|
// Attempts to fill in the provided ViolationInfo's
|
|
// durationMillis field if this thread has a Looper we can use
|
|
// to measure with. We measure from the time of violation
|
|
// until the time the looper is idle again (right before
|
|
// the next epoll_wait)
|
|
void handleViolationWithTimingAttempt(final ViolationInfo info) {
|
|
Looper looper = Looper.myLooper();
|
|
|
|
// Without a Looper, we're unable to time how long the
|
|
// violation takes place. This case should be rare, as
|
|
// most users will care about timing violations that
|
|
// happen on their main UI thread. Note that this case is
|
|
// also hit when a violation takes place in a Binder
|
|
// thread, in "gather" mode. In this case, the duration
|
|
// of the violation is computed by the ultimate caller and
|
|
// its Looper, if any.
|
|
//
|
|
// Also, as a special short-cut case when the only penalty
|
|
// bit is death, we die immediately, rather than timing
|
|
// the violation's duration. This makes it convenient to
|
|
// use in unit tests too, rather than waiting on a Looper.
|
|
//
|
|
// TODO: if in gather mode, ignore Looper.myLooper() and always
|
|
// go into this immediate mode?
|
|
if (looper == null || (info.mPenaltyMask == PENALTY_DEATH)) {
|
|
info.durationMillis = -1; // unknown (redundant, already set)
|
|
onThreadPolicyViolation(info);
|
|
return;
|
|
}
|
|
|
|
final ArrayList<ViolationInfo> records = violationsBeingTimed.get();
|
|
if (records.size() >= MAX_OFFENSES_PER_LOOP) {
|
|
// Not worth measuring. Too many offenses in one loop.
|
|
return;
|
|
}
|
|
records.add(info);
|
|
if (records.size() > 1) {
|
|
// There's already been a violation this loop, so we've already
|
|
// registered an idle handler to process the list of violations
|
|
// at the end of this Looper's loop.
|
|
return;
|
|
}
|
|
|
|
final IWindowManager windowManager =
|
|
info.penaltyEnabled(PENALTY_FLASH) ? sWindowManager.get() : null;
|
|
if (windowManager != null) {
|
|
try {
|
|
windowManager.showStrictModeViolation(true);
|
|
} catch (RemoteException unused) {
|
|
}
|
|
}
|
|
|
|
// We post a runnable to a Handler (== delay 0 ms) for
|
|
// measuring the end time of a violation instead of using
|
|
// an IdleHandler (as was previously used) because an
|
|
// IdleHandler may not run for quite a long period of time
|
|
// if an ongoing animation is happening and continually
|
|
// posting ASAP (0 ms) animation steps. Animations are
|
|
// throttled back to 60fps via SurfaceFlinger/View
|
|
// invalidates, _not_ by posting frame updates every 16
|
|
// milliseconds.
|
|
THREAD_HANDLER
|
|
.get()
|
|
.postAtFrontOfQueue(
|
|
() -> {
|
|
long loopFinishTime = SystemClock.uptimeMillis();
|
|
|
|
// Note: we do this early, before handling the
|
|
// violation below, as handling the violation
|
|
// may include PENALTY_DEATH and we don't want
|
|
// to keep the red border on.
|
|
if (windowManager != null) {
|
|
try {
|
|
windowManager.showStrictModeViolation(false);
|
|
} catch (RemoteException unused) {
|
|
}
|
|
}
|
|
|
|
for (int n = 0; n < records.size(); ++n) {
|
|
ViolationInfo v = records.get(n);
|
|
v.violationNumThisLoop = n + 1;
|
|
v.durationMillis =
|
|
(int) (loopFinishTime - v.violationUptimeMillis);
|
|
onThreadPolicyViolation(v);
|
|
}
|
|
records.clear();
|
|
});
|
|
}
|
|
|
|
// Note: It's possible (even quite likely) that the
|
|
// thread-local policy mask has changed from the time the
|
|
// violation fired and now (after the violating code ran) due
|
|
// to people who push/pop temporary policy in regions of code,
|
|
// hence the policy being passed around.
|
|
void onThreadPolicyViolation(final ViolationInfo info) {
|
|
if (LOG_V) Log.d(TAG, "onThreadPolicyViolation; penalty=" + info.mPenaltyMask);
|
|
|
|
if (info.penaltyEnabled(PENALTY_GATHER)) {
|
|
ArrayList<ViolationInfo> violations = gatheredViolations.get();
|
|
if (violations == null) {
|
|
violations = new ArrayList<>(1);
|
|
gatheredViolations.set(violations);
|
|
}
|
|
for (ViolationInfo previous : violations) {
|
|
if (info.getStackTrace().equals(previous.getStackTrace())) {
|
|
// Duplicate. Don't log.
|
|
return;
|
|
}
|
|
}
|
|
violations.add(info);
|
|
return;
|
|
}
|
|
|
|
// Not perfect, but fast and good enough for dup suppression.
|
|
Integer crashFingerprint = info.hashCode();
|
|
long lastViolationTime = 0;
|
|
long now = SystemClock.uptimeMillis();
|
|
if (sLogger == LOGCAT_LOGGER) { // Don't throttle it if there is a non-default logger
|
|
if (mRealLastViolationTime != null) {
|
|
Long vtime = mRealLastViolationTime.get(crashFingerprint);
|
|
if (vtime != null) {
|
|
lastViolationTime = vtime;
|
|
}
|
|
clampViolationTimeMap(mRealLastViolationTime, Math.max(MIN_LOG_INTERVAL_MS,
|
|
Math.max(MIN_DIALOG_INTERVAL_MS, MIN_DROPBOX_INTERVAL_MS)));
|
|
} else {
|
|
mRealLastViolationTime = new SparseLongArray(1);
|
|
}
|
|
mRealLastViolationTime.put(crashFingerprint, now);
|
|
}
|
|
long timeSinceLastViolationMillis =
|
|
lastViolationTime == 0 ? Long.MAX_VALUE : (now - lastViolationTime);
|
|
|
|
if (info.penaltyEnabled(PENALTY_LOG)
|
|
&& timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
|
|
sLogger.log(info);
|
|
}
|
|
|
|
final Violation violation = info.mViolation;
|
|
|
|
// Penalties that ActivityManager should execute on our behalf.
|
|
int penaltyMask = 0;
|
|
|
|
if (info.penaltyEnabled(PENALTY_DIALOG)
|
|
&& timeSinceLastViolationMillis > MIN_DIALOG_INTERVAL_MS) {
|
|
penaltyMask |= PENALTY_DIALOG;
|
|
}
|
|
|
|
if (info.penaltyEnabled(PENALTY_DROPBOX)
|
|
&& timeSinceLastViolationMillis > MIN_DROPBOX_INTERVAL_MS) {
|
|
penaltyMask |= PENALTY_DROPBOX;
|
|
}
|
|
|
|
if (penaltyMask != 0) {
|
|
final boolean justDropBox = (info.mPenaltyMask == PENALTY_DROPBOX);
|
|
if (justDropBox) {
|
|
// If all we're going to ask the activity manager
|
|
// to do is dropbox it (the common case during
|
|
// platform development), we can avoid doing this
|
|
// call synchronously which Binder data suggests
|
|
// isn't always super fast, despite the implementation
|
|
// in the ActivityManager trying to be mostly async.
|
|
dropboxViolationAsync(penaltyMask, info);
|
|
} else {
|
|
handleApplicationStrictModeViolation(penaltyMask, info);
|
|
}
|
|
}
|
|
|
|
if (info.penaltyEnabled(PENALTY_DEATH)) {
|
|
throw new RuntimeException("StrictMode ThreadPolicy violation", violation);
|
|
}
|
|
|
|
// penaltyDeath will cause penaltyCallback to no-op since we cannot guarantee the
|
|
// executor finishes before crashing.
|
|
final OnThreadViolationListener listener = sThreadViolationListener.get();
|
|
final Executor executor = sThreadViolationExecutor.get();
|
|
if (listener != null && executor != null) {
|
|
try {
|
|
executor.execute(
|
|
() -> {
|
|
// Lift violated policy to prevent infinite recursion.
|
|
ThreadPolicy oldPolicy = StrictMode.allowThreadViolations();
|
|
try {
|
|
listener.onThreadViolation(violation);
|
|
} finally {
|
|
StrictMode.setThreadPolicy(oldPolicy);
|
|
}
|
|
});
|
|
} catch (RejectedExecutionException e) {
|
|
Log.e(TAG, "ThreadPolicy penaltyCallback failed", e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static final BlockGuard.VmPolicy VM_ANDROID_POLICY = new BlockGuard.VmPolicy() {
|
|
@Override
|
|
public void onPathAccess(String path) {
|
|
if (path == null) return;
|
|
|
|
// NOTE: keep credential-protected paths in sync with Environment.java
|
|
if (path.startsWith("/data/user/")
|
|
|| path.startsWith("/data/media/")
|
|
|| path.startsWith("/data/system_ce/")
|
|
|| path.startsWith("/data/misc_ce/")
|
|
|| path.startsWith("/data/vendor_ce/")
|
|
|| path.startsWith("/storage/emulated/")) {
|
|
final int second = path.indexOf('/', 1);
|
|
final int third = path.indexOf('/', second + 1);
|
|
final int fourth = path.indexOf('/', third + 1);
|
|
if (fourth == -1) return;
|
|
|
|
try {
|
|
final int userId = Integer.parseInt(path.substring(third + 1, fourth));
|
|
onCredentialProtectedPathAccess(path, userId);
|
|
} catch (NumberFormatException ignored) {
|
|
}
|
|
} else if (path.startsWith("/data/data/")) {
|
|
onCredentialProtectedPathAccess(path, UserHandle.USER_SYSTEM);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* In the common case, as set by conditionallyEnableDebugLogging, we're just dropboxing any
|
|
* violations but not showing a dialog, not loggging, and not killing the process. In these
|
|
* cases we don't need to do a synchronous call to the ActivityManager. This is used by both
|
|
* per-thread and vm-wide violations when applicable.
|
|
*/
|
|
private static void dropboxViolationAsync(
|
|
final int penaltyMask, final ViolationInfo info) {
|
|
int outstanding = sDropboxCallsInFlight.incrementAndGet();
|
|
if (outstanding > 20) {
|
|
// What's going on? Let's not make make the situation
|
|
// worse and just not log.
|
|
sDropboxCallsInFlight.decrementAndGet();
|
|
return;
|
|
}
|
|
|
|
if (LOG_V) Log.d(TAG, "Dropboxing async; in-flight=" + outstanding);
|
|
|
|
BackgroundThread.getHandler().post(() -> {
|
|
handleApplicationStrictModeViolation(penaltyMask, info);
|
|
int outstandingInner = sDropboxCallsInFlight.decrementAndGet();
|
|
if (LOG_V) Log.d(TAG, "Dropbox complete; in-flight=" + outstandingInner);
|
|
});
|
|
}
|
|
|
|
private static void handleApplicationStrictModeViolation(int penaltyMask,
|
|
ViolationInfo info) {
|
|
final int oldMask = getThreadPolicyMask();
|
|
try {
|
|
// First, remove any policy before we call into the Activity Manager,
|
|
// otherwise we'll infinite recurse as we try to log policy violations
|
|
// to disk, thus violating policy, thus requiring logging, etc...
|
|
// We restore the current policy below, in the finally block.
|
|
setThreadPolicyMask(0);
|
|
|
|
IActivityManager am = ActivityManager.getService();
|
|
if (am == null) {
|
|
Log.w(TAG, "No activity manager; failed to Dropbox violation.");
|
|
} else {
|
|
am.handleApplicationStrictModeViolation(
|
|
RuntimeInit.getApplicationObject(), penaltyMask, info);
|
|
}
|
|
} catch (RemoteException e) {
|
|
if (e instanceof DeadObjectException) {
|
|
// System process is dead; ignore
|
|
} else {
|
|
Log.e(TAG, "RemoteException handling StrictMode violation", e);
|
|
}
|
|
} finally {
|
|
setThreadPolicyMask(oldMask);
|
|
}
|
|
}
|
|
|
|
private static class AndroidCloseGuardReporter implements CloseGuard.Reporter {
|
|
|
|
@Override
|
|
public void report(String message, Throwable allocationSite) {
|
|
onVmPolicyViolation(new LeakedClosableViolation(message, allocationSite));
|
|
}
|
|
|
|
@Override
|
|
public void report(String message) {
|
|
onVmPolicyViolation(new LeakedClosableViolation(message));
|
|
}
|
|
}
|
|
|
|
/** Called from Parcel.writeNoException() */
|
|
/* package */ static boolean hasGatheredViolations() {
|
|
return gatheredViolations.get() != null;
|
|
}
|
|
|
|
/**
|
|
* Called from Parcel.writeException(), so we drop this memory and don't incorrectly attribute
|
|
* it to the wrong caller on the next Binder call on this thread.
|
|
*/
|
|
/* package */ static void clearGatheredViolations() {
|
|
gatheredViolations.set(null);
|
|
}
|
|
|
|
/** @hide */
|
|
@UnsupportedAppUsage
|
|
@TestApi
|
|
public static void conditionallyCheckInstanceCounts() {
|
|
VmPolicy policy = getVmPolicy();
|
|
int policySize = policy.classInstanceLimit.size();
|
|
if (policySize == 0) {
|
|
return;
|
|
}
|
|
|
|
// Temporarily disable checks so that explicit GC is allowed.
|
|
final int oldMask = getThreadPolicyMask();
|
|
setThreadPolicyMask(0);
|
|
System.gc();
|
|
System.runFinalization();
|
|
System.gc();
|
|
setThreadPolicyMask(oldMask);
|
|
|
|
// Note: classInstanceLimit is immutable, so this is lock-free
|
|
// Create the classes array.
|
|
Class[] classes = policy.classInstanceLimit.keySet().toArray(new Class[policySize]);
|
|
long[] instanceCounts = VMDebug.countInstancesOfClasses(classes, false);
|
|
for (int i = 0; i < classes.length; ++i) {
|
|
Class klass = classes[i];
|
|
int limit = policy.classInstanceLimit.get(klass);
|
|
long instances = instanceCounts[i];
|
|
if (instances > limit) {
|
|
onVmPolicyViolation(new InstanceCountViolation(klass, instances, limit));
|
|
}
|
|
}
|
|
}
|
|
|
|
private static long sLastInstanceCountCheckMillis = 0;
|
|
private static boolean sIsIdlerRegistered = false; // guarded by StrictMode.class
|
|
private static final MessageQueue.IdleHandler sProcessIdleHandler =
|
|
new MessageQueue.IdleHandler() {
|
|
public boolean queueIdle() {
|
|
long now = SystemClock.uptimeMillis();
|
|
if (now - sLastInstanceCountCheckMillis > 30 * 1000) {
|
|
sLastInstanceCountCheckMillis = now;
|
|
conditionallyCheckInstanceCounts();
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Sets the policy for what actions in the VM process (on any thread) should be detected, as
|
|
* well as the penalty if such actions occur.
|
|
*
|
|
* @param policy the policy to put into place
|
|
*/
|
|
public static void setVmPolicy(final VmPolicy policy) {
|
|
synchronized (StrictMode.class) {
|
|
sVmPolicy = policy;
|
|
setCloseGuardEnabled(vmClosableObjectLeaksEnabled());
|
|
|
|
Looper looper = Looper.getMainLooper();
|
|
if (looper != null) {
|
|
MessageQueue mq = looper.mQueue;
|
|
if (policy.classInstanceLimit.size() == 0
|
|
|| (sVmPolicy.mask & PENALTY_ALL) == 0) {
|
|
mq.removeIdleHandler(sProcessIdleHandler);
|
|
sIsIdlerRegistered = false;
|
|
} else if (!sIsIdlerRegistered) {
|
|
mq.addIdleHandler(sProcessIdleHandler);
|
|
sIsIdlerRegistered = true;
|
|
}
|
|
}
|
|
|
|
int networkPolicy = NETWORK_POLICY_ACCEPT;
|
|
if ((sVmPolicy.mask & DETECT_VM_CLEARTEXT_NETWORK) != 0) {
|
|
if ((sVmPolicy.mask & PENALTY_DEATH) != 0
|
|
|| (sVmPolicy.mask & PENALTY_DEATH_ON_CLEARTEXT_NETWORK) != 0) {
|
|
networkPolicy = NETWORK_POLICY_REJECT;
|
|
} else {
|
|
networkPolicy = NETWORK_POLICY_LOG;
|
|
}
|
|
}
|
|
|
|
final INetworkManagementService netd =
|
|
INetworkManagementService.Stub.asInterface(
|
|
ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
|
|
if (netd != null) {
|
|
try {
|
|
netd.setUidCleartextNetworkPolicy(android.os.Process.myUid(), networkPolicy);
|
|
} catch (RemoteException ignored) {
|
|
}
|
|
} else if (networkPolicy != NETWORK_POLICY_ACCEPT) {
|
|
Log.w(TAG, "Dropping requested network policy due to missing service!");
|
|
}
|
|
|
|
|
|
if ((sVmPolicy.mask & DETECT_VM_NON_SDK_API_USAGE) != 0) {
|
|
VMRuntime.setNonSdkApiUsageConsumer(sNonSdkApiUsageConsumer);
|
|
VMRuntime.setDedupeHiddenApiWarnings(false);
|
|
} else {
|
|
VMRuntime.setNonSdkApiUsageConsumer(null);
|
|
VMRuntime.setDedupeHiddenApiWarnings(true);
|
|
}
|
|
|
|
if ((sVmPolicy.mask & DETECT_VM_UNSAFE_INTENT_LAUNCH) != 0) {
|
|
registerIntentMatchingRestrictionCallback();
|
|
}
|
|
|
|
setBlockGuardVmPolicy(sVmPolicy.mask);
|
|
}
|
|
}
|
|
|
|
private static void registerIntentMatchingRestrictionCallback() {
|
|
try {
|
|
ActivityManager.getService().registerStrictModeCallback(
|
|
new UnsafeIntentStrictModeCallback());
|
|
} catch (RemoteException e) {
|
|
/*
|
|
If exception is DeadObjectException it means system process is dead, so we can ignore
|
|
*/
|
|
if (!(e instanceof DeadObjectException)) {
|
|
Log.e(TAG, "RemoteException handling StrictMode violation", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static final class UnsafeIntentStrictModeCallback
|
|
extends IUnsafeIntentStrictModeCallback.Stub {
|
|
@Override
|
|
public void onImplicitIntentMatchedInternalComponent(Intent intent) {
|
|
if (StrictMode.vmUnsafeIntentLaunchEnabled()) {
|
|
StrictMode.onUnsafeIntentLaunch(intent,
|
|
"Launch of unsafe implicit intent: " + intent);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Gets the current VM policy. */
|
|
public static VmPolicy getVmPolicy() {
|
|
synchronized (StrictMode.class) {
|
|
return sVmPolicy;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enable the recommended StrictMode defaults, with violations just being logged.
|
|
*
|
|
* <p>This catches disk and network access on the main thread, as well as leaked SQLite cursors
|
|
* and unclosed resources. This is simply a wrapper around {@link #setVmPolicy} and {@link
|
|
* #setThreadPolicy}.
|
|
*/
|
|
public static void enableDefaults() {
|
|
setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
|
|
setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
|
|
}
|
|
|
|
/** @hide */
|
|
public static boolean vmSqliteObjectLeaksEnabled() {
|
|
return (sVmPolicy.mask & DETECT_VM_CURSOR_LEAKS) != 0;
|
|
}
|
|
|
|
/** @hide */
|
|
public static boolean vmClosableObjectLeaksEnabled() {
|
|
return (sVmPolicy.mask & DETECT_VM_CLOSABLE_LEAKS) != 0;
|
|
}
|
|
|
|
/** @hide */
|
|
public static boolean vmRegistrationLeaksEnabled() {
|
|
return (sVmPolicy.mask & DETECT_VM_REGISTRATION_LEAKS) != 0;
|
|
}
|
|
|
|
/** @hide */
|
|
public static boolean vmFileUriExposureEnabled() {
|
|
return (sVmPolicy.mask & DETECT_VM_FILE_URI_EXPOSURE) != 0;
|
|
}
|
|
|
|
/** @hide */
|
|
public static boolean vmCleartextNetworkEnabled() {
|
|
return (sVmPolicy.mask & DETECT_VM_CLEARTEXT_NETWORK) != 0;
|
|
}
|
|
|
|
/** @hide */
|
|
public static boolean vmContentUriWithoutPermissionEnabled() {
|
|
return (sVmPolicy.mask & DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION) != 0;
|
|
}
|
|
|
|
/** @hide */
|
|
public static boolean vmUntaggedSocketEnabled() {
|
|
return (sVmPolicy.mask & DETECT_VM_UNTAGGED_SOCKET) != 0;
|
|
}
|
|
|
|
/** @hide */
|
|
public static boolean vmImplicitDirectBootEnabled() {
|
|
return (sVmPolicy.mask & DETECT_VM_IMPLICIT_DIRECT_BOOT) != 0;
|
|
}
|
|
|
|
/** @hide */
|
|
public static boolean vmCredentialProtectedWhileLockedEnabled() {
|
|
return (sVmPolicy.mask & DETECT_VM_CREDENTIAL_PROTECTED_WHILE_LOCKED) != 0;
|
|
}
|
|
|
|
/** @hide */
|
|
public static boolean vmIncorrectContextUseEnabled() {
|
|
return (sVmPolicy.mask & DETECT_VM_INCORRECT_CONTEXT_USE) != 0;
|
|
}
|
|
|
|
/** @hide */
|
|
public static boolean vmUnsafeIntentLaunchEnabled() {
|
|
return (sVmPolicy.mask & DETECT_VM_UNSAFE_INTENT_LAUNCH) != 0;
|
|
}
|
|
|
|
/** @hide */
|
|
public static void onSqliteObjectLeaked(String message, Throwable originStack) {
|
|
onVmPolicyViolation(new SqliteObjectLeakedViolation(message, originStack));
|
|
}
|
|
|
|
/** @hide */
|
|
@UnsupportedAppUsage
|
|
public static void onWebViewMethodCalledOnWrongThread(Throwable originStack) {
|
|
onVmPolicyViolation(new WebViewMethodCalledOnWrongThreadViolation(originStack));
|
|
}
|
|
|
|
/** @hide */
|
|
public static void onIntentReceiverLeaked(Throwable originStack) {
|
|
onVmPolicyViolation(new IntentReceiverLeakedViolation(originStack));
|
|
}
|
|
|
|
/** @hide */
|
|
public static void onServiceConnectionLeaked(Throwable originStack) {
|
|
onVmPolicyViolation(new ServiceConnectionLeakedViolation(originStack));
|
|
}
|
|
|
|
/** @hide */
|
|
public static void onFileUriExposed(Uri uri, String location) {
|
|
final String message = uri + " exposed beyond app through " + location;
|
|
if ((sVmPolicy.mask & PENALTY_DEATH_ON_FILE_URI_EXPOSURE) != 0) {
|
|
throw new FileUriExposedException(message);
|
|
} else {
|
|
onVmPolicyViolation(new FileUriExposedViolation(message));
|
|
}
|
|
}
|
|
|
|
/** @hide */
|
|
public static void onContentUriWithoutPermission(Uri uri, String location) {
|
|
onVmPolicyViolation(new ContentUriWithoutPermissionViolation(uri, location));
|
|
}
|
|
|
|
/** @hide */
|
|
public static void onCleartextNetworkDetected(byte[] firstPacket) {
|
|
byte[] rawAddr = null;
|
|
if (firstPacket != null) {
|
|
if (firstPacket.length >= 20 && (firstPacket[0] & 0xf0) == 0x40) {
|
|
// IPv4
|
|
rawAddr = new byte[4];
|
|
System.arraycopy(firstPacket, 16, rawAddr, 0, 4);
|
|
} else if (firstPacket.length >= 40 && (firstPacket[0] & 0xf0) == 0x60) {
|
|
// IPv6
|
|
rawAddr = new byte[16];
|
|
System.arraycopy(firstPacket, 24, rawAddr, 0, 16);
|
|
}
|
|
}
|
|
|
|
final int uid = android.os.Process.myUid();
|
|
final StringBuilder msg = new StringBuilder("Detected cleartext network traffic from UID ")
|
|
.append(uid);
|
|
if (rawAddr != null) {
|
|
try {
|
|
msg.append(" to ").append(InetAddress.getByAddress(rawAddr));
|
|
} catch (UnknownHostException ignored) {
|
|
}
|
|
}
|
|
msg.append(HexDump.dumpHexString(firstPacket).trim()).append(' ');
|
|
final boolean forceDeath = (sVmPolicy.mask & PENALTY_DEATH_ON_CLEARTEXT_NETWORK) != 0;
|
|
onVmPolicyViolation(new CleartextNetworkViolation(msg.toString()), forceDeath);
|
|
}
|
|
|
|
/** @hide */
|
|
public static void onUntaggedSocket() {
|
|
onVmPolicyViolation(new UntaggedSocketViolation());
|
|
}
|
|
|
|
/** @hide */
|
|
public static void onImplicitDirectBoot() {
|
|
onVmPolicyViolation(new ImplicitDirectBootViolation());
|
|
}
|
|
|
|
/** @hide */
|
|
public static void onIncorrectContextUsed(String message, Throwable originStack) {
|
|
onVmPolicyViolation(new IncorrectContextUseViolation(message, originStack));
|
|
}
|
|
|
|
/**
|
|
* A helper method to verify if the {@code context} has a proper {@link Configuration} to obtain
|
|
* {@link android.view.LayoutInflater}, {@link android.view.ViewConfiguration} or
|
|
* {@link android.view.GestureDetector}. Throw {@link IncorrectContextUseViolation} if the
|
|
* {@code context} doesn't have a proper configuration.
|
|
* <p>
|
|
* Note that the context created via {@link Context#createConfigurationContext(Configuration)}
|
|
* is also regarded as a context with a proper configuration because the {@link Configuration}
|
|
* is handled by developers.
|
|
* </p>
|
|
* @param context The context to verify if it is a display associative context
|
|
* @param methodName The asserted method name
|
|
*
|
|
* @see Context#isConfigurationContext()
|
|
* @see Context#createConfigurationContext(Configuration)
|
|
* @see Context#getSystemService(String)
|
|
* @see Context#LAYOUT_INFLATER_SERVICE
|
|
* @see android.view.ViewConfiguration#get(Context)
|
|
* @see android.view.LayoutInflater#from(Context)
|
|
* @see IncorrectContextUseViolation
|
|
*
|
|
* @hide
|
|
*/
|
|
public static void assertConfigurationContext(@NonNull Context context,
|
|
@NonNull String methodName) {
|
|
if (vmIncorrectContextUseEnabled() && !context.isConfigurationContext()) {
|
|
final String errorMessage = "Tried to access the API:" + methodName + " which needs to"
|
|
+ " have proper configuration from a non-UI Context:" + context;
|
|
final String message = "The API:" + methodName + " needs a proper configuration."
|
|
+ " Use UI contexts such as an activity or a context created"
|
|
+ " via createWindowContext(Display, int, Bundle) or "
|
|
+ " createConfigurationContext(Configuration) with a proper configuration.";
|
|
final Exception exception = new IllegalAccessException(errorMessage);
|
|
StrictMode.onIncorrectContextUsed(message, exception);
|
|
Log.e(TAG, errorMessage + " " + message, exception);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A helper method to verify if the {@code context} is a UI context and throw
|
|
* {@link IncorrectContextUseViolation} if the {@code context} is not a UI context.
|
|
*
|
|
* @param context The context to verify if it is a UI context
|
|
* @param methodName The asserted method name
|
|
*
|
|
* @see Context#isUiContext()
|
|
* @see IncorrectContextUseViolation
|
|
*
|
|
* @hide
|
|
*/
|
|
public static void assertUiContext(@NonNull Context context, @NonNull String methodName) {
|
|
if (vmIncorrectContextUseEnabled() && !context.isUiContext()) {
|
|
final String errorMessage = "Tried to access UI related API:" + methodName
|
|
+ " from a non-UI Context:" + context;
|
|
final String message = methodName + " should be accessed from Activity or other UI "
|
|
+ "Contexts. Use an Activity or a Context created with "
|
|
+ "Context#createWindowContext(int, Bundle), which are adjusted to "
|
|
+ "the configuration and visual bounds of an area on screen.";
|
|
final Exception exception = new IllegalAccessException(errorMessage);
|
|
StrictMode.onIncorrectContextUsed(message, exception);
|
|
Log.e(TAG, errorMessage + " " + message, exception);
|
|
}
|
|
}
|
|
|
|
/** @hide */
|
|
public static void onUnsafeIntentLaunch(Intent intent) {
|
|
onVmPolicyViolation(new UnsafeIntentLaunchViolation(intent));
|
|
}
|
|
|
|
/** @hide */
|
|
public static void onUnsafeIntentLaunch(Intent intent, String message) {
|
|
onVmPolicyViolation(new UnsafeIntentLaunchViolation(intent, message));
|
|
}
|
|
|
|
/** Assume locked until we hear otherwise */
|
|
private static volatile boolean sCeStorageUnlocked = false;
|
|
|
|
/**
|
|
* Avoid (potentially) costly and repeated lookups to the same mount service.
|
|
* Note that we don't use the Singleton wrapper as lookup may fail early during boot.
|
|
*/
|
|
private static volatile IStorageManager sStorageManager;
|
|
|
|
private static boolean isCeStorageUnlocked(int userId) {
|
|
IStorageManager storage = sStorageManager;
|
|
if (storage == null) {
|
|
storage = IStorageManager.Stub
|
|
.asInterface(ServiceManager.getService("mount"));
|
|
// As the queried handle may be null early during boot, only stash valid handles,
|
|
// avoiding races with concurrent service queries.
|
|
if (storage != null) {
|
|
sStorageManager = storage;
|
|
}
|
|
}
|
|
if (storage != null) {
|
|
try {
|
|
return storage.isCeStorageUnlocked(userId);
|
|
} catch (RemoteException ignored) {
|
|
// Conservatively clear the ref, allowing refresh if the remote process restarts.
|
|
sStorageManager = null;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** @hide */
|
|
private static void onCredentialProtectedPathAccess(String path, int userId) {
|
|
// We can cache the unlocked state for the userId we're running as,
|
|
// since any relocking of that user will always result in our
|
|
// process being killed to release any CE FDs we're holding onto.
|
|
if (userId == UserHandle.myUserId()) {
|
|
if (sCeStorageUnlocked) {
|
|
return;
|
|
} else if (isCeStorageUnlocked(userId)) {
|
|
sCeStorageUnlocked = true;
|
|
return;
|
|
}
|
|
} else if (isCeStorageUnlocked(userId)) {
|
|
return;
|
|
}
|
|
|
|
onVmPolicyViolation(new CredentialProtectedWhileLockedViolation(
|
|
"Accessed credential protected path " + path + " while user " + userId
|
|
+ " was locked"));
|
|
}
|
|
|
|
// Map from VM violation fingerprint to uptime millis.
|
|
@UnsupportedAppUsage
|
|
private static final HashMap<Integer, Long> sLastVmViolationTime = new HashMap<>();
|
|
private static final SparseLongArray sRealLastVmViolationTime = new SparseLongArray();
|
|
|
|
/**
|
|
* Clamp the given map by removing elements with timestamp older than the given retainSince.
|
|
*/
|
|
private static void clampViolationTimeMap(final @NonNull SparseLongArray violationTime,
|
|
final long retainSince) {
|
|
for (int i = 0; i < violationTime.size(); ) {
|
|
if (violationTime.valueAt(i) < retainSince) {
|
|
// Remove stale entries
|
|
violationTime.removeAt(i);
|
|
} else {
|
|
i++;
|
|
}
|
|
}
|
|
// Ideally we'd cap the total size of the map, though it'll involve quickselect of topK,
|
|
// seems not worth it (saving some space immediately but they will be obsoleted soon anyway)
|
|
}
|
|
|
|
/** @hide */
|
|
public static void onVmPolicyViolation(Violation originStack) {
|
|
onVmPolicyViolation(originStack, false);
|
|
}
|
|
|
|
/** @hide */
|
|
public static void onVmPolicyViolation(Violation violation, boolean forceDeath) {
|
|
final VmPolicy vmPolicy = getVmPolicy();
|
|
final boolean penaltyDropbox = (vmPolicy.mask & PENALTY_DROPBOX) != 0;
|
|
final boolean penaltyDeath = ((vmPolicy.mask & PENALTY_DEATH) != 0) || forceDeath;
|
|
final boolean penaltyLog = (vmPolicy.mask & PENALTY_LOG) != 0;
|
|
|
|
final int penaltyMask = (vmPolicy.mask & PENALTY_ALL);
|
|
final ViolationInfo info = new ViolationInfo(violation, penaltyMask);
|
|
|
|
// Erase stuff not relevant for process-wide violations
|
|
info.numAnimationsRunning = 0;
|
|
info.tags = null;
|
|
info.broadcastIntentAction = null;
|
|
|
|
final Integer fingerprint = info.hashCode();
|
|
final long now = SystemClock.uptimeMillis();
|
|
long lastViolationTime;
|
|
long timeSinceLastViolationMillis = Long.MAX_VALUE;
|
|
if (sLogger == LOGCAT_LOGGER) { // Don't throttle it if there is a non-default logger
|
|
synchronized (sRealLastVmViolationTime) {
|
|
if (sRealLastVmViolationTime.indexOfKey(fingerprint) >= 0) {
|
|
lastViolationTime = sRealLastVmViolationTime.get(fingerprint);
|
|
timeSinceLastViolationMillis = now - lastViolationTime;
|
|
}
|
|
if (timeSinceLastViolationMillis > MIN_VM_INTERVAL_MS) {
|
|
sRealLastVmViolationTime.put(fingerprint, now);
|
|
}
|
|
clampViolationTimeMap(sRealLastVmViolationTime,
|
|
now - Math.max(MIN_VM_INTERVAL_MS, MIN_LOG_INTERVAL_MS));
|
|
}
|
|
}
|
|
if (timeSinceLastViolationMillis <= MIN_VM_INTERVAL_MS) {
|
|
// Rate limit all penalties.
|
|
return;
|
|
}
|
|
|
|
if (penaltyLog && sLogger != null && timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
|
|
sLogger.log(info);
|
|
}
|
|
|
|
if (penaltyDropbox) {
|
|
if (penaltyDeath) {
|
|
handleApplicationStrictModeViolation(PENALTY_DROPBOX, info);
|
|
} else {
|
|
// Common case for userdebug/eng builds. If no death and
|
|
// just dropboxing, we can do the ActivityManager call
|
|
// asynchronously.
|
|
dropboxViolationAsync(PENALTY_DROPBOX, info);
|
|
}
|
|
}
|
|
|
|
if (penaltyDeath) {
|
|
System.err.println("StrictMode VmPolicy violation with POLICY_DEATH; shutting down.");
|
|
Process.killProcess(Process.myPid());
|
|
System.exit(10);
|
|
}
|
|
|
|
// If penaltyDeath, we can't guarantee this callback finishes before the process dies for
|
|
// all executors. penaltyDeath supersedes penaltyCallback.
|
|
if (vmPolicy.mListener != null && vmPolicy.mCallbackExecutor != null) {
|
|
final OnVmViolationListener listener = vmPolicy.mListener;
|
|
try {
|
|
vmPolicy.mCallbackExecutor.execute(
|
|
() -> {
|
|
// Lift violated policy to prevent infinite recursion.
|
|
VmPolicy oldPolicy = allowVmViolations();
|
|
try {
|
|
listener.onVmViolation(violation);
|
|
} finally {
|
|
setVmPolicy(oldPolicy);
|
|
}
|
|
});
|
|
} catch (RejectedExecutionException e) {
|
|
Log.e(TAG, "VmPolicy penaltyCallback failed", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Called from Parcel.writeNoException() */
|
|
/* package */ static void writeGatheredViolationsToParcel(Parcel p) {
|
|
ArrayList<ViolationInfo> violations = gatheredViolations.get();
|
|
if (violations == null) {
|
|
p.writeInt(0);
|
|
} else {
|
|
// To avoid taking up too much transaction space, only include
|
|
// details for the first 3 violations. Deep inside, CrashInfo
|
|
// will truncate each stack trace to ~20kB.
|
|
final int size = Math.min(violations.size(), 3);
|
|
p.writeInt(size);
|
|
for (int i = 0; i < size; i++) {
|
|
violations.get(i).writeToParcel(p, 0);
|
|
}
|
|
}
|
|
gatheredViolations.set(null);
|
|
}
|
|
|
|
/**
|
|
* Called from Parcel.readException() when the exception is EX_STRICT_MODE_VIOLATIONS, we here
|
|
* read back all the encoded violations.
|
|
*/
|
|
/* package */ static void readAndHandleBinderCallViolations(Parcel p) {
|
|
Throwable localCallSite = new Throwable();
|
|
final int policyMask = getThreadPolicyMask();
|
|
final boolean currentlyGathering = (policyMask & PENALTY_GATHER) != 0;
|
|
|
|
final int size = p.readInt();
|
|
for (int i = 0; i < size; i++) {
|
|
final ViolationInfo info = new ViolationInfo(p, !currentlyGathering);
|
|
info.addLocalStack(localCallSite);
|
|
BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
|
|
if (policy instanceof AndroidBlockGuardPolicy) {
|
|
((AndroidBlockGuardPolicy) policy).handleViolationWithTimingAttempt(info);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called from android_util_Binder.cpp's android_os_Parcel_enforceInterface when an incoming
|
|
* Binder call requires changing the StrictMode policy mask. The role of this function is to ask
|
|
* Binder for its current (native) thread-local policy value and synchronize it to libcore's
|
|
* (Java) thread-local policy value.
|
|
*/
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
private static void onBinderStrictModePolicyChange(@ThreadPolicyMask int newPolicy) {
|
|
setBlockGuardPolicy(newPolicy);
|
|
}
|
|
|
|
/**
|
|
* A tracked, critical time span. (e.g. during an animation.)
|
|
*
|
|
* <p>The object itself is a linked list node, to avoid any allocations during rapid span
|
|
* entries and exits.
|
|
*
|
|
* @hide
|
|
*/
|
|
public static class Span {
|
|
private String mName;
|
|
private long mCreateMillis;
|
|
private Span mNext;
|
|
private Span mPrev; // not used when in freeList, only active
|
|
private final ThreadSpanState mContainerState;
|
|
|
|
Span(ThreadSpanState threadState) {
|
|
mContainerState = threadState;
|
|
}
|
|
|
|
// Empty constructor for the NO_OP_SPAN
|
|
protected Span() {
|
|
mContainerState = null;
|
|
}
|
|
|
|
/**
|
|
* To be called when the critical span is complete (i.e. the animation is done animating).
|
|
* This can be called on any thread (even a different one from where the animation was
|
|
* taking place), but that's only a defensive implementation measure. It really makes no
|
|
* sense for you to call this on thread other than that where you created it.
|
|
*
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public void finish() {
|
|
ThreadSpanState state = mContainerState;
|
|
synchronized (state) {
|
|
if (mName == null) {
|
|
// Duplicate finish call. Ignore.
|
|
return;
|
|
}
|
|
|
|
// Remove ourselves from the active list.
|
|
if (mPrev != null) {
|
|
mPrev.mNext = mNext;
|
|
}
|
|
if (mNext != null) {
|
|
mNext.mPrev = mPrev;
|
|
}
|
|
if (state.mActiveHead == this) {
|
|
state.mActiveHead = mNext;
|
|
}
|
|
|
|
state.mActiveSize--;
|
|
|
|
if (LOG_V) Log.d(TAG, "Span finished=" + mName + "; size=" + state.mActiveSize);
|
|
|
|
this.mCreateMillis = -1;
|
|
this.mName = null;
|
|
this.mPrev = null;
|
|
this.mNext = null;
|
|
|
|
// Add ourselves to the freeList, if it's not already
|
|
// too big.
|
|
if (state.mFreeListSize < 5) {
|
|
this.mNext = state.mFreeListHead;
|
|
state.mFreeListHead = this;
|
|
state.mFreeListSize++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// The no-op span that's used in user builds.
|
|
private static final Span NO_OP_SPAN =
|
|
new Span() {
|
|
public void finish() {
|
|
// Do nothing.
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Linked lists of active spans and a freelist.
|
|
*
|
|
* <p>Locking notes: there's one of these structures per thread and all members of this
|
|
* structure (as well as the Span nodes under it) are guarded by the ThreadSpanState object
|
|
* instance. While in theory there'd be no locking required because it's all local per-thread,
|
|
* the finish() method above is defensive against people calling it on a different thread from
|
|
* where they created the Span, hence the locking.
|
|
*/
|
|
private static class ThreadSpanState {
|
|
public Span mActiveHead; // doubly-linked list.
|
|
public int mActiveSize;
|
|
public Span mFreeListHead; // singly-linked list. only changes at head.
|
|
public int mFreeListSize;
|
|
}
|
|
|
|
private static final ThreadLocal<ThreadSpanState> sThisThreadSpanState =
|
|
new ThreadLocal<ThreadSpanState>() {
|
|
@Override
|
|
protected ThreadSpanState initialValue() {
|
|
return new ThreadSpanState();
|
|
}
|
|
};
|
|
|
|
@UnsupportedAppUsage
|
|
private static Singleton<IWindowManager> sWindowManager =
|
|
new Singleton<IWindowManager>() {
|
|
protected IWindowManager create() {
|
|
return IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Enter a named critical span (e.g. an animation)
|
|
*
|
|
* <p>The name is an arbitary label (or tag) that will be applied to any strictmode violation
|
|
* that happens while this span is active. You must call finish() on the span when done.
|
|
*
|
|
* <p>This will never return null, but on devices without debugging enabled, this may return a
|
|
* placeholder object on which the finish() method is a no-op.
|
|
*
|
|
* <p>TODO: add CloseGuard to this, verifying callers call finish.
|
|
*
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public static Span enterCriticalSpan(String name) {
|
|
if (Build.IS_USER) {
|
|
return NO_OP_SPAN;
|
|
}
|
|
if (name == null || name.isEmpty()) {
|
|
throw new IllegalArgumentException("name must be non-null and non-empty");
|
|
}
|
|
ThreadSpanState state = sThisThreadSpanState.get();
|
|
Span span = null;
|
|
synchronized (state) {
|
|
if (state.mFreeListHead != null) {
|
|
span = state.mFreeListHead;
|
|
state.mFreeListHead = span.mNext;
|
|
state.mFreeListSize--;
|
|
} else {
|
|
// Shouldn't have to do this often.
|
|
span = new Span(state);
|
|
}
|
|
span.mName = name;
|
|
span.mCreateMillis = SystemClock.uptimeMillis();
|
|
span.mNext = state.mActiveHead;
|
|
span.mPrev = null;
|
|
state.mActiveHead = span;
|
|
state.mActiveSize++;
|
|
if (span.mNext != null) {
|
|
span.mNext.mPrev = span;
|
|
}
|
|
if (LOG_V) Log.d(TAG, "Span enter=" + name + "; size=" + state.mActiveSize);
|
|
}
|
|
return span;
|
|
}
|
|
|
|
/**
|
|
* For code to note that it's slow. This is a no-op unless the current thread's {@link
|
|
* android.os.StrictMode.ThreadPolicy} has {@link
|
|
* android.os.StrictMode.ThreadPolicy.Builder#detectCustomSlowCalls} enabled.
|
|
*
|
|
* @param name a short string for the exception stack trace that's built if when this fires.
|
|
*/
|
|
public static void noteSlowCall(String name) {
|
|
BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
|
|
if (!(policy instanceof AndroidBlockGuardPolicy)) {
|
|
// StrictMode not enabled.
|
|
return;
|
|
}
|
|
((AndroidBlockGuardPolicy) policy).onCustomSlowCall(name);
|
|
}
|
|
|
|
/** @hide */
|
|
@SystemApi(client = MODULE_LIBRARIES)
|
|
public static void noteUntaggedSocket() {
|
|
if (vmUntaggedSocketEnabled()) onUntaggedSocket();
|
|
}
|
|
|
|
/**
|
|
* For code to note that a resource was obtained using a type other than its defined type. This
|
|
* is a no-op unless the current thread's {@link android.os.StrictMode.ThreadPolicy} has {@link
|
|
* android.os.StrictMode.ThreadPolicy.Builder#detectResourceMismatches()} enabled.
|
|
*
|
|
* @param tag an object for the exception stack trace that's built if when this fires.
|
|
* @hide
|
|
*/
|
|
public static void noteResourceMismatch(Object tag) {
|
|
BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
|
|
if (!(policy instanceof AndroidBlockGuardPolicy)) {
|
|
// StrictMode not enabled.
|
|
return;
|
|
}
|
|
((AndroidBlockGuardPolicy) policy).onResourceMismatch(tag);
|
|
}
|
|
|
|
/** @hide */
|
|
public static void noteUnbufferedIO() {
|
|
BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
|
|
if (!(policy instanceof AndroidBlockGuardPolicy)) {
|
|
// StrictMode not enabled.
|
|
return;
|
|
}
|
|
policy.onUnbufferedIO();
|
|
}
|
|
|
|
/** @hide */
|
|
public static void noteDiskRead() {
|
|
BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
|
|
if (!(policy instanceof AndroidBlockGuardPolicy)) {
|
|
// StrictMode not enabled.
|
|
return;
|
|
}
|
|
policy.onReadFromDisk();
|
|
}
|
|
|
|
/** @hide */
|
|
public static void noteDiskWrite() {
|
|
BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
|
|
if (!(policy instanceof AndroidBlockGuardPolicy)) {
|
|
// StrictMode not enabled.
|
|
return;
|
|
}
|
|
policy.onWriteToDisk();
|
|
}
|
|
|
|
@GuardedBy("StrictMode.class")
|
|
private static final HashMap<Class, Integer> sExpectedActivityInstanceCount = new HashMap<>();
|
|
|
|
/**
|
|
* Returns an object that is used to track instances of activites. The activity should store a
|
|
* reference to the tracker object in one of its fields.
|
|
*
|
|
* @hide
|
|
*/
|
|
public static Object trackActivity(Object instance) {
|
|
return new InstanceTracker(instance);
|
|
}
|
|
|
|
/** @hide */
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
public static void incrementExpectedActivityCount(Class klass) {
|
|
if (klass == null) {
|
|
return;
|
|
}
|
|
|
|
synchronized (StrictMode.class) {
|
|
if ((sVmPolicy.mask & DETECT_VM_ACTIVITY_LEAKS) == 0) {
|
|
return;
|
|
}
|
|
|
|
// Use the instance count from InstanceTracker as initial value.
|
|
Integer expected = sExpectedActivityInstanceCount.get(klass);
|
|
Integer newExpected =
|
|
expected == null ? InstanceTracker.getInstanceCount(klass) + 1 : expected + 1;
|
|
sExpectedActivityInstanceCount.put(klass, newExpected);
|
|
}
|
|
}
|
|
|
|
/** @hide */
|
|
public static void decrementExpectedActivityCount(Class klass) {
|
|
if (klass == null) {
|
|
return;
|
|
}
|
|
|
|
final int limit;
|
|
synchronized (StrictMode.class) {
|
|
if ((sVmPolicy.mask & DETECT_VM_ACTIVITY_LEAKS) == 0) {
|
|
return;
|
|
}
|
|
|
|
Integer expected = sExpectedActivityInstanceCount.get(klass);
|
|
int newExpected = (expected == null || expected == 0) ? 0 : expected - 1;
|
|
if (newExpected == 0) {
|
|
sExpectedActivityInstanceCount.remove(klass);
|
|
} else {
|
|
sExpectedActivityInstanceCount.put(klass, newExpected);
|
|
}
|
|
|
|
// Note: adding 1 here to give some breathing room during
|
|
// orientation changes. (shouldn't be necessary, though?)
|
|
limit = newExpected + 1;
|
|
}
|
|
|
|
// Quick check.
|
|
int actual = InstanceTracker.getInstanceCount(klass);
|
|
if (actual <= limit) {
|
|
return;
|
|
}
|
|
|
|
// Do a GC and explicit count to double-check.
|
|
// This is the work that we are trying to avoid by tracking the object instances
|
|
// explicity. Running an explicit GC can be expensive (80ms) and so can walking
|
|
// the heap to count instance (30ms). This extra work can make the system feel
|
|
// noticeably less responsive during orientation changes when activities are
|
|
// being restarted. Granted, it is only a problem when StrictMode is enabled
|
|
// but it is annoying.
|
|
|
|
System.gc();
|
|
System.runFinalization();
|
|
System.gc();
|
|
|
|
long instances = VMDebug.countInstancesOfClass(klass, false);
|
|
if (instances > limit) {
|
|
onVmPolicyViolation(new InstanceCountViolation(klass, instances, limit));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parcelable that gets sent in Binder call headers back to callers to report violations that
|
|
* happened during a cross-process call.
|
|
*
|
|
* @hide
|
|
*/
|
|
@TestApi
|
|
public static final class ViolationInfo implements Parcelable {
|
|
/** Stack and violation details. */
|
|
private final Violation mViolation;
|
|
|
|
/** Path leading to a violation that occurred across binder. */
|
|
private final Deque<StackTraceElement[]> mBinderStack = new ArrayDeque<>();
|
|
|
|
/** Memoized stack trace of full violation. */
|
|
@Nullable private String mStackTrace;
|
|
|
|
/** The strict mode penalty mask at the time of violation. */
|
|
private final int mPenaltyMask;
|
|
|
|
/** The wall time duration of the violation, when known. -1 when not known. */
|
|
public int durationMillis = -1;
|
|
|
|
/** The number of animations currently running. */
|
|
public int numAnimationsRunning = 0;
|
|
|
|
/** List of tags from active Span instances during this violation, or null for none. */
|
|
public String[] tags;
|
|
|
|
/**
|
|
* Which violation number this was (1-based) since the last Looper loop, from the
|
|
* perspective of the root caller (if it crossed any processes via Binder calls). The value
|
|
* is 0 if the root caller wasn't on a Looper thread.
|
|
*/
|
|
public int violationNumThisLoop;
|
|
|
|
/** The time (in terms of SystemClock.uptimeMillis()) that the violation occurred. */
|
|
public long violationUptimeMillis;
|
|
|
|
/**
|
|
* The action of the Intent being broadcast to somebody's onReceive on this thread right
|
|
* now, or null.
|
|
*/
|
|
public String broadcastIntentAction;
|
|
|
|
/** If this is a instance count violation, the number of instances in memory, else -1. */
|
|
public long numInstances = -1;
|
|
|
|
/** Create an instance of ViolationInfo initialized from an exception. */
|
|
ViolationInfo(Violation tr, int penaltyMask) {
|
|
this.mViolation = tr;
|
|
this.mPenaltyMask = penaltyMask;
|
|
violationUptimeMillis = SystemClock.uptimeMillis();
|
|
this.numAnimationsRunning = ValueAnimator.getCurrentAnimationsCount();
|
|
Intent broadcastIntent = ActivityThread.getIntentBeingBroadcast();
|
|
if (broadcastIntent != null) {
|
|
broadcastIntentAction = broadcastIntent.getAction();
|
|
}
|
|
ThreadSpanState state = sThisThreadSpanState.get();
|
|
if (tr instanceof InstanceCountViolation) {
|
|
this.numInstances = ((InstanceCountViolation) tr).getNumberOfInstances();
|
|
}
|
|
synchronized (state) {
|
|
int spanActiveCount = state.mActiveSize;
|
|
if (spanActiveCount > MAX_SPAN_TAGS) {
|
|
spanActiveCount = MAX_SPAN_TAGS;
|
|
}
|
|
if (spanActiveCount != 0) {
|
|
this.tags = new String[spanActiveCount];
|
|
Span iter = state.mActiveHead;
|
|
int index = 0;
|
|
while (iter != null && index < spanActiveCount) {
|
|
this.tags[index] = iter.mName;
|
|
index++;
|
|
iter = iter.mNext;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Equivalent output to
|
|
* {@link android.app.ApplicationErrorReport.CrashInfo#stackTrace}.
|
|
*/
|
|
public String getStackTrace() {
|
|
if (mStackTrace == null) {
|
|
StringWriter sw = new StringWriter();
|
|
PrintWriter pw = new FastPrintWriter(sw, false, 256);
|
|
mViolation.printStackTrace(pw);
|
|
for (StackTraceElement[] traces : mBinderStack) {
|
|
pw.append("# via Binder call with stack:\n");
|
|
for (StackTraceElement traceElement : traces) {
|
|
pw.append("\tat ");
|
|
pw.append(traceElement.toString());
|
|
pw.append('\n');
|
|
}
|
|
}
|
|
pw.flush();
|
|
pw.close();
|
|
mStackTrace = sw.toString();
|
|
}
|
|
return mStackTrace;
|
|
}
|
|
|
|
public Class<? extends Violation> getViolationClass() {
|
|
return mViolation.getClass();
|
|
}
|
|
|
|
/**
|
|
* Optional message describing this violation.
|
|
*
|
|
* @hide
|
|
*/
|
|
@TestApi
|
|
public String getViolationDetails() {
|
|
return mViolation.getMessage();
|
|
}
|
|
|
|
boolean penaltyEnabled(int p) {
|
|
return (mPenaltyMask & p) != 0;
|
|
}
|
|
|
|
/**
|
|
* Add a {@link Throwable} from the current process that caused the underlying violation. We
|
|
* only preserve the stack trace elements.
|
|
*
|
|
* @hide
|
|
*/
|
|
void addLocalStack(Throwable t) {
|
|
mBinderStack.addFirst(t.getStackTrace());
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
int result = 17;
|
|
if (mViolation != null) {
|
|
result = 37 * result + mViolation.hashCode();
|
|
}
|
|
if (numAnimationsRunning != 0) {
|
|
result *= 37;
|
|
}
|
|
if (broadcastIntentAction != null) {
|
|
result = 37 * result + broadcastIntentAction.hashCode();
|
|
}
|
|
if (tags != null) {
|
|
for (String tag : tags) {
|
|
result = 37 * result + tag.hashCode();
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/** Create an instance of ViolationInfo initialized from a Parcel. */
|
|
@UnsupportedAppUsage
|
|
public ViolationInfo(Parcel in) {
|
|
this(in, false);
|
|
}
|
|
|
|
/**
|
|
* Create an instance of ViolationInfo initialized from a Parcel.
|
|
*
|
|
* @param unsetGatheringBit if true, the caller is the root caller and the gathering penalty
|
|
* should be removed.
|
|
*/
|
|
public ViolationInfo(Parcel in, boolean unsetGatheringBit) {
|
|
mViolation = (Violation) in.readSerializable(android.os.strictmode.Violation.class.getClassLoader(), android.os.strictmode.Violation.class);
|
|
int binderStackSize = in.readInt();
|
|
for (int i = 0; i < binderStackSize; i++) {
|
|
StackTraceElement[] traceElements = new StackTraceElement[in.readInt()];
|
|
for (int j = 0; j < traceElements.length; j++) {
|
|
StackTraceElement element =
|
|
new StackTraceElement(
|
|
in.readString(),
|
|
in.readString(),
|
|
in.readString(),
|
|
in.readInt());
|
|
traceElements[j] = element;
|
|
}
|
|
mBinderStack.add(traceElements);
|
|
}
|
|
int rawPenaltyMask = in.readInt();
|
|
if (unsetGatheringBit) {
|
|
mPenaltyMask = rawPenaltyMask & ~PENALTY_GATHER;
|
|
} else {
|
|
mPenaltyMask = rawPenaltyMask;
|
|
}
|
|
durationMillis = in.readInt();
|
|
violationNumThisLoop = in.readInt();
|
|
numAnimationsRunning = in.readInt();
|
|
violationUptimeMillis = in.readLong();
|
|
numInstances = in.readLong();
|
|
broadcastIntentAction = in.readString();
|
|
tags = in.readStringArray();
|
|
}
|
|
|
|
/** Save a ViolationInfo instance to a parcel. */
|
|
@Override
|
|
public void writeToParcel(Parcel dest, int flags) {
|
|
dest.writeSerializable(mViolation);
|
|
dest.writeInt(mBinderStack.size());
|
|
for (StackTraceElement[] traceElements : mBinderStack) {
|
|
dest.writeInt(traceElements.length);
|
|
for (StackTraceElement element : traceElements) {
|
|
dest.writeString(element.getClassName());
|
|
dest.writeString(element.getMethodName());
|
|
dest.writeString(element.getFileName());
|
|
dest.writeInt(element.getLineNumber());
|
|
}
|
|
}
|
|
int start = dest.dataPosition();
|
|
dest.writeInt(mPenaltyMask);
|
|
dest.writeInt(durationMillis);
|
|
dest.writeInt(violationNumThisLoop);
|
|
dest.writeInt(numAnimationsRunning);
|
|
dest.writeLong(violationUptimeMillis);
|
|
dest.writeLong(numInstances);
|
|
dest.writeString(broadcastIntentAction);
|
|
dest.writeStringArray(tags);
|
|
int total = dest.dataPosition() - start;
|
|
if (Binder.CHECK_PARCEL_SIZE && total > 10 * 1024) {
|
|
Slog.d(
|
|
TAG,
|
|
"VIO: penalty="
|
|
+ mPenaltyMask
|
|
+ " dur="
|
|
+ durationMillis
|
|
+ " numLoop="
|
|
+ violationNumThisLoop
|
|
+ " anim="
|
|
+ numAnimationsRunning
|
|
+ " uptime="
|
|
+ violationUptimeMillis
|
|
+ " numInst="
|
|
+ numInstances);
|
|
Slog.d(TAG, "VIO: action=" + broadcastIntentAction);
|
|
Slog.d(TAG, "VIO: tags=" + Arrays.toString(tags));
|
|
Slog.d(TAG, "VIO: TOTAL BYTES WRITTEN: " + (dest.dataPosition() - start));
|
|
}
|
|
}
|
|
|
|
/** Dump a ViolationInfo instance to a Printer. */
|
|
public void dump(Printer pw, String prefix) {
|
|
pw.println(prefix + "stackTrace: " + getStackTrace());
|
|
pw.println(prefix + "penalty: " + mPenaltyMask);
|
|
if (durationMillis != -1) {
|
|
pw.println(prefix + "durationMillis: " + durationMillis);
|
|
}
|
|
if (numInstances != -1) {
|
|
pw.println(prefix + "numInstances: " + numInstances);
|
|
}
|
|
if (violationNumThisLoop != 0) {
|
|
pw.println(prefix + "violationNumThisLoop: " + violationNumThisLoop);
|
|
}
|
|
if (numAnimationsRunning != 0) {
|
|
pw.println(prefix + "numAnimationsRunning: " + numAnimationsRunning);
|
|
}
|
|
pw.println(prefix + "violationUptimeMillis: " + violationUptimeMillis);
|
|
if (broadcastIntentAction != null) {
|
|
pw.println(prefix + "broadcastIntentAction: " + broadcastIntentAction);
|
|
}
|
|
if (tags != null) {
|
|
int index = 0;
|
|
for (String tag : tags) {
|
|
pw.println(prefix + "tag[" + (index++) + "]: " + tag);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int describeContents() {
|
|
return 0;
|
|
}
|
|
|
|
public static final @android.annotation.NonNull Parcelable.Creator<ViolationInfo> CREATOR =
|
|
new Parcelable.Creator<ViolationInfo>() {
|
|
@Override
|
|
public ViolationInfo createFromParcel(Parcel in) {
|
|
return new ViolationInfo(in);
|
|
}
|
|
|
|
@Override
|
|
public ViolationInfo[] newArray(int size) {
|
|
return new ViolationInfo[size];
|
|
}
|
|
};
|
|
}
|
|
|
|
private static final class InstanceTracker {
|
|
private static final HashMap<Class<?>, Integer> sInstanceCounts =
|
|
new HashMap<Class<?>, Integer>();
|
|
|
|
private final Class<?> mKlass;
|
|
|
|
public InstanceTracker(Object instance) {
|
|
mKlass = instance.getClass();
|
|
|
|
synchronized (sInstanceCounts) {
|
|
final Integer value = sInstanceCounts.get(mKlass);
|
|
final int newValue = value != null ? value + 1 : 1;
|
|
sInstanceCounts.put(mKlass, newValue);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void finalize() throws Throwable {
|
|
try {
|
|
synchronized (sInstanceCounts) {
|
|
final Integer value = sInstanceCounts.get(mKlass);
|
|
if (value != null) {
|
|
final int newValue = value - 1;
|
|
if (newValue > 0) {
|
|
sInstanceCounts.put(mKlass, newValue);
|
|
} else {
|
|
sInstanceCounts.remove(mKlass);
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
super.finalize();
|
|
}
|
|
}
|
|
|
|
public static int getInstanceCount(Class<?> klass) {
|
|
synchronized (sInstanceCounts) {
|
|
final Integer value = sInstanceCounts.get(klass);
|
|
return value != null ? value : 0;
|
|
}
|
|
}
|
|
}
|
|
}
|