1 /* 2 * Copyright (C) 2021 The Android 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.os.cts; 18 19 import android.app.Activity; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.net.Uri; 23 import android.os.Bundle; 24 import android.util.Log; 25 26 import java.net.URISyntaxException; 27 28 /** 29 * Activity used to verify an UnsafeIntentLaunch StrictMode violation is reported when an Intent 30 * is unparceled from the delivered Intent and used to start another activity. 31 */ 32 public class IntentLaunchActivity extends Activity { 33 private static final String TAG = "IntentLaunchActivity"; 34 35 protected static final String EXTRA_INNER_INTENT = "inner-intent"; 36 private static final String EXTRA_INNER_INTENT_URI_STRING = "inner-intent-uri-string"; 37 38 private static final String ACTION_UNSAFE_INTENT_LAUNCH = "android.os.cts.UNSAFE_INTENT_LAUNCH"; 39 private static final String ACTION_UNSAFE_DATA_COPY_FROM_INTENT = 40 "android.os.cts.UNSAFE_DATA_COPY_FROM_INTENT"; 41 private static final String ACTION_DATA_COPY_FROM_DELIVERED_INTENT_WITH_UNPARCELED_EXTRAS = 42 "android.os.cts.DATA_COPY_FROM_DELIVERED_INTENT_WITH_UNPARCELED_EXTRAS"; 43 private static final String ACTION_UNSAFE_DATA_COPY_FROM_EXTRAS = 44 "android.os.cts.UNSAFE_DATA_COPY_FROM_EXTRAS"; 45 private static final String ACTION_UNSAFE_INTENT_FROM_URI_LAUNCH = 46 "android.os.cts.UNSAFE_INTENT_FROM_URI_LAUNCH"; 47 private static final String ACTION_SAFE_INTENT_FROM_URI_LAUNCH = 48 "android.os.cts.SAFE_INTENT_FROM_URI_LAUNCH"; 49 50 private static final String ACTION_BROWSABLE_INTENT_LAUNCH = 51 "android.os.cts.BROWSABLE_INTENT_LAUNCH"; 52 53 private static final String EXTRA_TEST_KEY = "android.os.cts.TEST_KEY"; 54 55 /** 56 * Returns an Intent containing a parceled inner Intent that can be used to start this Activity 57 * and verify the StrictMode UnsafeIntentLaunch violation is reported as expected. 58 */ getUnsafeIntentLaunchTestIntent(Context context)59 public static Intent getUnsafeIntentLaunchTestIntent(Context context) { 60 return getTestIntent(context, ACTION_UNSAFE_INTENT_LAUNCH); 61 } 62 63 /** 64 * Returns an Intent containing a parceled Intent with data in the extras; the returned Intent 65 * can be used to start this Activity and verify the StrictMode UnsafeIntentLaunch violation 66 * is reported as expected when copying data from an unparceled Intent. 67 */ getUnsafeDataCopyFromIntentTestIntent(Context context)68 public static Intent getUnsafeDataCopyFromIntentTestIntent(Context context) { 69 return getTestIntentWithExtrasInParceledIntent(context, 70 ACTION_UNSAFE_DATA_COPY_FROM_INTENT); 71 } 72 73 /** 74 * Returns an Intent containing a parceled Intent with data in the extras; the returned Intent 75 * can be used to start this Activity and verify the StrictMode UnsafeIntentLaunch violation is 76 * reported as expected when copying data from an Intent's extras without sanitation / 77 * validation. 78 */ getUnsafeDataCopyFromExtrasTestIntent(Context context)79 public static Intent getUnsafeDataCopyFromExtrasTestIntent(Context context) { 80 return getTestIntentWithExtrasInParceledIntent(context, 81 ACTION_UNSAFE_DATA_COPY_FROM_EXTRAS); 82 } 83 84 /** 85 * Returns an Intent containing data in the extras; the returned Intent can be used to start 86 * this Activity and verify the StrictMode UnsafeIntentLaunch violation is not reported since 87 * the extras of the Intent delivered to a protected component are considered safe. 88 */ getDataCopyFromDeliveredIntentWithUnparceledExtrasTestIntent( Context context)89 public static Intent getDataCopyFromDeliveredIntentWithUnparceledExtrasTestIntent( 90 Context context) { 91 // When an Intent is delivered to a protected component this delivered Intent's extras 92 // can be copied unfiltered to another Intent since only a trusted component could send 93 // this Intent. If the sending component were to attempt an unfiltered copy of extras from 94 // an unparceled Intent or Bundle the violation would be triggered by that call. 95 return getTestIntent(context, 96 ACTION_DATA_COPY_FROM_DELIVERED_INTENT_WITH_UNPARCELED_EXTRAS); 97 } 98 99 /** 100 * Returns an Intent with the specified {@code action} set and a parceled Intent with data in 101 * the extras. 102 */ getTestIntentWithExtrasInParceledIntent(Context context, String action)103 private static Intent getTestIntentWithExtrasInParceledIntent(Context context, String action) { 104 Intent intent = getTestIntent(context, action); 105 Intent innerIntent = intent.getParcelableExtra(EXTRA_INNER_INTENT); 106 // Add an extra to the Intent so that it contains the extras Bundle; the data itself is not 107 // important, just the fact that there is data to be copied without sanitation / validation. 108 innerIntent.putExtra(EXTRA_TEST_KEY, "TEST_VALUE"); 109 return intent; 110 } 111 112 /** 113 * Returns an Intent containing an Intent encoded as a URI in the extras; the returned Intent 114 * can be used to start this Activity and verify the StrictMode UnsafeIntentLaunch violation is 115 * reported as expected when launching an Intent parsed from a URI. 116 */ getUnsafeIntentFromUriLaunchTestIntent(Context context)117 public static Intent getUnsafeIntentFromUriLaunchTestIntent(Context context) { 118 return getTestIntentWithUriIntentInExtras(context, ACTION_UNSAFE_INTENT_FROM_URI_LAUNCH); 119 } 120 121 /** 122 * Returns an Intent containing an Intent encoded as a URI in the extras; the returned Intent 123 * can be used to start this Activity and verify the StrictMode UnsafeIntentLaunch violation is 124 * not reported when launching an Intent parsed from a URI with the browsable category and 125 * without an explicit component. 126 */ getSafeIntentFromUriLaunchTestIntent(Context context)127 public static Intent getSafeIntentFromUriLaunchTestIntent(Context context) { 128 return getTestIntentWithUriIntentInExtras(context, ACTION_SAFE_INTENT_FROM_URI_LAUNCH); 129 } 130 131 /** 132 * Returns an Intent with the specified {@code action} set and containing an Intent encoded as a 133 * URI in the extras. 134 */ getTestIntentWithUriIntentInExtras(Context context, String action)135 private static Intent getTestIntentWithUriIntentInExtras(Context context, String action) { 136 Intent intent = getTestIntent(context, action); 137 Intent innerIntent = intent.getParcelableExtra(EXTRA_INNER_INTENT); 138 innerIntent.addCategory(Intent.CATEGORY_BROWSABLE); 139 innerIntent.setAction(ACTION_BROWSABLE_INTENT_LAUNCH); 140 innerIntent.setPackage(context.getPackageName()); 141 String intentUriString = innerIntent.toUri(Intent.URI_ANDROID_APP_SCHEME); 142 intent.putExtra(EXTRA_INNER_INTENT_URI_STRING, intentUriString); 143 intent.removeExtra(EXTRA_INNER_INTENT); 144 return intent; 145 } 146 147 /** 148 * Returns an Intent with the specified {@code action} set and a parceled Intent. 149 */ getTestIntent(Context context, String action)150 private static Intent getTestIntent(Context context, String action) { 151 Intent intent = new Intent(context, IntentLaunchActivity.class); 152 intent.setAction(action); 153 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 154 Intent innerIntent = new Intent(context, SimpleTestActivity.class); 155 intent.putExtra(EXTRA_INNER_INTENT, innerIntent); 156 return intent; 157 } 158 159 @Override onCreate(Bundle savedInstanceState)160 protected void onCreate(Bundle savedInstanceState) { 161 super.onCreate(savedInstanceState); 162 Intent deliveredIntent = getIntent(); 163 Intent innerIntent = deliveredIntent.getParcelableExtra(EXTRA_INNER_INTENT); 164 String action = deliveredIntent.getAction(); 165 switch (action) { 166 case ACTION_UNSAFE_INTENT_LAUNCH: { 167 if (innerIntent != null) { 168 startActivity(innerIntent); 169 } 170 break; 171 } 172 case ACTION_UNSAFE_DATA_COPY_FROM_INTENT: { 173 if (innerIntent != null) { 174 // Instantiate a new Intent to be used as the target of the unfiltered data 175 // copy. 176 Intent intent = new Intent(getApplicationContext(), SimpleTestActivity.class); 177 intent.putExtras(innerIntent); 178 startActivity(intent); 179 } 180 break; 181 } 182 case ACTION_UNSAFE_DATA_COPY_FROM_EXTRAS: { 183 if (innerIntent != null) { 184 Intent intent = new Intent(getApplicationContext(), SimpleTestActivity.class); 185 intent.putExtras(innerIntent.getExtras()); 186 startActivity(intent); 187 } 188 break; 189 } 190 case ACTION_DATA_COPY_FROM_DELIVERED_INTENT_WITH_UNPARCELED_EXTRAS: { 191 Intent intent = new Intent(getApplicationContext(), SimpleTestActivity.class); 192 intent.putExtras(deliveredIntent); 193 startActivity(intent); 194 break; 195 } 196 case ACTION_UNSAFE_INTENT_FROM_URI_LAUNCH: 197 case ACTION_SAFE_INTENT_FROM_URI_LAUNCH: { 198 String intentUriString = deliveredIntent.getStringExtra( 199 EXTRA_INNER_INTENT_URI_STRING); 200 if (intentUriString != null) { 201 try { 202 Intent intent = Intent.parseUri(intentUriString, 203 Intent.URI_ANDROID_APP_SCHEME); 204 // If this is a safe intent from URI launch then clear the component as a 205 // browsable Intent without a component set should not result in a 206 // violation. 207 if (ACTION_SAFE_INTENT_FROM_URI_LAUNCH.equals(action)) { 208 intent.setComponent(null); 209 } 210 startActivity(intent); 211 } catch (URISyntaxException e) { 212 Log.e(TAG, "Exception parsing URI: " + intentUriString, e); 213 } 214 } 215 break; 216 } 217 default: 218 throw new IllegalArgumentException( 219 "An unexpected action of " + deliveredIntent.getAction() 220 + " was specified"); 221 } 222 finish(); 223 } 224 } 225