Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1772c34e authored by Brad Fitzpatrick's avatar Brad Fitzpatrick Committed by Android (Google) Code Review
Browse files

Merge "StrictMode: gather and return violating stacks in Binder replies" into gingerbread

parents c4b204bd 5b747191
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.os.Parcelable;
import android.os.SystemProperties;
import android.provider.Settings;
import android.util.Printer;

import java.io.PrintWriter;
import java.io.StringWriter;

+3 −3
Original line number Diff line number Diff line
@@ -108,7 +108,7 @@ public class DatabaseUtils {
     * @see Parcel#readException
     */
    public static final void readExceptionFromParcel(Parcel reply) {
        int code = reply.readInt();
        int code = reply.readExceptionCode();
        if (code == 0) return;
        String msg = reply.readString();
        DatabaseUtils.readExceptionFromParcel(reply, msg, code);
@@ -116,7 +116,7 @@ public class DatabaseUtils {

    public static void readExceptionWithFileNotFoundExceptionFromParcel(
            Parcel reply) throws FileNotFoundException {
        int code = reply.readInt();
        int code = reply.readExceptionCode();
        if (code == 0) return;
        String msg = reply.readString();
        if (code == 1) {
@@ -128,7 +128,7 @@ public class DatabaseUtils {

    public static void readExceptionWithOperationApplicationExceptionFromParcel(
            Parcel reply) throws OperationApplicationException {
        int code = reply.readInt();
        int code = reply.readExceptionCode();
        if (code == 0) return;
        String msg = reply.readString();
        if (code == 10) {
+2 −0
Original line number Diff line number Diff line
@@ -299,6 +299,8 @@ public class Binder implements IBinder {
    
    private native final void init();
    private native final void destroy();

    // Entry point from android_util_Binder.cpp's onTransact
    private boolean execTransact(int code, int dataObj, int replyObj,
            int flags) {
        Parcel data = Parcel.obtain(dataObj);
+74 −13
Original line number Diff line number Diff line
@@ -176,6 +176,7 @@ import java.util.Set;
 */
public final class Parcel {
    private static final boolean DEBUG_RECYCLE = false;
    private static final String TAG = "Parcel";

    @SuppressWarnings({"UnusedDeclaration"})
    private int mObject; // used by native code
@@ -214,11 +215,13 @@ public final class Parcel {
    private static final int VAL_BOOLEANARRAY = 23;
    private static final int VAL_CHARSEQUENCEARRAY = 24;

    // The initial int32 in a Binder call's reply Parcel header:
    private static final int EX_SECURITY = -1;
    private static final int EX_BAD_PARCELABLE = -2;
    private static final int EX_ILLEGAL_ARGUMENT = -3;
    private static final int EX_NULL_POINTER = -4;
    private static final int EX_ILLEGAL_STATE = -5;
    private static final int EX_HAS_REPLY_HEADER = -128;  // special; see below

    public final static Parcelable.Creator<String> STRING_CREATOR
             = new Parcelable.Creator<String>() {
@@ -1216,8 +1219,32 @@ public final class Parcel {
     * @see #readException
     */
    public final void writeNoException() {
        // Despite the name of this function ("write no exception"),
        // it should instead be thought of as "write the RPC response
        // header", but because this function name is written out by
        // the AIDL compiler, we're not going to rename it.
        //
        // The response header, in the non-exception case (see also
        // writeException above, also called by the AIDL compiler), is
        // either a 0 (the default case), or EX_HAS_REPLY_HEADER if
        // StrictMode has gathered up violations that have occurred
        // during a Binder call, in which case we write out the number
        // of violations and their details, serialized, before the
        // actual RPC respons data.  The receiving end of this is
        // readException(), below.
        if (StrictMode.hasGatheredViolations()) {
            writeInt(EX_HAS_REPLY_HEADER);
            final int sizePosition = dataPosition();
            writeInt(0);  // total size of fat header, to be filled in later
            StrictMode.writeGatheredViolationsToParcel(this);
            final int payloadPosition = dataPosition();
            setDataPosition(sizePosition);
            writeInt(payloadPosition - sizePosition);  // header size
            setDataPosition(payloadPosition);
        } else {
            writeInt(0);
        }
    }

    /**
     * Special function for reading an exception result from the header of
@@ -1229,11 +1256,45 @@ public final class Parcel {
     * @see #writeNoException
     */
    public final void readException() {
        int code = readInt();
        if (code == 0) return;
        int code = readExceptionCode();
        if (code != 0) {
            String msg = readString();
            readException(code, msg);
        }
    }

    /**
     * Parses the header of a Binder call's response Parcel and
     * returns the exception code.  Deals with lite or fat headers.
     * In the common successful case, this header is generally zero.
     * In less common cases, it's a small negative number and will be
     * followed by an error string.
     *
     * This exists purely for android.database.DatabaseUtils and
     * insulating it from having to handle fat headers as returned by
     * e.g. StrictMode-induced RPC responses.
     *
     * @hide
     */
    public final int readExceptionCode() {
        int code = readInt();
        if (code == EX_HAS_REPLY_HEADER) {
            int headerSize = readInt();
            if (headerSize == 0) {
                Log.e(TAG, "Unexpected zero-sized Parcel reply header.");
            } else {
                // Currently the only thing in the header is StrictMode stacks,
                // but discussions around event/RPC tracing suggest we might
                // put that here too.  If so, switch on sub-header tags here.
                // But for now, just parse out the StrictMode stuff.
                StrictMode.readAndHandleBinderCallViolations(this);
            }
            // And fat response headers are currently only used when
            // there are no exceptions, so return no error:
            return 0;
        }
        return code;
    }

    /**
     * Use this function for customized exception handling.
@@ -1872,13 +1933,13 @@ public final class Parcel {
                    creator = (Parcelable.Creator)f.get(null);
                }
                catch (IllegalAccessException e) {
                    Log.e("Parcel", "Class not found when unmarshalling: "
                    Log.e(TAG, "Class not found when unmarshalling: "
                                        + name + ", e: " + e);
                    throw new BadParcelableException(
                            "IllegalAccessException when unmarshalling: " + name);
                }
                catch (ClassNotFoundException e) {
                    Log.e("Parcel", "Class not found when unmarshalling: "
                    Log.e(TAG, "Class not found when unmarshalling: "
                                        + name + ", e: " + e);
                    throw new BadParcelableException(
                            "ClassNotFoundException when unmarshalling: " + name);
@@ -1983,7 +2044,7 @@ public final class Parcel {
        if (DEBUG_RECYCLE) {
            mStack = new RuntimeException();
        }
        //Log.i("Parcel", "Initializing obj=0x" + Integer.toHexString(obj), mStack);
        //Log.i(TAG, "Initializing obj=0x" + Integer.toHexString(obj), mStack);
        init(obj);
    }

@@ -1991,7 +2052,7 @@ public final class Parcel {
    protected void finalize() throws Throwable {
        if (DEBUG_RECYCLE) {
            if (mStack != null) {
                Log.w("Parcel", "Client did not call Parcel.recycle()", mStack);
                Log.w(TAG, "Client did not call Parcel.recycle()", mStack);
            }
        }
        destroy();
@@ -2015,7 +2076,7 @@ public final class Parcel {
        ClassLoader loader) {
        while (N > 0) {
            Object value = readValue(loader);
            //Log.d("Parcel", "Unmarshalling value=" + value);
            //Log.d(TAG, "Unmarshalling value=" + value);
            outVal.add(value);
            N--;
        }
@@ -2025,7 +2086,7 @@ public final class Parcel {
        ClassLoader loader) {
        for (int i = 0; i < N; i++) {
            Object value = readValue(loader);
            //Log.d("Parcel", "Unmarshalling value=" + value);
            //Log.d(TAG, "Unmarshalling value=" + value);
            outVal[i] = value;
        }
    }
@@ -2035,7 +2096,7 @@ public final class Parcel {
        while (N > 0) {
            int key = readInt();
            Object value = readValue(loader);
            //Log.i("Parcel", "Unmarshalling key=" + key + " value=" + value);
            //Log.i(TAG, "Unmarshalling key=" + key + " value=" + value);
            outVal.append(key, value);
            N--;
        }
@@ -2046,7 +2107,7 @@ public final class Parcel {
        while (N > 0) {
            int key = readInt();
            boolean value = this.readByte() == 1;
            //Log.i("Parcel", "Unmarshalling key=" + key + " value=" + value);
            //Log.i(TAG, "Unmarshalling key=" + key + " value=" + value);
            outVal.append(key, value);
            N--;
        }
+197 −30
Original line number Diff line number Diff line
@@ -23,6 +23,9 @@ import com.android.internal.os.RuntimeInit;

import dalvik.system.BlockGuard;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;

/**
@@ -31,6 +34,7 @@ import java.util.HashMap;
 */
public final class StrictMode {
    private static final String TAG = "StrictMode";
    private static final boolean LOG_V = false;

    // Only log a duplicate stack trace to the logs every second.
    private static final long MIN_LOG_INTERVAL_MS = 1000;
@@ -85,6 +89,19 @@ public final class StrictMode {
            PENALTY_LOG | PENALTY_DIALOG |
            PENALTY_DROPBOX | PENALTY_DEATH;

    /**
     * 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 ThreadLocal<ArrayList<ApplicationErrorReport.CrashInfo>> gatheredViolations =
            new ThreadLocal<ArrayList<ApplicationErrorReport.CrashInfo>>() {
        @Override protected ArrayList<ApplicationErrorReport.CrashInfo> initialValue() {
            return new ArrayList<ApplicationErrorReport.CrashInfo>(1);
        }
    };

    /**
     * Sets the policy for what actions the current thread is denied,
     * as well as the penalty for violating the policy.
@@ -118,6 +135,24 @@ public final class StrictMode {
        }
    }

    private static class StrictModeNetworkViolation extends BlockGuard.BlockGuardPolicyException {
        public StrictModeNetworkViolation(int policyMask) {
            super(policyMask, DISALLOW_NETWORK);
        }
    }

    private static class StrictModeDiskReadViolation extends BlockGuard.BlockGuardPolicyException {
        public StrictModeDiskReadViolation(int policyMask) {
            super(policyMask, DISALLOW_DISK_READ);
        }
    }

    private static class StrictModeDiskWriteViolation extends BlockGuard.BlockGuardPolicyException {
        public StrictModeDiskWriteViolation(int policyMask) {
            super(policyMask, DISALLOW_DISK_WRITE);
        }
    }

    /**
     * Returns the bitmask of the current thread's blocking policy.
     *
@@ -127,6 +162,52 @@ public final class StrictMode {
        return BlockGuard.getThreadPolicy().getPolicyMask();
    }

    /**
     * Parses the BlockGuard policy mask out from the Exception's
     * getMessage() String value.  Kinda gross, but least
     * invasive.  :/
     *
     * Input is of form "policy=137 violation=64"
     *
     * Returns 0 on failure, which is a valid policy, but not a
     * valid policy during a violation (else there must've been
     * some policy in effect to violate).
     */
    private static int parsePolicyFromMessage(String message) {
        if (message == null || !message.startsWith("policy=")) {
            return 0;
        }
        int spaceIndex = message.indexOf(' ');
        if (spaceIndex == -1) {
            return 0;
        }
        String policyString = message.substring(7, spaceIndex);
        try {
            return Integer.valueOf(policyString).intValue();
        } catch (NumberFormatException e) {
            return 0;
        }
    }

    /**
     * Like parsePolicyFromMessage(), but returns the violation.
     */
    private static int parseViolationFromMessage(String message) {
        if (message == null) {
            return 0;
        }
        int violationIndex = message.indexOf("violation=");
        if (violationIndex == -1) {
            return 0;
        }
        String violationString = message.substring(violationIndex + 10);
        try {
            return Integer.valueOf(violationString).intValue();
        } catch (NumberFormatException e) {
            return 0;
        }
    }

    private static class AndroidBlockGuardPolicy implements BlockGuard.Policy {
        private int mPolicyMask;

@@ -139,6 +220,11 @@ public final class StrictMode {
            mPolicyMask = policyMask;
        }

        @Override
        public String toString() {
            return "AndroidBlockGuardPolicy; mPolicyMask=" + mPolicyMask;
        }

        // Part of BlockGuard.Policy interface:
        public int getPolicyMask() {
            return mPolicyMask;
@@ -149,7 +235,7 @@ public final class StrictMode {
            if ((mPolicyMask & DISALLOW_DISK_WRITE) == 0) {
                return;
            }
            handleViolation(DISALLOW_DISK_WRITE);
            startHandlingViolationException(new StrictModeDiskWriteViolation(mPolicyMask));
        }

        // Part of BlockGuard.Policy interface:
@@ -157,7 +243,7 @@ public final class StrictMode {
            if ((mPolicyMask & DISALLOW_DISK_READ) == 0) {
                return;
            }
            handleViolation(DISALLOW_DISK_READ);
            startHandlingViolationException(new StrictModeDiskReadViolation(mPolicyMask));
        }

        // Part of BlockGuard.Policy interface:
@@ -165,17 +251,23 @@ public final class StrictMode {
            if ((mPolicyMask & DISALLOW_NETWORK) == 0) {
                return;
            }
            handleViolation(DISALLOW_NETWORK);
            startHandlingViolationException(new StrictModeNetworkViolation(mPolicyMask));
        }

        public void setPolicyMask(int policyMask) {
            mPolicyMask = policyMask;
        }

        private void handleViolation(int violationBit) {
            final BlockGuard.BlockGuardPolicyException violation =
                    new BlockGuard.BlockGuardPolicyException(mPolicyMask, violationBit);
            violation.fillInStackTrace();
        // 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(BlockGuard.BlockGuardPolicyException e) {
            e.fillInStackTrace();
            final ApplicationErrorReport.CrashInfo crashInfo = new ApplicationErrorReport.CrashInfo(e);
            crashInfo.durationMillis = -1;  // unknown
            final int savedPolicy = mPolicyMask;

            Looper looper = Looper.myLooper();
            if (looper == null) {
@@ -183,38 +275,50 @@ public final class StrictMode {
                // violation takes place.  This case should be rare,
                // as most users will care about timing violations
                // that happen on their main UI thread.
                handleViolationWithTime(violation, -1L /* no time */);
                handleViolation(crashInfo, savedPolicy);
            } else {
                MessageQueue queue = Looper.myQueue();
                final long violationTime = SystemClock.uptimeMillis();
                queue.addIdleHandler(new MessageQueue.IdleHandler() {
                        public boolean queueIdle() {
                            long afterViolationTime = SystemClock.uptimeMillis();
                            handleViolationWithTime(violation, afterViolationTime - violationTime);
                            crashInfo.durationMillis = afterViolationTime - violationTime;
                            handleViolation(crashInfo, savedPolicy);
                            return false;  // remove this idle handler from the array
                        }
                    });
            }
        }

        private void handleViolationWithTime(
            BlockGuard.BlockGuardPolicyException violation,
            long durationMillis) {
        }

            // It's possible (even quite likely) that mPolicyMask 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.  So use
            // the old policy here.
            int policy = violation.getPolicy();
        // 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 handleViolation(
            final ApplicationErrorReport.CrashInfo crashInfo,
            int policy) {
            if (crashInfo.stackTrace == null) {
                Log.d(TAG, "unexpected null stacktrace");
                return;
            }

            // Not _really_ a Crash, but we use the same data structure...
            ApplicationErrorReport.CrashInfo crashInfo =
                    new ApplicationErrorReport.CrashInfo(violation);
            crashInfo.durationMillis = durationMillis;
            if (LOG_V) Log.d(TAG, "handleViolation; policy=" + policy);

            if ((policy & PENALTY_GATHER) != 0) {
                Log.d(TAG, "StrictMode violation via Binder call; ignoring for now.");
                ArrayList<ApplicationErrorReport.CrashInfo> violations = gatheredViolations.get();
                if (violations.size() >= 5) {
                    // Too many.  In a loop or something?  Don't gather them all.
                    return;
                }
                for (ApplicationErrorReport.CrashInfo previous : violations) {
                    if (crashInfo.stackTrace.equals(previous.stackTrace)) {
                        // Duplicate. Don't log.
                        return;
                    }
                }
                violations.add(crashInfo);
                return;
            }

@@ -231,11 +335,11 @@ public final class StrictMode {

            if ((policy & PENALTY_LOG) != 0 &&
                timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
                if (durationMillis != -1) {
                    Log.d(TAG, "StrictMode policy violation; ~duration=" + durationMillis + " ms",
                          violation);
                if (crashInfo.durationMillis != -1) {
                    Log.d(TAG, "StrictMode policy violation; ~duration=" +
                          crashInfo.durationMillis + " ms: " + crashInfo.stackTrace);
                } else {
                    Log.d(TAG, "StrictMode policy violation.", violation);
                    Log.d(TAG, "StrictMode policy violation: " + crashInfo.stackTrace);
                }
            }

@@ -255,7 +359,8 @@ public final class StrictMode {
            }

            if (violationMask != 0) {
                violationMask |= violation.getPolicyViolation();
                int violationBit = parseViolationFromMessage(crashInfo.exceptionMessage);
                violationMask |= violationBit;
                final int savedPolicy = getThreadBlockingPolicy();
                try {
                    // First, remove any policy before we call into the Activity Manager,
@@ -267,7 +372,7 @@ public final class StrictMode {
                    ActivityManagerNative.getDefault().handleApplicationStrictModeViolation(
                        RuntimeInit.getApplicationObject(),
                        violationMask,
                        new ApplicationErrorReport.CrashInfo(violation));
                        crashInfo);
                } catch (RemoteException e) {
                    Log.e(TAG, "RemoteException trying to handle StrictMode violation", e);
                } finally {
@@ -284,6 +389,68 @@ public final class StrictMode {
        }
    }

    /**
     * Called from Parcel.writeNoException()
     */
    /* package */ static boolean hasGatheredViolations() {
        return !gatheredViolations.get().isEmpty();
    }

    /**
     * Called from Parcel.writeNoException()
     */
    /* package */ static void writeGatheredViolationsToParcel(Parcel p) {
        ArrayList<ApplicationErrorReport.CrashInfo> violations = gatheredViolations.get();
        p.writeInt(violations.size());
        for (int i = 0; i < violations.size(); ++i) {
            violations.get(i).writeToParcel(p, 0 /* unused flags? */);
        }

        if (LOG_V) Log.d(TAG, "wrote violations to response parcel; num=" + violations.size());
        violations.clear();
    }

    private static class LogStackTrace extends Exception {}

    /**
     * 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) {
        // Our own stack trace to append
        Exception e = new LogStackTrace();
        StringWriter sw = new StringWriter();
        e.printStackTrace(new PrintWriter(sw));
        String ourStack = sw.toString();

        int policyMask = getThreadBlockingPolicy();

        int numViolations = p.readInt();
        for (int i = 0; i < numViolations; ++i) {
            if (LOG_V) Log.d(TAG, "strict mode violation stacks read from binder call.  i=" + i);
            ApplicationErrorReport.CrashInfo crashInfo = new ApplicationErrorReport.CrashInfo(p);
            crashInfo.stackTrace += "# via Binder call with stack:\n" + ourStack;

            // Unlike the in-process violations in which case we
            // trigger an error _before_ the thing occurs, in this
            // case the violating thing has already occurred, so we
            // can't use our heuristic of waiting for the next event
            // loop idle cycle to measure the approximate violation
            // duration.  Instead, just skip that step and use -1
            // (unknown duration) for now.
            // TODO: keep a thread-local on remote process of first
            // violation time's uptimeMillis, and when writing that
            // back out in Parcel reply, include in the header the
            // violation time and use it here.
            crashInfo.durationMillis = -1;

            BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
            if (policy instanceof AndroidBlockGuardPolicy) {
                ((AndroidBlockGuardPolicy) policy).handleViolation(crashInfo, policyMask);
            }
        }
    }

    /**
     * Called from android_util_Binder.cpp's
     * android_os_Parcel_enforceInterface when an incoming Binder call
Loading