1 /* 2 * Copyright (C) 2018 The AndroCid Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.security.cts; 18 19 import android.test.AndroidTestCase; 20 21 import android.app.Activity; 22 import android.os.BaseBundle; 23 import android.os.Bundle; 24 import android.os.Parcel; 25 import android.annotation.SuppressLint; 26 27 import java.io.InputStream; 28 import java.lang.reflect.Field; 29 import java.util.Random; 30 31 import android.security.cts.R; 32 import android.platform.test.annotations.SecurityTest; 33 34 public class AmbiguousBundlesTest extends AndroidTestCase { 35 36 @SecurityTest 37 public void test_android_CVE_2017_13287() throws Exception { 38 Bundle bundle; 39 { 40 Bundle verifyMe = new Bundle(); 41 verifyMe.putString("cmd", "something_safe"); 42 Bundle useMe = new Bundle(); 43 useMe.putString("cmd", "replaced_thing"); 44 Ambiguator a = new Ambiguator() { 45 @Override 46 public Bundle make(Bundle preReSerialize, Bundle postReSerialize) throws Exception { 47 Random random = new Random(1234); 48 int minHash = 0; 49 for (String s : preReSerialize.keySet()) { 50 minHash = Math.min(minHash, s.hashCode()); 51 } 52 for (String s : postReSerialize.keySet()) { 53 minHash = Math.min(minHash, s.hashCode()); 54 } 55 56 String key; 57 int keyHash; 58 59 do { 60 key = randomString(random); 61 keyHash = key.hashCode(); 62 } while (keyHash >= minHash); 63 64 padBundle(postReSerialize, preReSerialize.size() + 1, minHash, random); 65 padBundle(preReSerialize, postReSerialize.size() - 1, minHash, random); 66 67 String key2; 68 int key2Hash; 69 do { 70 key2 = makeStringToInject(postReSerialize, random); 71 key2Hash = key2.hashCode(); 72 } while (key2Hash >= minHash || key2Hash <= keyHash); 73 74 75 Parcel parcel = Parcel.obtain(); 76 77 parcel.writeInt(preReSerialize.size() + 2); 78 parcel.writeString(key); 79 80 parcel.writeInt(VAL_PARCELABLE); 81 parcel.writeString("com.android.internal.widget.VerifyCredentialResponse"); 82 83 parcel.writeInt(0); 84 parcel.writeInt(0); 85 86 parcel.writeString(key2); 87 parcel.writeInt(VAL_NULL); 88 89 writeBundleSkippingHeaders(parcel, preReSerialize); 90 91 parcel.setDataPosition(0); 92 Bundle bundle = new Bundle(); 93 parcelledDataField.set(bundle, parcel); 94 return bundle; 95 } 96 97 @Override 98 protected String makeStringToInject(Bundle stuffToInject, Random random) { 99 Parcel p = Parcel.obtain(); 100 p.writeInt(0); 101 p.writeInt(0); 102 103 Parcel p2 = Parcel.obtain(); 104 stuffToInject.writeToParcel(p2, 0); 105 int p2Len = p2.dataPosition() - BUNDLE_SKIP; 106 107 for (int i = 0; i < p2Len / 4 + 4; i++) { 108 int paddingVal; 109 if (i > 3) { 110 paddingVal = i; 111 } else { 112 paddingVal = random.nextInt(); 113 } 114 p.writeInt(paddingVal); 115 116 } 117 118 p.appendFrom(p2, BUNDLE_SKIP, p2Len); 119 p2.recycle(); 120 121 while (p.dataPosition() % 8 != 0) p.writeInt(0); 122 for (int i = 0; i < 2; i++) { 123 p.writeInt(0); 124 } 125 126 int len = p.dataPosition() / 2 - 1; 127 p.writeInt(0); p.writeInt(0); 128 p.setDataPosition(0); 129 p.writeInt(len); 130 p.writeInt(len); 131 p.setDataPosition(0); 132 String result = p.readString(); 133 p.recycle(); 134 return result; 135 } 136 }; 137 bundle = a.make(verifyMe, useMe); 138 } 139 140 bundle = reparcel(bundle); 141 String value1 = bundle.getString("cmd"); 142 bundle = reparcel(bundle); 143 String value2 = bundle.getString("cmd"); 144 145 if (!value1.equals(value2)) { 146 fail("String " + value1 + "!=" + value2 + " after reparceling."); 147 } 148 } 149 150 @SuppressLint("ParcelClassLoader") 151 private Bundle reparcel(Bundle source) { 152 Parcel p = Parcel.obtain(); 153 p.writeBundle(source); 154 p.setDataPosition(0); 155 Bundle copy = p.readBundle(); 156 p.recycle(); 157 return copy; 158 } 159 160 static abstract class Ambiguator { 161 162 protected static final int VAL_PARCELABLE = 4; 163 protected static final int VAL_NULL = -1; 164 protected static final int BUNDLE_SKIP = 12; 165 166 protected final Field parcelledDataField; 167 168 public Ambiguator() throws Exception { 169 parcelledDataField = BaseBundle.class.getDeclaredField("mParcelledData"); 170 parcelledDataField.setAccessible(true); 171 } 172 173 abstract public Bundle make(Bundle preReSerialize, Bundle postReSerialize) throws Exception; 174 175 abstract protected String makeStringToInject(Bundle stuffToInject, Random random); 176 177 protected static void writeBundleSkippingHeaders(Parcel parcel, Bundle bundle) { 178 Parcel p2 = Parcel.obtain(); 179 bundle.writeToParcel(p2, 0); 180 parcel.appendFrom(p2, BUNDLE_SKIP, p2.dataPosition() - BUNDLE_SKIP); 181 p2.recycle(); 182 } 183 184 protected static String randomString(Random random) { 185 StringBuilder b = new StringBuilder(); 186 for (int i = 0; i < 6; i++) { 187 b.append((char)(' ' + random.nextInt('~' - ' ' + 1))); 188 } 189 return b.toString(); 190 } 191 192 protected static void padBundle(Bundle bundle, int size, int minHash, Random random) { 193 while (bundle.size() < size) { 194 String key; 195 do { 196 key = randomString(random); 197 } while (key.hashCode() < minHash || bundle.containsKey(key)); 198 bundle.putString(key, "PADDING"); 199 } 200 } 201 } 202 } 203