blob: ae5bc8e4e9b749f90680f118bfe6db0f56b61acd [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 android.test.AndroidTestCase;
import android.app.Activity;
import android.os.BaseBundle;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.annotation.SuppressLint;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Random;
import android.security.cts.R;
import android.platform.test.annotations.SecurityTest;
public class AmbiguousBundlesTest extends AndroidTestCase {
/*
* b/71508348
*/
@SecurityTest(minPatchLevel = "2018-06")
public void test_android_CVE_2018_9339() throws Exception {
Ambiguator ambiguator = new Ambiguator() {
private final Field parcelledDataField;
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
*/
@SecurityTest(minPatchLevel = "2017-10")
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
*/
@SecurityTest(minPatchLevel = "2018-05")
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
*/
@SecurityTest(minPatchLevel = "2018-04")
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);
}
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 INNER_BUNDLE_PADDING = 1;
protected final 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) {
StringBuilder b = new StringBuilder();
for (int i = 0; i < 6; 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");
}
}
}
}