Validate Bitmap_createFromParcel

Test: this
Bug: 213169612
Change-Id: Ida9653d29f81dd1c0f710098ebbf6ac3ab1a2013
diff --git a/tests/tests/security/Android.bp b/tests/tests/security/Android.bp
index 5838d27..5735bd8 100644
--- a/tests/tests/security/Android.bp
+++ b/tests/tests/security/Android.bp
@@ -60,6 +60,7 @@
         "src/**/*.java",
         "src/**/*.kt",
         "src/android/security/cts/activity/ISecureRandomService.aidl",
+        "aidl/android/security/cts/IBitmapService.aidl",
         "aidl/android/security/cts/IIsolatedService.aidl",
         "aidl/android/security/cts/CVE_2021_0327/IBadProvider.aidl",
     ],
diff --git a/tests/tests/security/AndroidManifest.xml b/tests/tests/security/AndroidManifest.xml
index 17aa9e8..e43d6aa 100644
--- a/tests/tests/security/AndroidManifest.xml
+++ b/tests/tests/security/AndroidManifest.xml
@@ -48,6 +48,10 @@
 
         <service android:name="android.security.cts.activity.SecureRandomService"
              android:process=":secureRandom"/>
+
+        <service android:name="android.security.cts.BitmapService"
+                 android:process=":bitmap_service" />
+
         <activity android:name="android.security.cts.MotionEventTestActivity"
              android:label="Test MotionEvent"
              android:exported="true">
diff --git a/tests/tests/security/aidl/android/security/cts/IBitmapService.aidl b/tests/tests/security/aidl/android/security/cts/IBitmapService.aidl
new file mode 100644
index 0000000..b9694c3
--- /dev/null
+++ b/tests/tests/security/aidl/android/security/cts/IBitmapService.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+parcelable BitmapWrapper;
+
+interface IBitmapService {
+    int getAllocationSize(in BitmapWrapper bitmap);
+    boolean didReceiveBitmap(in BitmapWrapper bitmap);
+    boolean ping();
+}
diff --git a/tests/tests/security/src/android/security/cts/BitmapService.java b/tests/tests/security/src/android/security/cts/BitmapService.java
new file mode 100644
index 0000000..c532e05
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/BitmapService.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+import androidx.annotation.Nullable;
+
+public class BitmapService extends Service {
+
+    private final IBitmapService.Stub mBinder = new IBitmapService.Stub() {
+        @Override
+        public int getAllocationSize(BitmapWrapper wrapper) {
+            return wrapper.getBitmap().getAllocationByteCount();
+        }
+
+        @Override
+        public boolean didReceiveBitmap(BitmapWrapper wrapper) {
+            return true;
+        }
+
+
+        @Override
+        public boolean ping() {
+            return true;
+        }
+    };
+
+    @Nullable
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+}
diff --git a/tests/tests/security/src/android/security/cts/BitmapTest.java b/tests/tests/security/src/android/security/cts/BitmapTest.java
index 40cb139..5be9098 100644
--- a/tests/tests/security/src/android/security/cts/BitmapTest.java
+++ b/tests/tests/security/src/android/security/cts/BitmapTest.java
@@ -16,16 +16,88 @@
 
 package android.security.cts;
 
+import android.app.Instrumentation;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
 import android.graphics.Bitmap;
+import android.os.BadParcelableException;
+import android.os.IBinder;
 import android.platform.test.annotations.AsbSecurityTest;
 
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.google.common.util.concurrent.AbstractFuture;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
 @RunWith(AndroidJUnit4.class)
 public class BitmapTest {
+
+    private Instrumentation mInstrumentation;
+    private PeerConnection mRemoteConnection;
+    private IBitmapService mRemote;
+
+    public static class PeerConnection extends AbstractFuture<IBitmapService>
+            implements ServiceConnection {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            set(IBitmapService.Stub.asInterface(service));
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+        }
+
+        @Override
+        public IBitmapService get() throws InterruptedException, ExecutionException {
+            try {
+                return get(5, TimeUnit.SECONDS);
+            } catch (TimeoutException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+    }
+
+    @After
+    public void tearDown() {
+        if (mRemoteConnection != null) {
+            final Context context = mInstrumentation.getContext();
+            context.unbindService(mRemoteConnection);
+            mRemote = null;
+            mRemoteConnection = null;
+        }
+    }
+
+    IBitmapService getRemoteService() throws ExecutionException, InterruptedException {
+        if (mRemote == null) {
+            final Context context = mInstrumentation.getContext();
+            Intent intent = new Intent();
+            intent.setComponent(new ComponentName(
+                    "android.security.cts", "android.security.cts.BitmapService"));
+            mRemoteConnection = new PeerConnection();
+            context.bindService(intent, mRemoteConnection,
+                    Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
+            mRemote = mRemoteConnection.get();
+        }
+        return mRemote;
+    }
+
     /**
      * Test Bitmap.createBitmap properly throws OOME on large inputs.
      *
@@ -39,4 +111,102 @@
         // which might be passed to createBitmap from a Java decoder.
         Bitmap.createBitmap(65535, 65535, Bitmap.Config.ARGB_8888);
     }
+
+    @Test
+    @AsbSecurityTest(cveBugId = 213169612)
+    public void test_inplace_213169612() throws Exception {
+        IBitmapService remote = getRemoteService();
+        Assert.assertTrue("Binder should be alive", remote.ping());
+        BitmapWrapper wrapper = new BitmapWrapper(
+                Bitmap.createBitmap(2, 4, Bitmap.Config.ARGB_8888));
+        final int expectedAllocationSize = wrapper.getBitmap().getAllocationByteCount();
+        int allocationSize = remote.getAllocationSize(wrapper);
+        Assert.assertEquals(expectedAllocationSize, allocationSize);
+        Assert.assertTrue("Binder should be alive", remote.ping());
+
+        // Override the bitmap size to 500KiB; larger than the actual size
+        wrapper.reset()
+                .replace(BitmapWrapper.Field.DataSize, 500 * 1024);
+        allocationSize = remote.getAllocationSize(wrapper);
+        Assert.assertEquals(expectedAllocationSize, allocationSize);
+        Assert.assertTrue("Binder should be alive", remote.ping());
+
+        // Override the bitmap size to 2 bytes; smaller than the actual size
+        wrapper.reset()
+                .replace(BitmapWrapper.Field.DataSize, 2);
+        try {
+            Assert.assertFalse("Should have failed to unparcel",
+                    remote.didReceiveBitmap(wrapper));
+        } catch (BadParcelableException ex) {
+            // We'll also accept a BadParcelableException
+        }
+        Assert.assertTrue("Binder should be alive", remote.ping());
+
+        // Keep the blob size accurate, but change computed allocation size to be too large
+        wrapper.reset()
+                .replace(BitmapWrapper.Field.Height, 10_000)
+                .replace(BitmapWrapper.Field.RowBytes, 50_000);
+        try {
+            Assert.assertFalse("Should have failed to unparcel",
+                    remote.didReceiveBitmap(wrapper));
+        } catch (BadParcelableException ex) {
+            // We'll also accept a BadParcelableException
+        }
+        Assert.assertTrue("Binder should be alive", remote.ping());
+    }
+
+    @Test
+    @AsbSecurityTest(cveBugId = 213169612)
+    public void test_ashmem_213169612() throws Exception {
+        IBitmapService remote = getRemoteService();
+        Assert.assertTrue("Binder should be alive", remote.ping());
+        BitmapWrapper wrapper = new BitmapWrapper(
+                Bitmap.createBitmap(1000, 1000, Bitmap.Config.ARGB_8888)
+                        .createAshmemBitmap());
+        final int expectedAllocationSize = wrapper.getBitmap().getAllocationByteCount();
+        int allocationSize = remote.getAllocationSize(wrapper);
+        Assert.assertEquals(expectedAllocationSize, allocationSize);
+        Assert.assertTrue("Binder should be alive", remote.ping());
+
+        // Override the bitmap size to be larger than the initial size
+        wrapper.reset()
+                .replace(BitmapWrapper.Field.DataSize, expectedAllocationSize * 2);
+        try {
+            Assert.assertFalse("Should have failed to unparcel",
+                    remote.didReceiveBitmap(wrapper));
+        } catch (BadParcelableException ex) {
+            // We'll also accept a BadParcelableException
+        }
+        Assert.assertTrue("Binder should be alive", remote.ping());
+
+        // Override the bitmap size to 2 bytes; smaller than the actual size
+        wrapper.reset()
+                .replace(BitmapWrapper.Field.DataSize, 2);
+        try {
+            Assert.assertFalse("Should have failed to unparcel",
+                    remote.didReceiveBitmap(wrapper));
+        } catch (BadParcelableException ex) {
+            // We'll also accept a BadParcelableException
+        }
+        Assert.assertTrue("Binder should be alive", remote.ping());
+
+        // Keep the ashmem size accurate, but change computed allocation size to be too large
+        wrapper.reset()
+                .replace(BitmapWrapper.Field.Height, 10_000)
+                .replace(BitmapWrapper.Field.RowBytes, 50_000);
+        try {
+            Assert.assertFalse("Should have failed to unparcel",
+                    remote.didReceiveBitmap(wrapper));
+        } catch (BadParcelableException ex) {
+            // We'll also accept a BadParcelableException
+        }
+        Assert.assertTrue("Binder should be alive", remote.ping());
+
+        // Keep the ashmem size accurate, but change computed allocation size to be smaller
+        wrapper.reset()
+                .replace(BitmapWrapper.Field.Height, 100);
+        allocationSize = remote.getAllocationSize(wrapper);
+        Assert.assertEquals(expectedAllocationSize, allocationSize);
+        Assert.assertTrue("Binder should be alive", remote.ping());
+    }
 }
diff --git a/tests/tests/security/src/android/security/cts/BitmapWrapper.java b/tests/tests/security/src/android/security/cts/BitmapWrapper.java
new file mode 100644
index 0000000..dbcf498
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/BitmapWrapper.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Assert;
+
+public class BitmapWrapper implements Parcelable {
+    enum Field {
+        DataSize,
+        Height,
+        RowBytes,
+    }
+
+    private final Bitmap mBitmap;
+    private final ArrayMap<Field, Integer> mReplaceFields = new ArrayMap<>();
+
+    public BitmapWrapper(Bitmap bitmap) {
+        mBitmap = bitmap;
+    }
+
+    private BitmapWrapper(Parcel in) {
+        mBitmap = Bitmap.CREATOR.createFromParcel(in);
+    }
+
+    public Bitmap getBitmap() {
+        return mBitmap;
+    }
+
+    public BitmapWrapper reset() {
+        mReplaceFields.clear();
+        return this;
+    }
+
+    public BitmapWrapper replace(Field field, int newValue) {
+        mReplaceFields.put(field, newValue);
+        return this;
+    }
+
+    @Override
+    public int describeContents() {
+        return mBitmap.describeContents();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        final int before = dest.dataPosition();
+        mBitmap.writeToParcel(dest, flags);
+        final int oldEnd = dest.dataPosition();
+        if (!mReplaceFields.isEmpty()) {
+            dest.setDataPosition(before
+                    + 4 /* immutable */
+                    + 4 /* colortype */
+                    + 4 /* alpha type */);
+            // Skip sizeof colorspace
+            int colorSpaceLen = dest.readInt();
+            dest.setDataPosition(dest.dataPosition() + colorSpaceLen);
+            Assert.assertEquals(mBitmap.getWidth(), dest.readInt());
+            Assert.assertEquals(mBitmap.getHeight(), dest.readInt());
+            if (mReplaceFields.containsKey(Field.Height)) {
+                dest.setDataPosition(dest.dataPosition() - 4);
+                dest.writeInt(mReplaceFields.get(Field.Height));
+            }
+            Assert.assertEquals(mBitmap.getRowBytes(), dest.readInt());
+            if (mReplaceFields.containsKey(Field.RowBytes)) {
+                dest.setDataPosition(dest.dataPosition() - 4);
+                dest.writeInt(mReplaceFields.get(Field.RowBytes));
+            }
+            Assert.assertEquals(mBitmap.getDensity(), dest.readInt());
+            int type = dest.readInt();
+            if (type == 0) { // in-place
+                if (mReplaceFields.containsKey(Field.DataSize)) {
+                    int dataSize = mReplaceFields.get(Field.DataSize);
+                    dest.writeInt(dataSize);
+                    int newEnd = dest.dataPosition() + dataSize;
+                    dest.setDataSize(newEnd);
+                    dest.setDataPosition(newEnd);
+                } else {
+                    int skip = dest.readInt();
+                    dest.setDataPosition(dest.dataPosition() + skip);
+                }
+            } else if (type == 1) { // ashmem
+                if (mReplaceFields.containsKey(Field.DataSize)) {
+                    int dataSize = mReplaceFields.get(Field.DataSize);
+                    dest.writeInt(dataSize);
+                }
+                dest.setDataPosition(oldEnd);
+            } else {
+                Assert.fail("Unknown type " + type);
+            }
+        }
+    }
+
+    public static final Parcelable.Creator<BitmapWrapper> CREATOR =
+            new Parcelable.Creator<BitmapWrapper>() {
+        public BitmapWrapper createFromParcel(Parcel in) {
+            return new BitmapWrapper(in);
+        }
+
+        public BitmapWrapper[] newArray(int size) {
+            return new BitmapWrapper[size];
+        }
+    };
+
+}