Loading core/java/android/os/BaseBundle.java +37 −18 Original line number Original line Diff line number Diff line Loading @@ -43,18 +43,22 @@ public class BaseBundle { protected static final String TAG = "Bundle"; protected static final String TAG = "Bundle"; static final boolean DEBUG = false; static final boolean DEBUG = false; // Keep them in sync with frameworks/native/libs/binder/PersistableBundle.cpp. /** private static final int BUNDLE_MAGIC = 0x4C444E42; // 'B' 'N' 'D' 'L' * Keep them in sync with frameworks/native/libs/binder/PersistableBundle.cpp. * * @hide */ @VisibleForTesting static final int BUNDLE_MAGIC = 0x4C444E42; // 'B' 'N' 'D' 'L' private static final int BUNDLE_MAGIC_NATIVE = 0x4C444E44; // 'B' 'N' 'D' 'N' private static final int BUNDLE_MAGIC_NATIVE = 0x4C444E44; // 'B' 'N' 'D' 'N' /** /** * Flag indicating that this Bundle is okay to "defuse." That is, it's okay * Flag indicating that this Bundle is okay to "defuse", see {@link #setShouldDefuse(boolean)} * for system processes to ignore any {@link BadParcelableException} * for more details. * encountered when unparceling it, leaving an empty bundle in its place. * <p> * <p> * This should <em>only</em> be set when the Bundle reaches its final * This should <em>only</em> be set when the Bundle reaches its final destination, otherwise a * destination, otherwise a system process may clobber contents that were * system process may clobber contents that were destined for an app that could have unparceled * destined for an app that could have unparceled them. * them. */ */ static final int FLAG_DEFUSABLE = 1 << 0; static final int FLAG_DEFUSABLE = 1 << 0; Loading @@ -63,10 +67,15 @@ public class BaseBundle { private static volatile boolean sShouldDefuse = false; private static volatile boolean sShouldDefuse = false; /** /** * Set global variable indicating that any Bundles parsed in this process * Set global variable indicating that any Bundles parsed in this process should be "defused". * should be "defused." That is, any {@link BadParcelableException} * That is, any {@link BadParcelableException} encountered will be suppressed and logged. Also: * encountered will be suppressed and logged, leaving an empty Bundle * <ul> * instead of crashing. * <li>If it was the deserialization of a custom item (eg. {@link Parcelable}) that caused the * exception, {@code null} will be returned but the item will be held in the map in its * serialized form (lazy value). * <li>If the exception happened during partial deserialization, that is, during the read of * the map and its basic types (while skipping custom types), the map will be left empty. * </ul> * * * @hide * @hide */ */ Loading Loading @@ -249,6 +258,12 @@ public class BaseBundle { } } } } if (itemwise) { if (itemwise) { if (LOG_DEFUSABLE && sShouldDefuse && (mFlags & FLAG_DEFUSABLE) == 0) { Slog.wtf(TAG, "Attempting to unparcel all items in a Bundle while in transit; this " + "may remove elements intended for the final desitination.", new Throwable()); } for (int i = 0, n = mMap.size(); i < n; i++) { for (int i = 0, n = mMap.size(); i < n; i++) { // Triggers deserialization of i-th item, if needed // Triggers deserialization of i-th item, if needed getValueAt(i); getValueAt(i); Loading Loading @@ -281,7 +296,16 @@ public class BaseBundle { final Object getValueAt(int i) { final Object getValueAt(int i) { Object object = mMap.valueAt(i); Object object = mMap.valueAt(i); if (object instanceof Supplier<?>) { if (object instanceof Supplier<?>) { try { object = ((Supplier<?>) object).get(); object = ((Supplier<?>) object).get(); } catch (BadParcelableException e) { if (sShouldDefuse) { Log.w(TAG, "Failed to parse item " + mMap.keyAt(i) + ", returning null.", e); return null; } else { throw e; } } mMap.setValueAt(i, object); mMap.setValueAt(i, object); } } return object; return object; Loading @@ -289,11 +313,6 @@ public class BaseBundle { private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel, private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel, boolean parcelledByNative) { boolean parcelledByNative) { if (LOG_DEFUSABLE && sShouldDefuse && (mFlags & FLAG_DEFUSABLE) == 0) { Slog.wtf(TAG, "Attempting to unparcel a Bundle while in transit; this may " + "clobber all data inside!", new Throwable()); } if (isEmptyParcel(parcelledData)) { if (isEmptyParcel(parcelledData)) { if (DEBUG) { if (DEBUG) { Log.d(TAG, "unparcel " Log.d(TAG, "unparcel " Loading core/java/android/os/Parcel.java +1 −1 Original line number Original line Diff line number Diff line Loading @@ -3776,7 +3776,7 @@ public final class Parcel { default: default: int off = dataPosition() - 4; int off = dataPosition() - 4; throw new RuntimeException( throw new BadParcelableException( "Parcel " + this + ": Unmarshalling unknown type code " + type "Parcel " + this + ": Unmarshalling unknown type code " + type + " at offset " + off); + " at offset " + off); } } Loading core/tests/coretests/src/android/os/BundleTest.java +76 −0 Original line number Original line Diff line number Diff line Loading @@ -47,6 +47,7 @@ public class BundleTest { @After @After public void tearDown() throws Exception { public void tearDown() throws Exception { BaseBundle.setShouldDefuse(false); if (mWtfHandler != null) { if (mWtfHandler != null) { Log.setWtfHandler(mWtfHandler); Log.setWtfHandler(mWtfHandler); } } Loading Loading @@ -355,6 +356,81 @@ public class BundleTest { assertThat(e.getCause()).isInstanceOf(Log.TerribleFailure.class); assertThat(e.getCause()).isInstanceOf(Log.TerribleFailure.class); } } @Test public void getParcelable_whenThrowingAndNotDefusing_throws() throws Exception { Bundle.setShouldDefuse(false); Bundle bundle = new Bundle(); bundle.putParcelable("key", new CustomParcelable(13, "Tiramisu")); bundle.readFromParcel(getParcelledBundle(bundle)); // Default class-loader is the bootpath class-loader, which doesn't contain // CustomParcelable, so trying to read it will throw BadParcelableException. assertThrows(BadParcelableException.class, () -> bundle.getParcelable("key")); } @Test public void getParcelable_whenThrowingAndDefusing_returnsNull() throws Exception { Bundle.setShouldDefuse(true); Bundle bundle = new Bundle(); bundle.putParcelable("key", new CustomParcelable(13, "Tiramisu")); bundle.putString("string", "value"); bundle.readFromParcel(getParcelledBundle(bundle)); // Default class-loader is the bootpath class-loader, which doesn't contain // CustomParcelable, so trying to read it will throw BadParcelableException. assertThat(bundle.<Parcelable>getParcelable("key")).isNull(); // Doesn't affect other items assertThat(bundle.getString("string")).isEqualTo("value"); } @Test public void getParcelable_whenThrowingAndDefusing_leavesElement() throws Exception { Bundle.setShouldDefuse(true); Bundle bundle = new Bundle(); Parcelable parcelable = new CustomParcelable(13, "Tiramisu"); bundle.putParcelable("key", parcelable); bundle.putString("string", "value"); bundle.readFromParcel(getParcelledBundle(bundle)); assertThat(bundle.<Parcelable>getParcelable("key")).isNull(); // Now, we simulate reserializing and assign the proper class loader to not throw anymore bundle.readFromParcel(getParcelledBundle(bundle)); bundle.setClassLoader(getClass().getClassLoader()); // We're able to retrieve it even though we failed before assertThat(bundle.<Parcelable>getParcelable("key")).isEqualTo(parcelable); } @Test public void partialDeserialization_whenNotDefusing_throws() throws Exception { Bundle.setShouldDefuse(false); Bundle bundle = getMalformedBundle(); assertThrows(BadParcelableException.class, bundle::isEmpty); } @Test public void partialDeserialization_whenDefusing_emptiesMap() throws Exception { Bundle.setShouldDefuse(true); Bundle bundle = getMalformedBundle(); bundle.isEmpty(); // Nothing thrown assertThat(bundle.size()).isEqualTo(0); } private Bundle getMalformedBundle() { Parcel p = Parcel.obtain(); p.writeInt(BaseBundle.BUNDLE_MAGIC); int start = p.dataPosition(); p.writeInt(1); // Number of items p.writeString("key"); p.writeInt(131313); // Invalid type p.writeInt(0); // Anything, really int end = p.dataPosition(); p.setDataPosition(0); return new Bundle(p, end - start); } private static class CustomParcelable implements Parcelable { private static class CustomParcelable implements Parcelable { public final int integer; public final int integer; public final String string; public final String string; Loading Loading
core/java/android/os/BaseBundle.java +37 −18 Original line number Original line Diff line number Diff line Loading @@ -43,18 +43,22 @@ public class BaseBundle { protected static final String TAG = "Bundle"; protected static final String TAG = "Bundle"; static final boolean DEBUG = false; static final boolean DEBUG = false; // Keep them in sync with frameworks/native/libs/binder/PersistableBundle.cpp. /** private static final int BUNDLE_MAGIC = 0x4C444E42; // 'B' 'N' 'D' 'L' * Keep them in sync with frameworks/native/libs/binder/PersistableBundle.cpp. * * @hide */ @VisibleForTesting static final int BUNDLE_MAGIC = 0x4C444E42; // 'B' 'N' 'D' 'L' private static final int BUNDLE_MAGIC_NATIVE = 0x4C444E44; // 'B' 'N' 'D' 'N' private static final int BUNDLE_MAGIC_NATIVE = 0x4C444E44; // 'B' 'N' 'D' 'N' /** /** * Flag indicating that this Bundle is okay to "defuse." That is, it's okay * Flag indicating that this Bundle is okay to "defuse", see {@link #setShouldDefuse(boolean)} * for system processes to ignore any {@link BadParcelableException} * for more details. * encountered when unparceling it, leaving an empty bundle in its place. * <p> * <p> * This should <em>only</em> be set when the Bundle reaches its final * This should <em>only</em> be set when the Bundle reaches its final destination, otherwise a * destination, otherwise a system process may clobber contents that were * system process may clobber contents that were destined for an app that could have unparceled * destined for an app that could have unparceled them. * them. */ */ static final int FLAG_DEFUSABLE = 1 << 0; static final int FLAG_DEFUSABLE = 1 << 0; Loading @@ -63,10 +67,15 @@ public class BaseBundle { private static volatile boolean sShouldDefuse = false; private static volatile boolean sShouldDefuse = false; /** /** * Set global variable indicating that any Bundles parsed in this process * Set global variable indicating that any Bundles parsed in this process should be "defused". * should be "defused." That is, any {@link BadParcelableException} * That is, any {@link BadParcelableException} encountered will be suppressed and logged. Also: * encountered will be suppressed and logged, leaving an empty Bundle * <ul> * instead of crashing. * <li>If it was the deserialization of a custom item (eg. {@link Parcelable}) that caused the * exception, {@code null} will be returned but the item will be held in the map in its * serialized form (lazy value). * <li>If the exception happened during partial deserialization, that is, during the read of * the map and its basic types (while skipping custom types), the map will be left empty. * </ul> * * * @hide * @hide */ */ Loading Loading @@ -249,6 +258,12 @@ public class BaseBundle { } } } } if (itemwise) { if (itemwise) { if (LOG_DEFUSABLE && sShouldDefuse && (mFlags & FLAG_DEFUSABLE) == 0) { Slog.wtf(TAG, "Attempting to unparcel all items in a Bundle while in transit; this " + "may remove elements intended for the final desitination.", new Throwable()); } for (int i = 0, n = mMap.size(); i < n; i++) { for (int i = 0, n = mMap.size(); i < n; i++) { // Triggers deserialization of i-th item, if needed // Triggers deserialization of i-th item, if needed getValueAt(i); getValueAt(i); Loading Loading @@ -281,7 +296,16 @@ public class BaseBundle { final Object getValueAt(int i) { final Object getValueAt(int i) { Object object = mMap.valueAt(i); Object object = mMap.valueAt(i); if (object instanceof Supplier<?>) { if (object instanceof Supplier<?>) { try { object = ((Supplier<?>) object).get(); object = ((Supplier<?>) object).get(); } catch (BadParcelableException e) { if (sShouldDefuse) { Log.w(TAG, "Failed to parse item " + mMap.keyAt(i) + ", returning null.", e); return null; } else { throw e; } } mMap.setValueAt(i, object); mMap.setValueAt(i, object); } } return object; return object; Loading @@ -289,11 +313,6 @@ public class BaseBundle { private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel, private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel, boolean parcelledByNative) { boolean parcelledByNative) { if (LOG_DEFUSABLE && sShouldDefuse && (mFlags & FLAG_DEFUSABLE) == 0) { Slog.wtf(TAG, "Attempting to unparcel a Bundle while in transit; this may " + "clobber all data inside!", new Throwable()); } if (isEmptyParcel(parcelledData)) { if (isEmptyParcel(parcelledData)) { if (DEBUG) { if (DEBUG) { Log.d(TAG, "unparcel " Log.d(TAG, "unparcel " Loading
core/java/android/os/Parcel.java +1 −1 Original line number Original line Diff line number Diff line Loading @@ -3776,7 +3776,7 @@ public final class Parcel { default: default: int off = dataPosition() - 4; int off = dataPosition() - 4; throw new RuntimeException( throw new BadParcelableException( "Parcel " + this + ": Unmarshalling unknown type code " + type "Parcel " + this + ": Unmarshalling unknown type code " + type + " at offset " + off); + " at offset " + off); } } Loading
core/tests/coretests/src/android/os/BundleTest.java +76 −0 Original line number Original line Diff line number Diff line Loading @@ -47,6 +47,7 @@ public class BundleTest { @After @After public void tearDown() throws Exception { public void tearDown() throws Exception { BaseBundle.setShouldDefuse(false); if (mWtfHandler != null) { if (mWtfHandler != null) { Log.setWtfHandler(mWtfHandler); Log.setWtfHandler(mWtfHandler); } } Loading Loading @@ -355,6 +356,81 @@ public class BundleTest { assertThat(e.getCause()).isInstanceOf(Log.TerribleFailure.class); assertThat(e.getCause()).isInstanceOf(Log.TerribleFailure.class); } } @Test public void getParcelable_whenThrowingAndNotDefusing_throws() throws Exception { Bundle.setShouldDefuse(false); Bundle bundle = new Bundle(); bundle.putParcelable("key", new CustomParcelable(13, "Tiramisu")); bundle.readFromParcel(getParcelledBundle(bundle)); // Default class-loader is the bootpath class-loader, which doesn't contain // CustomParcelable, so trying to read it will throw BadParcelableException. assertThrows(BadParcelableException.class, () -> bundle.getParcelable("key")); } @Test public void getParcelable_whenThrowingAndDefusing_returnsNull() throws Exception { Bundle.setShouldDefuse(true); Bundle bundle = new Bundle(); bundle.putParcelable("key", new CustomParcelable(13, "Tiramisu")); bundle.putString("string", "value"); bundle.readFromParcel(getParcelledBundle(bundle)); // Default class-loader is the bootpath class-loader, which doesn't contain // CustomParcelable, so trying to read it will throw BadParcelableException. assertThat(bundle.<Parcelable>getParcelable("key")).isNull(); // Doesn't affect other items assertThat(bundle.getString("string")).isEqualTo("value"); } @Test public void getParcelable_whenThrowingAndDefusing_leavesElement() throws Exception { Bundle.setShouldDefuse(true); Bundle bundle = new Bundle(); Parcelable parcelable = new CustomParcelable(13, "Tiramisu"); bundle.putParcelable("key", parcelable); bundle.putString("string", "value"); bundle.readFromParcel(getParcelledBundle(bundle)); assertThat(bundle.<Parcelable>getParcelable("key")).isNull(); // Now, we simulate reserializing and assign the proper class loader to not throw anymore bundle.readFromParcel(getParcelledBundle(bundle)); bundle.setClassLoader(getClass().getClassLoader()); // We're able to retrieve it even though we failed before assertThat(bundle.<Parcelable>getParcelable("key")).isEqualTo(parcelable); } @Test public void partialDeserialization_whenNotDefusing_throws() throws Exception { Bundle.setShouldDefuse(false); Bundle bundle = getMalformedBundle(); assertThrows(BadParcelableException.class, bundle::isEmpty); } @Test public void partialDeserialization_whenDefusing_emptiesMap() throws Exception { Bundle.setShouldDefuse(true); Bundle bundle = getMalformedBundle(); bundle.isEmpty(); // Nothing thrown assertThat(bundle.size()).isEqualTo(0); } private Bundle getMalformedBundle() { Parcel p = Parcel.obtain(); p.writeInt(BaseBundle.BUNDLE_MAGIC); int start = p.dataPosition(); p.writeInt(1); // Number of items p.writeString("key"); p.writeInt(131313); // Invalid type p.writeInt(0); // Anything, really int end = p.dataPosition(); p.setDataPosition(0); return new Bundle(p, end - start); } private static class CustomParcelable implements Parcelable { private static class CustomParcelable implements Parcelable { public final int integer; public final int integer; public final String string; public final String string; Loading