StrictMode.java revision 703e5d3c7fbeb8ca0978045db01d40318f838612
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16package android.os; 17 18import android.app.ActivityManagerNative; 19import android.app.ApplicationErrorReport; 20import android.util.Log; 21 22import com.android.internal.os.RuntimeInit; 23 24import dalvik.system.BlockGuard; 25 26import java.io.PrintWriter; 27import java.io.StringWriter; 28import java.util.ArrayList; 29import java.util.HashMap; 30 31/** 32 * <p>StrictMode lets you impose stricter rules under which your 33 * application runs.</p> 34 */ 35public final class StrictMode { 36 private static final String TAG = "StrictMode"; 37 private static final boolean LOG_V = false; 38 39 // Only log a duplicate stack trace to the logs every second. 40 private static final long MIN_LOG_INTERVAL_MS = 1000; 41 42 // Only show an annoying dialog at most every 30 seconds 43 private static final long MIN_DIALOG_INTERVAL_MS = 30000; 44 45 private StrictMode() {} 46 47 public static final int DISALLOW_DISK_WRITE = 0x01; 48 public static final int DISALLOW_DISK_READ = 0x02; 49 public static final int DISALLOW_NETWORK = 0x04; 50 51 /** @hide */ 52 public static final int DISALLOW_MASK = 53 DISALLOW_DISK_WRITE | DISALLOW_DISK_READ | DISALLOW_NETWORK; 54 55 /** 56 * Flag to log to the system log. 57 */ 58 public static final int PENALTY_LOG = 0x10; // normal android.util.Log 59 60 /** 61 * Show an annoying dialog to the user. Will be rate-limited to be only 62 * a little annoying. 63 */ 64 public static final int PENALTY_DIALOG = 0x20; 65 66 /** 67 * Crash hard if policy is violated. 68 */ 69 public static final int PENALTY_DEATH = 0x40; 70 71 /** 72 * Log a stacktrace to the DropBox on policy violation. 73 */ 74 public static final int PENALTY_DROPBOX = 0x80; 75 76 /** 77 * Non-public penalty mode which overrides all the other penalty 78 * bits and signals that we're in a Binder call and we should 79 * ignore the other penalty bits and instead serialize back all 80 * our offending stack traces to the caller to ultimately handle 81 * in the originating process. 82 * 83 * This must be kept in sync with the constant in libs/binder/Parcel.cpp 84 * 85 * @hide 86 */ 87 public static final int PENALTY_GATHER = 0x100; 88 89 /** @hide */ 90 public static final int PENALTY_MASK = 91 PENALTY_LOG | PENALTY_DIALOG | 92 PENALTY_DROPBOX | PENALTY_DEATH; 93 94 /** 95 * Log of strict mode violation stack traces that have occurred 96 * during a Binder call, to be serialized back later to the caller 97 * via Parcel.writeNoException() (amusingly) where the caller can 98 * choose how to react. 99 */ 100 private static ThreadLocal<ArrayList<ApplicationErrorReport.CrashInfo>> gatheredViolations = 101 new ThreadLocal<ArrayList<ApplicationErrorReport.CrashInfo>>() { 102 @Override protected ArrayList<ApplicationErrorReport.CrashInfo> initialValue() { 103 // Starts null to avoid unnecessary allocations when 104 // checking whether there are any violations or not in 105 // hasGatheredViolations() below. 106 return null; 107 } 108 }; 109 110 /** 111 * Sets the policy for what actions the current thread is denied, 112 * as well as the penalty for violating the policy. 113 * 114 * @param policyMask a bitmask of DISALLOW_* and PENALTY_* values. 115 */ 116 public static void setThreadBlockingPolicy(final int policyMask) { 117 // In addition to the Java-level thread-local in Dalvik's 118 // BlockGuard, we also need to keep a native thread-local in 119 // Binder in order to propagate the value across Binder calls, 120 // even across native-only processes. The two are kept in 121 // sync via the callback to onStrictModePolicyChange, below. 122 setBlockGuardPolicy(policyMask); 123 124 // And set the Android native version... 125 Binder.setThreadStrictModePolicy(policyMask); 126 } 127 128 // Sets the policy in Dalvik/libcore (BlockGuard) 129 private static void setBlockGuardPolicy(final int policyMask) { 130 if (policyMask == 0) { 131 BlockGuard.setThreadPolicy(BlockGuard.LAX_POLICY); 132 return; 133 } 134 BlockGuard.Policy policy = BlockGuard.getThreadPolicy(); 135 if (!(policy instanceof AndroidBlockGuardPolicy)) { 136 BlockGuard.setThreadPolicy(new AndroidBlockGuardPolicy(policyMask)); 137 } else { 138 AndroidBlockGuardPolicy androidPolicy = (AndroidBlockGuardPolicy) policy; 139 androidPolicy.setPolicyMask(policyMask); 140 } 141 } 142 143 private static class StrictModeNetworkViolation extends BlockGuard.BlockGuardPolicyException { 144 public StrictModeNetworkViolation(int policyMask) { 145 super(policyMask, DISALLOW_NETWORK); 146 } 147 } 148 149 private static class StrictModeDiskReadViolation extends BlockGuard.BlockGuardPolicyException { 150 public StrictModeDiskReadViolation(int policyMask) { 151 super(policyMask, DISALLOW_DISK_READ); 152 } 153 } 154 155 private static class StrictModeDiskWriteViolation extends BlockGuard.BlockGuardPolicyException { 156 public StrictModeDiskWriteViolation(int policyMask) { 157 super(policyMask, DISALLOW_DISK_WRITE); 158 } 159 } 160 161 /** 162 * Returns the bitmask of the current thread's blocking policy. 163 * 164 * @return the bitmask of all the DISALLOW_* and PENALTY_* bits currently enabled 165 */ 166 public static int getThreadBlockingPolicy() { 167 return BlockGuard.getThreadPolicy().getPolicyMask(); 168 } 169 170 /** 171 * Parses the BlockGuard policy mask out from the Exception's 172 * getMessage() String value. Kinda gross, but least 173 * invasive. :/ 174 * 175 * Input is of form "policy=137 violation=64" 176 * 177 * Returns 0 on failure, which is a valid policy, but not a 178 * valid policy during a violation (else there must've been 179 * some policy in effect to violate). 180 */ 181 private static int parsePolicyFromMessage(String message) { 182 if (message == null || !message.startsWith("policy=")) { 183 return 0; 184 } 185 int spaceIndex = message.indexOf(' '); 186 if (spaceIndex == -1) { 187 return 0; 188 } 189 String policyString = message.substring(7, spaceIndex); 190 try { 191 return Integer.valueOf(policyString).intValue(); 192 } catch (NumberFormatException e) { 193 return 0; 194 } 195 } 196 197 /** 198 * Like parsePolicyFromMessage(), but returns the violation. 199 */ 200 private static int parseViolationFromMessage(String message) { 201 if (message == null) { 202 return 0; 203 } 204 int violationIndex = message.indexOf("violation="); 205 if (violationIndex == -1) { 206 return 0; 207 } 208 String violationString = message.substring(violationIndex + 10); 209 try { 210 return Integer.valueOf(violationString).intValue(); 211 } catch (NumberFormatException e) { 212 return 0; 213 } 214 } 215 216 private static class AndroidBlockGuardPolicy implements BlockGuard.Policy { 217 private int mPolicyMask; 218 219 // Map from violation stacktrace hashcode -> uptimeMillis of 220 // last violation. No locking needed, as this is only 221 // accessed by the same thread. 222 private final HashMap<Integer, Long> mLastViolationTime = new HashMap<Integer, Long>(); 223 224 public AndroidBlockGuardPolicy(final int policyMask) { 225 mPolicyMask = policyMask; 226 } 227 228 @Override 229 public String toString() { 230 return "AndroidBlockGuardPolicy; mPolicyMask=" + mPolicyMask; 231 } 232 233 // Part of BlockGuard.Policy interface: 234 public int getPolicyMask() { 235 return mPolicyMask; 236 } 237 238 // Part of BlockGuard.Policy interface: 239 public void onWriteToDisk() { 240 if ((mPolicyMask & DISALLOW_DISK_WRITE) == 0) { 241 return; 242 } 243 startHandlingViolationException(new StrictModeDiskWriteViolation(mPolicyMask)); 244 } 245 246 // Part of BlockGuard.Policy interface: 247 public void onReadFromDisk() { 248 if ((mPolicyMask & DISALLOW_DISK_READ) == 0) { 249 return; 250 } 251 startHandlingViolationException(new StrictModeDiskReadViolation(mPolicyMask)); 252 } 253 254 // Part of BlockGuard.Policy interface: 255 public void onNetwork() { 256 if ((mPolicyMask & DISALLOW_NETWORK) == 0) { 257 return; 258 } 259 startHandlingViolationException(new StrictModeNetworkViolation(mPolicyMask)); 260 } 261 262 public void setPolicyMask(int policyMask) { 263 mPolicyMask = policyMask; 264 } 265 266 // Start handling a violation that just started and hasn't 267 // actually run yet (e.g. no disk write or network operation 268 // has yet occurred). This sees if we're in an event loop 269 // thread and, if so, uses it to roughly measure how long the 270 // violation took. 271 void startHandlingViolationException(BlockGuard.BlockGuardPolicyException e) { 272 e.fillInStackTrace(); 273 final ApplicationErrorReport.CrashInfo crashInfo = new ApplicationErrorReport.CrashInfo(e); 274 crashInfo.durationMillis = -1; // unknown 275 final int savedPolicy = mPolicyMask; 276 277 Looper looper = Looper.myLooper(); 278 if (looper == null) { 279 // Without a Looper, we're unable to time how long the 280 // violation takes place. This case should be rare, 281 // as most users will care about timing violations 282 // that happen on their main UI thread. 283 handleViolation(crashInfo, savedPolicy); 284 } else { 285 MessageQueue queue = Looper.myQueue(); 286 final long violationTime = SystemClock.uptimeMillis(); 287 queue.addIdleHandler(new MessageQueue.IdleHandler() { 288 public boolean queueIdle() { 289 long afterViolationTime = SystemClock.uptimeMillis(); 290 crashInfo.durationMillis = afterViolationTime - violationTime; 291 handleViolation(crashInfo, savedPolicy); 292 return false; // remove this idle handler from the array 293 } 294 }); 295 } 296 297 } 298 299 // Note: It's possible (even quite likely) that the 300 // thread-local policy mask has changed from the time the 301 // violation fired and now (after the violating code ran) due 302 // to people who push/pop temporary policy in regions of code, 303 // hence the policy being passed around. 304 void handleViolation( 305 final ApplicationErrorReport.CrashInfo crashInfo, 306 int policy) { 307 if (crashInfo.stackTrace == null) { 308 Log.d(TAG, "unexpected null stacktrace"); 309 return; 310 } 311 312 if (LOG_V) Log.d(TAG, "handleViolation; policy=" + policy); 313 314 if ((policy & PENALTY_GATHER) != 0) { 315 ArrayList<ApplicationErrorReport.CrashInfo> violations = gatheredViolations.get(); 316 if (violations == null) { 317 violations = new ArrayList<ApplicationErrorReport.CrashInfo>(1); 318 gatheredViolations.set(violations); 319 } else if (violations.size() >= 5) { 320 // Too many. In a loop or something? Don't gather them all. 321 return; 322 } 323 for (ApplicationErrorReport.CrashInfo previous : violations) { 324 if (crashInfo.stackTrace.equals(previous.stackTrace)) { 325 // Duplicate. Don't log. 326 return; 327 } 328 } 329 violations.add(crashInfo); 330 return; 331 } 332 333 // Not perfect, but fast and good enough for dup suppression. 334 Integer crashFingerprint = crashInfo.stackTrace.hashCode(); 335 long lastViolationTime = 0; 336 if (mLastViolationTime.containsKey(crashFingerprint)) { 337 lastViolationTime = mLastViolationTime.get(crashFingerprint); 338 } 339 long now = SystemClock.uptimeMillis(); 340 mLastViolationTime.put(crashFingerprint, now); 341 long timeSinceLastViolationMillis = lastViolationTime == 0 ? 342 Long.MAX_VALUE : (now - lastViolationTime); 343 344 if ((policy & PENALTY_LOG) != 0 && 345 timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) { 346 if (crashInfo.durationMillis != -1) { 347 Log.d(TAG, "StrictMode policy violation; ~duration=" + 348 crashInfo.durationMillis + " ms: " + crashInfo.stackTrace); 349 } else { 350 Log.d(TAG, "StrictMode policy violation: " + crashInfo.stackTrace); 351 } 352 } 353 354 // The violationMask, passed to ActivityManager, is a 355 // subset of the original StrictMode policy bitmask, with 356 // only the bit violated and penalty bits to be executed 357 // by the ActivityManagerService remaining set. 358 int violationMask = 0; 359 360 if ((policy & PENALTY_DIALOG) != 0 && 361 timeSinceLastViolationMillis > MIN_DIALOG_INTERVAL_MS) { 362 violationMask |= PENALTY_DIALOG; 363 } 364 365 if ((policy & PENALTY_DROPBOX) != 0 && lastViolationTime == 0) { 366 violationMask |= PENALTY_DROPBOX; 367 } 368 369 if (violationMask != 0) { 370 int violationBit = parseViolationFromMessage(crashInfo.exceptionMessage); 371 violationMask |= violationBit; 372 final int savedPolicy = getThreadBlockingPolicy(); 373 try { 374 // First, remove any policy before we call into the Activity Manager, 375 // otherwise we'll infinite recurse as we try to log policy violations 376 // to disk, thus violating policy, thus requiring logging, etc... 377 // We restore the current policy below, in the finally block. 378 setThreadBlockingPolicy(0); 379 380 ActivityManagerNative.getDefault().handleApplicationStrictModeViolation( 381 RuntimeInit.getApplicationObject(), 382 violationMask, 383 crashInfo); 384 } catch (RemoteException e) { 385 Log.e(TAG, "RemoteException trying to handle StrictMode violation", e); 386 } finally { 387 // Restore the policy. 388 setThreadBlockingPolicy(savedPolicy); 389 } 390 } 391 392 if ((policy & PENALTY_DEATH) != 0) { 393 System.err.println("StrictMode policy violation with POLICY_DEATH; shutting down."); 394 Process.killProcess(Process.myPid()); 395 System.exit(10); 396 } 397 } 398 } 399 400 /** 401 * Called from Parcel.writeNoException() 402 */ 403 /* package */ static boolean hasGatheredViolations() { 404 return gatheredViolations.get() != null; 405 } 406 407 /** 408 * Called from Parcel.writeException(), so we drop this memory and 409 * don't incorrectly attribute it to the wrong caller on the next 410 * Binder call on this thread. 411 */ 412 /* package */ static void clearGatheredViolations() { 413 gatheredViolations.set(null); 414 } 415 416 /** 417 * Called from Parcel.writeNoException() 418 */ 419 /* package */ static void writeGatheredViolationsToParcel(Parcel p) { 420 ArrayList<ApplicationErrorReport.CrashInfo> violations = gatheredViolations.get(); 421 if (violations == null) { 422 p.writeInt(0); 423 } else { 424 p.writeInt(violations.size()); 425 for (int i = 0; i < violations.size(); ++i) { 426 violations.get(i).writeToParcel(p, 0 /* unused flags? */); 427 } 428 if (LOG_V) Log.d(TAG, "wrote violations to response parcel; num=" + violations.size()); 429 violations.clear(); // somewhat redundant, as we're about to null the threadlocal 430 } 431 gatheredViolations.set(null); 432 } 433 434 private static class LogStackTrace extends Exception {} 435 436 /** 437 * Called from Parcel.readException() when the exception is EX_STRICT_MODE_VIOLATIONS, 438 * we here read back all the encoded violations. 439 */ 440 /* package */ static void readAndHandleBinderCallViolations(Parcel p) { 441 // Our own stack trace to append 442 Exception e = new LogStackTrace(); 443 StringWriter sw = new StringWriter(); 444 e.printStackTrace(new PrintWriter(sw)); 445 String ourStack = sw.toString(); 446 447 int policyMask = getThreadBlockingPolicy(); 448 449 int numViolations = p.readInt(); 450 for (int i = 0; i < numViolations; ++i) { 451 if (LOG_V) Log.d(TAG, "strict mode violation stacks read from binder call. i=" + i); 452 ApplicationErrorReport.CrashInfo crashInfo = new ApplicationErrorReport.CrashInfo(p); 453 crashInfo.stackTrace += "# via Binder call with stack:\n" + ourStack; 454 455 // Unlike the in-process violations in which case we 456 // trigger an error _before_ the thing occurs, in this 457 // case the violating thing has already occurred, so we 458 // can't use our heuristic of waiting for the next event 459 // loop idle cycle to measure the approximate violation 460 // duration. Instead, just skip that step and use -1 461 // (unknown duration) for now. 462 // TODO: keep a thread-local on remote process of first 463 // violation time's uptimeMillis, and when writing that 464 // back out in Parcel reply, include in the header the 465 // violation time and use it here. 466 crashInfo.durationMillis = -1; 467 468 BlockGuard.Policy policy = BlockGuard.getThreadPolicy(); 469 if (policy instanceof AndroidBlockGuardPolicy) { 470 ((AndroidBlockGuardPolicy) policy).handleViolation(crashInfo, policyMask); 471 } 472 } 473 } 474 475 /** 476 * Called from android_util_Binder.cpp's 477 * android_os_Parcel_enforceInterface when an incoming Binder call 478 * requires changing the StrictMode policy mask. The role of this 479 * function is to ask Binder for its current (native) thread-local 480 * policy value and synchronize it to libcore's (Java) 481 * thread-local policy value. 482 */ 483 private static void onBinderStrictModePolicyChange(int newPolicy) { 484 setBlockGuardPolicy(newPolicy); 485 } 486} 487