| /* |
| * 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.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 { |
| |
| @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; |
| } |
| |
| @Override |
| 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; |
| } |
| }; |
| |
| testAmbiguator(ambiguator); |
| } |
| |
| @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; |
| } |
| |
| @Override |
| protected String makeStringToInject(Bundle stuffToInject, Random random) { |
| return null; |
| } |
| }; |
| |
| testAmbiguator(ambiguator); |
| } |
| |
| @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; |
| } |
| |
| @Override |
| 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; |
| } |
| }; |
| |
| 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 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; |
| |
| abstract protected String makeStringToInject(Bundle stuffToInject, Random random); |
| |
| 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"); |
| } |
| } |
| } |
| } |