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];
+ }
+ };
+
+}