From 4875dd14eb2d98e5c490f985977cfae5567dec17 Mon Sep 17 00:00:00 2001 From: Hao Ke Date: Tue, 12 Oct 2021 20:00:00 +0000 Subject: [PATCH] Adding typed Parcel read APIs 2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added typed read APIs of `readArray`, `readArrayList`, `readParcelableArray` and `readSparseArray`. To avoid unexpected types of objects being unparcelled, ideally clients would use the readTypedXXX() methods that take the parcelable creator. However, this won’t be an option for use cases involving deserializing children objects inherited from non-final parcelable or serializable objects. Currently out of ~4k parcelable classes, only ~1.5k are marked as “final” in the platform. Hence it would be necessary to introduce new replacements that take an extra Class parameter and before deserializing we check that the class written on the wire is the same or a descendant from the one provided as argument. Doing so could enhance the security of Parcel deserialization, More details can be found at go/safer-parcel. Test: atest -d android.os.cts.ParcelTest Bug: 195622897 Change-Id: Iaba24c35a0c0acc77ae2d22ac77c5a90efd93329 --- core/api/current.txt | 4 + core/java/android/os/Parcel.java | 183 ++++++++++++++++++++++++------- 2 files changed, 146 insertions(+), 41 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index 0eba9d75fdfcf..2c075ad8567bc 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -31432,7 +31432,9 @@ package android.os { method @NonNull public static android.os.Parcel obtain(); method @NonNull public static android.os.Parcel obtain(@NonNull android.os.IBinder); method @Nullable public Object[] readArray(@Nullable ClassLoader); + method @Nullable public T[] readArray(@Nullable ClassLoader, @NonNull Class); method @Nullable public java.util.ArrayList readArrayList(@Nullable ClassLoader); + method @Nullable public java.util.ArrayList readArrayList(@Nullable ClassLoader, @NonNull Class); method public void readBinderArray(@NonNull android.os.IBinder[]); method public void readBinderList(@NonNull java.util.List); method public boolean readBoolean(); @@ -31460,6 +31462,7 @@ package android.os { method @Nullable public T readParcelable(@Nullable ClassLoader); method @Nullable public T readParcelable(@Nullable ClassLoader, @NonNull Class); method @Nullable public android.os.Parcelable[] readParcelableArray(@Nullable ClassLoader); + method @Nullable public T[] readParcelableArray(@Nullable ClassLoader, @NonNull Class); method @Nullable public android.os.Parcelable.Creator readParcelableCreator(@Nullable ClassLoader); method @Nullable public android.os.Parcelable.Creator readParcelableCreator(@Nullable ClassLoader, @NonNull Class); method @NonNull public java.util.List readParcelableList(@NonNull java.util.List, @Nullable ClassLoader); @@ -31469,6 +31472,7 @@ package android.os { method @NonNull public android.util.Size readSize(); method @NonNull public android.util.SizeF readSizeF(); method @Nullable public android.util.SparseArray readSparseArray(@Nullable ClassLoader); + method @Nullable public android.util.SparseArray readSparseArray(@Nullable ClassLoader, @NonNull Class); method @Nullable public android.util.SparseBooleanArray readSparseBooleanArray(); method @Nullable public String readString(); method public void readStringArray(@NonNull String[]); diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 6be86da9a9259..153cbb027c0df 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -20,6 +20,7 @@ import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.TestApi; import android.app.AppOpsManager; import android.compat.annotation.UnsupportedAppUsage; @@ -3094,14 +3095,24 @@ public final class Parcel { * Parcelables. */ @Nullable - public final ArrayList readArrayList(@Nullable ClassLoader loader) { - int N = readInt(); - if (N < 0) { - return null; - } - ArrayList l = new ArrayList(N); - readListInternal(l, N, loader, /* clazz */ null); - return l; + public ArrayList readArrayList(@Nullable ClassLoader loader) { + return readArrayListInternal(loader, /* clazz */ null); + } + + /** + * Same as {@link #readArrayList(ClassLoader)} but accepts {@code clazz} parameter as + * the type required for each item. + * + * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized + * is not an instance of that class or any of its children classes or there was an error + * trying to instantiate an element. + */ + @SuppressLint({"ConcreteCollection", "NullableCollection"}) + @Nullable + public ArrayList readArrayList(@Nullable ClassLoader loader, + @NonNull Class clazz) { + Objects.requireNonNull(clazz); + return readArrayListInternal(loader, clazz); } /** @@ -3111,14 +3122,23 @@ public final class Parcel { * Parcelables. */ @Nullable - public final Object[] readArray(@Nullable ClassLoader loader) { - int N = readInt(); - if (N < 0) { - return null; - } - Object[] l = new Object[N]; - readArrayInternal(l, N, loader); - return l; + public Object[] readArray(@Nullable ClassLoader loader) { + return readArrayInternal(loader, /* clazz */ null); + } + + /** + * Same as {@link #readArray(ClassLoader)} but accepts {@code clazz} parameter as + * the type required for each item. + * + * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized + * is not an instance of that class or any of its children classes or there was an error + * trying to instantiate an element. + */ + @SuppressLint({"ArrayReturn", "NullableCollection"}) + @Nullable + public T[] readArray(@Nullable ClassLoader loader, @NonNull Class clazz) { + Objects.requireNonNull(clazz); + return readArrayInternal(loader, clazz); } /** @@ -3128,14 +3148,23 @@ public final class Parcel { * Parcelables. */ @Nullable - public final SparseArray readSparseArray(@Nullable ClassLoader loader) { - int N = readInt(); - if (N < 0) { - return null; - } - SparseArray sa = new SparseArray(N); - readSparseArrayInternal(sa, N, loader); - return sa; + public SparseArray readSparseArray(@Nullable ClassLoader loader) { + return readSparseArrayInternal(loader, /* clazz */ null); + } + + /** + * Same as {@link #readSparseArray(ClassLoader)} but accepts {@code clazz} parameter as + * the type required for each item. + * + * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized + * is not an instance of that class or any of its children classes or there was an error + * trying to instantiate an element. + */ + @Nullable + public SparseArray readSparseArray(@Nullable ClassLoader loader, + @NonNull Class clazz) { + Objects.requireNonNull(clazz); + return readSparseArrayInternal(loader, clazz); } /** @@ -3851,7 +3880,7 @@ public final class Parcel { "Parcel " + this + ": Unmarshalling unknown type code " + type + " at offset " + off); } - if (clazz != null && !clazz.isInstance(object)) { + if (object != null && clazz != null && !clazz.isInstance(object)) { throw new BadParcelableException("Unparcelled object " + object + " is not an instance of required class " + clazz.getName() + " provided in the parameter"); @@ -3910,7 +3939,6 @@ public final class Parcel { } /** - * * @param clazz The type of the parcelable expected or {@code null} for performing no checks. */ @SuppressWarnings("unchecked") @@ -3969,7 +3997,7 @@ public final class Parcel { * as the required type. * * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized - * is not an instance of that class or any of its children class or there there was an error + * is not an instance of that class or any of its children classes or there there was an error * trying to read the {@link Parcelable.Creator}. */ @Nullable @@ -4092,17 +4120,25 @@ public final class Parcel { return p; } - /** @hide */ + /** + * Same as {@link #readParcelableArray(ClassLoader)} but accepts {@code clazz} parameter as + * the type required for each item. + * + * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized + * is not an instance of that class or any of its children classes or there was an error + * trying to instantiate an element. + */ + @SuppressLint({"ArrayReturn", "NullableCollection"}) + @SuppressWarnings("unchecked") @Nullable - public final T[] readParcelableArray(@Nullable ClassLoader loader, - @NonNull Class clazz) { - int N = readInt(); - if (N < 0) { + public T[] readParcelableArray(@Nullable ClassLoader loader, @NonNull Class clazz) { + int n = readInt(); + if (n < 0) { return null; } - T[] p = (T[]) Array.newInstance(clazz, N); - for (int i = 0; i < N; i++) { - p[i] = readParcelable(loader); + T[] p = (T[]) Array.newInstance(clazz, n); + for (int i = 0; i < n; i++) { + p[i] = readParcelableInternal(loader, clazz); } return p; } @@ -4320,9 +4356,12 @@ public final class Parcel { return result; } - private void readListInternal(@NonNull List outVal, int n, - @Nullable ClassLoader loader) { - readListInternal(outVal, n, loader, null); + /** + * The method is replaced by {@link #readListInternal(List, int, ClassLoader, Class)}, however + * we are keeping this unused method here to allow unsupported app usages. + */ + private void readListInternal(@NonNull List outVal, int n, @Nullable ClassLoader loader) { + readListInternal(outVal, n, loader, /* clazz */ null); } /** @@ -4338,26 +4377,88 @@ public final class Parcel { } } + /** + * @param clazz The type of the object expected or {@code null} for performing no checks. + */ + @SuppressLint({"ConcreteCollection", "NullableCollection"}) + @Nullable + private ArrayList readArrayListInternal(@Nullable ClassLoader loader, + @Nullable Class clazz) { + int n = readInt(); + if (n < 0) { + return null; + } + ArrayList l = new ArrayList<>(n); + readListInternal(l, n, loader, clazz); + return l; + } + + /** + * The method is replaced by {@link #readArrayInternal(ClassLoader, Class)}, however + * we are keeping this unused method here to allow unsupported app usages. + */ private void readArrayInternal(@NonNull Object[] outVal, int N, @Nullable ClassLoader loader) { for (int i = 0; i < N; i++) { - Object value = readValue(loader); - //Log.d(TAG, "Unmarshalling value=" + value); + Object value = readValue(loader, /* clazz */ null); outVal[i] = value; } } + /** + * @param clazz The type of the object expected or {@code null} for performing no checks. + */ + @SuppressWarnings("unchecked") + @Nullable + private T[] readArrayInternal(@Nullable ClassLoader loader, @Nullable Class clazz) { + int n = readInt(); + if (n < 0) { + return null; + } + T[] outVal = (T[]) ((clazz == null) ? new Object[n] : Array.newInstance(clazz, n)); + + for (int i = 0; i < n; i++) { + T value = readValue(loader, clazz); + outVal[i] = value; + } + return outVal; + } + + /** + * The method is replaced by {@link #readSparseArray(ClassLoader, Class)}, however + * we are keeping this unused method here to allow unsupported app usages. + */ private void readSparseArrayInternal(@NonNull SparseArray outVal, int N, @Nullable ClassLoader loader) { while (N > 0) { int key = readInt(); Object value = readValue(loader); - //Log.i(TAG, "Unmarshalling key=" + key + " value=" + value); outVal.append(key, value); N--; } } + /** + * @param clazz The type of the object expected or {@code null} for performing no checks. + */ + @Nullable + private SparseArray readSparseArrayInternal(@Nullable ClassLoader loader, + @Nullable Class clazz) { + int n = readInt(); + if (n < 0) { + return null; + } + SparseArray outVal = new SparseArray<>(n); + + while (n > 0) { + int key = readInt(); + T value = readValue(loader, clazz); + outVal.append(key, value); + n--; + } + return outVal; + } + private void readSparseBooleanArrayInternal(@NonNull SparseBooleanArray outVal, int N) { while (N > 0) {