StrictMode.java revision 97461bd25c3821f3fb6af9705f0612259c6b4492
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; 21import android.util.Printer; 22 23import com.android.internal.os.RuntimeInit; 24 25import dalvik.system.BlockGuard; 26 27import java.io.PrintWriter; 28import java.io.StringWriter; 29import java.util.ArrayList; 30import java.util.HashMap; 31 32/** 33 * <p>StrictMode lets you impose stricter rules under which your 34 * application runs.</p> 35 */ 36public final class StrictMode { 37 private static final String TAG = "StrictMode"; 38 private static final boolean LOG_V = false; 39 40 // Only log a duplicate stack trace to the logs every second. 41 private static final long MIN_LOG_INTERVAL_MS = 1000; 42 43 // Only show an annoying dialog at most every 30 seconds 44 private static final long MIN_DIALOG_INTERVAL_MS = 30000; 45 46 private StrictMode() {} 47 48 public static final int DISALLOW_DISK_WRITE = 0x01; 49 public static final int DISALLOW_DISK_READ = 0x02; 50 public static final int DISALLOW_NETWORK = 0x04; 51 52 /** @hide */ 53 public static final int DISALLOW_MASK = 54 DISALLOW_DISK_WRITE | DISALLOW_DISK_READ | DISALLOW_NETWORK; 55 56 /** 57 * Flag to log to the system log. 58 */ 59 public static final int PENALTY_LOG = 0x10; // normal android.util.Log 60 61 /** 62 * Show an annoying dialog to the user. Will be rate-limited to be only 63 * a little annoying. 64 */ 65 public static final int PENALTY_DIALOG = 0x20; 66 67 /** 68 * Crash hard if policy is violated. 69 */ 70 public static final int PENALTY_DEATH = 0x40; 71 72 /** 73 * Log a stacktrace to the DropBox on policy violation. 74 */ 75 public static final int PENALTY_DROPBOX = 0x80; 76 77 /** 78 * Non-public penalty mode which overrides all the other penalty 79 * bits and signals that we're in a Binder call and we should 80 * ignore the other penalty bits and instead serialize back all 81 * our offending stack traces to the caller to ultimately handle 82 * in the originating process. 83 * 84 * This must be kept in sync with the constant in libs/binder/Parcel.cpp 85 * 86 * @hide 87 */ 88 public static final int PENALTY_GATHER = 0x100; 89 90 /** @hide */ 91 public static final int PENALTY_MASK = 92 PENALTY_LOG | PENALTY_DIALOG | 93 PENALTY_DROPBOX | PENALTY_DEATH; 94 95 /** 96 * Log of strict mode violation stack traces that have occurred 97 * during a Binder call, to be serialized back later to the caller 98 * via Parcel.writeNoException() (amusingly) where the caller can 99 * choose how to react. 100 */ 101 private static final ThreadLocal<ArrayList<ViolationInfo>> gatheredViolations = 102 new ThreadLocal<ArrayList<ViolationInfo>>() { 103 @Override protected ArrayList<ViolationInfo> initialValue() { 104 // Starts null to avoid unnecessary allocations when 105 // checking whether there are any violations or not in 106 // hasGatheredViolations() below. 107 return null; 108 } 109 }; 110 111 /** 112 * Sets the policy for what actions the current thread is denied, 113 * as well as the penalty for violating the policy. 114 * 115 * @param policyMask a bitmask of DISALLOW_* and PENALTY_* values. 116 */ 117 public static void setThreadPolicy(final int policyMask) { 118 // In addition to the Java-level thread-local in Dalvik's 119 // BlockGuard, we also need to keep a native thread-local in 120 // Binder in order to propagate the value across Binder calls, 121 // even across native-only processes. The two are kept in 122 // sync via the callback to onStrictModePolicyChange, below. 123 setBlockGuardPolicy(policyMask); 124 125 // And set the Android native version... 126 Binder.setThreadStrictModePolicy(policyMask); 127 } 128 129 // Sets the policy in Dalvik/libcore (BlockGuard) 130 private static void setBlockGuardPolicy(final int policyMask) { 131 if (policyMask == 0) { 132 BlockGuard.setThreadPolicy(BlockGuard.LAX_POLICY); 133 return; 134 } 135 BlockGuard.Policy policy = BlockGuard.getThreadPolicy(); 136 if (!(policy instanceof AndroidBlockGuardPolicy)) { 137 BlockGuard.setThreadPolicy(new AndroidBlockGuardPolicy(policyMask)); 138 } else { 139 AndroidBlockGuardPolicy androidPolicy = (AndroidBlockGuardPolicy) policy; 140 androidPolicy.setPolicyMask(policyMask); 141 } 142 } 143 144 private static class StrictModeNetworkViolation extends BlockGuard.BlockGuardPolicyException { 145 public StrictModeNetworkViolation(int policyMask) { 146 super(policyMask, DISALLOW_NETWORK); 147 } 148 } 149 150 private static class StrictModeDiskReadViolation extends BlockGuard.BlockGuardPolicyException { 151 public StrictModeDiskReadViolation(int policyMask) { 152 super(policyMask, DISALLOW_DISK_READ); 153 } 154 } 155 156 private static class StrictModeDiskWriteViolation extends BlockGuard.BlockGuardPolicyException { 157 public StrictModeDiskWriteViolation(int policyMask) { 158 super(policyMask, DISALLOW_DISK_WRITE); 159 } 160 } 161 162 /** 163 * Returns the bitmask of the current thread's blocking policy. 164 * 165 * @return the bitmask of all the DISALLOW_* and PENALTY_* bits currently enabled 166 */ 167 public static int getThreadPolicy() { 168 return BlockGuard.getThreadPolicy().getPolicyMask(); 169 } 170 171 /** 172 * Updates the current thread's policy mask to allow reading & 173 * writing to disk. 174 * 175 * @return the old policy mask, to be passed to setThreadPolicy to 176 * restore the policy. 177 */ 178 public static int allowThreadDiskWrites() { 179 int oldPolicy = getThreadPolicy(); 180 int newPolicy = oldPolicy & ~(DISALLOW_DISK_WRITE | DISALLOW_DISK_READ); 181 if (newPolicy != oldPolicy) { 182 setThreadPolicy(newPolicy); 183 } 184 return oldPolicy; 185 } 186 187 /** 188 * Updates the current thread's policy mask to allow reading from 189 * disk. 190 * 191 * @return the old policy mask, to be passed to setThreadPolicy to 192 * restore the policy. 193 */ 194 public static int allowThreadDiskReads() { 195 int oldPolicy = getThreadPolicy(); 196 int newPolicy = oldPolicy & ~(DISALLOW_DISK_READ); 197 if (newPolicy != oldPolicy) { 198 setThreadPolicy(newPolicy); 199 } 200 return oldPolicy; 201 } 202 203 /** 204 * Parses the BlockGuard policy mask out from the Exception's 205 * getMessage() String value. Kinda gross, but least 206 * invasive. :/ 207 * 208 * Input is of form "policy=137 violation=64" 209 * 210 * Returns 0 on failure, which is a valid policy, but not a 211 * valid policy during a violation (else there must've been 212 * some policy in effect to violate). 213 */ 214 private static int parsePolicyFromMessage(String message) { 215 if (message == null || !message.startsWith("policy=")) { 216 return 0; 217 } 218 int spaceIndex = message.indexOf(' '); 219 if (spaceIndex == -1) { 220 return 0; 221 } 222 String policyString = message.substring(7, spaceIndex); 223 try { 224 return Integer.valueOf(policyString).intValue(); 225 } catch (NumberFormatException e) { 226 return 0; 227 } 228 } 229 230 /** 231 * Like parsePolicyFromMessage(), but returns the violation. 232 */ 233 private static int parseViolationFromMessage(String message) { 234 if (message == null) { 235 return 0; 236 } 237 int violationIndex = message.indexOf("violation="); 238 if (violationIndex == -1) { 239 return 0; 240 } 241 String violationString = message.substring(violationIndex + 10); 242 try { 243 return Integer.valueOf(violationString).intValue(); 244 } catch (NumberFormatException e) { 245 return 0; 246 } 247 } 248 249 private static class AndroidBlockGuardPolicy implements BlockGuard.Policy { 250 private int mPolicyMask; 251 252 // Map from violation stacktrace hashcode -> uptimeMillis of 253 // last violation. No locking needed, as this is only 254 // accessed by the same thread. 255 private final HashMap<Integer, Long> mLastViolationTime = new HashMap<Integer, Long>(); 256 257 public AndroidBlockGuardPolicy(final int policyMask) { 258 mPolicyMask = policyMask; 259 } 260 261 @Override 262 public String toString() { 263 return "AndroidBlockGuardPolicy; mPolicyMask=" + mPolicyMask; 264 } 265 266 // Part of BlockGuard.Policy interface: 267 public int getPolicyMask() { 268 return mPolicyMask; 269 } 270 271 // Part of BlockGuard.Policy interface: 272 public void onWriteToDisk() { 273 if ((mPolicyMask & DISALLOW_DISK_WRITE) == 0) { 274 return; 275 } 276 BlockGuard.BlockGuardPolicyException e = new StrictModeDiskWriteViolation(mPolicyMask); 277 e.fillInStackTrace(); 278 startHandlingViolationException(e); 279 } 280 281 // Part of BlockGuard.Policy interface: 282 public void onReadFromDisk() { 283 if ((mPolicyMask & DISALLOW_DISK_READ) == 0) { 284 return; 285 } 286 BlockGuard.BlockGuardPolicyException e = new StrictModeDiskReadViolation(mPolicyMask); 287 e.fillInStackTrace(); 288 startHandlingViolationException(e); 289 } 290 291 // Part of BlockGuard.Policy interface: 292 public void onNetwork() { 293 if ((mPolicyMask & DISALLOW_NETWORK) == 0) { 294 return; 295 } 296 BlockGuard.BlockGuardPolicyException e = new StrictModeNetworkViolation(mPolicyMask); 297 e.fillInStackTrace(); 298 startHandlingViolationException(e); 299 } 300 301 public void setPolicyMask(int policyMask) { 302 mPolicyMask = policyMask; 303 } 304 305 // Start handling a violation that just started and hasn't 306 // actually run yet (e.g. no disk write or network operation 307 // has yet occurred). This sees if we're in an event loop 308 // thread and, if so, uses it to roughly measure how long the 309 // violation took. 310 void startHandlingViolationException(BlockGuard.BlockGuardPolicyException e) { 311 final ViolationInfo info = new ViolationInfo(e, e.getPolicy()); 312 info.violationUptimeMillis = SystemClock.uptimeMillis(); 313 handleViolationWithTimingAttempt(info); 314 } 315 316 private static final ThreadLocal<ArrayList<ViolationInfo>> violationsBeingTimed = 317 new ThreadLocal<ArrayList<ViolationInfo>>() { 318 @Override protected ArrayList<ViolationInfo> initialValue() { 319 return new ArrayList<ViolationInfo>(); 320 } 321 }; 322 323 // Attempts to fill in the provided ViolationInfo's 324 // durationMillis field if this thread has a Looper we can use 325 // to measure with. We measure from the time of violation 326 // until the time the looper is idle again (right before 327 // the next epoll_wait) 328 void handleViolationWithTimingAttempt(final ViolationInfo info) { 329 Looper looper = Looper.myLooper(); 330 331 // Without a Looper, we're unable to time how long the 332 // violation takes place. This case should be rare, as 333 // most users will care about timing violations that 334 // happen on their main UI thread. Note that this case is 335 // also hit when a violation takes place in a Binder 336 // thread, in "gather" mode. In this case, the duration 337 // of the violation is computed by the ultimate caller and 338 // its Looper, if any. 339 // TODO: if in gather mode, ignore Looper.myLooper() and always 340 // go into this immediate mode? 341 if (looper == null) { 342 info.durationMillis = -1; // unknown (redundant, already set) 343 handleViolation(info); 344 return; 345 } 346 347 MessageQueue queue = Looper.myQueue(); 348 final ArrayList<ViolationInfo> records = violationsBeingTimed.get(); 349 if (records.size() >= 10) { 350 // Not worth measuring. Too many offenses in one loop. 351 return; 352 } 353 records.add(info); 354 if (records.size() > 1) { 355 // There's already been a violation this loop, so we've already 356 // registered an idle handler to process the list of violations 357 // at the end of this Looper's loop. 358 return; 359 } 360 361 queue.addIdleHandler(new MessageQueue.IdleHandler() { 362 public boolean queueIdle() { 363 long loopFinishTime = SystemClock.uptimeMillis(); 364 for (int n = 0; n < records.size(); ++n) { 365 ViolationInfo v = records.get(n); 366 v.violationNumThisLoop = n + 1; 367 v.durationMillis = 368 (int) (loopFinishTime - v.violationUptimeMillis); 369 handleViolation(v); 370 } 371 records.clear(); 372 return false; // remove this idle handler from the array 373 } 374 }); 375 } 376 377 // Note: It's possible (even quite likely) that the 378 // thread-local policy mask has changed from the time the 379 // violation fired and now (after the violating code ran) due 380 // to people who push/pop temporary policy in regions of code, 381 // hence the policy being passed around. 382 void handleViolation(final ViolationInfo info) { 383 if (info == null || info.crashInfo == null || info.crashInfo.stackTrace == null) { 384 Log.wtf(TAG, "unexpected null stacktrace"); 385 return; 386 } 387 388 if (LOG_V) Log.d(TAG, "handleViolation; policy=" + info.policy); 389 390 if ((info.policy & PENALTY_GATHER) != 0) { 391 ArrayList<ViolationInfo> violations = gatheredViolations.get(); 392 if (violations == null) { 393 violations = new ArrayList<ViolationInfo>(1); 394 gatheredViolations.set(violations); 395 } else if (violations.size() >= 5) { 396 // Too many. In a loop or something? Don't gather them all. 397 return; 398 } 399 for (ViolationInfo previous : violations) { 400 if (info.crashInfo.stackTrace.equals(previous.crashInfo.stackTrace)) { 401 // Duplicate. Don't log. 402 return; 403 } 404 } 405 violations.add(info); 406 return; 407 } 408 409 // Not perfect, but fast and good enough for dup suppression. 410 Integer crashFingerprint = info.crashInfo.stackTrace.hashCode(); 411 long lastViolationTime = 0; 412 if (mLastViolationTime.containsKey(crashFingerprint)) { 413 lastViolationTime = mLastViolationTime.get(crashFingerprint); 414 } 415 long now = SystemClock.uptimeMillis(); 416 mLastViolationTime.put(crashFingerprint, now); 417 long timeSinceLastViolationMillis = lastViolationTime == 0 ? 418 Long.MAX_VALUE : (now - lastViolationTime); 419 420 if ((info.policy & PENALTY_LOG) != 0 && 421 timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) { 422 if (info.durationMillis != -1) { 423 Log.d(TAG, "StrictMode policy violation; ~duration=" + 424 info.durationMillis + " ms: " + info.crashInfo.stackTrace); 425 } else { 426 Log.d(TAG, "StrictMode policy violation: " + info.crashInfo.stackTrace); 427 } 428 } 429 430 // The violationMask, passed to ActivityManager, is a 431 // subset of the original StrictMode policy bitmask, with 432 // only the bit violated and penalty bits to be executed 433 // by the ActivityManagerService remaining set. 434 int violationMaskSubset = 0; 435 436 if ((info.policy & PENALTY_DIALOG) != 0 && 437 timeSinceLastViolationMillis > MIN_DIALOG_INTERVAL_MS) { 438 violationMaskSubset |= PENALTY_DIALOG; 439 } 440 441 if ((info.policy & PENALTY_DROPBOX) != 0 && lastViolationTime == 0) { 442 violationMaskSubset |= PENALTY_DROPBOX; 443 } 444 445 if (violationMaskSubset != 0) { 446 int violationBit = parseViolationFromMessage(info.crashInfo.exceptionMessage); 447 violationMaskSubset |= violationBit; 448 final int savedPolicy = getThreadPolicy(); 449 try { 450 // First, remove any policy before we call into the Activity Manager, 451 // otherwise we'll infinite recurse as we try to log policy violations 452 // to disk, thus violating policy, thus requiring logging, etc... 453 // We restore the current policy below, in the finally block. 454 setThreadPolicy(0); 455 456 ActivityManagerNative.getDefault().handleApplicationStrictModeViolation( 457 RuntimeInit.getApplicationObject(), 458 violationMaskSubset, 459 info); 460 } catch (RemoteException e) { 461 Log.e(TAG, "RemoteException trying to handle StrictMode violation", e); 462 } finally { 463 // Restore the policy. 464 setThreadPolicy(savedPolicy); 465 } 466 } 467 468 if ((info.policy & PENALTY_DEATH) != 0) { 469 System.err.println("StrictMode policy violation with POLICY_DEATH; shutting down."); 470 Process.killProcess(Process.myPid()); 471 System.exit(10); 472 } 473 } 474 } 475 476 /** 477 * Called from Parcel.writeNoException() 478 */ 479 /* package */ static boolean hasGatheredViolations() { 480 return gatheredViolations.get() != null; 481 } 482 483 /** 484 * Called from Parcel.writeException(), so we drop this memory and 485 * don't incorrectly attribute it to the wrong caller on the next 486 * Binder call on this thread. 487 */ 488 /* package */ static void clearGatheredViolations() { 489 gatheredViolations.set(null); 490 } 491 492 /** 493 * Called from Parcel.writeNoException() 494 */ 495 /* package */ static void writeGatheredViolationsToParcel(Parcel p) { 496 ArrayList<ViolationInfo> violations = gatheredViolations.get(); 497 if (violations == null) { 498 p.writeInt(0); 499 } else { 500 p.writeInt(violations.size()); 501 for (int i = 0; i < violations.size(); ++i) { 502 violations.get(i).writeToParcel(p, 0 /* unused flags? */); 503 } 504 if (LOG_V) Log.d(TAG, "wrote violations to response parcel; num=" + violations.size()); 505 violations.clear(); // somewhat redundant, as we're about to null the threadlocal 506 } 507 gatheredViolations.set(null); 508 } 509 510 private static class LogStackTrace extends Exception {} 511 512 /** 513 * Called from Parcel.readException() when the exception is EX_STRICT_MODE_VIOLATIONS, 514 * we here read back all the encoded violations. 515 */ 516 /* package */ static void readAndHandleBinderCallViolations(Parcel p) { 517 // Our own stack trace to append 518 StringWriter sw = new StringWriter(); 519 new LogStackTrace().printStackTrace(new PrintWriter(sw)); 520 String ourStack = sw.toString(); 521 522 int policyMask = getThreadPolicy(); 523 boolean currentlyGathering = (policyMask & PENALTY_GATHER) != 0; 524 525 int numViolations = p.readInt(); 526 for (int i = 0; i < numViolations; ++i) { 527 if (LOG_V) Log.d(TAG, "strict mode violation stacks read from binder call. i=" + i); 528 ViolationInfo info = new ViolationInfo(p, !currentlyGathering); 529 info.crashInfo.stackTrace += "# via Binder call with stack:\n" + ourStack; 530 BlockGuard.Policy policy = BlockGuard.getThreadPolicy(); 531 if (policy instanceof AndroidBlockGuardPolicy) { 532 ((AndroidBlockGuardPolicy) policy).handleViolationWithTimingAttempt(info); 533 } 534 } 535 } 536 537 /** 538 * Called from android_util_Binder.cpp's 539 * android_os_Parcel_enforceInterface when an incoming Binder call 540 * requires changing the StrictMode policy mask. The role of this 541 * function is to ask Binder for its current (native) thread-local 542 * policy value and synchronize it to libcore's (Java) 543 * thread-local policy value. 544 */ 545 private static void onBinderStrictModePolicyChange(int newPolicy) { 546 setBlockGuardPolicy(newPolicy); 547 } 548 549 /** 550 * Parcelable that gets sent in Binder call headers back to callers 551 * to report violations that happened during a cross-process call. 552 * 553 * @hide 554 */ 555 public static class ViolationInfo { 556 /** 557 * Stack and other stuff info. 558 */ 559 public final ApplicationErrorReport.CrashInfo crashInfo; 560 561 /** 562 * The strict mode policy mask at the time of violation. 563 */ 564 public final int policy; 565 566 /** 567 * The wall time duration of the violation, when known. -1 when 568 * not known. 569 */ 570 public int durationMillis = -1; 571 572 /** 573 * Which violation number this was (1-based) since the last Looper loop, 574 * from the perspective of the root caller (if it crossed any processes 575 * via Binder calls). The value is 0 if the root caller wasn't on a Looper 576 * thread. 577 */ 578 public int violationNumThisLoop; 579 580 /** 581 * The time (in terms of SystemClock.uptimeMillis()) that the 582 * violation occurred. 583 */ 584 public long violationUptimeMillis; 585 586 /** 587 * Create an uninitialized instance of ViolationInfo 588 */ 589 public ViolationInfo() { 590 crashInfo = null; 591 policy = 0; 592 } 593 594 /** 595 * Create an instance of ViolationInfo initialized from an exception. 596 */ 597 public ViolationInfo(Throwable tr, int policy) { 598 crashInfo = new ApplicationErrorReport.CrashInfo(tr); 599 violationUptimeMillis = SystemClock.uptimeMillis(); 600 this.policy = policy; 601 } 602 603 /** 604 * Create an instance of ViolationInfo initialized from a Parcel. 605 */ 606 public ViolationInfo(Parcel in) { 607 this(in, false); 608 } 609 610 /** 611 * Create an instance of ViolationInfo initialized from a Parcel. 612 * 613 * @param unsetGatheringBit if true, the caller is the root caller 614 * and the gathering penalty should be removed. 615 */ 616 public ViolationInfo(Parcel in, boolean unsetGatheringBit) { 617 crashInfo = new ApplicationErrorReport.CrashInfo(in); 618 int rawPolicy = in.readInt(); 619 if (unsetGatheringBit) { 620 policy = rawPolicy & ~PENALTY_GATHER; 621 } else { 622 policy = rawPolicy; 623 } 624 durationMillis = in.readInt(); 625 violationNumThisLoop = in.readInt(); 626 violationUptimeMillis = in.readLong(); 627 } 628 629 /** 630 * Save a ViolationInfo instance to a parcel. 631 */ 632 public void writeToParcel(Parcel dest, int flags) { 633 crashInfo.writeToParcel(dest, flags); 634 dest.writeInt(policy); 635 dest.writeInt(durationMillis); 636 dest.writeInt(violationNumThisLoop); 637 dest.writeLong(violationUptimeMillis); 638 } 639 640 641 /** 642 * Dump a ViolationInfo instance to a Printer. 643 */ 644 public void dump(Printer pw, String prefix) { 645 crashInfo.dump(pw, prefix); 646 pw.println(prefix + "policy: " + policy); 647 if (durationMillis != -1) { 648 pw.println(prefix + "durationMillis: " + durationMillis); 649 } 650 if (violationNumThisLoop != 0) { 651 pw.println(prefix + "violationNumThisLoop: " + violationNumThisLoop); 652 } 653 pw.println(prefix + "violationUptimeMillis: " + violationUptimeMillis); 654 } 655 656 } 657} 658